[PATCH v3 1/5] ASoC: cs35l45: Support for GPIO pins configuration.
From: "Vlad.Karpovich" vkarpovi@opensource.cirrus.com
Adds device tree configuration for cs35l45 GPIOs
Signed-off-by: Vlad Karpovich vkarpovi@opensource.cirrus.com --- include/dt-bindings/sound/cs35l45.h | 57 +++++++++++++++++++++++++++++ sound/soc/codecs/cs35l45-tables.c | 14 +++++++ sound/soc/codecs/cs35l45.c | 56 ++++++++++++++++++++++++++++ sound/soc/codecs/cs35l45.h | 27 +++++++++++++- 4 files changed, 152 insertions(+), 2 deletions(-)
diff --git a/include/dt-bindings/sound/cs35l45.h b/include/dt-bindings/sound/cs35l45.h index 076da4b2c28d..25386af18445 100644 --- a/include/dt-bindings/sound/cs35l45.h +++ b/include/dt-bindings/sound/cs35l45.h @@ -17,4 +17,61 @@ #define CS35L45_ASP_TX_HIZ_UNUSED 0x1 #define CS35L45_ASP_TX_HIZ_DISABLED 0x2
+/* + * Optional GPIOX Sub-nodes: + * The cs35l45 node can have up to three "cirrus,gpio-ctrlX" ('X' = [1,2,3]) + * sub-nodes for configuring the GPIO pins. + * + * - gpio-dir : GPIO pin direction. Valid only when 'gpio-ctrl' + * is 1. + * 0 = Output + * 1 = Input (Default) + * + * - gpio-lvl : GPIO level. Valid only when 'gpio-ctrl' is 1 and 'gpio-dir' is 0. + * + * 0 = Low (Default) + * 1 = High + * + * - gpio-op-cfg : GPIO output configuration. Valid only when 'gpio-ctrl' is 1 + * and 'gpio-dir' is 0. + * + * 0 = CMOS (Default) + * 1 = Open Drain + * + * - gpio-pol : GPIO output polarity select. Valid only when 'gpio-ctrl' is 1 + * and 'gpio-dir' is 0. + * + * 0 = Non-inverted, Active High (Default) + * 1 = Inverted, Active Low + * + * - gpio-invert : Defines the polarity of the GPIO pin if configured + * as input. + * + * 0 = Not inverted (Default) + * 1 = Inverted + * + * - gpio-ctrl : Defines the function of the GPIO pin. + * + * GPIO1: + * 0 = High impedance input (Default) + * 1 = Pin acts as a GPIO, direction controlled by 'gpio-dir' + * 2 = Pin acts as MDSYNC, direction controlled by MDSYNC + * 3-7 = Reserved + * + * GPIO2: + * 0 = High impedance input (Default) + * 1 = Pin acts as a GPIO, direction controlled by 'gpio-dir' + * 2 = Pin acts as open drain INT + * 3 = Reserved + * 4 = Pin acts as push-pull output INT. Active low. + * 5 = Pin acts as push-pull output INT. Active high. + * 6,7 = Reserved + * + * GPIO3: + * 0 = High impedance input (Default) + * 1 = Pin acts as a GPIO, direction controlled by 'gpio-dir' + * 2-7 = Reserved + */ +#define CS35L45_NUM_GPIOS 0x3 + #endif /* DT_CS35L45_H */ diff --git a/sound/soc/codecs/cs35l45-tables.c b/sound/soc/codecs/cs35l45-tables.c index 5a2c2e684ef9..bca7e830821f 100644 --- a/sound/soc/codecs/cs35l45-tables.c +++ b/sound/soc/codecs/cs35l45-tables.c @@ -43,6 +43,9 @@ EXPORT_SYMBOL_NS_GPL(cs35l45_apply_patch, SND_SOC_CS35L45_TABLES); static const struct reg_default cs35l45_defaults[] = { { CS35L45_BLOCK_ENABLES, 0x00003323 }, { CS35L45_BLOCK_ENABLES2, 0x00000010 }, + { CS35L45_SYNC_GPIO1, 0x00000007 }, + { CS35L45_INTB_GPIO2_MCLK_REF, 0x00000005 }, + { CS35L45_GPIO3, 0x00000005 }, { CS35L45_REFCLK_INPUT, 0x00000510 }, { CS35L45_GLOBAL_SAMPLE_RATE, 0x00000003 }, { CS35L45_ASP_ENABLES1, 0x00000000 }, @@ -61,6 +64,9 @@ static const struct reg_default cs35l45_defaults[] = { { CS35L45_ASPTX4_INPUT, 0x00000028 }, { CS35L45_ASPTX5_INPUT, 0x00000048 }, { CS35L45_AMP_PCM_CONTROL, 0x00100000 }, + { CS35L45_GPIO1_CTRL1, 0x81000001 }, + { CS35L45_GPIO2_CTRL1, 0x81000001 }, + { CS35L45_GPIO3_CTRL1, 0x81000001 }, };
static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) @@ -72,6 +78,9 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_BLOCK_ENABLES: case CS35L45_BLOCK_ENABLES2: case CS35L45_ERROR_RELEASE: + case CS35L45_SYNC_GPIO1: + case CS35L45_INTB_GPIO2_MCLK_REF: + case CS35L45_GPIO3: case CS35L45_REFCLK_INPUT: case CS35L45_GLOBAL_SAMPLE_RATE: case CS35L45_ASP_ENABLES1: @@ -92,6 +101,10 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_AMP_PCM_CONTROL: case CS35L45_AMP_PCM_HPF_TST: case CS35L45_IRQ1_EINT_4: + case CS35L45_GPIO_STATUS1: + case CS35L45_GPIO1_CTRL1: + case CS35L45_GPIO2_CTRL1: + case CS35L45_GPIO3_CTRL1: return true; default: return false; @@ -107,6 +120,7 @@ static bool cs35l45_volatile_reg(struct device *dev, unsigned int reg) case CS35L45_ERROR_RELEASE: case CS35L45_AMP_PCM_HPF_TST: /* not cachable */ case CS35L45_IRQ1_EINT_4: + case CS35L45_GPIO_STATUS1: return true; default: return false; diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index d15b3b77c7eb..901f3647fbda 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -536,7 +536,63 @@ static int __maybe_unused cs35l45_runtime_resume(struct device *dev)
static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45) { + struct device_node *node = cs35l45->dev->of_node; + unsigned int gpio_regs[] = {CS35L45_GPIO1_CTRL1, CS35L45_GPIO2_CTRL1, + CS35L45_GPIO3_CTRL1}; + unsigned int pad_regs[] = {CS35L45_SYNC_GPIO1, + CS35L45_INTB_GPIO2_MCLK_REF, CS35L45_GPIO3}; + struct device_node *child; unsigned int val; + char of_name[32]; + int ret, i; + + if (!node) + return 0; + + for (i = 0; i < CS35L45_NUM_GPIOS; i++) { + sprintf(of_name, "cirrus,gpio-ctrl%d", i + 1); + child = of_get_child_by_name(node, of_name); + if (!child) + continue; + + ret = of_property_read_u32(child, "gpio-dir", &val); + if (!ret) + regmap_update_bits(cs35l45->regmap, gpio_regs[i], + CS35L45_GPIO_DIR_MASK, + val << CS35L45_GPIO_DIR_SHIFT); + + ret = of_property_read_u32(child, "gpio-lvl", &val); + if (!ret) + regmap_update_bits(cs35l45->regmap, gpio_regs[i], + CS35L45_GPIO_LVL_MASK, + val << CS35L45_GPIO_LVL_SHIFT); + + ret = of_property_read_u32(child, "gpio-op-cfg", &val); + if (!ret) + regmap_update_bits(cs35l45->regmap, gpio_regs[i], + CS35L45_GPIO_OP_CFG_MASK, + val << CS35L45_GPIO_OP_CFG_SHIFT); + + ret = of_property_read_u32(child, "gpio-pol", &val); + if (!ret) + regmap_update_bits(cs35l45->regmap, gpio_regs[i], + CS35L45_GPIO_POL_MASK, + val << CS35L45_GPIO_POL_SHIFT); + + ret = of_property_read_u32(child, "gpio-ctrl", &val); + if (!ret) + regmap_update_bits(cs35l45->regmap, pad_regs[i], + CS35L45_GPIO_CTRL_MASK, + val << CS35L45_GPIO_CTRL_SHIFT); + + ret = of_property_read_u32(child, "gpio-invert", &val); + if (!ret) + regmap_update_bits(cs35l45->regmap, pad_regs[i], + CS35L45_GPIO_INVERT_MASK, + val << CS35L45_GPIO_INVERT_SHIFT); + + of_node_put(child); + }
if (device_property_read_u32(cs35l45->dev, "cirrus,asp-sdout-hiz-ctrl", &val) == 0) { diff --git a/sound/soc/codecs/cs35l45.h b/sound/soc/codecs/cs35l45.h index 53fe9d2b7b15..f3a54fc57d53 100644 --- a/sound/soc/codecs/cs35l45.h +++ b/sound/soc/codecs/cs35l45.h @@ -14,6 +14,7 @@ #include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> +#include <dt-bindings/sound/cs35l45.h>
#define CS35L45_DEVID 0x00000000 #define CS35L45_REVID 0x00000004 @@ -24,6 +25,9 @@ #define CS35L45_BLOCK_ENABLES 0x00002018 #define CS35L45_BLOCK_ENABLES2 0x0000201C #define CS35L45_ERROR_RELEASE 0x00002034 +#define CS35L45_SYNC_GPIO1 0x00002430 +#define CS35L45_INTB_GPIO2_MCLK_REF 0x00002434 +#define CS35L45_GPIO3 0x00002438 #define CS35L45_REFCLK_INPUT 0x00002C04 #define CS35L45_GLOBAL_SAMPLE_RATE 0x00002C0C #define CS35L45_BOOST_CCM_CFG 0x00003808 @@ -48,8 +52,11 @@ #define CS35L45_AMP_PCM_CONTROL 0x00007000 #define CS35L45_AMP_PCM_HPF_TST 0x00007004 #define CS35L45_IRQ1_EINT_4 0x0000E01C -#define CS35L45_LASTREG 0x0000E01C - +#define CS35L45_GPIO_STATUS1 0x0000F000 +#define CS35L45_GPIO1_CTRL1 0x0000F008 +#define CS35L45_GPIO2_CTRL1 0x0000F00C +#define CS35L45_GPIO3_CTRL1 0x0000F010 +#define CS35L45_LASTREG 0x0000F010 /* SFT_RESET */ #define CS35L45_SOFT_RESET_TRIGGER 0x5A000000
@@ -165,6 +172,22 @@ #define CS35L45_OTP_BOOT_DONE_STS_MASK BIT(1) #define CS35L45_OTP_BUSY_MASK BIT(0)
+/* GPIOX_CTRL1 */ +#define CS35L45_GPIO_DIR_SHIFT 31 +#define CS35L45_GPIO_DIR_MASK BIT(31) +#define CS35L45_GPIO_LVL_SHIFT 15 +#define CS35L45_GPIO_LVL_MASK BIT(15) +#define CS35L45_GPIO_OP_CFG_SHIFT 14 +#define CS35L45_GPIO_OP_CFG_MASK BIT(14) +#define CS35L45_GPIO_POL_SHIFT 12 +#define CS35L45_GPIO_POL_MASK BIT(12) + +/* SYNC_GPIO1, INTB_GPIO2_MCLK_REF, GPIO3 */ +#define CS35L45_GPIO_CTRL_SHIFT 20 +#define CS35L45_GPIO_CTRL_MASK GENMASK(22, 20) +#define CS35L45_GPIO_INVERT_SHIFT 19 +#define CS35L45_GPIO_INVERT_MASK BIT(19) + /* Mixer sources */ #define CS35L45_PCM_SRC_MASK 0x7F #define CS35L45_PCM_SRC_ZERO 0x00
From: "Vlad.Karpovich" vkarpovi@opensource.cirrus.com
This adds description of CS35L45 GPIOs configuration.
Signed-off-by: Vlad Karpovich vkarpovi@opensource.cirrus.com --- .../bindings/sound/cirrus,cs35l45.yaml | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml index 184a1344ea76..34c2867e2fd9 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml @@ -47,6 +47,74 @@ properties: maximum: 3 default: 2
+patternProperties: + "^cirrus,gpio-ctrl[1-3]$": + description: + GPIO pins configuration. + type: object + additionalProperties: false + properties: + gpio-dir: + description: + GPIO pin direction. Valid only when 'gpio-ctrl' is 1 + 0 = Output + 1 = Input + $ref: "/schemas/types.yaml#/definitions/uint32" + minimum: 0 + maximum: 1 + default: 1 + gpio-lvl: + description: + GPIO level. Valid only when 'gpio-ctrl' is 1 and 'gpio-dir' is 0 + 0 = Low + 1 = High + $ref: "/schemas/types.yaml#/definitions/uint32" + minimum: 0 + maximum: 1 + default: 0 + gpio-op-cfg: + description: + GPIO level. Valid only when 'gpio-ctrl' is 1 and 'gpio-dir' is 0 + 0 = CMOS + 1 = Open Drain + $ref: "/schemas/types.yaml#/definitions/uint32" + minimum: 0 + maximum: 1 + default: 0 + gpio-pol: + description: + GPIO output polarity select. Valid only when 'gpio-ctrl' is 1 + and 'gpio-dir' is 0 + 0 = Non-inverted, Active High + 1 = Inverted, Active Low + $ref: "/schemas/types.yaml#/definitions/uint32" + minimum: 0 + maximum: 1 + default: 0 + gpio-ctrl: + description: + Defines the function of the GPIO pin. + GPIO1 + 0 = High impedance input + 1 = Pin acts as a GPIO, direction controlled by 'gpio-dir' + 2 = Pin acts as MDSYNC, direction controlled by MDSYNC + 3-7 = Reserved + GPIO2 + 0 = High impedance input + 1 = Pin acts as a GPIO, direction controlled by 'gpio-dir' + 2 = Pin acts as open drain INT + 3 = Reserved + 4 = Pin acts as push-pull output INT. Active low. + 5 = Pin acts as push-pull output INT. Active high. + 6,7 = Reserved + GPIO3 + 0 = High impedance input + 1 = Pin acts as a GPIO, direction controlled by 'gpio-dir' + 2-7 = Reserved + $ref: "/schemas/types.yaml#/definitions/uint32" + minimum: 0 + maximum: 7 + default: 0 required: - compatible - reg @@ -71,5 +139,15 @@ examples: reset-gpios = <&gpio 110 0>; cirrus,asp-sdout-hiz-ctrl = <(CS35L45_ASP_TX_HIZ_UNUSED | CS35L45_ASP_TX_HIZ_DISABLED)>; + cirrus,gpio-ctrl1 { + gpio-ctrl = <0x2>; + }; + cirrus,gpio-ctrl2 { + gpio-ctrl = <0x2>; + }; + cirrus,gpio-ctrl3 { + gpio-ctrl = <0x1>; + gpio-dir = <0x1>; + }; }; };
From: "Vlad.Karpovich" vkarpovi@opensource.cirrus.com
Adds IRQ handlers
Signed-off-by: Vlad Karpovich vkarpovi@opensource.cirrus.com --- sound/soc/codecs/cs35l45-i2c.c | 1 + sound/soc/codecs/cs35l45-spi.c | 1 + sound/soc/codecs/cs35l45-tables.c | 29 +++++++- sound/soc/codecs/cs35l45.c | 111 +++++++++++++++++++++++++++++- sound/soc/codecs/cs35l45.h | 107 ++++++++++++++++++++++++++++ 5 files changed, 245 insertions(+), 4 deletions(-)
diff --git a/sound/soc/codecs/cs35l45-i2c.c b/sound/soc/codecs/cs35l45-i2c.c index 39d28641429e..5224aaf584b8 100644 --- a/sound/soc/codecs/cs35l45-i2c.c +++ b/sound/soc/codecs/cs35l45-i2c.c @@ -32,6 +32,7 @@ static int cs35l45_i2c_probe(struct i2c_client *client) }
cs35l45->dev = dev; + cs35l45->irq = client->irq;
return cs35l45_probe(cs35l45); } diff --git a/sound/soc/codecs/cs35l45-spi.c b/sound/soc/codecs/cs35l45-spi.c index baaf6e0f4fb9..b885ad3f3d4e 100644 --- a/sound/soc/codecs/cs35l45-spi.c +++ b/sound/soc/codecs/cs35l45-spi.c @@ -32,6 +32,7 @@ static int cs35l45_spi_probe(struct spi_device *spi) }
cs35l45->dev = dev; + cs35l45->irq = spi->irq;
return cs35l45_probe(cs35l45); } diff --git a/sound/soc/codecs/cs35l45-tables.c b/sound/soc/codecs/cs35l45-tables.c index bca7e830821f..c39c1e6876a4 100644 --- a/sound/soc/codecs/cs35l45-tables.c +++ b/sound/soc/codecs/cs35l45-tables.c @@ -64,6 +64,25 @@ static const struct reg_default cs35l45_defaults[] = { { CS35L45_ASPTX4_INPUT, 0x00000028 }, { CS35L45_ASPTX5_INPUT, 0x00000048 }, { CS35L45_AMP_PCM_CONTROL, 0x00100000 }, + { CS35L45_IRQ1_CFG, 0x00000000 }, + { CS35L45_IRQ1_MASK_1, 0xBFEFFFBF }, + { CS35L45_IRQ1_MASK_2, 0xFFFFFFFF }, + { CS35L45_IRQ1_MASK_3, 0xFFFF87FF }, + { CS35L45_IRQ1_MASK_4, 0xF8FFFFFF }, + { CS35L45_IRQ1_MASK_5, 0x0EF80000 }, + { CS35L45_IRQ1_MASK_6, 0x00000000 }, + { CS35L45_IRQ1_MASK_7, 0xFFFFFF78 }, + { CS35L45_IRQ1_MASK_8, 0x00003FFF }, + { CS35L45_IRQ1_MASK_9, 0x00000000 }, + { CS35L45_IRQ1_MASK_10, 0x00000000 }, + { CS35L45_IRQ1_MASK_11, 0x00000000 }, + { CS35L45_IRQ1_MASK_12, 0x00000000 }, + { CS35L45_IRQ1_MASK_13, 0x00000000 }, + { CS35L45_IRQ1_MASK_14, 0x00000001 }, + { CS35L45_IRQ1_MASK_15, 0x00000000 }, + { CS35L45_IRQ1_MASK_16, 0x00000000 }, + { CS35L45_IRQ1_MASK_17, 0x00000000 }, + { CS35L45_IRQ1_MASK_18, 0x3FE5D0FF }, { CS35L45_GPIO1_CTRL1, 0x81000001 }, { CS35L45_GPIO2_CTRL1, 0x81000001 }, { CS35L45_GPIO3_CTRL1, 0x81000001 }, @@ -100,7 +119,11 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_ASPTX5_INPUT: case CS35L45_AMP_PCM_CONTROL: case CS35L45_AMP_PCM_HPF_TST: - case CS35L45_IRQ1_EINT_4: + case CS35L45_IRQ1_CFG: + case CS35L45_IRQ1_STATUS: + case CS35L45_IRQ1_EINT_1 ... CS35L45_IRQ1_EINT_18: + case CS35L45_IRQ1_STS_1 ... CS35L45_IRQ1_STS_18: + case CS35L45_IRQ1_MASK_1 ... CS35L45_IRQ1_MASK_18: case CS35L45_GPIO_STATUS1: case CS35L45_GPIO1_CTRL1: case CS35L45_GPIO2_CTRL1: @@ -119,7 +142,9 @@ static bool cs35l45_volatile_reg(struct device *dev, unsigned int reg) case CS35L45_GLOBAL_ENABLES: case CS35L45_ERROR_RELEASE: case CS35L45_AMP_PCM_HPF_TST: /* not cachable */ - case CS35L45_IRQ1_EINT_4: + case CS35L45_IRQ1_STATUS: + case CS35L45_IRQ1_EINT_1 ... CS35L45_IRQ1_EINT_18: + case CS35L45_IRQ1_STS_1 ... CS35L45_IRQ1_STS_18: case CS35L45_GPIO_STATUS1: return true; default: diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index 901f3647fbda..0c3d01363c8d 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -586,10 +586,13 @@ static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45) val << CS35L45_GPIO_CTRL_SHIFT);
ret = of_property_read_u32(child, "gpio-invert", &val); - if (!ret) + if (!ret) { regmap_update_bits(cs35l45->regmap, pad_regs[i], CS35L45_GPIO_INVERT_MASK, val << CS35L45_GPIO_INVERT_SHIFT); + if (i == 1) + cs35l45->irq_invert = val; + }
of_node_put(child); } @@ -604,6 +607,78 @@ static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45) return 0; }
+static irqreturn_t cs35l45_pll_unlock(int irq, void *data) +{ + struct cs35l45_private *cs35l45 = data; + + dev_dbg(cs35l45->dev, "PLL unlock detected!"); + + return IRQ_HANDLED; +} + +static irqreturn_t cs35l45_pll_lock(int irq, void *data) +{ + struct cs35l45_private *cs35l45 = data; + + dev_dbg(cs35l45->dev, "PLL lock detected!"); + + return IRQ_HANDLED; +} + +static irqreturn_t cs35l45_spk_safe_err(int irq, void *data); + +static const struct cs35l45_irq cs35l45_irqs[] = { + CS35L45_IRQ(AMP_SHORT_ERR, "Amplifier short error", cs35l45_spk_safe_err), + CS35L45_IRQ(UVLO_VDDBATT_ERR, "VDDBATT undervoltage error", cs35l45_spk_safe_err), + CS35L45_IRQ(BST_SHORT_ERR, "Boost inductor error", cs35l45_spk_safe_err), + CS35L45_IRQ(BST_UVP_ERR, "Boost undervoltage error", cs35l45_spk_safe_err), + CS35L45_IRQ(TEMP_ERR, "Overtemperature error", cs35l45_spk_safe_err), + CS35L45_IRQ(AMP_CAL_ERR, "Amplifier calibration error", cs35l45_spk_safe_err), + CS35L45_IRQ(UVLO_VDDLV_ERR, "LV threshold detector error", cs35l45_spk_safe_err), + CS35L45_IRQ(GLOBAL_ERROR, "Global error", cs35l45_spk_safe_err), + CS35L45_IRQ(DSP_WDT_EXPIRE, "DSP Watchdog Timer", cs35l45_spk_safe_err), + CS35L45_IRQ(PLL_UNLOCK_FLAG_RISE, "PLL unlock", cs35l45_pll_unlock), + CS35L45_IRQ(PLL_LOCK_FLAG, "PLL lock", cs35l45_pll_lock), +}; + +static irqreturn_t cs35l45_spk_safe_err(int irq, void *data) +{ + struct cs35l45_private *cs35l45 = data; + int i; + + i = irq - regmap_irq_get_virq(cs35l45->irq_data, 0); + + dev_err(cs35l45->dev, "%s condition detected!\n", cs35l45_irqs[i].name); + + return IRQ_HANDLED; +} + +static const struct regmap_irq cs35l45_reg_irqs[] = { + CS35L45_REG_IRQ(IRQ1_EINT_1, AMP_SHORT_ERR), + CS35L45_REG_IRQ(IRQ1_EINT_1, UVLO_VDDBATT_ERR), + CS35L45_REG_IRQ(IRQ1_EINT_1, BST_SHORT_ERR), + CS35L45_REG_IRQ(IRQ1_EINT_1, BST_UVP_ERR), + CS35L45_REG_IRQ(IRQ1_EINT_1, TEMP_ERR), + CS35L45_REG_IRQ(IRQ1_EINT_3, AMP_CAL_ERR), + CS35L45_REG_IRQ(IRQ1_EINT_18, UVLO_VDDLV_ERR), + CS35L45_REG_IRQ(IRQ1_EINT_18, GLOBAL_ERROR), + CS35L45_REG_IRQ(IRQ1_EINT_2, DSP_WDT_EXPIRE), + CS35L45_REG_IRQ(IRQ1_EINT_3, PLL_UNLOCK_FLAG_RISE), + CS35L45_REG_IRQ(IRQ1_EINT_3, PLL_LOCK_FLAG), +}; + +static const struct regmap_irq_chip cs35l45_regmap_irq_chip = { + .name = "cs35l45 IRQ1 Controller", + .main_status = CS35L45_IRQ1_STATUS, + .status_base = CS35L45_IRQ1_EINT_1, + .mask_base = CS35L45_IRQ1_MASK_1, + .ack_base = CS35L45_IRQ1_EINT_1, + .num_regs = 18, + .irqs = cs35l45_reg_irqs, + .num_irqs = ARRAY_SIZE(cs35l45_reg_irqs), + .runtime_pm = true, +}; + static int cs35l45_initialize(struct cs35l45_private *cs35l45) { struct device *dev = cs35l45->dev; @@ -660,7 +735,8 @@ static int cs35l45_initialize(struct cs35l45_private *cs35l45) int cs35l45_probe(struct cs35l45_private *cs35l45) { struct device *dev = cs35l45->dev; - int ret; + unsigned long irq_pol = IRQF_ONESHOT | IRQF_SHARED; + int ret, i, irq;
cs35l45->vdd_batt = devm_regulator_get(dev, "vdd-batt"); if (IS_ERR(cs35l45->vdd_batt)) @@ -705,6 +781,37 @@ int cs35l45_probe(struct cs35l45_private *cs35l45) if (ret < 0) goto err_reset;
+ if (cs35l45->irq) { + if (cs35l45->irq_invert) + irq_pol |= IRQF_TRIGGER_HIGH; + else + irq_pol |= IRQF_TRIGGER_LOW; + + ret = devm_regmap_add_irq_chip(dev, cs35l45->regmap, cs35l45->irq, irq_pol, 0, + &cs35l45_regmap_irq_chip, &cs35l45->irq_data); + if (ret) { + dev_err(dev, "Failed to register IRQ chip: %d\n", ret); + goto err_reset; + } + + for (i = 0; i < ARRAY_SIZE(cs35l45_irqs); i++) { + irq = regmap_irq_get_virq(cs35l45->irq_data, cs35l45_irqs[i].irq); + if (irq < 0) { + dev_err(dev, "Failed to get %s\n", cs35l45_irqs[i].name); + ret = irq; + goto err_reset; + } + + ret = devm_request_threaded_irq(dev, irq, NULL, cs35l45_irqs[i].handler, + irq_pol, cs35l45_irqs[i].name, cs35l45); + if (ret) { + dev_err(dev, "Failed to request IRQ %s: %d\n", + cs35l45_irqs[i].name, ret); + goto err_reset; + } + } + } + ret = devm_snd_soc_register_component(dev, &cs35l45_component, cs35l45_dai, ARRAY_SIZE(cs35l45_dai)); diff --git a/sound/soc/codecs/cs35l45.h b/sound/soc/codecs/cs35l45.h index f3a54fc57d53..ce92f5068ac5 100644 --- a/sound/soc/codecs/cs35l45.h +++ b/sound/soc/codecs/cs35l45.h @@ -51,7 +51,42 @@ #define CS35L45_LDPM_CONFIG 0x00006404 #define CS35L45_AMP_PCM_CONTROL 0x00007000 #define CS35L45_AMP_PCM_HPF_TST 0x00007004 +#define CS35L45_IRQ1_CFG 0x0000E000 +#define CS35L45_IRQ1_STATUS 0x0000E004 +#define CS35L45_IRQ1_EINT_1 0x0000E010 +#define CS35L45_IRQ1_EINT_2 0x0000E014 +#define CS35L45_IRQ1_EINT_3 0x0000E018 #define CS35L45_IRQ1_EINT_4 0x0000E01C +#define CS35L45_IRQ1_EINT_5 0x0000E020 +#define CS35L45_IRQ1_EINT_7 0x0000E028 +#define CS35L45_IRQ1_EINT_8 0x0000E02C +#define CS35L45_IRQ1_EINT_18 0x0000E054 +#define CS35L45_IRQ1_STS_1 0x0000E090 +#define CS35L45_IRQ1_STS_2 0x0000E094 +#define CS35L45_IRQ1_STS_3 0x0000E098 +#define CS35L45_IRQ1_STS_4 0x0000E09C +#define CS35L45_IRQ1_STS_5 0x0000E0A0 +#define CS35L45_IRQ1_STS_7 0x0000E0A8 +#define CS35L45_IRQ1_STS_8 0x0000E0AC +#define CS35L45_IRQ1_STS_18 0x0000E0D4 +#define CS35L45_IRQ1_MASK_1 0x0000E110 +#define CS35L45_IRQ1_MASK_2 0x0000E114 +#define CS35L45_IRQ1_MASK_3 0x0000E118 +#define CS35L45_IRQ1_MASK_4 0x0000E11C +#define CS35L45_IRQ1_MASK_5 0x0000E120 +#define CS35L45_IRQ1_MASK_6 0x0000E124 +#define CS35L45_IRQ1_MASK_7 0x0000E128 +#define CS35L45_IRQ1_MASK_8 0x0000E12C +#define CS35L45_IRQ1_MASK_9 0x0000E130 +#define CS35L45_IRQ1_MASK_10 0x0000E134 +#define CS35L45_IRQ1_MASK_11 0x0000E138 +#define CS35L45_IRQ1_MASK_12 0x0000E13C +#define CS35L45_IRQ1_MASK_13 0x0000E140 +#define CS35L45_IRQ1_MASK_14 0x0000E144 +#define CS35L45_IRQ1_MASK_15 0x0000E148 +#define CS35L45_IRQ1_MASK_16 0x0000E14C +#define CS35L45_IRQ1_MASK_17 0x0000E150 +#define CS35L45_IRQ1_MASK_18 0x0000E154 #define CS35L45_GPIO_STATUS1 0x0000F000 #define CS35L45_GPIO1_CTRL1 0x0000F008 #define CS35L45_GPIO2_CTRL1 0x0000F00C @@ -188,6 +223,38 @@ #define CS35L45_GPIO_INVERT_SHIFT 19 #define CS35L45_GPIO_INVERT_MASK BIT(19)
+/* CS35L45_IRQ1_EINT_1 */ +#define CS35L45_BST_UVP_ERR_SHIFT 7 +#define CS35L45_BST_UVP_ERR_MASK BIT(7) +#define CS35L45_BST_SHORT_ERR_SHIFT 8 +#define CS35L45_BST_SHORT_ERR_MASK BIT(8) +#define CS35L45_TEMP_ERR_SHIFT 17 +#define CS35L45_TEMP_ERR_MASK BIT(17) +#define CS35L45_MSM_GLOBAL_EN_ASSERT_SHIFT 22 +#define CS35L45_MSM_GLOBAL_EN_ASSERT_MASK BIT(22) +#define CS35L45_UVLO_VDDBATT_ERR_SHIFT 29 +#define CS35L45_UVLO_VDDBATT_ERR_MASK BIT(29) +#define CS35L45_AMP_SHORT_ERR_SHIFT 31 +#define CS35L45_AMP_SHORT_ERR_MASK BIT(31) + +/* CS35L45_IRQ1_EINT_2 */ +#define CS35L45_DSP_WDT_EXPIRE_SHIFT 4 +#define CS35L45_DSP_WDT_EXPIRE_MASK BIT(4) + +/* CS35L45_IRQ1_EINT_3 */ +#define CS35L45_PLL_LOCK_FLAG_SHIFT 1 +#define CS35L45_PLL_LOCK_FLAG_MASK BIT(1) +#define CS35L45_PLL_UNLOCK_FLAG_RISE_SHIFT 4 +#define CS35L45_PLL_UNLOCK_FLAG_RISE_MASK BIT(4) +#define CS35L45_AMP_CAL_ERR_SHIFT 25 +#define CS35L45_AMP_CAL_ERR_MASK BIT(25) + +/* CS35L45_IRQ1_EINT_18 */ +#define CS35L45_GLOBAL_ERROR_SHIFT 15 +#define CS35L45_GLOBAL_ERROR_MASK BIT(15) +#define CS35L45_UVLO_VDDLV_ERR_SHIFT 16 +#define CS35L45_UVLO_VDDLV_ERR_MASK BIT(16) + /* Mixer sources */ #define CS35L45_PCM_SRC_MASK 0x7F #define CS35L45_PCM_SRC_ZERO 0x00 @@ -217,6 +284,43 @@ SNDRV_PCM_RATE_88200 | \ SNDRV_PCM_RATE_96000)
+/* + * IRQs + */ +#define CS35L45_IRQ(_irq, _name, _hand) \ + { \ + .irq = CS35L45_ ## _irq ## _IRQ,\ + .name = _name, \ + .handler = _hand, \ + } + +struct cs35l45_irq { + int irq; + const char *name; + irqreturn_t (*handler)(int irq, void *data); +}; + +#define CS35L45_REG_IRQ(_reg, _irq) \ + [CS35L45_ ## _irq ## _IRQ] = { \ + .reg_offset = (CS35L45_ ## _reg) - CS35L45_IRQ1_EINT_1, \ + .mask = CS35L45_ ## _irq ## _MASK \ + } + +enum cs35l45_irq_list { + CS35L45_AMP_SHORT_ERR_IRQ, + CS35L45_UVLO_VDDBATT_ERR_IRQ, + CS35L45_BST_SHORT_ERR_IRQ, + CS35L45_BST_UVP_ERR_IRQ, + CS35L45_TEMP_ERR_IRQ, + CS35L45_AMP_CAL_ERR_IRQ, + CS35L45_UVLO_VDDLV_ERR_IRQ, + CS35L45_GLOBAL_ERROR_IRQ, + CS35L45_DSP_WDT_EXPIRE_IRQ, + CS35L45_PLL_UNLOCK_FLAG_RISE_IRQ, + CS35L45_PLL_LOCK_FLAG_IRQ, + CS35L45_NUM_IRQ +}; + struct cs35l45_private { struct device *dev; struct regmap *regmap; @@ -227,6 +331,9 @@ struct cs35l45_private { bool sysclk_set; u8 slot_width; u8 slot_count; + int irq_invert; + int irq; + struct regmap_irq_chip_data *irq_data; };
extern const struct dev_pm_ops cs35l45_pm_ops;
From: "Vlad.Karpovich" vkarpovi@opensource.cirrus.com
The CS35L45 digital core incorporates one programmable DSP block, capable of running a wide range of audio enhancement and speaker and battery protection functions.
Signed-off-by: Vlad Karpovich vkarpovi@opensource.cirrus.com --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/cs35l45-spi.c | 3 + sound/soc/codecs/cs35l45-tables.c | 86 +++++++ sound/soc/codecs/cs35l45.c | 391 +++++++++++++++++++++++++++++- sound/soc/codecs/cs35l45.h | 108 ++++++++- 5 files changed, 579 insertions(+), 13 deletions(-)
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 7022e6286e6c..5a0287e3bbec 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -357,6 +357,8 @@ config SND_SOC_WM_ADSP default y if SND_SOC_WM2200=y default y if SND_SOC_CS35L41_SPI=y default y if SND_SOC_CS35L41_I2C=y + default y if SND_SOC_CS35L45_SPI=y + default y if SND_SOC_CS35L45_I2C=y default m if SND_SOC_MADERA=m default m if SND_SOC_CS47L24=m default m if SND_SOC_WM5102=m @@ -364,6 +366,8 @@ config SND_SOC_WM_ADSP default m if SND_SOC_WM2200=m default m if SND_SOC_CS35L41_SPI=m default m if SND_SOC_CS35L41_I2C=m + default m if SND_SOC_CS35L45_SPI=m + default m if SND_SOC_CS35L45_I2C=m
config SND_SOC_AB8500_CODEC tristate diff --git a/sound/soc/codecs/cs35l45-spi.c b/sound/soc/codecs/cs35l45-spi.c index b885ad3f3d4e..bfb79255783e 100644 --- a/sound/soc/codecs/cs35l45-spi.c +++ b/sound/soc/codecs/cs35l45-spi.c @@ -23,6 +23,9 @@ static int cs35l45_spi_probe(struct spi_device *spi) if (cs35l45 == NULL) return -ENOMEM;
+ spi->max_speed_hz = CS35L45_SPI_MAX_FREQ; + spi_setup(spi); + spi_set_drvdata(spi, cs35l45); cs35l45->regmap = devm_regmap_init_spi(spi, &cs35l45_spi_regmap); if (IS_ERR(cs35l45->regmap)) { diff --git a/sound/soc/codecs/cs35l45-tables.c b/sound/soc/codecs/cs35l45-tables.c index c39c1e6876a4..6b68773979ec 100644 --- a/sound/soc/codecs/cs35l45-tables.c +++ b/sound/soc/codecs/cs35l45-tables.c @@ -46,6 +46,7 @@ static const struct reg_default cs35l45_defaults[] = { { CS35L45_SYNC_GPIO1, 0x00000007 }, { CS35L45_INTB_GPIO2_MCLK_REF, 0x00000005 }, { CS35L45_GPIO3, 0x00000005 }, + { CS35L45_PWRMGT_CTL, 0x00000000 }, { CS35L45_REFCLK_INPUT, 0x00000510 }, { CS35L45_GLOBAL_SAMPLE_RATE, 0x00000003 }, { CS35L45_ASP_ENABLES1, 0x00000000 }, @@ -63,6 +64,30 @@ static const struct reg_default cs35l45_defaults[] = { { CS35L45_ASPTX3_INPUT, 0x00000020 }, { CS35L45_ASPTX4_INPUT, 0x00000028 }, { CS35L45_ASPTX5_INPUT, 0x00000048 }, + { CS35L45_DSP1_RX1_RATE, 0x00000001 }, + { CS35L45_DSP1_RX2_RATE, 0x00000001 }, + { CS35L45_DSP1_RX3_RATE, 0x00000001 }, + { CS35L45_DSP1_RX4_RATE, 0x00000001 }, + { CS35L45_DSP1_RX5_RATE, 0x00000001 }, + { CS35L45_DSP1_RX6_RATE, 0x00000001 }, + { CS35L45_DSP1_RX7_RATE, 0x00000001 }, + { CS35L45_DSP1_RX8_RATE, 0x00000001 }, + { CS35L45_DSP1_TX1_RATE, 0x00000001 }, + { CS35L45_DSP1_TX2_RATE, 0x00000001 }, + { CS35L45_DSP1_TX3_RATE, 0x00000001 }, + { CS35L45_DSP1_TX4_RATE, 0x00000001 }, + { CS35L45_DSP1_TX5_RATE, 0x00000001 }, + { CS35L45_DSP1_TX6_RATE, 0x00000001 }, + { CS35L45_DSP1_TX7_RATE, 0x00000001 }, + { CS35L45_DSP1_TX8_RATE, 0x00000001 }, + { CS35L45_DSP1RX1_INPUT, 0x00000008 }, + { CS35L45_DSP1RX2_INPUT, 0x00000009 }, + { CS35L45_DSP1RX3_INPUT, 0x00000018 }, + { CS35L45_DSP1RX4_INPUT, 0x00000019 }, + { CS35L45_DSP1RX5_INPUT, 0x00000020 }, + { CS35L45_DSP1RX6_INPUT, 0x00000028 }, + { CS35L45_DSP1RX7_INPUT, 0x0000003A }, + { CS35L45_DSP1RX8_INPUT, 0x00000028 }, { CS35L45_AMP_PCM_CONTROL, 0x00100000 }, { CS35L45_IRQ1_CFG, 0x00000000 }, { CS35L45_IRQ1_MASK_1, 0xBFEFFFBF }, @@ -100,6 +125,7 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_SYNC_GPIO1: case CS35L45_INTB_GPIO2_MCLK_REF: case CS35L45_GPIO3: + case CS35L45_PWRMGT_CTL: case CS35L45_REFCLK_INPUT: case CS35L45_GLOBAL_SAMPLE_RATE: case CS35L45_ASP_ENABLES1: @@ -117,6 +143,14 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_ASPTX3_INPUT: case CS35L45_ASPTX4_INPUT: case CS35L45_ASPTX5_INPUT: + case CS35L45_DSP1RX1_INPUT: + case CS35L45_DSP1RX2_INPUT: + case CS35L45_DSP1RX3_INPUT: + case CS35L45_DSP1RX4_INPUT: + case CS35L45_DSP1RX5_INPUT: + case CS35L45_DSP1RX6_INPUT: + case CS35L45_DSP1RX7_INPUT: + case CS35L45_DSP1RX8_INPUT: case CS35L45_AMP_PCM_CONTROL: case CS35L45_AMP_PCM_HPF_TST: case CS35L45_IRQ1_CFG: @@ -128,6 +162,40 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_GPIO1_CTRL1: case CS35L45_GPIO2_CTRL1: case CS35L45_GPIO3_CTRL1: + case CS35L45_DSP_MBOX_1: + case CS35L45_DSP_MBOX_2: + case CS35L45_DSP_VIRT1_MBOX_1 ... CS35L45_DSP_VIRT1_MBOX_4: + case CS35L45_DSP_VIRT2_MBOX_1 ... CS35L45_DSP_VIRT2_MBOX_4: + case CS35L45_DSP1_SYS_ID: + case CS35L45_DSP1_CLOCK_FREQ: + case CS35L45_DSP1_RX1_RATE: + case CS35L45_DSP1_RX2_RATE: + case CS35L45_DSP1_RX3_RATE: + case CS35L45_DSP1_RX4_RATE: + case CS35L45_DSP1_RX5_RATE: + case CS35L45_DSP1_RX6_RATE: + case CS35L45_DSP1_RX7_RATE: + case CS35L45_DSP1_RX8_RATE: + case CS35L45_DSP1_TX1_RATE: + case CS35L45_DSP1_TX2_RATE: + case CS35L45_DSP1_TX3_RATE: + case CS35L45_DSP1_TX4_RATE: + case CS35L45_DSP1_TX5_RATE: + case CS35L45_DSP1_TX6_RATE: + case CS35L45_DSP1_TX7_RATE: + case CS35L45_DSP1_TX8_RATE: + case CS35L45_DSP1_SCRATCH1: + case CS35L45_DSP1_SCRATCH2: + case CS35L45_DSP1_SCRATCH3: + case CS35L45_DSP1_SCRATCH4: + case CS35L45_DSP1_CCM_CORE_CONTROL: + case CS35L45_DSP1_XMEM_PACK_0 ... CS35L45_DSP1_XMEM_PACK_4607: + case CS35L45_DSP1_XMEM_UNPACK32_0 ... CS35L45_DSP1_XMEM_UNPACK32_3071: + case CS35L45_DSP1_XMEM_UNPACK24_0 ... CS35L45_DSP1_XMEM_UNPACK24_6143: + case CS35L45_DSP1_YMEM_PACK_0 ... CS35L45_DSP1_YMEM_PACK_1532: + case CS35L45_DSP1_YMEM_UNPACK32_0 ... CS35L45_DSP1_YMEM_UNPACK32_1022: + case CS35L45_DSP1_YMEM_UNPACK24_0 ... CS35L45_DSP1_YMEM_UNPACK24_2043: + case CS35L45_DSP1_PMEM_0 ... CS35L45_DSP1_PMEM_3834: return true; default: return false; @@ -146,6 +214,24 @@ static bool cs35l45_volatile_reg(struct device *dev, unsigned int reg) case CS35L45_IRQ1_EINT_1 ... CS35L45_IRQ1_EINT_18: case CS35L45_IRQ1_STS_1 ... CS35L45_IRQ1_STS_18: case CS35L45_GPIO_STATUS1: + case CS35L45_DSP_MBOX_1: + case CS35L45_DSP_MBOX_2: + case CS35L45_DSP_VIRT1_MBOX_1 ... CS35L45_DSP_VIRT1_MBOX_4: + case CS35L45_DSP_VIRT2_MBOX_1 ... CS35L45_DSP_VIRT2_MBOX_4: + case CS35L45_DSP1_SYS_ID: + case CS35L45_DSP1_CLOCK_FREQ: + case CS35L45_DSP1_SCRATCH1: + case CS35L45_DSP1_SCRATCH2: + case CS35L45_DSP1_SCRATCH3: + case CS35L45_DSP1_SCRATCH4: + case CS35L45_DSP1_CCM_CORE_CONTROL: + case CS35L45_DSP1_XMEM_PACK_0 ... CS35L45_DSP1_XMEM_PACK_4607: + case CS35L45_DSP1_XMEM_UNPACK32_0 ... CS35L45_DSP1_XMEM_UNPACK32_3071: + case CS35L45_DSP1_XMEM_UNPACK24_0 ... CS35L45_DSP1_XMEM_UNPACK24_6143: + case CS35L45_DSP1_YMEM_PACK_0 ... CS35L45_DSP1_YMEM_PACK_1532: + case CS35L45_DSP1_YMEM_UNPACK32_0 ... CS35L45_DSP1_YMEM_UNPACK32_1022: + case CS35L45_DSP1_YMEM_UNPACK24_0 ... CS35L45_DSP1_YMEM_UNPACK24_2043: + case CS35L45_DSP1_PMEM_0 ... CS35L45_DSP1_PMEM_3834: return true; default: return false; diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index 0c3d01363c8d..e7b40b063eb2 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -10,6 +10,7 @@ #include <linux/module.h> #include <linux/pm_runtime.h> #include <linux/property.h> +#include <linux/firmware.h> #include <linux/regulator/consumer.h> #include <sound/core.h> #include <sound/pcm.h> @@ -19,6 +20,69 @@
#include "cs35l45.h"
+static bool cs35l45_check_cspl_mbox_sts(const enum cs35l45_cspl_mboxcmd cmd, + enum cs35l45_cspl_mboxstate sts) +{ + switch (cmd) { + case CSPL_MBOX_CMD_NONE: + case CSPL_MBOX_CMD_UNKNOWN_CMD: + return true; + case CSPL_MBOX_CMD_PAUSE: + case CSPL_MBOX_CMD_OUT_OF_HIBERNATE: + return (sts == CSPL_MBOX_STS_PAUSED); + case CSPL_MBOX_CMD_RESUME: + return (sts == CSPL_MBOX_STS_RUNNING); + case CSPL_MBOX_CMD_REINIT: + return (sts == CSPL_MBOX_STS_RUNNING); + case CSPL_MBOX_CMD_STOP_PRE_REINIT: + return (sts == CSPL_MBOX_STS_RDY_FOR_REINIT); + default: + return false; + } +} + +static int cs35l45_set_cspl_mbox_cmd(struct cs35l45_private *cs35l45, + struct regmap *regmap, + const enum cs35l45_cspl_mboxcmd cmd) +{ + unsigned int sts = 0, i; + int ret; + + if (!cs35l45->dsp.cs_dsp.running) { + dev_err(cs35l45->dev, "DSP not running\n"); + return -EPERM; + } + + // Set mailbox cmd + ret = regmap_write(regmap, CS35L45_DSP_VIRT1_MBOX_1, cmd); + if (ret < 0) { + if (cmd != CSPL_MBOX_CMD_OUT_OF_HIBERNATE) + dev_err(cs35l45->dev, "Failed to write MBOX: %d\n", ret); + return ret; + } + + // Read mailbox status and verify it is appropriate for the given cmd + for (i = 0; i < 5; i++) { + usleep_range(1000, 1100); + + ret = regmap_read(regmap, CS35L45_DSP_MBOX_2, &sts); + if (ret < 0) { + dev_err(cs35l45->dev, "Failed to read MBOX STS: %d\n", ret); + continue; + } + + if (!cs35l45_check_cspl_mbox_sts(cmd, sts)) + dev_dbg(cs35l45->dev, "[%u] cmd %u returned invalid sts %u", i, cmd, sts); + else + return 0; + } + + if (cmd != CSPL_MBOX_CMD_OUT_OF_HIBERNATE) + dev_err(cs35l45->dev, "Failed to set mailbox cmd %u (status %u)\n", cmd, sts); + + return -ENOMSG; +} + static int cs35l45_global_en_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -46,10 +110,68 @@ static int cs35l45_global_en_ev(struct snd_soc_dapm_widget *w, return 0; }
+static int cs35l45_dsp_preload_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component); + int ret; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (cs35l45->dsp.cs_dsp.booted) + return 0; + + return wm_adsp_early_event(w, kcontrol, event); + case SND_SOC_DAPM_POST_PMU: + if (cs35l45->dsp.cs_dsp.running) + return 0; + + regmap_set_bits(cs35l45->regmap, CS35L45_PWRMGT_CTL, + CS35L45_MEM_RDY_MASK); + + return wm_adsp_event(w, kcontrol, event); + case SND_SOC_DAPM_PRE_PMD: + if (cs35l45->dsp.preloaded) + return 0; + + if (cs35l45->dsp.cs_dsp.running) { + ret = wm_adsp_event(w, kcontrol, event); + if (ret) + return ret; + } + + return wm_adsp_early_event(w, kcontrol, event); + default: + return 0; + } +} + +static int cs35l45_dsp_audio_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + return cs35l45_set_cspl_mbox_cmd(cs35l45, cs35l45->regmap, + CSPL_MBOX_CMD_RESUME); + case SND_SOC_DAPM_PRE_PMD: + return cs35l45_set_cspl_mbox_cmd(cs35l45, cs35l45->regmap, + CSPL_MBOX_CMD_PAUSE); + default: + return 0; + } + + return 0; +} + static const char * const cs35l45_asp_tx_txt[] = { "Zero", "ASP_RX1", "ASP_RX2", "VMON", "IMON", "ERR_VOL", "VDD_BATTMON", "VDD_BSTMON", + "DSP_TX1", "DSP_TX2", "Interpolator", "IL_TARGET", };
@@ -57,6 +179,7 @@ static const unsigned int cs35l45_asp_tx_val[] = { CS35L45_PCM_SRC_ZERO, CS35L45_PCM_SRC_ASP_RX1, CS35L45_PCM_SRC_ASP_RX2, CS35L45_PCM_SRC_VMON, CS35L45_PCM_SRC_IMON, CS35L45_PCM_SRC_ERR_VOL, CS35L45_PCM_SRC_VDD_BATTMON, CS35L45_PCM_SRC_VDD_BSTMON, + CS35L45_PCM_SRC_DSP_TX1, CS35L45_PCM_SRC_DSP_TX2, CS35L45_PCM_SRC_INTERPOLATOR, CS35L45_PCM_SRC_IL_TARGET, };
@@ -78,12 +201,54 @@ static const struct soc_enum cs35l45_asp_tx_enums[] = { cs35l45_asp_tx_val), };
+static const char * const cs35l45_dsp_rx_txt[] = { + "Zero", "ASP_RX1", "ASP_RX2", + "VMON", "IMON", "ERR_VOL", + "CLASSH_TGT", "VDD_BATTMON", + "VDD_BSTMON", "TEMPMON", +}; + +static const unsigned int cs35l45_dsp_rx_val[] = { + CS35L45_PCM_SRC_ZERO, CS35L45_PCM_SRC_ASP_RX1, CS35L45_PCM_SRC_ASP_RX2, + CS35L45_PCM_SRC_VMON, CS35L45_PCM_SRC_IMON, CS35L45_PCM_SRC_ERR_VOL, + CS35L45_PCM_SRC_CLASSH_TGT, CS35L45_PCM_SRC_VDD_BATTMON, + CS35L45_PCM_SRC_VDD_BSTMON, CS35L45_PCM_SRC_TEMPMON, +}; + +static const struct soc_enum cs35l45_dsp_rx_enums[] = { + SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX1_INPUT, 0, CS35L45_PCM_SRC_MASK, + ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt, + cs35l45_dsp_rx_val), + SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX2_INPUT, 0, CS35L45_PCM_SRC_MASK, + ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt, + cs35l45_dsp_rx_val), + SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX3_INPUT, 0, CS35L45_PCM_SRC_MASK, + ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt, + cs35l45_dsp_rx_val), + SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX4_INPUT, 0, CS35L45_PCM_SRC_MASK, + ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt, + cs35l45_dsp_rx_val), + SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX5_INPUT, 0, CS35L45_PCM_SRC_MASK, + ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt, + cs35l45_dsp_rx_val), + SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX6_INPUT, 0, CS35L45_PCM_SRC_MASK, + ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt, + cs35l45_dsp_rx_val), + SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX7_INPUT, 0, CS35L45_PCM_SRC_MASK, + ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt, + cs35l45_dsp_rx_val), + SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX8_INPUT, 0, CS35L45_PCM_SRC_MASK, + ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt, + cs35l45_dsp_rx_val), +}; + static const char * const cs35l45_dac_txt[] = { - "Zero", "ASP_RX1", "ASP_RX2" + "Zero", "ASP_RX1", "ASP_RX2", "DSP_TX1", "DSP_TX2" };
static const unsigned int cs35l45_dac_val[] = { - CS35L45_PCM_SRC_ZERO, CS35L45_PCM_SRC_ASP_RX1, CS35L45_PCM_SRC_ASP_RX2 + CS35L45_PCM_SRC_ZERO, CS35L45_PCM_SRC_ASP_RX1, CS35L45_PCM_SRC_ASP_RX2, + CS35L45_PCM_SRC_DSP_TX1, CS35L45_PCM_SRC_DSP_TX2 };
static const struct soc_enum cs35l45_dacpcm_enums[] = { @@ -100,11 +265,29 @@ static const struct snd_kcontrol_new cs35l45_asp_muxes[] = { SOC_DAPM_ENUM("ASP_TX5 Source", cs35l45_asp_tx_enums[4]), };
+static const struct snd_kcontrol_new cs35l45_dsp_muxes[] = { + SOC_DAPM_ENUM("DSP_RX1 Source", cs35l45_dsp_rx_enums[0]), + SOC_DAPM_ENUM("DSP_RX2 Source", cs35l45_dsp_rx_enums[1]), + SOC_DAPM_ENUM("DSP_RX3 Source", cs35l45_dsp_rx_enums[2]), + SOC_DAPM_ENUM("DSP_RX4 Source", cs35l45_dsp_rx_enums[3]), + SOC_DAPM_ENUM("DSP_RX5 Source", cs35l45_dsp_rx_enums[4]), + SOC_DAPM_ENUM("DSP_RX6 Source", cs35l45_dsp_rx_enums[5]), + SOC_DAPM_ENUM("DSP_RX7 Source", cs35l45_dsp_rx_enums[6]), + SOC_DAPM_ENUM("DSP_RX8 Source", cs35l45_dsp_rx_enums[7]), +}; + static const struct snd_kcontrol_new cs35l45_dac_muxes[] = { SOC_DAPM_ENUM("DACPCM1 Source", cs35l45_dacpcm_enums[0]), };
static const struct snd_soc_dapm_widget cs35l45_dapm_widgets[] = { + SND_SOC_DAPM_SPK("DSP1 Preload", NULL), + SND_SOC_DAPM_SUPPLY_S("DSP1 Preloader", 100, SND_SOC_NOPM, 0, 0, + cs35l45_dsp_preload_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_OUT_DRV_E("DSP1", SND_SOC_NOPM, 0, 0, NULL, 0, + cs35l45_dsp_audio_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_SUPPLY("GLOBAL_EN", SND_SOC_NOPM, 0, 0, cs35l45_global_en_ev, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), @@ -139,6 +322,15 @@ static const struct snd_soc_dapm_widget cs35l45_dapm_widgets[] = { SND_SOC_DAPM_MUX("ASP_TX4 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[3]), SND_SOC_DAPM_MUX("ASP_TX5 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[4]),
+ SND_SOC_DAPM_MUX("DSP_RX1 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[0]), + SND_SOC_DAPM_MUX("DSP_RX2 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[1]), + SND_SOC_DAPM_MUX("DSP_RX3 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[2]), + SND_SOC_DAPM_MUX("DSP_RX4 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[3]), + SND_SOC_DAPM_MUX("DSP_RX5 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[4]), + SND_SOC_DAPM_MUX("DSP_RX6 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[5]), + SND_SOC_DAPM_MUX("DSP_RX7 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[6]), + SND_SOC_DAPM_MUX("DSP_RX8 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[7]), + SND_SOC_DAPM_MUX("DACPCM1 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dac_muxes[0]),
SND_SOC_DAPM_OUT_DRV("AMP", SND_SOC_NOPM, 0, 0, NULL, 0), @@ -149,6 +341,8 @@ static const struct snd_soc_dapm_widget cs35l45_dapm_widgets[] = { #define CS35L45_ASP_MUX_ROUTE(name) \ { name" Source", "ASP_RX1", "ASP_RX1" }, \ { name" Source", "ASP_RX2", "ASP_RX2" }, \ + { name" Source", "DSP_TX1", "DSP1" }, \ + { name" Source", "DSP_TX2", "DSP1" }, \ { name" Source", "VMON", "VMON" }, \ { name" Source", "IMON", "IMON" }, \ { name" Source", "ERR_VOL", "ERR_VOL" }, \ @@ -157,10 +351,16 @@ static const struct snd_soc_dapm_widget cs35l45_dapm_widgets[] = { { name" Source", "Interpolator", "AMP_INTP" }, \ { name" Source", "IL_TARGET", "IL_TARGET" }
-#define CS35L45_DAC_MUX_ROUTE(name) \ +#define CS35L45_DSP_MUX_ROUTE(name) \ { name" Source", "ASP_RX1", "ASP_RX1" }, \ { name" Source", "ASP_RX2", "ASP_RX2" }
+#define CS35L45_DAC_MUX_ROUTE(name) \ + { name" Source", "ASP_RX1", "ASP_RX1" }, \ + { name" Source", "ASP_RX2", "ASP_RX2" }, \ + { name" Source", "DSP_TX1", "DSP1" }, \ + { name" Source", "DSP_TX2", "DSP1" } + static const struct snd_soc_dapm_route cs35l45_dapm_routes[] = { /* Feedback */ { "VMON", NULL, "VMON_SRC" }, @@ -204,6 +404,27 @@ static const struct snd_soc_dapm_route cs35l45_dapm_routes[] = { { "AMP", NULL, "DACPCM1 Source"}, { "AMP", NULL, "GLOBAL_EN"},
+ CS35L45_DSP_MUX_ROUTE("DSP_RX1"), + CS35L45_DSP_MUX_ROUTE("DSP_RX2"), + CS35L45_DSP_MUX_ROUTE("DSP_RX3"), + CS35L45_DSP_MUX_ROUTE("DSP_RX4"), + CS35L45_DSP_MUX_ROUTE("DSP_RX5"), + CS35L45_DSP_MUX_ROUTE("DSP_RX6"), + CS35L45_DSP_MUX_ROUTE("DSP_RX7"), + CS35L45_DSP_MUX_ROUTE("DSP_RX8"), + + {"DSP1", NULL, "DSP_RX1 Source"}, + {"DSP1", NULL, "DSP_RX2 Source"}, + {"DSP1", NULL, "DSP_RX3 Source"}, + {"DSP1", NULL, "DSP_RX4 Source"}, + {"DSP1", NULL, "DSP_RX5 Source"}, + {"DSP1", NULL, "DSP_RX6 Source"}, + {"DSP1", NULL, "DSP_RX7 Source"}, + {"DSP1", NULL, "DSP_RX8 Source"}, + + {"DSP1 Preload", NULL, "DSP1 Preloader"}, + {"DSP1", NULL, "DSP1 Preloader"}, + CS35L45_DAC_MUX_ROUTE("DACPCM1"),
{ "SPK", NULL, "AMP"}, @@ -219,6 +440,8 @@ static const struct snd_kcontrol_new cs35l45_controls[] = { -409, 48, (CS35L45_AMP_VOL_PCM_WIDTH - 1) - 1, 0, cs35l45_dig_pcm_vol_tlv), + WM_ADSP2_PRELOAD_SWITCH("DSP1", 1), + WM_ADSP_FW_CONTROL("DSP1", 0), };
static int cs35l45_set_pll(struct cs35l45_private *cs35l45, unsigned int freq) @@ -489,7 +712,24 @@ static struct snd_soc_dai_driver cs35l45_dai[] = { }, };
+static int cs35l45_component_probe(struct snd_soc_component *component) +{ + struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component); + + return wm_adsp2_component_probe(&cs35l45->dsp, component); +} + +static void cs35l45_component_remove(struct snd_soc_component *component) +{ + struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component); + + wm_adsp2_component_remove(&cs35l45->dsp, component); +} + static const struct snd_soc_component_driver cs35l45_component = { + .probe = cs35l45_component_probe, + .remove = cs35l45_component_remove, + .dapm_widgets = cs35l45_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(cs35l45_dapm_widgets),
@@ -607,6 +847,60 @@ static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45) return 0; }
+static int cs35l45_dsp_virt2_mbox3_irq_handle(struct cs35l45_private *cs35l45, + const unsigned int cmd, + unsigned int data) +{ + static char *speak_status = "Unknown"; + + switch (cmd) { + case EVENT_SPEAKER_STATUS: + switch (data) { + case 1: + speak_status = "All Clear"; + break; + case 2: + speak_status = "Open Circuit"; + break; + case 4: + speak_status = "Short Circuit"; + break; + } + + dev_info(cs35l45->dev, "MBOX event (SPEAKER_STATUS): %s\n", + speak_status); + break; + case EVENT_BOOT_DONE: + dev_dbg(cs35l45->dev, "MBOX event (BOOT_DONE)\n"); + break; + default: + dev_err(cs35l45->dev, "MBOX event not supported %u\n", cmd); + return -EINVAL; + } + + return 0; +} + +static irqreturn_t cs35l45_dsp_virt2_mbox_cb(int irq, void *data) +{ + struct cs35l45_private *cs35l45 = data; + unsigned int mbox_val; + int ret = 0; + + ret = regmap_read(cs35l45->regmap, CS35L45_DSP_VIRT2_MBOX_3, &mbox_val); + if (!ret && mbox_val) + ret = cs35l45_dsp_virt2_mbox3_irq_handle(cs35l45, mbox_val & CS35L45_MBOX3_CMD_MASK, + (mbox_val & CS35L45_MBOX3_DATA_MASK) >> CS35L45_MBOX3_DATA_SHIFT); + + /* Handle DSP trace log IRQ */ + ret = regmap_read(cs35l45->regmap, CS35L45_DSP_VIRT2_MBOX_4, &mbox_val); + if (!ret && mbox_val != 0) { + dev_err(cs35l45->dev, "Spurious DSP MBOX4 IRQ\n"); + } + + return IRQ_RETVAL(ret); +} + static irqreturn_t cs35l45_pll_unlock(int irq, void *data) { struct cs35l45_private *cs35l45 = data; @@ -639,6 +933,7 @@ static const struct cs35l45_irq cs35l45_irqs[] = { CS35L45_IRQ(DSP_WDT_EXPIRE, "DSP Watchdog Timer", cs35l45_spk_safe_err), CS35L45_IRQ(PLL_UNLOCK_FLAG_RISE, "PLL unlock", cs35l45_pll_unlock), CS35L45_IRQ(PLL_LOCK_FLAG, "PLL lock", cs35l45_pll_lock), + CS35L45_IRQ(DSP_VIRT2_MBOX, "DSP virtual MBOX 2 write flag", cs35l45_dsp_virt2_mbox_cb), };
static irqreturn_t cs35l45_spk_safe_err(int irq, void *data) @@ -665,6 +960,7 @@ static const struct regmap_irq cs35l45_reg_irqs[] = { CS35L45_REG_IRQ(IRQ1_EINT_2, DSP_WDT_EXPIRE), CS35L45_REG_IRQ(IRQ1_EINT_3, PLL_UNLOCK_FLAG_RISE), CS35L45_REG_IRQ(IRQ1_EINT_3, PLL_LOCK_FLAG), + CS35L45_REG_IRQ(IRQ1_EINT_2, DSP_VIRT2_MBOX), };
static const struct regmap_irq_chip cs35l45_regmap_irq_chip = { @@ -724,14 +1020,63 @@ static int cs35l45_initialize(struct cs35l45_private *cs35l45) if (ret < 0) return ret;
- pm_runtime_set_autosuspend_delay(cs35l45->dev, 3000); - pm_runtime_use_autosuspend(cs35l45->dev); - pm_runtime_set_active(cs35l45->dev); - pm_runtime_enable(cs35l45->dev); - return 0; }
+static const struct reg_sequence cs35l45_fs_errata_patch[] = { + {0x02B80080, 0x00000001}, + {0x02B80088, 0x00000001}, + {0x02B80090, 0x00000001}, + {0x02B80098, 0x00000001}, + {0x02B800A0, 0x00000001}, + {0x02B800A8, 0x00000001}, + {0x02B800B0, 0x00000001}, + {0x02B800B8, 0x00000001}, + {0x02B80280, 0x00000001}, + {0x02B80288, 0x00000001}, + {0x02B80290, 0x00000001}, + {0x02B80298, 0x00000001}, + {0x02B802A0, 0x00000001}, + {0x02B802A8, 0x00000001}, + {0x02B802B0, 0x00000001}, + {0x02B802B8, 0x00000001}, +}; + +static const struct cs_dsp_region cs35l45_dsp1_regions[] = { + { .type = WMFW_HALO_PM_PACKED, .base = CS35L45_DSP1_PMEM_0 }, + { .type = WMFW_HALO_XM_PACKED, .base = CS35L45_DSP1_XMEM_PACK_0 }, + { .type = WMFW_HALO_YM_PACKED, .base = CS35L45_DSP1_YMEM_PACK_0 }, + {. type = WMFW_ADSP2_XM, .base = CS35L45_DSP1_XMEM_UNPACK24_0}, + {. type = WMFW_ADSP2_YM, .base = CS35L45_DSP1_YMEM_UNPACK24_0}, +}; + +static int cs35l45_dsp_init(struct cs35l45_private *cs35l45) +{ + struct wm_adsp *dsp = &cs35l45->dsp; + int ret; + + dsp->part = "cs35l45"; + dsp->fw = 9; /* 9 is WM_ADSP_FW_SPK_PROT in wm_adsp.c */ + dsp->toggle_preload = true; + dsp->cs_dsp.num = 1; + dsp->cs_dsp.type = WMFW_HALO; + dsp->cs_dsp.rev = 0; + dsp->cs_dsp.dev = cs35l45->dev; + dsp->cs_dsp.regmap = cs35l45->regmap; + dsp->cs_dsp.base = CS35L45_DSP1_CLOCK_FREQ; + dsp->cs_dsp.base_sysinfo = CS35L45_DSP1_SYS_ID; + dsp->cs_dsp.mem = cs35l45_dsp1_regions; + dsp->cs_dsp.num_mems = ARRAY_SIZE(cs35l45_dsp1_regions); + dsp->cs_dsp.lock_regions = 0xFFFFFFFF; + + ret = wm_halo_init(dsp); + + regmap_multi_reg_write(cs35l45->regmap, cs35l45_fs_errata_patch, + ARRAY_SIZE(cs35l45_fs_errata_patch)); + + return ret; +} + int cs35l45_probe(struct cs35l45_private *cs35l45) { struct device *dev = cs35l45->dev; @@ -781,6 +1126,17 @@ int cs35l45_probe(struct cs35l45_private *cs35l45) if (ret < 0) goto err_reset;
+ ret = cs35l45_dsp_init(cs35l45); + if (ret < 0) + goto err_reset; + + pm_runtime_set_autosuspend_delay(cs35l45->dev, 3000); + pm_runtime_use_autosuspend(cs35l45->dev); + pm_runtime_mark_last_busy(cs35l45->dev); + pm_runtime_set_active(cs35l45->dev); + pm_runtime_get_noresume(cs35l45->dev); + pm_runtime_enable(cs35l45->dev); + if (cs35l45->irq) { if (cs35l45->irq_invert) irq_pol |= IRQF_TRIGGER_HIGH; @@ -791,7 +1147,7 @@ int cs35l45_probe(struct cs35l45_private *cs35l45) &cs35l45_regmap_irq_chip, &cs35l45->irq_data); if (ret) { dev_err(dev, "Failed to register IRQ chip: %d\n", ret); - goto err_reset; + goto err_dsp; }
for (i = 0; i < ARRAY_SIZE(cs35l45_irqs); i++) { @@ -799,7 +1155,7 @@ int cs35l45_probe(struct cs35l45_private *cs35l45) if (irq < 0) { dev_err(dev, "Failed to get %s\n", cs35l45_irqs[i].name); ret = irq; - goto err_reset; + goto err_dsp; }
ret = devm_request_threaded_irq(dev, irq, NULL, cs35l45_irqs[i].handler, @@ -807,7 +1163,7 @@ int cs35l45_probe(struct cs35l45_private *cs35l45) if (ret) { dev_err(dev, "Failed to request IRQ %s: %d\n", cs35l45_irqs[i].name, ret); - goto err_reset; + goto err_dsp; } } } @@ -816,10 +1172,17 @@ int cs35l45_probe(struct cs35l45_private *cs35l45) cs35l45_dai, ARRAY_SIZE(cs35l45_dai)); if (ret < 0) - goto err_reset; + goto err_dsp; + + pm_runtime_put_autosuspend(cs35l45->dev);
return 0;
+err_dsp: + pm_runtime_disable(cs35l45->dev); + pm_runtime_put_noidle(cs35l45->dev); + wm_adsp2_remove(&cs35l45->dsp); + err_reset: gpiod_set_value_cansleep(cs35l45->reset_gpio, 0); err: @@ -832,9 +1195,13 @@ EXPORT_SYMBOL_NS_GPL(cs35l45_probe, SND_SOC_CS35L45);
void cs35l45_remove(struct cs35l45_private *cs35l45) { + pm_runtime_get_sync(cs35l45->dev); pm_runtime_disable(cs35l45->dev); + wm_adsp2_remove(&cs35l45->dsp);
gpiod_set_value_cansleep(cs35l45->reset_gpio, 0); + + pm_runtime_put_noidle(cs35l45->dev); regulator_disable(cs35l45->vdd_a); /* VDD_BATT must be the last to power-off */ regulator_disable(cs35l45->vdd_batt); diff --git a/sound/soc/codecs/cs35l45.h b/sound/soc/codecs/cs35l45.h index ce92f5068ac5..87032619b341 100644 --- a/sound/soc/codecs/cs35l45.h +++ b/sound/soc/codecs/cs35l45.h @@ -15,6 +15,7 @@ #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <dt-bindings/sound/cs35l45.h> +#include "wm_adsp.h"
#define CS35L45_DEVID 0x00000000 #define CS35L45_REVID 0x00000004 @@ -28,6 +29,7 @@ #define CS35L45_SYNC_GPIO1 0x00002430 #define CS35L45_INTB_GPIO2_MCLK_REF 0x00002434 #define CS35L45_GPIO3 0x00002438 +#define CS35L45_PWRMGT_CTL 0x00002900 #define CS35L45_REFCLK_INPUT 0x00002C04 #define CS35L45_GLOBAL_SAMPLE_RATE 0x00002C0C #define CS35L45_BOOST_CCM_CFG 0x00003808 @@ -48,6 +50,14 @@ #define CS35L45_ASPTX3_INPUT 0x00004C28 #define CS35L45_ASPTX4_INPUT 0x00004C2C #define CS35L45_ASPTX5_INPUT 0x00004C30 +#define CS35L45_DSP1RX1_INPUT 0x00004C40 +#define CS35L45_DSP1RX2_INPUT 0x00004C44 +#define CS35L45_DSP1RX3_INPUT 0x00004C48 +#define CS35L45_DSP1RX4_INPUT 0x00004C4C +#define CS35L45_DSP1RX5_INPUT 0x00004C50 +#define CS35L45_DSP1RX6_INPUT 0x00004C54 +#define CS35L45_DSP1RX7_INPUT 0x00004C58 +#define CS35L45_DSP1RX8_INPUT 0x00004C5C #define CS35L45_LDPM_CONFIG 0x00006404 #define CS35L45_AMP_PCM_CONTROL 0x00007000 #define CS35L45_AMP_PCM_HPF_TST 0x00007004 @@ -91,7 +101,55 @@ #define CS35L45_GPIO1_CTRL1 0x0000F008 #define CS35L45_GPIO2_CTRL1 0x0000F00C #define CS35L45_GPIO3_CTRL1 0x0000F010 -#define CS35L45_LASTREG 0x0000F010 +#define CS35L45_DSP_MBOX_1 0x00011000 +#define CS35L45_DSP_MBOX_2 0x00011004 +#define CS35L45_DSP_VIRT1_MBOX_1 0x00011020 +#define CS35L45_DSP_VIRT1_MBOX_2 0x00011024 +#define CS35L45_DSP_VIRT1_MBOX_3 0x00011028 +#define CS35L45_DSP_VIRT1_MBOX_4 0x0001102C +#define CS35L45_DSP_VIRT2_MBOX_1 0x00011040 +#define CS35L45_DSP_VIRT2_MBOX_2 0x00011044 +#define CS35L45_DSP_VIRT2_MBOX_3 0x00011048 +#define CS35L45_DSP_VIRT2_MBOX_4 0x0001104C +#define CS35L45_DSP1_XMEM_PACK_0 0x02000000 +#define CS35L45_DSP1_XMEM_PACK_4607 0x020047FC +#define CS35L45_DSP1_XMEM_UNPACK32_0 0x02400000 +#define CS35L45_DSP1_XMEM_UNPACK32_3071 0x02402FFC +#define CS35L45_DSP1_SYS_ID 0x025E0000 +#define CS35L45_DSP1_XMEM_UNPACK24_0 0x02800000 +#define CS35L45_DSP1_XMEM_UNPACK24_6143 0x02805FFC +#define CS35L45_DSP1_CLOCK_FREQ 0x02B80000 +#define CS35L45_DSP1_RX1_RATE 0x02B80080 +#define CS35L45_DSP1_RX2_RATE 0x02B80088 +#define CS35L45_DSP1_RX3_RATE 0x02B80090 +#define CS35L45_DSP1_RX4_RATE 0x02B80098 +#define CS35L45_DSP1_RX5_RATE 0x02B800A0 +#define CS35L45_DSP1_RX6_RATE 0x02B800A8 +#define CS35L45_DSP1_RX7_RATE 0x02B800B0 +#define CS35L45_DSP1_RX8_RATE 0x02B800B8 +#define CS35L45_DSP1_TX1_RATE 0x02B80280 +#define CS35L45_DSP1_TX2_RATE 0x02B80288 +#define CS35L45_DSP1_TX3_RATE 0x02B80290 +#define CS35L45_DSP1_TX4_RATE 0x02B80298 +#define CS35L45_DSP1_TX5_RATE 0x02B802A0 +#define CS35L45_DSP1_TX6_RATE 0x02B802A8 +#define CS35L45_DSP1_TX7_RATE 0x02B802B0 +#define CS35L45_DSP1_TX8_RATE 0x02B802B8 +#define CS35L45_DSP1_SCRATCH1 0x02B805C0 +#define CS35L45_DSP1_SCRATCH2 0x02B805C8 +#define CS35L45_DSP1_SCRATCH3 0x02B805D0 +#define CS35L45_DSP1_SCRATCH4 0x02B805D8 +#define CS35L45_DSP1_CCM_CORE_CONTROL 0x02BC1000 +#define CS35L45_DSP1_YMEM_PACK_0 0x02C00000 +#define CS35L45_DSP1_YMEM_PACK_1532 0x02C017F0 +#define CS35L45_DSP1_YMEM_UNPACK32_0 0x03000000 +#define CS35L45_DSP1_YMEM_UNPACK32_1022 0x03000FF8 +#define CS35L45_DSP1_YMEM_UNPACK24_0 0x03400000 +#define CS35L45_DSP1_YMEM_UNPACK24_2043 0x03401FEC +#define CS35L45_DSP1_PMEM_0 0x03800000 +#define CS35L45_DSP1_PMEM_3834 0x03803BE8 +#define CS35L45_LASTREG 0x03C6EFE8 + /* SFT_RESET */ #define CS35L45_SOFT_RESET_TRIGGER 0x5A000000
@@ -112,9 +170,20 @@ /* BLOCK_ENABLES2 */ #define CS35L45_ASP_EN_SHIFT 27
+#define CS35L45_MEM_RDY_SHIFT 1 +#define CS35L45_MEM_RDY_MASK BIT(1) + /* ERROR_RELEASE */ #define CS35L45_GLOBAL_ERR_RLS_MASK BIT(11)
+/* CCM_CORE */ +#define CS35L45_CCM_CORE_RESET_SHIFT 9 +#define CS35L45_CCM_CORE_RESET_MASK BIT(9) +#define CS35L45_CCM_PM_REMAP_SHIFT 7 +#define CS35L45_CCM_PM_REMAP_MASK BIT(7) +#define CS35L45_CCM_CORE_EN_SHIFT 0 +#define CS35L45_CCM_CORE_EN_MASK BIT(0) + /* REFCLK_INPUT */ #define CS35L45_PLL_FORCE_EN_SHIFT 16 #define CS35L45_PLL_FORCE_EN_MASK BIT(16) @@ -240,6 +309,8 @@ /* CS35L45_IRQ1_EINT_2 */ #define CS35L45_DSP_WDT_EXPIRE_SHIFT 4 #define CS35L45_DSP_WDT_EXPIRE_MASK BIT(4) +#define CS35L45_DSP_VIRT2_MBOX_SHIFT 21 +#define CS35L45_DSP_VIRT2_MBOX_MASK BIT(21)
/* CS35L45_IRQ1_EINT_3 */ #define CS35L45_PLL_LOCK_FLAG_SHIFT 1 @@ -266,6 +337,8 @@ #define CS35L45_PCM_SRC_CLASSH_TGT 0x21 #define CS35L45_PCM_SRC_VDD_BATTMON 0x28 #define CS35L45_PCM_SRC_VDD_BSTMON 0x29 +#define CS35L45_PCM_SRC_DSP_TX1 0x32 +#define CS35L45_PCM_SRC_DSP_TX2 0x33 #define CS35L45_PCM_SRC_TEMPMON 0x3A #define CS35L45_PCM_SRC_INTERPOLATOR 0x40 #define CS35L45_PCM_SRC_IL_TARGET 0x48 @@ -275,6 +348,27 @@ #define CS35L45_POST_GLOBAL_EN_US 5000 #define CS35L45_PRE_GLOBAL_DIS_US 3000
+#define CS35L45_SPI_MAX_FREQ 4000000 + +enum cs35l45_cspl_mboxstate { + CSPL_MBOX_STS_RUNNING = 0, + CSPL_MBOX_STS_PAUSED = 1, + CSPL_MBOX_STS_RDY_FOR_REINIT = 2, + CSPL_MBOX_STS_HIBERNATE = 3, +}; + +enum cs35l45_cspl_mboxcmd { + CSPL_MBOX_CMD_NONE = 0, + CSPL_MBOX_CMD_PAUSE = 1, + CSPL_MBOX_CMD_RESUME = 2, + CSPL_MBOX_CMD_REINIT = 3, + CSPL_MBOX_CMD_STOP_PRE_REINIT = 4, + CSPL_MBOX_CMD_HIBERNATE = 5, + CSPL_MBOX_CMD_OUT_OF_HIBERNATE = 6, + CSPL_MBOX_CMD_UNKNOWN_CMD = -1, + CSPL_MBOX_CMD_INVALID_SEQUENCE = -2, +}; + #define CS35L45_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_3LE| \ SNDRV_PCM_FMTBIT_S24_LE) @@ -318,10 +412,22 @@ enum cs35l45_irq_list { CS35L45_DSP_WDT_EXPIRE_IRQ, CS35L45_PLL_UNLOCK_FLAG_RISE_IRQ, CS35L45_PLL_LOCK_FLAG_IRQ, + CS35L45_DSP_VIRT2_MBOX_IRQ, CS35L45_NUM_IRQ };
+#define CS35L45_MBOX3_CMD_MASK 0xFF +#define CS35L45_MBOX3_CMD_SHIFT 0 +#define CS35L45_MBOX3_DATA_MASK 0xFFFFFF00 +#define CS35L45_MBOX3_DATA_SHIFT 8 + +enum mbox3_events { + EVENT_SPEAKER_STATUS = 0x66, + EVENT_BOOT_DONE = 0x67, +}; + struct cs35l45_private { + struct wm_adsp dsp; /* needs to be first member */ struct device *dev; struct regmap *regmap; struct gpio_desc *reset_gpio;
From: "Vlad.Karpovich" vkarpovi@opensource.cirrus.com
Adds support for a low-power Hibernation State. Add support for a low-power hibernation state for the DSP. In this state the DSP RAM contents are maintained, such that firmware does not need to be re-downloaded, but the rest of the chip's register state is lost. Entry to this state is achieved via the register interface (either by an external driver using the control port, or the programmable DSP). Exit from this state is triggered by activity on device GPIO pins, intended SPI transaction, or I2C transaction with intended slave address.
Signed-off-by: Vlad Karpovich vkarpovi@opensource.cirrus.com --- sound/soc/codecs/cs35l45-i2c.c | 2 + sound/soc/codecs/cs35l45-spi.c | 1 + sound/soc/codecs/cs35l45-tables.c | 6 +++ sound/soc/codecs/cs35l45.c | 80 +++++++++++++++++++++++++++++++ sound/soc/codecs/cs35l45.h | 29 +++++++++++ 5 files changed, 118 insertions(+)
diff --git a/sound/soc/codecs/cs35l45-i2c.c b/sound/soc/codecs/cs35l45-i2c.c index 5224aaf584b8..1c644ddf66bc 100644 --- a/sound/soc/codecs/cs35l45-i2c.c +++ b/sound/soc/codecs/cs35l45-i2c.c @@ -33,6 +33,8 @@ static int cs35l45_i2c_probe(struct i2c_client *client)
cs35l45->dev = dev; cs35l45->irq = client->irq; + cs35l45->bus_type = CONTROL_BUS_I2C; + cs35l45->i2c_addr = client->addr;
return cs35l45_probe(cs35l45); } diff --git a/sound/soc/codecs/cs35l45-spi.c b/sound/soc/codecs/cs35l45-spi.c index bfb79255783e..3ea2e6fd2b88 100644 --- a/sound/soc/codecs/cs35l45-spi.c +++ b/sound/soc/codecs/cs35l45-spi.c @@ -36,6 +36,7 @@ static int cs35l45_spi_probe(struct spi_device *spi)
cs35l45->dev = dev; cs35l45->irq = spi->irq; + cs35l45->bus_type = CONTROL_BUS_SPI;
return cs35l45_probe(cs35l45); } diff --git a/sound/soc/codecs/cs35l45-tables.c b/sound/soc/codecs/cs35l45-tables.c index 6b68773979ec..212d9cb5a4fb 100644 --- a/sound/soc/codecs/cs35l45-tables.c +++ b/sound/soc/codecs/cs35l45-tables.c @@ -47,6 +47,8 @@ static const struct reg_default cs35l45_defaults[] = { { CS35L45_INTB_GPIO2_MCLK_REF, 0x00000005 }, { CS35L45_GPIO3, 0x00000005 }, { CS35L45_PWRMGT_CTL, 0x00000000 }, + { CS35L45_WAKESRC_CTL, 0x00000008 }, + { CS35L45_WKI2C_CTL, 0x00000030 }, { CS35L45_REFCLK_INPUT, 0x00000510 }, { CS35L45_GLOBAL_SAMPLE_RATE, 0x00000003 }, { CS35L45_ASP_ENABLES1, 0x00000000 }, @@ -126,6 +128,9 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_INTB_GPIO2_MCLK_REF: case CS35L45_GPIO3: case CS35L45_PWRMGT_CTL: + case CS35L45_WAKESRC_CTL: + case CS35L45_WKI2C_CTL: + case CS35L45_PWRMGT_STS: case CS35L45_REFCLK_INPUT: case CS35L45_GLOBAL_SAMPLE_RATE: case CS35L45_ASP_ENABLES1: @@ -210,6 +215,7 @@ static bool cs35l45_volatile_reg(struct device *dev, unsigned int reg) case CS35L45_GLOBAL_ENABLES: case CS35L45_ERROR_RELEASE: case CS35L45_AMP_PCM_HPF_TST: /* not cachable */ + case CS35L45_PWRMGT_STS: case CS35L45_IRQ1_STATUS: case CS35L45_IRQ1_EINT_1 ... CS35L45_IRQ1_EINT_18: case CS35L45_IRQ1_STS_1 ... CS35L45_IRQ1_STS_18: diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index e7b40b063eb2..8a351cb6d692 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -36,6 +36,8 @@ static bool cs35l45_check_cspl_mbox_sts(const enum cs35l45_cspl_mboxcmd cmd, return (sts == CSPL_MBOX_STS_RUNNING); case CSPL_MBOX_CMD_STOP_PRE_REINIT: return (sts == CSPL_MBOX_STS_RDY_FOR_REINIT); + case CSPL_MBOX_CMD_HIBERNATE: + return (sts == CSPL_MBOX_STS_HIBERNATE); default: return false; } @@ -744,11 +746,81 @@ static const struct snd_soc_component_driver cs35l45_component = { .endianness = 1, };
+static void cs35l45_setup_hibernate(struct cs35l45_private *cs35l45) +{ + unsigned int wksrc; + + if (cs35l45->bus_type == CONTROL_BUS_I2C) + wksrc = CS35L45_WKSRC_I2C; + else + wksrc = CS35L45_WKSRC_SPI; + + regmap_update_bits(cs35l45->regmap, CS35L45_WAKESRC_CTL, + CS35L45_WKSRC_EN_MASK, + wksrc << CS35L45_WKSRC_EN_SHIFT); + + regmap_set_bits(cs35l45->regmap, CS35L45_WAKESRC_CTL, + CS35L45_UPDT_WKCTL_MASK); + + regmap_update_bits(cs35l45->regmap, CS35L45_WKI2C_CTL, + CS35L45_WKI2C_ADDR_MASK, cs35l45->i2c_addr); + + regmap_set_bits(cs35l45->regmap, CS35L45_WKI2C_CTL, + CS35L45_UPDT_WKI2C_MASK); +} + +static int cs35l45_enter_hibernate(struct cs35l45_private *cs35l45) +{ + dev_dbg(cs35l45->dev, "Enter hibernate\n"); + + cs35l45_setup_hibernate(cs35l45); + + // Don't wait for ACK since bus activity would wake the device + regmap_write(cs35l45->regmap, CS35L45_DSP_VIRT1_MBOX_1, CSPL_MBOX_CMD_HIBERNATE); + + return 0; +} + +static int cs35l45_exit_hibernate(struct cs35l45_private *cs35l45) +{ + const int wake_retries = 20; + const int sleep_retries = 5; + int ret, i, j; + + for (i = 0; i < sleep_retries; i++) { + dev_dbg(cs35l45->dev, "Exit hibernate\n"); + + for (j = 0; j < wake_retries; j++) { + ret = cs35l45_set_cspl_mbox_cmd(cs35l45, cs35l45->regmap, + CSPL_MBOX_CMD_OUT_OF_HIBERNATE); + if (!ret) { + dev_dbg(cs35l45->dev, "Wake success at cycle: %d\n", j); + return 0; + } + usleep_range(100, 200); + } + + dev_err(cs35l45->dev, "Wake failed, re-enter hibernate: %d\n", ret); + + cs35l45_setup_hibernate(cs35l45); + } + + dev_err(cs35l45->dev, "Timed out waking device\n"); + + return -ETIMEDOUT; +} + static int __maybe_unused cs35l45_runtime_suspend(struct device *dev) { struct cs35l45_private *cs35l45 = dev_get_drvdata(dev);
+ if (!cs35l45->dsp.preloaded || !cs35l45->dsp.cs_dsp.running) + return 0; + + cs35l45_enter_hibernate(cs35l45); + regcache_cache_only(cs35l45->regmap, true); + regcache_mark_dirty(cs35l45->regmap);
dev_dbg(cs35l45->dev, "Runtime suspended\n");
@@ -760,9 +832,17 @@ static int __maybe_unused cs35l45_runtime_resume(struct device *dev) struct cs35l45_private *cs35l45 = dev_get_drvdata(dev); int ret;
+ if (!cs35l45->dsp.preloaded || !cs35l45->dsp.cs_dsp.running) + return 0; + dev_dbg(cs35l45->dev, "Runtime resume\n");
regcache_cache_only(cs35l45->regmap, false); + + ret = cs35l45_exit_hibernate(cs35l45); + if (ret) + return ret; + ret = regcache_sync(cs35l45->regmap); if (ret != 0) dev_warn(cs35l45->dev, "regcache_sync failed: %d\n", ret); diff --git a/sound/soc/codecs/cs35l45.h b/sound/soc/codecs/cs35l45.h index 87032619b341..0da28439f628 100644 --- a/sound/soc/codecs/cs35l45.h +++ b/sound/soc/codecs/cs35l45.h @@ -30,6 +30,9 @@ #define CS35L45_INTB_GPIO2_MCLK_REF 0x00002434 #define CS35L45_GPIO3 0x00002438 #define CS35L45_PWRMGT_CTL 0x00002900 +#define CS35L45_WAKESRC_CTL 0x00002904 +#define CS35L45_WKI2C_CTL 0x00002908 +#define CS35L45_PWRMGT_STS 0x0000290C #define CS35L45_REFCLK_INPUT 0x00002C04 #define CS35L45_GLOBAL_SAMPLE_RATE 0x00002C0C #define CS35L45_BOOST_CCM_CFG 0x00003808 @@ -348,6 +351,25 @@ #define CS35L45_POST_GLOBAL_EN_US 5000 #define CS35L45_PRE_GLOBAL_DIS_US 3000
+/* WAKESRC_CTL */ +#define CS35L45_WKSRC_SYNC_GPIO1 BIT(0) +#define CS35L45_WKSRC_INT_GPIO2 BIT(1) +#define CS35L45_WKSRC_GPIO3 BIT(2) +#define CS35L45_WKSRC_SPI BIT(3) +#define CS35L45_WKSRC_I2C BIT(4) +#define CS35L45_UPDT_WKCTL_SHIFT 15 +#define CS35L45_UPDT_WKCTL_MASK BIT(15) +#define CS35L45_WKSRC_EN_SHIFT 8 +#define CS35L45_WKSRC_EN_MASK GENMASK(12, 8) +#define CS35L45_WKSRC_POL_SHIFT 0 +#define CS35L45_WKSRC_POL_MASK GENMASK(3, 0) + +/* WAKEI2C_CTL */ +#define CS35L45_UPDT_WKI2C_SHIFT 15 +#define CS35L45_UPDT_WKI2C_MASK BIT(15) +#define CS35L45_WKI2C_ADDR_SHIFT 0 +#define CS35L45_WKI2C_ADDR_MASK GENMASK(6, 0) + #define CS35L45_SPI_MAX_FREQ 4000000
enum cs35l45_cspl_mboxstate { @@ -369,6 +391,11 @@ enum cs35l45_cspl_mboxcmd { CSPL_MBOX_CMD_INVALID_SEQUENCE = -2, };
+enum control_bus_type { + CONTROL_BUS_I2C = 0, + CONTROL_BUS_SPI = 1, +}; + #define CS35L45_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_3LE| \ SNDRV_PCM_FMTBIT_S24_LE) @@ -439,6 +466,8 @@ struct cs35l45_private { u8 slot_count; int irq_invert; int irq; + unsigned int i2c_addr; + enum control_bus_type bus_type; struct regmap_irq_chip_data *irq_data; };
On Wed, Mar 15, 2023 at 10:47:18AM -0500, Vlad Karpovich wrote:
From: "Vlad.Karpovich" vkarpovi@opensource.cirrus.com
Signed-off-by: Vlad Karpovich vkarpovi@opensource.cirrus.com
Your From doesn't match your signoff which upsets tools if nothing else.
On Wed, 15 Mar 2023 10:47:18 -0500, Vlad Karpovich wrote:
Adds device tree configuration for cs35l45 GPIOs
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
Thanks!
[1/5] ASoC: cs35l45: Support for GPIO pins configuration. commit: fa8c052b4c614aa1d2d60e5c9f40e9d885bf9511 [2/5] ASoC: dt-bindings: cs35l45: GPIOs configuration commit: c6cec088ab037b57e08e0694e2b150b1b034826c [3/5] ASoC: cs35l45: IRQ support commit: 6085f9e6dc1973cf98ee7f5dcf629939e50f1b84 [4/5] ASoC: cs35l45: DSP Support commit: 74b14e2850a34740c121cf2758d4181063d4c77c [5/5] ASoC: cs35l45: Hibernation support commit: 6c07be8fe92c6b0c24ee1c599601dce3506b83c7
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
participants (2)
-
Mark Brown
-
Vlad Karpovich