1 /*
2  * Driver for UniPhier AIDET (ARM Interrupt Detector)
3  *
4  * Copyright (C) 2017 Socionext Inc.
5  *   Author: Masahiro Yamada <yamada.masahiro@socionext.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License version 2 as
9  * published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  */
16 
17 #include <linux/bitops.h>
18 #include <linux/init.h>
19 #include <linux/irq.h>
20 #include <linux/irqdomain.h>
21 #include <linux/kernel.h>
22 #include <linux/of.h>
23 #include <linux/of_device.h>
24 #include <linux/of_irq.h>
25 #include <linux/platform_device.h>
26 #include <linux/spinlock.h>
27 
28 #define UNIPHIER_AIDET_NR_IRQS		256
29 
30 #define UNIPHIER_AIDET_DETCONF		0x04	/* inverter register base */
31 
32 struct uniphier_aidet_priv {
33 	struct irq_domain *domain;
34 	void __iomem *reg_base;
35 	spinlock_t lock;
36 	u32 saved_vals[UNIPHIER_AIDET_NR_IRQS / 32];
37 };
38 
uniphier_aidet_reg_update(struct uniphier_aidet_priv * priv,unsigned int reg,u32 mask,u32 val)39 static void uniphier_aidet_reg_update(struct uniphier_aidet_priv *priv,
40 				      unsigned int reg, u32 mask, u32 val)
41 {
42 	unsigned long flags;
43 	u32 tmp;
44 
45 	spin_lock_irqsave(&priv->lock, flags);
46 	tmp = readl_relaxed(priv->reg_base + reg);
47 	tmp &= ~mask;
48 	tmp |= mask & val;
49 	writel_relaxed(tmp, priv->reg_base + reg);
50 	spin_unlock_irqrestore(&priv->lock, flags);
51 }
52 
uniphier_aidet_detconf_update(struct uniphier_aidet_priv * priv,unsigned long index,unsigned int val)53 static void uniphier_aidet_detconf_update(struct uniphier_aidet_priv *priv,
54 					  unsigned long index, unsigned int val)
55 {
56 	unsigned int reg;
57 	u32 mask;
58 
59 	reg = UNIPHIER_AIDET_DETCONF + index / 32 * 4;
60 	mask = BIT(index % 32);
61 
62 	uniphier_aidet_reg_update(priv, reg, mask, val ? mask : 0);
63 }
64 
uniphier_aidet_irq_set_type(struct irq_data * data,unsigned int type)65 static int uniphier_aidet_irq_set_type(struct irq_data *data, unsigned int type)
66 {
67 	struct uniphier_aidet_priv *priv = data->chip_data;
68 	unsigned int val;
69 
70 	/* enable inverter for active low triggers */
71 	switch (type) {
72 	case IRQ_TYPE_EDGE_RISING:
73 	case IRQ_TYPE_LEVEL_HIGH:
74 		val = 0;
75 		break;
76 	case IRQ_TYPE_EDGE_FALLING:
77 		val = 1;
78 		type = IRQ_TYPE_EDGE_RISING;
79 		break;
80 	case IRQ_TYPE_LEVEL_LOW:
81 		val = 1;
82 		type = IRQ_TYPE_LEVEL_HIGH;
83 		break;
84 	default:
85 		return -EINVAL;
86 	}
87 
88 	uniphier_aidet_detconf_update(priv, data->hwirq, val);
89 
90 	return irq_chip_set_type_parent(data, type);
91 }
92 
93 static struct irq_chip uniphier_aidet_irq_chip = {
94 	.name = "AIDET",
95 	.irq_mask = irq_chip_mask_parent,
96 	.irq_unmask = irq_chip_unmask_parent,
97 	.irq_eoi = irq_chip_eoi_parent,
98 	.irq_set_affinity = irq_chip_set_affinity_parent,
99 	.irq_set_type = uniphier_aidet_irq_set_type,
100 };
101 
uniphier_aidet_domain_translate(struct irq_domain * domain,struct irq_fwspec * fwspec,unsigned long * out_hwirq,unsigned int * out_type)102 static int uniphier_aidet_domain_translate(struct irq_domain *domain,
103 					   struct irq_fwspec *fwspec,
104 					   unsigned long *out_hwirq,
105 					   unsigned int *out_type)
106 {
107 	if (WARN_ON(fwspec->param_count < 2))
108 		return -EINVAL;
109 
110 	*out_hwirq = fwspec->param[0];
111 	*out_type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
112 
113 	return 0;
114 }
115 
uniphier_aidet_domain_alloc(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs,void * arg)116 static int uniphier_aidet_domain_alloc(struct irq_domain *domain,
117 				       unsigned int virq, unsigned int nr_irqs,
118 				       void *arg)
119 {
120 	struct irq_fwspec parent_fwspec;
121 	irq_hw_number_t hwirq;
122 	unsigned int type;
123 	int ret;
124 
125 	if (nr_irqs != 1)
126 		return -EINVAL;
127 
128 	ret = uniphier_aidet_domain_translate(domain, arg, &hwirq, &type);
129 	if (ret)
130 		return ret;
131 
132 	switch (type) {
133 	case IRQ_TYPE_EDGE_RISING:
134 	case IRQ_TYPE_LEVEL_HIGH:
135 		break;
136 	case IRQ_TYPE_EDGE_FALLING:
137 		type = IRQ_TYPE_EDGE_RISING;
138 		break;
139 	case IRQ_TYPE_LEVEL_LOW:
140 		type = IRQ_TYPE_LEVEL_HIGH;
141 		break;
142 	default:
143 		return -EINVAL;
144 	}
145 
146 	if (hwirq >= UNIPHIER_AIDET_NR_IRQS)
147 		return -ENXIO;
148 
149 	ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
150 					    &uniphier_aidet_irq_chip,
151 					    domain->host_data);
152 	if (ret)
153 		return ret;
154 
155 	/* parent is GIC */
156 	parent_fwspec.fwnode = domain->parent->fwnode;
157 	parent_fwspec.param_count = 3;
158 	parent_fwspec.param[0] = 0;		/* SPI */
159 	parent_fwspec.param[1] = hwirq;
160 	parent_fwspec.param[2] = type;
161 
162 	return irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec);
163 }
164 
165 static const struct irq_domain_ops uniphier_aidet_domain_ops = {
166 	.alloc = uniphier_aidet_domain_alloc,
167 	.free = irq_domain_free_irqs_common,
168 	.translate = uniphier_aidet_domain_translate,
169 };
170 
uniphier_aidet_probe(struct platform_device * pdev)171 static int uniphier_aidet_probe(struct platform_device *pdev)
172 {
173 	struct device *dev = &pdev->dev;
174 	struct device_node *parent_np;
175 	struct irq_domain *parent_domain;
176 	struct uniphier_aidet_priv *priv;
177 	struct resource *res;
178 
179 	parent_np = of_irq_find_parent(dev->of_node);
180 	if (!parent_np)
181 		return -ENXIO;
182 
183 	parent_domain = irq_find_host(parent_np);
184 	of_node_put(parent_np);
185 	if (!parent_domain)
186 		return -EPROBE_DEFER;
187 
188 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
189 	if (!priv)
190 		return -ENOMEM;
191 
192 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
193 	priv->reg_base = devm_ioremap_resource(dev, res);
194 	if (IS_ERR(priv->reg_base))
195 		return PTR_ERR(priv->reg_base);
196 
197 	spin_lock_init(&priv->lock);
198 
199 	priv->domain = irq_domain_create_hierarchy(
200 					parent_domain, 0,
201 					UNIPHIER_AIDET_NR_IRQS,
202 					of_node_to_fwnode(dev->of_node),
203 					&uniphier_aidet_domain_ops, priv);
204 	if (!priv->domain)
205 		return -ENOMEM;
206 
207 	platform_set_drvdata(pdev, priv);
208 
209 	return 0;
210 }
211 
uniphier_aidet_suspend(struct device * dev)212 static int __maybe_unused uniphier_aidet_suspend(struct device *dev)
213 {
214 	struct uniphier_aidet_priv *priv = dev_get_drvdata(dev);
215 	int i;
216 
217 	for (i = 0; i < ARRAY_SIZE(priv->saved_vals); i++)
218 		priv->saved_vals[i] = readl_relaxed(
219 			priv->reg_base + UNIPHIER_AIDET_DETCONF + i * 4);
220 
221 	return 0;
222 }
223 
uniphier_aidet_resume(struct device * dev)224 static int __maybe_unused uniphier_aidet_resume(struct device *dev)
225 {
226 	struct uniphier_aidet_priv *priv = dev_get_drvdata(dev);
227 	int i;
228 
229 	for (i = 0; i < ARRAY_SIZE(priv->saved_vals); i++)
230 		writel_relaxed(priv->saved_vals[i],
231 			       priv->reg_base + UNIPHIER_AIDET_DETCONF + i * 4);
232 
233 	return 0;
234 }
235 
236 static const struct dev_pm_ops uniphier_aidet_pm_ops = {
237 	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(uniphier_aidet_suspend,
238 				      uniphier_aidet_resume)
239 };
240 
241 static const struct of_device_id uniphier_aidet_match[] = {
242 	{ .compatible = "socionext,uniphier-ld4-aidet" },
243 	{ .compatible = "socionext,uniphier-pro4-aidet" },
244 	{ .compatible = "socionext,uniphier-sld8-aidet" },
245 	{ .compatible = "socionext,uniphier-pro5-aidet" },
246 	{ .compatible = "socionext,uniphier-pxs2-aidet" },
247 	{ .compatible = "socionext,uniphier-ld11-aidet" },
248 	{ .compatible = "socionext,uniphier-ld20-aidet" },
249 	{ .compatible = "socionext,uniphier-pxs3-aidet" },
250 	{ /* sentinel */ }
251 };
252 
253 static struct platform_driver uniphier_aidet_driver = {
254 	.probe = uniphier_aidet_probe,
255 	.driver = {
256 		.name = "uniphier-aidet",
257 		.of_match_table = uniphier_aidet_match,
258 		.pm = &uniphier_aidet_pm_ops,
259 	},
260 };
261 builtin_platform_driver(uniphier_aidet_driver);
262