ALSA: hda: cs35l41: Add Support for Interrupts
authorStefan Binding <sbinding@opensource.cirrus.com>
Mon, 9 May 2022 21:46:41 +0000 (22:46 +0100)
committerTakashi Iwai <tiwai@suse.de>
Mon, 16 May 2022 07:51:21 +0000 (09:51 +0200)
The CS35L41 can produce interrupts on error.

When the interrupts occur, the driver will report
the error, but errors will only be fixed after playback
finishes.

Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
Signed-off-by: Vitaly Rodionov <vitalyr@opensource.cirrus.com>
Link: https://lore.kernel.org/r/20220509214703.4482-5-vitalyr@opensource.cirrus.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
include/sound/cs35l41.h
sound/pci/hda/Kconfig
sound/pci/hda/cs35l41_hda.c
sound/pci/hda/cs35l41_hda.h

index dbe8d9c..8e4b125 100644 (file)
 #define CS35L41_TEMP_WARN_ERR_RLS      0x20
 #define CS35L41_TEMP_ERR_RLS           0x40
 
+#define CS35L41_AMP_SHORT_ERR_RLS_SHIFT        1
+#define CS35L41_BST_SHORT_ERR_RLS_SHIFT        2
+#define CS35L41_BST_OVP_ERR_RLS_SHIFT  3
+#define CS35L41_BST_UVP_ERR_RLS_SHIFT  4
+#define CS35L41_TEMP_WARN_ERR_RLS_SHIFT        5
+#define CS35L41_TEMP_ERR_RLS_SHIFT     6
+
 #define CS35L41_INT1_MASK_DEFAULT      0x7FFCFE3F
 #define CS35L41_INT1_UNMASK_PUP                0xFEFFFFFF
 #define CS35L41_INT1_UNMASK_PDN                0xFF7FFFFF
@@ -794,6 +801,53 @@ struct cs35l41_otp_map_element_t {
        u32 word_offset;
 };
 
+/*
+ * IRQs
+ */
+#define CS35L41_IRQ(_irq, _name, _hand)                \
+       {                                       \
+               .irq = CS35L41_ ## _irq ## _IRQ,\
+               .name = _name,                  \
+               .handler = _hand,               \
+       }
+
+struct cs35l41_irq {
+       int irq;
+       const char *name;
+       irqreturn_t (*handler)(int irq, void *data);
+};
+
+#define CS35L41_REG_IRQ(_reg, _irq)                                    \
+       [CS35L41_ ## _irq ## _IRQ] = {                                  \
+               .reg_offset = (CS35L41_ ## _reg) - CS35L41_IRQ1_STATUS1,\
+               .mask = CS35L41_ ## _irq ## _MASK                       \
+       }
+
+/* (0x0000E010) CS35L41_IRQ1_STATUS1 */
+#define CS35L41_BST_OVP_ERR_SHIFT              6
+#define CS35L41_BST_OVP_ERR_MASK               BIT(CS35L41_BST_OVP_ERR_SHIFT)
+#define CS35L41_BST_DCM_UVP_ERR_SHIFT          7
+#define CS35L41_BST_DCM_UVP_ERR_MASK           BIT(CS35L41_BST_DCM_UVP_ERR_SHIFT)
+#define CS35L41_BST_SHORT_ERR_SHIFT            8
+#define CS35L41_BST_SHORT_ERR_MASK             BIT(CS35L41_BST_SHORT_ERR_SHIFT)
+#define CS35L41_TEMP_WARN_SHIFT                        15
+#define CS35L41_TEMP_WARN_MASK                 BIT(CS35L41_TEMP_WARN_SHIFT)
+#define CS35L41_TEMP_ERR_SHIFT                 17
+#define CS35L41_TEMP_ERR_MASK                  BIT(CS35L41_TEMP_ERR_SHIFT)
+#define CS35L41_AMP_SHORT_ERR_SHIFT            31
+#define CS35L41_AMP_SHORT_ERR_MASK             BIT(CS35L41_AMP_SHORT_ERR_SHIFT)
+
+enum cs35l41_irq_list {
+       CS35L41_BST_OVP_ERR_IRQ,
+       CS35L41_BST_DCM_UVP_ERR_IRQ,
+       CS35L41_BST_SHORT_ERR_IRQ,
+       CS35L41_TEMP_WARN_IRQ,
+       CS35L41_TEMP_ERR_IRQ,
+       CS35L41_AMP_SHORT_ERR_IRQ,
+
+       CS35L41_NUM_IRQ
+};
+
 extern struct regmap_config cs35l41_regmap_i2c;
 extern struct regmap_config cs35l41_regmap_spi;
 
index 9f6c99c..79ade47 100644 (file)
@@ -102,6 +102,7 @@ config SND_HDA_SCODEC_CS35L41_I2C
        select SND_HDA_GENERIC
        select SND_SOC_CS35L41_LIB
        select SND_HDA_SCODEC_CS35L41
+       select REGMAP_IRQ
        help
          Say Y or M here to include CS35L41 I2C HD-audio side codec support
          in snd-hda-intel driver, such as ALC287.
@@ -117,6 +118,7 @@ config SND_HDA_SCODEC_CS35L41_SPI
        select SND_HDA_GENERIC
        select SND_SOC_CS35L41_LIB
        select SND_HDA_SCODEC_CS35L41
+       select REGMAP_IRQ
        help
          Say Y or M here to include CS35L41 SPI HD-audio side codec support
          in snd-hda-intel driver, such as ALC287.
index 96c3e54..bd52e0c 100644 (file)
@@ -32,6 +32,21 @@ static const struct reg_sequence cs35l41_hda_mute[] = {
        { CS35L41_AMP_DIG_VOL_CTRL,     0x0000A678 }, // AMP_VOL_PCM Mute
 };
 
+/* Protection release cycle to get the speaker out of Safe-Mode */
+static void cs35l41_error_release(struct device *dev, struct regmap *regmap, unsigned int mask)
+{
+       regmap_write(regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
+       regmap_set_bits(regmap, CS35L41_PROTECT_REL_ERR_IGN, mask);
+       regmap_clear_bits(regmap, CS35L41_PROTECT_REL_ERR_IGN, mask);
+}
+
+/* Clear all errors to release safe mode. Global Enable must be cleared first. */
+static void cs35l41_irq_release(struct cs35l41_hda *cs35l41)
+{
+       cs35l41_error_release(cs35l41->dev, cs35l41->regmap, cs35l41->irq_errors);
+       cs35l41->irq_errors = 0;
+}
+
 static void cs35l41_hda_playback_hook(struct device *dev, int action)
 {
        struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
@@ -58,6 +73,7 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action)
                                         CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT);
                if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
                        regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00000001);
+               cs35l41_irq_release(cs35l41);
                break;
        default:
                dev_warn(cs35l41->dev, "Playback action not supported: %d\n", action);
@@ -110,10 +126,101 @@ static const struct component_ops cs35l41_hda_comp_ops = {
        .unbind = cs35l41_hda_unbind,
 };
 
+static irqreturn_t cs35l41_bst_short_err(int irq, void *data)
+{
+       struct cs35l41_hda *cs35l41 = data;
+
+       dev_crit_ratelimited(cs35l41->dev, "LBST Error\n");
+       set_bit(CS35L41_BST_SHORT_ERR_RLS_SHIFT, &cs35l41->irq_errors);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t cs35l41_bst_dcm_uvp_err(int irq, void *data)
+{
+       struct cs35l41_hda *cs35l41 = data;
+
+       dev_crit_ratelimited(cs35l41->dev, "DCM VBST Under Voltage Error\n");
+       set_bit(CS35L41_BST_UVP_ERR_RLS_SHIFT, &cs35l41->irq_errors);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t cs35l41_bst_ovp_err(int irq, void *data)
+{
+       struct cs35l41_hda *cs35l41 = data;
+
+       dev_crit_ratelimited(cs35l41->dev, "VBST Over Voltage error\n");
+       set_bit(CS35L41_BST_OVP_ERR_RLS_SHIFT, &cs35l41->irq_errors);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t cs35l41_temp_err(int irq, void *data)
+{
+       struct cs35l41_hda *cs35l41 = data;
+
+       dev_crit_ratelimited(cs35l41->dev, "Over temperature error\n");
+       set_bit(CS35L41_TEMP_ERR_RLS_SHIFT, &cs35l41->irq_errors);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t cs35l41_temp_warn(int irq, void *data)
+{
+       struct cs35l41_hda *cs35l41 = data;
+
+       dev_crit_ratelimited(cs35l41->dev, "Over temperature warning\n");
+       set_bit(CS35L41_TEMP_WARN_ERR_RLS_SHIFT, &cs35l41->irq_errors);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t cs35l41_amp_short(int irq, void *data)
+{
+       struct cs35l41_hda *cs35l41 = data;
+
+       dev_crit_ratelimited(cs35l41->dev, "Amp short error\n");
+       set_bit(CS35L41_AMP_SHORT_ERR_RLS_SHIFT, &cs35l41->irq_errors);
+
+       return IRQ_HANDLED;
+}
+
+static const struct cs35l41_irq cs35l41_irqs[] = {
+       CS35L41_IRQ(BST_OVP_ERR, "Boost Overvoltage Error", cs35l41_bst_ovp_err),
+       CS35L41_IRQ(BST_DCM_UVP_ERR, "Boost Undervoltage Error", cs35l41_bst_dcm_uvp_err),
+       CS35L41_IRQ(BST_SHORT_ERR, "Boost Inductor Short Error", cs35l41_bst_short_err),
+       CS35L41_IRQ(TEMP_WARN, "Temperature Warning", cs35l41_temp_warn),
+       CS35L41_IRQ(TEMP_ERR, "Temperature Error", cs35l41_temp_err),
+       CS35L41_IRQ(AMP_SHORT_ERR, "Amp Short", cs35l41_amp_short),
+};
+
+static const struct regmap_irq cs35l41_reg_irqs[] = {
+       CS35L41_REG_IRQ(IRQ1_STATUS1, BST_OVP_ERR),
+       CS35L41_REG_IRQ(IRQ1_STATUS1, BST_DCM_UVP_ERR),
+       CS35L41_REG_IRQ(IRQ1_STATUS1, BST_SHORT_ERR),
+       CS35L41_REG_IRQ(IRQ1_STATUS1, TEMP_WARN),
+       CS35L41_REG_IRQ(IRQ1_STATUS1, TEMP_ERR),
+       CS35L41_REG_IRQ(IRQ1_STATUS1, AMP_SHORT_ERR),
+};
+
+static const struct regmap_irq_chip cs35l41_regmap_irq_chip = {
+       .name = "cs35l41 IRQ1 Controller",
+       .status_base = CS35L41_IRQ1_STATUS1,
+       .mask_base = CS35L41_IRQ1_MASK1,
+       .ack_base = CS35L41_IRQ1_STATUS1,
+       .num_regs = 4,
+       .irqs = cs35l41_reg_irqs,
+       .num_irqs = ARRAY_SIZE(cs35l41_reg_irqs),
+};
+
 static int cs35l41_hda_apply_properties(struct cs35l41_hda *cs35l41)
 {
        struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg;
+       bool using_irq = false;
+       int irq, irq_pol;
        int ret;
+       int i;
 
        if (!cs35l41->hw_cfg.valid)
                return -EINVAL;
@@ -145,6 +252,7 @@ static int cs35l41_hda_apply_properties(struct cs35l41_hda *cs35l41)
                case CS35L41_NOT_USED:
                        break;
                case CS35L41_INTERRUPT:
+                       using_irq = true;
                        break;
                default:
                        dev_err(cs35l41->dev, "Invalid GPIO2 function %d\n", hw_cfg->gpio2.func);
@@ -152,7 +260,28 @@ static int cs35l41_hda_apply_properties(struct cs35l41_hda *cs35l41)
                }
        }
 
-       cs35l41_gpio_config(cs35l41->regmap, hw_cfg);
+       irq_pol = cs35l41_gpio_config(cs35l41->regmap, hw_cfg);
+
+       if (cs35l41->irq && using_irq) {
+               ret = devm_regmap_add_irq_chip(cs35l41->dev, cs35l41->regmap, cs35l41->irq,
+                                              IRQF_ONESHOT | IRQF_SHARED | irq_pol,
+                                              0, &cs35l41_regmap_irq_chip, &cs35l41->irq_data);
+               if (ret)
+                       return ret;
+
+               for (i = 0; i < ARRAY_SIZE(cs35l41_irqs); i++) {
+                       irq = regmap_irq_get_virq(cs35l41->irq_data, cs35l41_irqs[i].irq);
+                       if (irq < 0)
+                               return irq;
+
+                       ret = devm_request_threaded_irq(cs35l41->dev, irq, NULL,
+                                                       cs35l41_irqs[i].handler,
+                                                       IRQF_ONESHOT | IRQF_SHARED | irq_pol,
+                                                       cs35l41_irqs[i].name, cs35l41);
+                       if (ret)
+                               return ret;
+               }
+       }
 
        return cs35l41_hda_channel_map(cs35l41->dev, 0, NULL, 1, &hw_cfg->spk_pos);
 }
@@ -296,6 +425,9 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i
        struct cs35l41_hda *cs35l41;
        int ret;
 
+       BUILD_BUG_ON(ARRAY_SIZE(cs35l41_irqs) != ARRAY_SIZE(cs35l41_reg_irqs));
+       BUILD_BUG_ON(ARRAY_SIZE(cs35l41_irqs) != CS35L41_NUM_IRQ);
+
        if (IS_ERR(regmap))
                return PTR_ERR(regmap);
 
index 44d9204..c486e4a 100644 (file)
@@ -35,6 +35,8 @@ struct cs35l41_hda {
 
        int irq;
        int index;
+       unsigned volatile long irq_errors;
+       struct regmap_irq_chip_data *irq_data;
 };
 
 int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq,