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