1 // SPDX-License-Identifier: GPL-2.0-only
3 * Driver for UniPhier AIDET (ARM Interrupt Detector)
5 * Copyright (C) 2017 Socionext Inc.
6 * Author: Masahiro Yamada <yamada.masahiro@socionext.com>
9 #include <linux/bitops.h>
10 #include <linux/init.h>
11 #include <linux/irq.h>
12 #include <linux/irqdomain.h>
13 #include <linux/kernel.h>
15 #include <linux/of_irq.h>
16 #include <linux/platform_device.h>
17 #include <linux/spinlock.h>
19 #define UNIPHIER_AIDET_NR_IRQS 256
21 #define UNIPHIER_AIDET_DETCONF 0x04 /* inverter register base */
23 struct uniphier_aidet_priv
{
24 struct irq_domain
*domain
;
25 void __iomem
*reg_base
;
27 u32 saved_vals
[UNIPHIER_AIDET_NR_IRQS
/ 32];
30 static void uniphier_aidet_reg_update(struct uniphier_aidet_priv
*priv
,
31 unsigned int reg
, u32 mask
, u32 val
)
36 spin_lock_irqsave(&priv
->lock
, flags
);
37 tmp
= readl_relaxed(priv
->reg_base
+ reg
);
40 writel_relaxed(tmp
, priv
->reg_base
+ reg
);
41 spin_unlock_irqrestore(&priv
->lock
, flags
);
44 static void uniphier_aidet_detconf_update(struct uniphier_aidet_priv
*priv
,
45 unsigned long index
, unsigned int val
)
50 reg
= UNIPHIER_AIDET_DETCONF
+ index
/ 32 * 4;
51 mask
= BIT(index
% 32);
53 uniphier_aidet_reg_update(priv
, reg
, mask
, val
? mask
: 0);
56 static int uniphier_aidet_irq_set_type(struct irq_data
*data
, unsigned int type
)
58 struct uniphier_aidet_priv
*priv
= data
->chip_data
;
61 /* enable inverter for active low triggers */
63 case IRQ_TYPE_EDGE_RISING
:
64 case IRQ_TYPE_LEVEL_HIGH
:
67 case IRQ_TYPE_EDGE_FALLING
:
69 type
= IRQ_TYPE_EDGE_RISING
;
71 case IRQ_TYPE_LEVEL_LOW
:
73 type
= IRQ_TYPE_LEVEL_HIGH
;
79 uniphier_aidet_detconf_update(priv
, data
->hwirq
, val
);
81 return irq_chip_set_type_parent(data
, type
);
84 static struct irq_chip uniphier_aidet_irq_chip
= {
86 .irq_mask
= irq_chip_mask_parent
,
87 .irq_unmask
= irq_chip_unmask_parent
,
88 .irq_eoi
= irq_chip_eoi_parent
,
89 .irq_set_affinity
= irq_chip_set_affinity_parent
,
90 .irq_set_type
= uniphier_aidet_irq_set_type
,
93 static int uniphier_aidet_domain_translate(struct irq_domain
*domain
,
94 struct irq_fwspec
*fwspec
,
95 unsigned long *out_hwirq
,
96 unsigned int *out_type
)
98 if (WARN_ON(fwspec
->param_count
< 2))
101 *out_hwirq
= fwspec
->param
[0];
102 *out_type
= fwspec
->param
[1] & IRQ_TYPE_SENSE_MASK
;
107 static int uniphier_aidet_domain_alloc(struct irq_domain
*domain
,
108 unsigned int virq
, unsigned int nr_irqs
,
111 struct irq_fwspec parent_fwspec
;
112 irq_hw_number_t hwirq
;
119 ret
= uniphier_aidet_domain_translate(domain
, arg
, &hwirq
, &type
);
124 case IRQ_TYPE_EDGE_RISING
:
125 case IRQ_TYPE_LEVEL_HIGH
:
127 case IRQ_TYPE_EDGE_FALLING
:
128 type
= IRQ_TYPE_EDGE_RISING
;
130 case IRQ_TYPE_LEVEL_LOW
:
131 type
= IRQ_TYPE_LEVEL_HIGH
;
137 if (hwirq
>= UNIPHIER_AIDET_NR_IRQS
)
140 ret
= irq_domain_set_hwirq_and_chip(domain
, virq
, hwirq
,
141 &uniphier_aidet_irq_chip
,
147 parent_fwspec
.fwnode
= domain
->parent
->fwnode
;
148 parent_fwspec
.param_count
= 3;
149 parent_fwspec
.param
[0] = 0; /* SPI */
150 parent_fwspec
.param
[1] = hwirq
;
151 parent_fwspec
.param
[2] = type
;
153 return irq_domain_alloc_irqs_parent(domain
, virq
, 1, &parent_fwspec
);
156 static const struct irq_domain_ops uniphier_aidet_domain_ops
= {
157 .alloc
= uniphier_aidet_domain_alloc
,
158 .free
= irq_domain_free_irqs_common
,
159 .translate
= uniphier_aidet_domain_translate
,
162 static int uniphier_aidet_probe(struct platform_device
*pdev
)
164 struct device
*dev
= &pdev
->dev
;
165 struct device_node
*parent_np
;
166 struct irq_domain
*parent_domain
;
167 struct uniphier_aidet_priv
*priv
;
169 parent_np
= of_irq_find_parent(dev
->of_node
);
173 parent_domain
= irq_find_host(parent_np
);
174 of_node_put(parent_np
);
176 return -EPROBE_DEFER
;
178 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
182 priv
->reg_base
= devm_platform_ioremap_resource(pdev
, 0);
183 if (IS_ERR(priv
->reg_base
))
184 return PTR_ERR(priv
->reg_base
);
186 spin_lock_init(&priv
->lock
);
188 priv
->domain
= irq_domain_create_hierarchy(
190 UNIPHIER_AIDET_NR_IRQS
,
191 of_node_to_fwnode(dev
->of_node
),
192 &uniphier_aidet_domain_ops
, priv
);
196 platform_set_drvdata(pdev
, priv
);
201 static int __maybe_unused
uniphier_aidet_suspend(struct device
*dev
)
203 struct uniphier_aidet_priv
*priv
= dev_get_drvdata(dev
);
206 for (i
= 0; i
< ARRAY_SIZE(priv
->saved_vals
); i
++)
207 priv
->saved_vals
[i
] = readl_relaxed(
208 priv
->reg_base
+ UNIPHIER_AIDET_DETCONF
+ i
* 4);
213 static int __maybe_unused
uniphier_aidet_resume(struct device
*dev
)
215 struct uniphier_aidet_priv
*priv
= dev_get_drvdata(dev
);
218 for (i
= 0; i
< ARRAY_SIZE(priv
->saved_vals
); i
++)
219 writel_relaxed(priv
->saved_vals
[i
],
220 priv
->reg_base
+ UNIPHIER_AIDET_DETCONF
+ i
* 4);
225 static const struct dev_pm_ops uniphier_aidet_pm_ops
= {
226 SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(uniphier_aidet_suspend
,
227 uniphier_aidet_resume
)
230 static const struct of_device_id uniphier_aidet_match
[] = {
231 { .compatible
= "socionext,uniphier-ld4-aidet" },
232 { .compatible
= "socionext,uniphier-pro4-aidet" },
233 { .compatible
= "socionext,uniphier-sld8-aidet" },
234 { .compatible
= "socionext,uniphier-pro5-aidet" },
235 { .compatible
= "socionext,uniphier-pxs2-aidet" },
236 { .compatible
= "socionext,uniphier-ld11-aidet" },
237 { .compatible
= "socionext,uniphier-ld20-aidet" },
238 { .compatible
= "socionext,uniphier-pxs3-aidet" },
239 { .compatible
= "socionext,uniphier-nx1-aidet" },
243 static struct platform_driver uniphier_aidet_driver
= {
244 .probe
= uniphier_aidet_probe
,
246 .name
= "uniphier-aidet",
247 .of_match_table
= uniphier_aidet_match
,
248 .pm
= &uniphier_aidet_pm_ops
,
251 builtin_platform_driver(uniphier_aidet_driver
);