Commit ed3b4b87 authored by Mark Brown's avatar Mark Brown

Add CS35L41 shared boost feature

Merge series from Lucas Tanure <lucas.tanure@collabora.com>:

Valve's Steam Deck uses CS35L41 in shared boost mode, where both speakers
share the boost circuit.
Add this support in the shared lib, but for now, shared boost is not
supported in HDA systems as would require BIOS changes.

Based on David Rhodes shared boost patches.

Also, fix boost config overwriting in IRQ found in the review and do a
small refactor of the code.
parents 6a534cfc 340307d7
......@@ -85,11 +85,19 @@ properties:
boost-cap-microfarad.
External Boost must have GPIO1 as GPIO output. GPIO1 will be set high to
enable boost voltage.
Shared boost allows two amplifiers to share a single boost circuit by
communicating on the MDSYNC bus. The active amplifier controls the boost
circuit using combined data from both amplifiers. GPIO1 should be
configured for Sync when shared boost is used. Shared boost is not
compatible with External boost. Active amplifier requires
boost-peak-milliamp, boost-ind-nanohenry and boost-cap-microfarad.
0 = Internal Boost
1 = External Boost
2 = Shared Boost Active
3 = Shared Boost Passive
$ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 1
maximum: 3
cirrus,gpio1-polarity-invert:
description:
......
......@@ -11,6 +11,7 @@
#define __CS35L41_H
#include <linux/regmap.h>
#include <linux/completion.h>
#include <linux/firmware/cirrus/cs_dsp.h>
#define CS35L41_FIRSTREG 0x00000000
......@@ -677,6 +678,7 @@
#define CS35L36_PUP_DONE_IRQ_UNMASK 0x5F
#define CS35L36_PUP_DONE_IRQ_MASK 0xBF
#define CS35L41_SYNC_EN_MASK BIT(8)
#define CS35L41_AMP_SHORT_ERR 0x80000000
#define CS35L41_BST_SHORT_ERR 0x0100
......@@ -686,6 +688,7 @@
#define CS35L41_BST_DCM_UVP_ERR 0x80
#define CS35L41_OTP_BOOT_DONE 0x02
#define CS35L41_PLL_UNLOCK 0x10
#define CS35L41_PLL_LOCK BIT(1)
#define CS35L41_OTP_BOOT_ERR 0x80000000
#define CS35L41_AMP_SHORT_ERR_RLS 0x02
......@@ -705,6 +708,8 @@
#define CS35L41_INT1_MASK_DEFAULT 0x7FFCFE3F
#define CS35L41_INT1_UNMASK_PUP 0xFEFFFFFF
#define CS35L41_INT1_UNMASK_PDN 0xFF7FFFFF
#define CS35L41_INT3_PLL_LOCK_SHIFT 1
#define CS35L41_INT3_PLL_LOCK_MASK BIT(CS35L41_INT3_PLL_LOCK_SHIFT)
#define CS35L41_GPIO_DIR_MASK 0x80000000
#define CS35L41_GPIO_DIR_SHIFT 31
......@@ -742,6 +747,11 @@
enum cs35l41_boost_type {
CS35L41_INT_BOOST,
CS35L41_EXT_BOOST,
CS35L41_SHD_BOOST_ACTV,
CS35L41_SHD_BOOST_PASS,
// Not present in Binding Documentation, so no system should use this value.
// This value is only used in CLSA0100 Laptop
CS35L41_EXT_BOOST_NO_VSPK_SWITCH,
};
......@@ -891,6 +901,7 @@ int cs35l41_exit_hibernate(struct device *dev, struct regmap *regmap);
int cs35l41_init_boost(struct device *dev, struct regmap *regmap,
struct cs35l41_hw_cfg *hw_cfg);
bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type);
int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable);
int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable,
struct completion *pll_lock);
#endif /* __CS35L41_H */
......@@ -514,13 +514,13 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action)
break;
case HDA_GEN_PCM_ACT_PREPARE:
mutex_lock(&cs35l41->fw_mutex);
ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 1);
ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 1, NULL);
mutex_unlock(&cs35l41->fw_mutex);
break;
case HDA_GEN_PCM_ACT_CLEANUP:
mutex_lock(&cs35l41->fw_mutex);
regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute));
ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 0);
ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 0, NULL);
mutex_unlock(&cs35l41->fw_mutex);
break;
case HDA_GEN_PCM_ACT_CLOSE:
......@@ -672,7 +672,7 @@ static int cs35l41_runtime_suspend(struct device *dev)
if (cs35l41->playback_started) {
regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute,
ARRAY_SIZE(cs35l41_hda_mute));
cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0);
cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0, NULL);
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT);
if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
......
......@@ -1114,12 +1114,31 @@ static const struct reg_sequence cs35l41_reset_to_safe[] = {
{ 0x00000040, 0x00000033 },
};
static const struct reg_sequence cs35l41_actv_seq[] = {
/* SYNC_BST_CTL_RX_EN = 1; SYNC_BST_CTL_TX_EN = 1 */
{CS35L41_MDSYNC_EN, 0x00003000},
/* BST_CTL_SEL = MDSYNC */
{CS35L41_BSTCVRT_VCTRL2, 0x00000002},
};
static const struct reg_sequence cs35l41_pass_seq[] = {
/* SYNC_BST_CTL_RX_EN = 0; SYNC_BST_CTL_TX_EN = 1 */
{CS35L41_MDSYNC_EN, 0x00001000},
/* BST_EN = 0 */
{CS35L41_PWR_CTRL2, 0x00003300},
/* BST_CTL_SEL = MDSYNC */
{CS35L41_BSTCVRT_VCTRL2, 0x00000002},
};
int cs35l41_init_boost(struct device *dev, struct regmap *regmap,
struct cs35l41_hw_cfg *hw_cfg)
{
int ret;
switch (hw_cfg->bst_type) {
case CS35L41_SHD_BOOST_ACTV:
regmap_multi_reg_write(regmap, cs35l41_actv_seq, ARRAY_SIZE(cs35l41_actv_seq));
fallthrough;
case CS35L41_INT_BOOST:
ret = cs35l41_boost_config(dev, regmap, hw_cfg->bst_ind,
hw_cfg->bst_cap, hw_cfg->bst_ipk);
......@@ -1138,6 +1157,10 @@ int cs35l41_init_boost(struct device *dev, struct regmap *regmap,
ret = regmap_update_bits(regmap, CS35L41_PWR_CTRL2, CS35L41_BST_EN_MASK,
CS35L41_BST_DIS_FET_OFF << CS35L41_BST_EN_SHIFT);
break;
case CS35L41_SHD_BOOST_PASS:
ret = regmap_multi_reg_write(regmap, cs35l41_pass_seq,
ARRAY_SIZE(cs35l41_pass_seq));
break;
default:
dev_err(dev, "Boost type %d not supported\n", hw_cfg->bst_type);
ret = -EINVAL;
......@@ -1165,11 +1188,59 @@ bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type)
}
EXPORT_SYMBOL_GPL(cs35l41_safe_reset);
int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable)
int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable,
struct completion *pll_lock)
{
int ret;
unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3;
struct reg_sequence cs35l41_mdsync_down_seq[] = {
{CS35L41_PWR_CTRL3, 0},
{CS35L41_GPIO_PAD_CONTROL, 0},
{CS35L41_PWR_CTRL1, 0, 3000},
};
struct reg_sequence cs35l41_mdsync_up_seq[] = {
{CS35L41_PWR_CTRL3, 0},
{CS35L41_PWR_CTRL1, 0x00000000, 3000},
{CS35L41_PWR_CTRL1, 0x00000001, 3000},
};
switch (b_type) {
case CS35L41_SHD_BOOST_ACTV:
case CS35L41_SHD_BOOST_PASS:
regmap_read(regmap, CS35L41_PWR_CTRL3, &pwr_ctrl3);
regmap_read(regmap, CS35L41_GPIO_PAD_CONTROL, &pad_control);
pwr_ctrl3 &= ~CS35L41_SYNC_EN_MASK;
pwr_ctrl1 = enable << CS35L41_GLOBAL_EN_SHIFT;
gpio1_func = enable ? CS35L41_GPIO1_MDSYNC : CS35L41_GPIO1_HIZ;
gpio1_func <<= CS35L41_GPIO1_CTRL_SHIFT;
pad_control &= ~CS35L41_GPIO1_CTRL_MASK;
pad_control |= gpio1_func & CS35L41_GPIO1_CTRL_MASK;
cs35l41_mdsync_down_seq[0].def = pwr_ctrl3;
cs35l41_mdsync_down_seq[1].def = pad_control;
cs35l41_mdsync_down_seq[2].def = pwr_ctrl1;
ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_down_seq,
ARRAY_SIZE(cs35l41_mdsync_down_seq));
if (!enable)
break;
if (!pll_lock)
return -EINVAL;
ret = wait_for_completion_timeout(pll_lock, msecs_to_jiffies(1000));
if (ret == 0) {
ret = -ETIMEDOUT;
} else {
regmap_read(regmap, CS35L41_PWR_CTRL3, &pwr_ctrl3);
pwr_ctrl3 |= CS35L41_SYNC_EN_MASK;
cs35l41_mdsync_up_seq[0].def = pwr_ctrl3;
ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_up_seq,
ARRAY_SIZE(cs35l41_mdsync_up_seq));
}
break;
case CS35L41_INT_BOOST:
ret = regmap_update_bits(regmap, CS35L41_PWR_CTRL1, CS35L41_GLOBAL_EN_MASK,
enable << CS35L41_GLOBAL_EN_SHIFT);
......
......@@ -356,6 +356,30 @@ static const struct snd_kcontrol_new cs35l41_aud_controls[] = {
WM_ADSP_FW_CONTROL("DSP1", 0),
};
static void cs35l41_boost_enable(struct cs35l41_private *cs35l41, unsigned int enable)
{
switch (cs35l41->hw_cfg.bst_type) {
case CS35L41_INT_BOOST:
case CS35L41_SHD_BOOST_ACTV:
enable = enable ? CS35L41_BST_EN_DEFAULT : CS35L41_BST_DIS_FET_OFF;
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, CS35L41_BST_EN_MASK,
enable << CS35L41_BST_EN_SHIFT);
break;
default:
break;
}
}
static void cs35l41_error_release(struct cs35l41_private *cs35l41, unsigned int irq_err_bit,
unsigned int rel_err_bit)
{
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1, irq_err_bit);
regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, rel_err_bit, rel_err_bit);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, rel_err_bit, 0);
}
static irqreturn_t cs35l41_irq(int irq, void *data)
{
struct cs35l41_private *cs35l41 = data;
......@@ -392,94 +416,49 @@ static irqreturn_t cs35l41_irq(int irq, void *data)
*/
if (status[0] & CS35L41_AMP_SHORT_ERR) {
dev_crit_ratelimited(cs35l41->dev, "Amp short error\n");
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
CS35L41_AMP_SHORT_ERR);
regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
CS35L41_AMP_SHORT_ERR_RLS,
CS35L41_AMP_SHORT_ERR_RLS);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
CS35L41_AMP_SHORT_ERR_RLS, 0);
cs35l41_error_release(cs35l41, CS35L41_AMP_SHORT_ERR, CS35L41_AMP_SHORT_ERR_RLS);
ret = IRQ_HANDLED;
}
if (status[0] & CS35L41_TEMP_WARN) {
dev_crit_ratelimited(cs35l41->dev, "Over temperature warning\n");
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
CS35L41_TEMP_WARN);
regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
CS35L41_TEMP_WARN_ERR_RLS,
CS35L41_TEMP_WARN_ERR_RLS);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
CS35L41_TEMP_WARN_ERR_RLS, 0);
cs35l41_error_release(cs35l41, CS35L41_TEMP_WARN, CS35L41_TEMP_WARN_ERR_RLS);
ret = IRQ_HANDLED;
}
if (status[0] & CS35L41_TEMP_ERR) {
dev_crit_ratelimited(cs35l41->dev, "Over temperature error\n");
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
CS35L41_TEMP_ERR);
regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
CS35L41_TEMP_ERR_RLS,
CS35L41_TEMP_ERR_RLS);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
CS35L41_TEMP_ERR_RLS, 0);
cs35l41_error_release(cs35l41, CS35L41_TEMP_ERR, CS35L41_TEMP_ERR_RLS);
ret = IRQ_HANDLED;
}
if (status[0] & CS35L41_BST_OVP_ERR) {
dev_crit_ratelimited(cs35l41->dev, "VBST Over Voltage error\n");
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
CS35L41_BST_EN_MASK, 0);
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
CS35L41_BST_OVP_ERR);
regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
CS35L41_BST_OVP_ERR_RLS,
CS35L41_BST_OVP_ERR_RLS);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
CS35L41_BST_OVP_ERR_RLS, 0);
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
CS35L41_BST_EN_MASK,
CS35L41_BST_EN_DEFAULT << CS35L41_BST_EN_SHIFT);
cs35l41_boost_enable(cs35l41, 0);
cs35l41_error_release(cs35l41, CS35L41_BST_OVP_ERR, CS35L41_BST_OVP_ERR_RLS);
cs35l41_boost_enable(cs35l41, 1);
ret = IRQ_HANDLED;
}
if (status[0] & CS35L41_BST_DCM_UVP_ERR) {
dev_crit_ratelimited(cs35l41->dev, "DCM VBST Under Voltage Error\n");
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
CS35L41_BST_EN_MASK, 0);
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
CS35L41_BST_DCM_UVP_ERR);
regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
CS35L41_BST_UVP_ERR_RLS,
CS35L41_BST_UVP_ERR_RLS);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
CS35L41_BST_UVP_ERR_RLS, 0);
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
CS35L41_BST_EN_MASK,
CS35L41_BST_EN_DEFAULT << CS35L41_BST_EN_SHIFT);
cs35l41_boost_enable(cs35l41, 0);
cs35l41_error_release(cs35l41, CS35L41_BST_DCM_UVP_ERR, CS35L41_BST_UVP_ERR_RLS);
cs35l41_boost_enable(cs35l41, 1);
ret = IRQ_HANDLED;
}
if (status[0] & CS35L41_BST_SHORT_ERR) {
dev_crit_ratelimited(cs35l41->dev, "LBST error: powering off!\n");
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
CS35L41_BST_EN_MASK, 0);
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
CS35L41_BST_SHORT_ERR);
regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
CS35L41_BST_SHORT_ERR_RLS,
CS35L41_BST_SHORT_ERR_RLS);
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
CS35L41_BST_SHORT_ERR_RLS, 0);
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
CS35L41_BST_EN_MASK,
CS35L41_BST_EN_DEFAULT << CS35L41_BST_EN_SHIFT);
cs35l41_boost_enable(cs35l41, 0);
cs35l41_error_release(cs35l41, CS35L41_BST_SHORT_ERR, CS35L41_BST_SHORT_ERR_RLS);
cs35l41_boost_enable(cs35l41, 1);
ret = IRQ_HANDLED;
}
if (status[2] & CS35L41_PLL_LOCK) {
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS3, CS35L41_PLL_LOCK);
complete(&cs35l41->pll_lock);
ret = IRQ_HANDLED;
}
......@@ -520,10 +499,12 @@ static int cs35l41_main_amp_event(struct snd_soc_dapm_widget *w,
cs35l41_pup_patch,
ARRAY_SIZE(cs35l41_pup_patch));
cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 1);
cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 1,
&cs35l41->pll_lock);
break;
case SND_SOC_DAPM_POST_PMD:
cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0);
cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0,
&cs35l41->pll_lock);
ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
val, val & CS35L41_PDN_DONE_MASK,
......@@ -830,6 +811,10 @@ static const struct snd_pcm_hw_constraint_list cs35l41_constraints = {
static int cs35l41_pcm_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(dai->component);
reinit_completion(&cs35l41->pll_lock);
if (substream->runtime)
return snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
......@@ -1280,6 +1265,10 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *
/* Set interrupt masks for critical errors */
regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1,
CS35L41_INT1_MASK_DEFAULT);
if (cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_PASS ||
cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_ACTV)
regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK3, CS35L41_INT3_PLL_LOCK_MASK,
0 << CS35L41_INT3_PLL_LOCK_SHIFT);
ret = devm_request_threaded_irq(cs35l41->dev, cs35l41->irq, NULL, cs35l41_irq,
IRQF_ONESHOT | IRQF_SHARED | irq_pol,
......@@ -1303,6 +1292,8 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *
if (ret < 0)
goto err;
init_completion(&cs35l41->pll_lock);
pm_runtime_set_autosuspend_delay(cs35l41->dev, 3000);
pm_runtime_use_autosuspend(cs35l41->dev);
pm_runtime_mark_last_busy(cs35l41->dev);
......@@ -1345,6 +1336,10 @@ void cs35l41_remove(struct cs35l41_private *cs35l41)
pm_runtime_disable(cs35l41->dev);
regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1, 0xFFFFFFFF);
if (cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_PASS ||
cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_ACTV)
regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK3, CS35L41_INT3_PLL_LOCK_MASK,
1 << CS35L41_INT3_PLL_LOCK_SHIFT);
kfree(cs35l41->dsp.system_name);
wm_adsp2_remove(&cs35l41->dsp);
cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type);
......
......@@ -33,6 +33,7 @@ struct cs35l41_private {
int irq;
/* GPIO for /RST */
struct gpio_desc *reset_gpio;
struct completion pll_lock;
};
int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *hw_cfg);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment