diff mbox series

[04/26] ALSA: hda: cs35l41: Add Support for Interrupts

Message ID 20220427150720.9194-5-vitalyr@opensource.cirrus.com
State New
Headers show
Series ALSA: hda: cirrus: Add initial DSP support and firmware loading | expand

Commit Message

Vitaly Rodionov April 27, 2022, 3:06 p.m. UTC
From: Stefan Binding <sbinding@opensource.cirrus.com>

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>
---
 include/sound/cs35l41.h     |  54 +++++++++++++++
 sound/pci/hda/Kconfig       |   2 +
 sound/pci/hda/cs35l41_hda.c | 134 +++++++++++++++++++++++++++++++++++-
 sound/pci/hda/cs35l41_hda.h |   2 +
 4 files changed, 191 insertions(+), 1 deletion(-)
diff mbox series

Patch

diff --git a/include/sound/cs35l41.h b/include/sound/cs35l41.h
index dbe8d9c0191b..8e4b125c81c8 100644
--- a/include/sound/cs35l41.h
+++ b/include/sound/cs35l41.h
@@ -691,6 +691,13 @@ 
 #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;
 
diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig
index 9f6c99c1d87b..79ade4787d95 100644
--- a/sound/pci/hda/Kconfig
+++ b/sound/pci/hda/Kconfig
@@ -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.
diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
index 96c3e541696d..bd52e0c2c772 100644
--- a/sound/pci/hda/cs35l41_hda.c
+++ b/sound/pci/hda/cs35l41_hda.c
@@ -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);
 
diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h
index 44d9204ffdf1..c486e4a5bb24 100644
--- a/sound/pci/hda/cs35l41_hda.h
+++ b/sound/pci/hda/cs35l41_hda.h
@@ -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,