Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Richard Fitzgerald and committed by
Marc Zyngier
da0abe1a 8ca66b7c

+394
+2
MAINTAINERS
··· 3611 3611 S: Supported 3612 3612 F: Documentation/devicetree/bindings/mfd/madera.txt 3613 3613 F: Documentation/devicetree/bindings/pinctrl/cirrus,madera-pinctrl.txt 3614 + F: include/linux/irqchip/irq-madera* 3614 3615 F: include/linux/mfd/madera/* 3615 3616 F: drivers/gpio/gpio-madera* 3617 + F: drivers/irqchip/irq-madera* 3616 3618 F: drivers/mfd/madera* 3617 3619 F: drivers/mfd/cs47l* 3618 3620 F: drivers/pinctrl/cirrus/*
+3
drivers/irqchip/Kconfig
··· 150 150 select GENERIC_IRQ_CHIP 151 151 select IRQ_DOMAIN 152 152 153 + config MADERA_IRQ 154 + tristate 155 + 153 156 config IRQ_MIPS_CPU 154 157 bool 155 158 select GENERIC_IRQ_CHIP
+1
drivers/irqchip/Makefile
··· 92 92 obj-$(CONFIG_CSKY_MPINTC) += irq-csky-mpintc.o 93 93 obj-$(CONFIG_CSKY_APB_INTC) += irq-csky-apb-intc.o 94 94 obj-$(CONFIG_SIFIVE_PLIC) += irq-sifive-plic.o 95 + obj-$(CONFIG_MADERA_IRQ) += irq-madera.o
+256
drivers/irqchip/irq-madera.c
··· 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 */ 33 + static 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 + 91 + static 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 103 + static 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 + 119 + static 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 + 131 + static 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 + 146 + static 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 + 159 + static 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 + 165 + static 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 + 229 + static 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 + 243 + static 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 + }; 251 + module_platform_driver(madera_irq_driver); 252 + 253 + MODULE_SOFTDEP("pre: madera"); 254 + MODULE_DESCRIPTION("Madera IRQ driver"); 255 + MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>"); 256 + MODULE_LICENSE("GPL v2");
+132
include/linux/irqchip/irq-madera.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * Interrupt support for Cirrus Logic Madera codecs 4 + * 5 + * Copyright (C) 2016-2018 Cirrus Logic, Inc. and 6 + * Cirrus Logic International Semiconductor Ltd. 7 + */ 8 + 9 + #ifndef IRQCHIP_MADERA_H 10 + #define IRQCHIP_MADERA_H 11 + 12 + #include <linux/interrupt.h> 13 + #include <linux/mfd/madera/core.h> 14 + 15 + #define MADERA_IRQ_FLL1_LOCK 0 16 + #define MADERA_IRQ_FLL2_LOCK 1 17 + #define MADERA_IRQ_FLL3_LOCK 2 18 + #define MADERA_IRQ_FLLAO_LOCK 3 19 + #define MADERA_IRQ_CLK_SYS_ERR 4 20 + #define MADERA_IRQ_CLK_ASYNC_ERR 5 21 + #define MADERA_IRQ_CLK_DSP_ERR 6 22 + #define MADERA_IRQ_HPDET 7 23 + #define MADERA_IRQ_MICDET1 8 24 + #define MADERA_IRQ_MICDET2 9 25 + #define MADERA_IRQ_JD1_RISE 10 26 + #define MADERA_IRQ_JD1_FALL 11 27 + #define MADERA_IRQ_JD2_RISE 12 28 + #define MADERA_IRQ_JD2_FALL 13 29 + #define MADERA_IRQ_MICD_CLAMP_RISE 14 30 + #define MADERA_IRQ_MICD_CLAMP_FALL 15 31 + #define MADERA_IRQ_DRC2_SIG_DET 16 32 + #define MADERA_IRQ_DRC1_SIG_DET 17 33 + #define MADERA_IRQ_ASRC1_IN1_LOCK 18 34 + #define MADERA_IRQ_ASRC1_IN2_LOCK 19 35 + #define MADERA_IRQ_ASRC2_IN1_LOCK 20 36 + #define MADERA_IRQ_ASRC2_IN2_LOCK 21 37 + #define MADERA_IRQ_DSP_IRQ1 22 38 + #define MADERA_IRQ_DSP_IRQ2 23 39 + #define MADERA_IRQ_DSP_IRQ3 24 40 + #define MADERA_IRQ_DSP_IRQ4 25 41 + #define MADERA_IRQ_DSP_IRQ5 26 42 + #define MADERA_IRQ_DSP_IRQ6 27 43 + #define MADERA_IRQ_DSP_IRQ7 28 44 + #define MADERA_IRQ_DSP_IRQ8 29 45 + #define MADERA_IRQ_DSP_IRQ9 30 46 + #define MADERA_IRQ_DSP_IRQ10 31 47 + #define MADERA_IRQ_DSP_IRQ11 32 48 + #define MADERA_IRQ_DSP_IRQ12 33 49 + #define MADERA_IRQ_DSP_IRQ13 34 50 + #define MADERA_IRQ_DSP_IRQ14 35 51 + #define MADERA_IRQ_DSP_IRQ15 36 52 + #define MADERA_IRQ_DSP_IRQ16 37 53 + #define MADERA_IRQ_HP1L_SC 38 54 + #define MADERA_IRQ_HP1R_SC 39 55 + #define MADERA_IRQ_HP2L_SC 40 56 + #define MADERA_IRQ_HP2R_SC 41 57 + #define MADERA_IRQ_HP3L_SC 42 58 + #define MADERA_IRQ_HP3R_SC 43 59 + #define MADERA_IRQ_SPKOUTL_SC 44 60 + #define MADERA_IRQ_SPKOUTR_SC 45 61 + #define MADERA_IRQ_HP1L_ENABLE_DONE 46 62 + #define MADERA_IRQ_HP1R_ENABLE_DONE 47 63 + #define MADERA_IRQ_HP2L_ENABLE_DONE 48 64 + #define MADERA_IRQ_HP2R_ENABLE_DONE 49 65 + #define MADERA_IRQ_HP3L_ENABLE_DONE 50 66 + #define MADERA_IRQ_HP3R_ENABLE_DONE 51 67 + #define MADERA_IRQ_SPKOUTL_ENABLE_DONE 52 68 + #define MADERA_IRQ_SPKOUTR_ENABLE_DONE 53 69 + #define MADERA_IRQ_SPK_SHUTDOWN 54 70 + #define MADERA_IRQ_SPK_OVERHEAT 55 71 + #define MADERA_IRQ_SPK_OVERHEAT_WARN 56 72 + #define MADERA_IRQ_GPIO1 57 73 + #define MADERA_IRQ_GPIO2 58 74 + #define MADERA_IRQ_GPIO3 59 75 + #define MADERA_IRQ_GPIO4 60 76 + #define MADERA_IRQ_GPIO5 61 77 + #define MADERA_IRQ_GPIO6 62 78 + #define MADERA_IRQ_GPIO7 63 79 + #define MADERA_IRQ_GPIO8 64 80 + #define MADERA_IRQ_DSP1_BUS_ERR 65 81 + #define MADERA_IRQ_DSP2_BUS_ERR 66 82 + #define MADERA_IRQ_DSP3_BUS_ERR 67 83 + #define MADERA_IRQ_DSP4_BUS_ERR 68 84 + #define MADERA_IRQ_DSP5_BUS_ERR 69 85 + #define MADERA_IRQ_DSP6_BUS_ERR 70 86 + #define MADERA_IRQ_DSP7_BUS_ERR 71 87 + 88 + #define MADERA_NUM_IRQ 72 89 + 90 + /* 91 + * These wrapper functions are for use by other child drivers of the 92 + * same parent MFD. 93 + */ 94 + static inline int madera_get_irq_mapping(struct madera *madera, int irq) 95 + { 96 + if (!madera->irq_dev) 97 + return -ENODEV; 98 + 99 + return regmap_irq_get_virq(madera->irq_data, irq); 100 + } 101 + 102 + static inline int madera_request_irq(struct madera *madera, int irq, 103 + const char *name, 104 + irq_handler_t handler, void *data) 105 + { 106 + irq = madera_get_irq_mapping(madera, irq); 107 + if (irq < 0) 108 + return irq; 109 + 110 + return request_threaded_irq(irq, NULL, handler, IRQF_ONESHOT, name, 111 + data); 112 + } 113 + 114 + static inline void madera_free_irq(struct madera *madera, int irq, void *data) 115 + { 116 + irq = madera_get_irq_mapping(madera, irq); 117 + if (irq < 0) 118 + return; 119 + 120 + free_irq(irq, data); 121 + } 122 + 123 + static inline int madera_set_irq_wake(struct madera *madera, int irq, int on) 124 + { 125 + irq = madera_get_irq_mapping(madera, irq); 126 + if (irq < 0) 127 + return irq; 128 + 129 + return irq_set_irq_wake(irq, on); 130 + } 131 + 132 + #endif