GNU Linux-libre 4.14.332-gnu1
[releases.git] / drivers / irqchip / irq-uniphier-aidet.c
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
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
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
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
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
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
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
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
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);