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/interrupt.h> |
11 | #include <linux/irq.h> |
12 | #include <linux/irqdomain.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/pm_runtime.h> |
15 | #include <linux/regmap.h> |
16 | #include <linux/slab.h> |
17 | #include <linux/irqchip/irq-madera.h> |
18 | #include <linux/mfd/madera/core.h> |
19 | #include <linux/mfd/madera/pdata.h> |
20 | #include <linux/mfd/madera/registers.h> |
21 | |
22 | #define MADERA_IRQ(_irq, _reg) \ |
23 | [MADERA_IRQ_ ## _irq] = { \ |
24 | .reg_offset = (_reg) - MADERA_IRQ1_STATUS_2, \ |
25 | .mask = MADERA_ ## _irq ## _EINT1 \ |
26 | } |
27 | |
28 | /* Mappings are the same for all Madera codecs */ |
29 | static const struct regmap_irq madera_irqs[MADERA_NUM_IRQ] = { |
30 | MADERA_IRQ(FLL1_LOCK, MADERA_IRQ1_STATUS_2), |
31 | MADERA_IRQ(FLL2_LOCK, MADERA_IRQ1_STATUS_2), |
32 | MADERA_IRQ(FLL3_LOCK, MADERA_IRQ1_STATUS_2), |
33 | MADERA_IRQ(FLLAO_LOCK, MADERA_IRQ1_STATUS_2), |
34 | |
35 | MADERA_IRQ(MICDET1, MADERA_IRQ1_STATUS_6), |
36 | MADERA_IRQ(MICDET2, MADERA_IRQ1_STATUS_6), |
37 | MADERA_IRQ(HPDET, MADERA_IRQ1_STATUS_6), |
38 | |
39 | MADERA_IRQ(MICD_CLAMP_RISE, MADERA_IRQ1_STATUS_7), |
40 | MADERA_IRQ(MICD_CLAMP_FALL, MADERA_IRQ1_STATUS_7), |
41 | MADERA_IRQ(JD1_RISE, MADERA_IRQ1_STATUS_7), |
42 | MADERA_IRQ(JD1_FALL, MADERA_IRQ1_STATUS_7), |
43 | |
44 | MADERA_IRQ(ASRC2_IN1_LOCK, MADERA_IRQ1_STATUS_9), |
45 | MADERA_IRQ(ASRC2_IN2_LOCK, MADERA_IRQ1_STATUS_9), |
46 | MADERA_IRQ(ASRC1_IN1_LOCK, MADERA_IRQ1_STATUS_9), |
47 | MADERA_IRQ(ASRC1_IN2_LOCK, MADERA_IRQ1_STATUS_9), |
48 | MADERA_IRQ(DRC2_SIG_DET, MADERA_IRQ1_STATUS_9), |
49 | MADERA_IRQ(DRC1_SIG_DET, MADERA_IRQ1_STATUS_9), |
50 | |
51 | MADERA_IRQ(DSP_IRQ1, MADERA_IRQ1_STATUS_11), |
52 | MADERA_IRQ(DSP_IRQ2, MADERA_IRQ1_STATUS_11), |
53 | MADERA_IRQ(DSP_IRQ3, MADERA_IRQ1_STATUS_11), |
54 | MADERA_IRQ(DSP_IRQ4, MADERA_IRQ1_STATUS_11), |
55 | MADERA_IRQ(DSP_IRQ5, MADERA_IRQ1_STATUS_11), |
56 | MADERA_IRQ(DSP_IRQ6, MADERA_IRQ1_STATUS_11), |
57 | MADERA_IRQ(DSP_IRQ7, MADERA_IRQ1_STATUS_11), |
58 | MADERA_IRQ(DSP_IRQ8, MADERA_IRQ1_STATUS_11), |
59 | MADERA_IRQ(DSP_IRQ9, MADERA_IRQ1_STATUS_11), |
60 | MADERA_IRQ(DSP_IRQ10, MADERA_IRQ1_STATUS_11), |
61 | MADERA_IRQ(DSP_IRQ11, MADERA_IRQ1_STATUS_11), |
62 | MADERA_IRQ(DSP_IRQ12, MADERA_IRQ1_STATUS_11), |
63 | MADERA_IRQ(DSP_IRQ13, MADERA_IRQ1_STATUS_11), |
64 | MADERA_IRQ(DSP_IRQ14, MADERA_IRQ1_STATUS_11), |
65 | MADERA_IRQ(DSP_IRQ15, MADERA_IRQ1_STATUS_11), |
66 | MADERA_IRQ(DSP_IRQ16, MADERA_IRQ1_STATUS_11), |
67 | |
68 | MADERA_IRQ(HP3R_SC, MADERA_IRQ1_STATUS_12), |
69 | MADERA_IRQ(HP3L_SC, MADERA_IRQ1_STATUS_12), |
70 | MADERA_IRQ(HP2R_SC, MADERA_IRQ1_STATUS_12), |
71 | MADERA_IRQ(HP2L_SC, MADERA_IRQ1_STATUS_12), |
72 | MADERA_IRQ(HP1R_SC, MADERA_IRQ1_STATUS_12), |
73 | MADERA_IRQ(HP1L_SC, MADERA_IRQ1_STATUS_12), |
74 | |
75 | MADERA_IRQ(SPK_OVERHEAT_WARN, MADERA_IRQ1_STATUS_15), |
76 | MADERA_IRQ(SPK_OVERHEAT, MADERA_IRQ1_STATUS_15), |
77 | |
78 | MADERA_IRQ(DSP1_BUS_ERR, MADERA_IRQ1_STATUS_33), |
79 | MADERA_IRQ(DSP2_BUS_ERR, MADERA_IRQ1_STATUS_33), |
80 | MADERA_IRQ(DSP3_BUS_ERR, MADERA_IRQ1_STATUS_33), |
81 | MADERA_IRQ(DSP4_BUS_ERR, MADERA_IRQ1_STATUS_33), |
82 | MADERA_IRQ(DSP5_BUS_ERR, MADERA_IRQ1_STATUS_33), |
83 | MADERA_IRQ(DSP6_BUS_ERR, MADERA_IRQ1_STATUS_33), |
84 | MADERA_IRQ(DSP7_BUS_ERR, MADERA_IRQ1_STATUS_33), |
85 | }; |
86 | |
87 | static const struct regmap_irq_chip madera_irq_chip = { |
88 | .name = "madera IRQ" , |
89 | .status_base = MADERA_IRQ1_STATUS_2, |
90 | .mask_base = MADERA_IRQ1_MASK_2, |
91 | .ack_base = MADERA_IRQ1_STATUS_2, |
92 | .runtime_pm = true, |
93 | .num_regs = 32, |
94 | .irqs = madera_irqs, |
95 | .num_irqs = ARRAY_SIZE(madera_irqs), |
96 | }; |
97 | |
98 | #ifdef CONFIG_PM_SLEEP |
99 | static int madera_suspend(struct device *dev) |
100 | { |
101 | struct madera *madera = dev_get_drvdata(dev: dev->parent); |
102 | |
103 | dev_dbg(madera->irq_dev, "Suspend, disabling IRQ\n" ); |
104 | |
105 | /* |
106 | * A runtime resume would be needed to access the chip interrupt |
107 | * controller but runtime pm doesn't function during suspend. |
108 | * Temporarily disable interrupts until we reach suspend_noirq state. |
109 | */ |
110 | disable_irq(irq: madera->irq); |
111 | |
112 | return 0; |
113 | } |
114 | |
115 | static int madera_suspend_noirq(struct device *dev) |
116 | { |
117 | struct madera *madera = dev_get_drvdata(dev: dev->parent); |
118 | |
119 | dev_dbg(madera->irq_dev, "No IRQ suspend, reenabling IRQ\n" ); |
120 | |
121 | /* Re-enable interrupts to service wakeup interrupts from the chip */ |
122 | enable_irq(irq: madera->irq); |
123 | |
124 | return 0; |
125 | } |
126 | |
127 | static int madera_resume_noirq(struct device *dev) |
128 | { |
129 | struct madera *madera = dev_get_drvdata(dev: dev->parent); |
130 | |
131 | dev_dbg(madera->irq_dev, "No IRQ resume, disabling IRQ\n" ); |
132 | |
133 | /* |
134 | * We can't handle interrupts until runtime pm is available again. |
135 | * Disable them temporarily. |
136 | */ |
137 | disable_irq(irq: madera->irq); |
138 | |
139 | return 0; |
140 | } |
141 | |
142 | static int madera_resume(struct device *dev) |
143 | { |
144 | struct madera *madera = dev_get_drvdata(dev: dev->parent); |
145 | |
146 | dev_dbg(madera->irq_dev, "Resume, reenabling IRQ\n" ); |
147 | |
148 | /* Interrupts can now be handled */ |
149 | enable_irq(irq: madera->irq); |
150 | |
151 | return 0; |
152 | } |
153 | #endif |
154 | |
155 | static const struct dev_pm_ops madera_irq_pm_ops = { |
156 | SET_SYSTEM_SLEEP_PM_OPS(madera_suspend, madera_resume) |
157 | SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(madera_suspend_noirq, |
158 | madera_resume_noirq) |
159 | }; |
160 | |
161 | static int madera_irq_probe(struct platform_device *pdev) |
162 | { |
163 | struct madera *madera = dev_get_drvdata(dev: pdev->dev.parent); |
164 | struct irq_data *irq_data; |
165 | unsigned int irq_flags = 0; |
166 | int ret; |
167 | |
168 | dev_dbg(&pdev->dev, "probe\n" ); |
169 | |
170 | /* |
171 | * Read the flags from the interrupt controller if not specified |
172 | * by pdata |
173 | */ |
174 | irq_flags = madera->pdata.irq_flags; |
175 | if (!irq_flags) { |
176 | irq_data = irq_get_irq_data(irq: madera->irq); |
177 | if (!irq_data) { |
178 | dev_err(&pdev->dev, "Invalid IRQ: %d\n" , madera->irq); |
179 | return -EINVAL; |
180 | } |
181 | |
182 | irq_flags = irqd_get_trigger_type(d: irq_data); |
183 | |
184 | /* Codec defaults to trigger low, use this if no flags given */ |
185 | if (irq_flags == IRQ_TYPE_NONE) |
186 | irq_flags = IRQF_TRIGGER_LOW; |
187 | } |
188 | |
189 | if (irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { |
190 | dev_err(&pdev->dev, "Host interrupt not level-triggered\n" ); |
191 | return -EINVAL; |
192 | } |
193 | |
194 | /* |
195 | * The silicon always starts at active-low, check if we need to |
196 | * switch to active-high. |
197 | */ |
198 | if (irq_flags & IRQF_TRIGGER_HIGH) { |
199 | ret = regmap_update_bits(map: madera->regmap, MADERA_IRQ1_CTRL, |
200 | MADERA_IRQ_POL_MASK, val: 0); |
201 | if (ret) { |
202 | dev_err(&pdev->dev, |
203 | "Failed to set IRQ polarity: %d\n" , ret); |
204 | return ret; |
205 | } |
206 | } |
207 | |
208 | /* |
209 | * NOTE: regmap registers this against the OF node of the parent of |
210 | * the regmap - that is, against the mfd driver |
211 | */ |
212 | ret = regmap_add_irq_chip(map: madera->regmap, irq: madera->irq, IRQF_ONESHOT, irq_base: 0, |
213 | chip: &madera_irq_chip, data: &madera->irq_data); |
214 | if (ret) { |
215 | dev_err(&pdev->dev, "add_irq_chip failed: %d\n" , ret); |
216 | return ret; |
217 | } |
218 | |
219 | /* Save dev in parent MFD struct so it is accessible to siblings */ |
220 | madera->irq_dev = &pdev->dev; |
221 | |
222 | return 0; |
223 | } |
224 | |
225 | static void madera_irq_remove(struct platform_device *pdev) |
226 | { |
227 | struct madera *madera = dev_get_drvdata(dev: pdev->dev.parent); |
228 | |
229 | /* |
230 | * The IRQ is disabled by the parent MFD driver before |
231 | * it starts cleaning up all child drivers |
232 | */ |
233 | madera->irq_dev = NULL; |
234 | regmap_del_irq_chip(irq: madera->irq, data: madera->irq_data); |
235 | } |
236 | |
237 | static struct platform_driver madera_irq_driver = { |
238 | .probe = madera_irq_probe, |
239 | .remove_new = madera_irq_remove, |
240 | .driver = { |
241 | .name = "madera-irq" , |
242 | .pm = &madera_irq_pm_ops, |
243 | } |
244 | }; |
245 | module_platform_driver(madera_irq_driver); |
246 | |
247 | MODULE_SOFTDEP("pre: madera" ); |
248 | MODULE_DESCRIPTION("Madera IRQ driver" ); |
249 | MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>" ); |
250 | MODULE_LICENSE("GPL v2" ); |
251 | |