2 * Driver for UniPhier AIDET (ARM Interrupt Detector)
4 * Copyright (C) 2017 Socionext Inc.
5 * Author: Masahiro Yamada <yamada.masahiro@socionext.com>
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.
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.
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>
23 #include <linux/of_device.h>
24 #include <linux/of_irq.h>
25 #include <linux/platform_device.h>
26 #include <linux/spinlock.h>
28 #define UNIPHIER_AIDET_NR_IRQS 256
30 #define UNIPHIER_AIDET_DETCONF 0x04 /* inverter register base */
32 struct uniphier_aidet_priv
{
33 struct irq_domain
*domain
;
34 void __iomem
*reg_base
;
36 u32 saved_vals
[UNIPHIER_AIDET_NR_IRQS
/ 32];
39 static void uniphier_aidet_reg_update(struct uniphier_aidet_priv
*priv
,
40 unsigned int reg
, u32 mask
, u32 val
)
45 spin_lock_irqsave(&priv
->lock
, flags
);
46 tmp
= readl_relaxed(priv
->reg_base
+ reg
);
49 writel_relaxed(tmp
, priv
->reg_base
+ reg
);
50 spin_unlock_irqrestore(&priv
->lock
, flags
);
53 static void uniphier_aidet_detconf_update(struct uniphier_aidet_priv
*priv
,
54 unsigned long index
, unsigned int val
)
59 reg
= UNIPHIER_AIDET_DETCONF
+ index
/ 32 * 4;
60 mask
= BIT(index
% 32);
62 uniphier_aidet_reg_update(priv
, reg
, mask
, val
? mask
: 0);
65 static int uniphier_aidet_irq_set_type(struct irq_data
*data
, unsigned int type
)
67 struct uniphier_aidet_priv
*priv
= data
->chip_data
;
70 /* enable inverter for active low triggers */
72 case IRQ_TYPE_EDGE_RISING
:
73 case IRQ_TYPE_LEVEL_HIGH
:
76 case IRQ_TYPE_EDGE_FALLING
:
78 type
= IRQ_TYPE_EDGE_RISING
;
80 case IRQ_TYPE_LEVEL_LOW
:
82 type
= IRQ_TYPE_LEVEL_HIGH
;
88 uniphier_aidet_detconf_update(priv
, data
->hwirq
, val
);
90 return irq_chip_set_type_parent(data
, type
);
93 static struct irq_chip uniphier_aidet_irq_chip
= {
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
,
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
)
107 if (WARN_ON(fwspec
->param_count
< 2))
110 *out_hwirq
= fwspec
->param
[0];
111 *out_type
= fwspec
->param
[1] & IRQ_TYPE_SENSE_MASK
;
116 static int uniphier_aidet_domain_alloc(struct irq_domain
*domain
,
117 unsigned int virq
, unsigned int nr_irqs
,
120 struct irq_fwspec parent_fwspec
;
121 irq_hw_number_t hwirq
;
128 ret
= uniphier_aidet_domain_translate(domain
, arg
, &hwirq
, &type
);
133 case IRQ_TYPE_EDGE_RISING
:
134 case IRQ_TYPE_LEVEL_HIGH
:
136 case IRQ_TYPE_EDGE_FALLING
:
137 type
= IRQ_TYPE_EDGE_RISING
;
139 case IRQ_TYPE_LEVEL_LOW
:
140 type
= IRQ_TYPE_LEVEL_HIGH
;
146 if (hwirq
>= UNIPHIER_AIDET_NR_IRQS
)
149 ret
= irq_domain_set_hwirq_and_chip(domain
, virq
, hwirq
,
150 &uniphier_aidet_irq_chip
,
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
;
162 return irq_domain_alloc_irqs_parent(domain
, virq
, 1, &parent_fwspec
);
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
,
171 static int uniphier_aidet_probe(struct platform_device
*pdev
)
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
;
179 parent_np
= of_irq_find_parent(dev
->of_node
);
183 parent_domain
= irq_find_host(parent_np
);
184 of_node_put(parent_np
);
186 return -EPROBE_DEFER
;
188 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
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
);
197 spin_lock_init(&priv
->lock
);
199 priv
->domain
= irq_domain_create_hierarchy(
201 UNIPHIER_AIDET_NR_IRQS
,
202 of_node_to_fwnode(dev
->of_node
),
203 &uniphier_aidet_domain_ops
, priv
);
207 platform_set_drvdata(pdev
, priv
);
212 static int __maybe_unused
uniphier_aidet_suspend(struct device
*dev
)
214 struct uniphier_aidet_priv
*priv
= dev_get_drvdata(dev
);
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);
224 static int __maybe_unused
uniphier_aidet_resume(struct device
*dev
)
226 struct uniphier_aidet_priv
*priv
= dev_get_drvdata(dev
);
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);
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
)
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" },
253 static struct platform_driver uniphier_aidet_driver
= {
254 .probe
= uniphier_aidet_probe
,
256 .name
= "uniphier-aidet",
257 .of_match_table
= uniphier_aidet_match
,
258 .pm
= &uniphier_aidet_pm_ops
,
261 builtin_platform_driver(uniphier_aidet_driver
);