summaryrefslogtreecommitdiffstats
path: root/drivers/irqchip/irq-madera.c
diff options
context:
space:
mode:
authorRichard Fitzgerald <rf@opensource.cirrus.com>2018-12-14 09:44:16 -0500
committerMarc Zyngier <marc.zyngier@arm.com>2018-12-18 09:06:56 -0500
commitda0abe1a04110491697ca9ff146e1107f40b4808 (patch)
treec608934cdb3160fc67ffad2c6c4ed54a10c1cef2 /drivers/irqchip/irq-madera.c
parent8ca66b7ccf3c9aeb3b6796967bf48f776bfd5523 (diff)
irqchip: Add driver for Cirrus Logic Madera codecs
The Cirrus Logic Madera codecs (Cirrus Logic CS47L35/85/90/91 and WM1840) are highly complex devices containing up to 7 programmable DSPs and many other internal sources of interrupts plus a number of GPIOs that can be used as interrupt inputs. The large number (>150) of internal interrupt sources are managed by an on-board interrupt controller. This driver provides the handling for the interrupt controller. As the codec is accessed via regmap, we can make use of the generic IRQ functionality from regmap to do most of the work. Only around half of the possible interrupt source are currently of interest from the driver so only this subset is defined. Others can be added in future if needed. The KConfig options are not user-configurable because this driver is mandatory so is automatically included when the parent MFD driver is selected. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com> Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Diffstat (limited to 'drivers/irqchip/irq-madera.c')
-rw-r--r--drivers/irqchip/irq-madera.c256
1 files changed, 256 insertions, 0 deletions
diff --git a/drivers/irqchip/irq-madera.c b/drivers/irqchip/irq-madera.c
new file mode 100644
index 000000000000..e9256dee1a45
--- /dev/null
+++ b/drivers/irqchip/irq-madera.c
@@ -0,0 +1,256 @@
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Interrupt support for Cirrus Logic Madera codecs
4 *
5 * Copyright (C) 2015-2018 Cirrus Logic, Inc. and
6 * Cirrus Logic International Semiconductor Ltd.
7 */
8
9#include <linux/module.h>
10#include <linux/gpio.h>
11#include <linux/interrupt.h>
12#include <linux/irq.h>
13#include <linux/irqdomain.h>
14#include <linux/pm_runtime.h>
15#include <linux/regmap.h>
16#include <linux/slab.h>
17#include <linux/of.h>
18#include <linux/of_device.h>
19#include <linux/of_gpio.h>
20#include <linux/of_irq.h>
21#include <linux/irqchip/irq-madera.h>
22#include <linux/mfd/madera/core.h>
23#include <linux/mfd/madera/pdata.h>
24#include <linux/mfd/madera/registers.h>
25
26#define MADERA_IRQ(_irq, _reg) \
27 [MADERA_IRQ_ ## _irq] = { \
28 .reg_offset = (_reg) - MADERA_IRQ1_STATUS_2, \
29 .mask = MADERA_ ## _irq ## _EINT1 \
30 }
31
32/* Mappings are the same for all Madera codecs */
33static const struct regmap_irq madera_irqs[MADERA_NUM_IRQ] = {
34 MADERA_IRQ(FLL1_LOCK, MADERA_IRQ1_STATUS_2),
35 MADERA_IRQ(FLL2_LOCK, MADERA_IRQ1_STATUS_2),
36 MADERA_IRQ(FLL3_LOCK, MADERA_IRQ1_STATUS_2),
37 MADERA_IRQ(FLLAO_LOCK, MADERA_IRQ1_STATUS_2),
38
39 MADERA_IRQ(MICDET1, MADERA_IRQ1_STATUS_6),
40 MADERA_IRQ(MICDET2, MADERA_IRQ1_STATUS_6),
41 MADERA_IRQ(HPDET, MADERA_IRQ1_STATUS_6),
42
43 MADERA_IRQ(MICD_CLAMP_RISE, MADERA_IRQ1_STATUS_7),
44 MADERA_IRQ(MICD_CLAMP_FALL, MADERA_IRQ1_STATUS_7),
45 MADERA_IRQ(JD1_RISE, MADERA_IRQ1_STATUS_7),
46 MADERA_IRQ(JD1_FALL, MADERA_IRQ1_STATUS_7),
47
48 MADERA_IRQ(ASRC2_IN1_LOCK, MADERA_IRQ1_STATUS_9),
49 MADERA_IRQ(ASRC2_IN2_LOCK, MADERA_IRQ1_STATUS_9),
50 MADERA_IRQ(ASRC1_IN1_LOCK, MADERA_IRQ1_STATUS_9),
51 MADERA_IRQ(ASRC1_IN2_LOCK, MADERA_IRQ1_STATUS_9),
52 MADERA_IRQ(DRC2_SIG_DET, MADERA_IRQ1_STATUS_9),
53 MADERA_IRQ(DRC1_SIG_DET, MADERA_IRQ1_STATUS_9),
54
55 MADERA_IRQ(DSP_IRQ1, MADERA_IRQ1_STATUS_11),
56 MADERA_IRQ(DSP_IRQ2, MADERA_IRQ1_STATUS_11),
57 MADERA_IRQ(DSP_IRQ3, MADERA_IRQ1_STATUS_11),
58 MADERA_IRQ(DSP_IRQ4, MADERA_IRQ1_STATUS_11),
59 MADERA_IRQ(DSP_IRQ5, MADERA_IRQ1_STATUS_11),
60 MADERA_IRQ(DSP_IRQ6, MADERA_IRQ1_STATUS_11),
61 MADERA_IRQ(DSP_IRQ7, MADERA_IRQ1_STATUS_11),
62 MADERA_IRQ(DSP_IRQ8, MADERA_IRQ1_STATUS_11),
63 MADERA_IRQ(DSP_IRQ9, MADERA_IRQ1_STATUS_11),
64 MADERA_IRQ(DSP_IRQ10, MADERA_IRQ1_STATUS_11),
65 MADERA_IRQ(DSP_IRQ11, MADERA_IRQ1_STATUS_11),
66 MADERA_IRQ(DSP_IRQ12, MADERA_IRQ1_STATUS_11),
67 MADERA_IRQ(DSP_IRQ13, MADERA_IRQ1_STATUS_11),
68 MADERA_IRQ(DSP_IRQ14, MADERA_IRQ1_STATUS_11),
69 MADERA_IRQ(DSP_IRQ15, MADERA_IRQ1_STATUS_11),
70 MADERA_IRQ(DSP_IRQ16, MADERA_IRQ1_STATUS_11),
71
72 MADERA_IRQ(HP3R_SC, MADERA_IRQ1_STATUS_12),
73 MADERA_IRQ(HP3L_SC, MADERA_IRQ1_STATUS_12),
74 MADERA_IRQ(HP2R_SC, MADERA_IRQ1_STATUS_12),
75 MADERA_IRQ(HP2L_SC, MADERA_IRQ1_STATUS_12),
76 MADERA_IRQ(HP1R_SC, MADERA_IRQ1_STATUS_12),
77 MADERA_IRQ(HP1L_SC, MADERA_IRQ1_STATUS_12),
78
79 MADERA_IRQ(SPK_OVERHEAT_WARN, MADERA_IRQ1_STATUS_15),
80 MADERA_IRQ(SPK_OVERHEAT, MADERA_IRQ1_STATUS_15),
81
82 MADERA_IRQ(DSP1_BUS_ERR, MADERA_IRQ1_STATUS_33),
83 MADERA_IRQ(DSP2_BUS_ERR, MADERA_IRQ1_STATUS_33),
84 MADERA_IRQ(DSP3_BUS_ERR, MADERA_IRQ1_STATUS_33),
85 MADERA_IRQ(DSP4_BUS_ERR, MADERA_IRQ1_STATUS_33),
86 MADERA_IRQ(DSP5_BUS_ERR, MADERA_IRQ1_STATUS_33),
87 MADERA_IRQ(DSP6_BUS_ERR, MADERA_IRQ1_STATUS_33),
88 MADERA_IRQ(DSP7_BUS_ERR, MADERA_IRQ1_STATUS_33),
89};
90
91static const struct regmap_irq_chip madera_irq_chip = {
92 .name = "madera IRQ",
93 .status_base = MADERA_IRQ1_STATUS_2,
94 .mask_base = MADERA_IRQ1_MASK_2,
95 .ack_base = MADERA_IRQ1_STATUS_2,
96 .runtime_pm = true,
97 .num_regs = 32,
98 .irqs = madera_irqs,
99 .num_irqs = ARRAY_SIZE(madera_irqs),
100};
101
102#ifdef CONFIG_PM_SLEEP
103static int madera_suspend(struct device *dev)
104{
105 struct madera *madera = dev_get_drvdata(dev->parent);
106
107 dev_dbg(madera->irq_dev, "Suspend, disabling IRQ\n");
108
109 /*
110 * A runtime resume would be needed to access the chip interrupt
111 * controller but runtime pm doesn't function during suspend.
112 * Temporarily disable interrupts until we reach suspend_noirq state.
113 */
114 disable_irq(madera->irq);
115
116 return 0;
117}
118
119static int madera_suspend_noirq(struct device *dev)
120{
121 struct madera *madera = dev_get_drvdata(dev->parent);
122
123 dev_dbg(madera->irq_dev, "No IRQ suspend, reenabling IRQ\n");
124
125 /* Re-enable interrupts to service wakeup interrupts from the chip */
126 enable_irq(madera->irq);
127
128 return 0;
129}
130
131static int madera_resume_noirq(struct device *dev)
132{
133 struct madera *madera = dev_get_drvdata(dev->parent);
134
135 dev_dbg(madera->irq_dev, "No IRQ resume, disabling IRQ\n");
136
137 /*
138 * We can't handle interrupts until runtime pm is available again.
139 * Disable them temporarily.
140 */
141 disable_irq(madera->irq);
142
143 return 0;
144}
145
146static int madera_resume(struct device *dev)
147{
148 struct madera *madera = dev_get_drvdata(dev->parent);
149
150 dev_dbg(madera->irq_dev, "Resume, reenabling IRQ\n");
151
152 /* Interrupts can now be handled */
153 enable_irq(madera->irq);
154
155 return 0;
156}
157#endif
158
159static const struct dev_pm_ops madera_irq_pm_ops = {
160 SET_SYSTEM_SLEEP_PM_OPS(madera_suspend, madera_resume)
161 SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(madera_suspend_noirq,
162 madera_resume_noirq)
163};
164
165static int madera_irq_probe(struct platform_device *pdev)
166{
167 struct madera *madera = dev_get_drvdata(pdev->dev.parent);
168 struct irq_data *irq_data;
169 unsigned int irq_flags = 0;
170 int ret;
171
172 dev_dbg(&pdev->dev, "probe\n");
173
174 /*
175 * Read the flags from the interrupt controller if not specified
176 * by pdata
177 */
178 irq_flags = madera->pdata.irq_flags;
179 if (!irq_flags) {
180 irq_data = irq_get_irq_data(madera->irq);
181 if (!irq_data) {
182 dev_err(&pdev->dev, "Invalid IRQ: %d\n", madera->irq);
183 return -EINVAL;
184 }
185
186 irq_flags = irqd_get_trigger_type(irq_data);
187
188 /* Codec defaults to trigger low, use this if no flags given */
189 if (irq_flags == IRQ_TYPE_NONE)
190 irq_flags = IRQF_TRIGGER_LOW;
191 }
192
193 if (irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) {
194 dev_err(&pdev->dev, "Host interrupt not level-triggered\n");
195 return -EINVAL;
196 }
197
198 /*
199 * The silicon always starts at active-low, check if we need to
200 * switch to active-high.
201 */
202 if (irq_flags & IRQF_TRIGGER_HIGH) {
203 ret = regmap_update_bits(madera->regmap, MADERA_IRQ1_CTRL,
204 MADERA_IRQ_POL_MASK, 0);
205 if (ret) {
206 dev_err(&pdev->dev,
207 "Failed to set IRQ polarity: %d\n", ret);
208 return ret;
209 }
210 }
211
212 /*
213 * NOTE: regmap registers this against the OF node of the parent of
214 * the regmap - that is, against the mfd driver
215 */
216 ret = regmap_add_irq_chip(madera->regmap, madera->irq, IRQF_ONESHOT, 0,
217 &madera_irq_chip, &madera->irq_data);
218 if (ret) {
219 dev_err(&pdev->dev, "add_irq_chip failed: %d\n", ret);
220 return ret;
221 }
222
223 /* Save dev in parent MFD struct so it is accessible to siblings */
224 madera->irq_dev = &pdev->dev;
225
226 return 0;
227}
228
229static int madera_irq_remove(struct platform_device *pdev)
230{
231 struct madera *madera = dev_get_drvdata(pdev->dev.parent);
232
233 /*
234 * The IRQ is disabled by the parent MFD driver before
235 * it starts cleaning up all child drivers
236 */
237 madera->irq_dev = NULL;
238 regmap_del_irq_chip(madera->irq, madera->irq_data);
239
240 return 0;
241}
242
243static struct platform_driver madera_irq_driver = {
244 .probe = &madera_irq_probe,
245 .remove = &madera_irq_remove,
246 .driver = {
247 .name = "madera-irq",
248 .pm = &madera_irq_pm_ops,
249 }
250};
251module_platform_driver(madera_irq_driver);
252
253MODULE_SOFTDEP("pre: madera");
254MODULE_DESCRIPTION("Madera IRQ driver");
255MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
256MODULE_LICENSE("GPL v2");