![](https://secure.gravatar.com/avatar/f60a7edd8e11b88b69978210ae97fd05.jpg?s=120&d=mm&r=g)
On Tue, Dec 13, 2016 at 10:59:03AM -0600, Li Xu wrote:
Add driver support for Cirrus Logic CS35L35 boosted speaker amplifier
Signed-off-by: Li Xu li.xu@cirrus.com
Mostly just some minor comments.
+struct classh_cfg {
- /*
* Class H Algorithm Control Variables
* You can either have it done
* automatically or you can adjust
* these variables for tuning
*
* if you do not enable the internal algorithm
* you will get a set of mixer controls for
* Class H tuning
*
* Section 4.3 of the datasheet
*/
- /* Internal ClassH Algorithm */
Feels redundant to have this extra comment after the large comment before it.
+#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/workqueue.h>
Do we need the workqueue header we don't seem to use any workqueues?
+static int cs35l35_sdin_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
+{
- struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
- struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
- int ret = 0;
- switch (event) {
- case SND_SOC_DAPM_PRE_PMU:
regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
CS35L35_MCLK_DIS_MASK, 0 << CS35L35_MCLK_DIS_SHIFT);
regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
CS35L35_DISCHG_FILT_MASK, 0 << CS35L35_DISCHG_FILT_SHIFT);
regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
CS35L35_PDN_ALL_MASK, 0);
- break;
Break should be indented for kernel coding style.
- case SND_SOC_DAPM_POST_PMD:
regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
CS35L35_PDN_ALL_MASK, 1);
regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
CS35L35_DISCHG_FILT_MASK, 1 << CS35L35_DISCHG_FILT_SHIFT);
ret = wait_for_completion_timeout(&cs35l35->pdn_done,
msecs_to_jiffies(100));
if (ret == 0) {
dev_err(codec->dev, "TIMEOUT PDN_DONE did not complete in 100ms\n");
ret = -ETIMEDOUT;
}
regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
CS35L35_MCLK_DIS_MASK, 1 << CS35L35_MCLK_DIS_SHIFT);
- break;
Ditto.
- default:
dev_err(codec->dev, "Invalid event = 0x%x\n", event);
ret = -EINVAL;
- }
- return ret;
+}
+static int cs35l35_main_amp_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
+{
- struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
- struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
- unsigned int reg[4];
- int i;
- switch (event) {
- case SND_SOC_DAPM_PRE_PMU:
if (cs35l35->pdata.bst_pdn_fet_on)
regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
CS35L35_PDN_BST_MASK, 0 << CS35L35_PDN_BST_FETON_SHIFT);
else
regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
CS35L35_PDN_BST_MASK, 0 << CS35L35_PDN_BST_FETOFF_SHIFT);
regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL,
CS35L35_AMP_MUTE_MASK, 0 << CS35L35_AMP_MUTE_SHIFT);
break;
- case SND_SOC_DAPM_POST_PMU:
usleep_range(5000, 5100);
/* If PDM mode we must use VP
* for Voltage control
*/
Does this comment need to split across multiple lines?
+static int cs35l35_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{
- struct snd_soc_codec *codec = codec_dai->codec;
- struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
- switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBM_CFM:
regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
CS35L35_MS_MASK, 1 << CS35L35_MS_SHIFT);
cs35l35->slave_mode = false;
break;
- case SND_SOC_DAIFMT_CBS_CFS:
regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
CS35L35_MS_MASK, 0 << CS35L35_MS_SHIFT);
cs35l35->slave_mode = true;
break;
- default:
return -EINVAL;
- }
- switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
- case SND_SOC_DAIFMT_I2S:
cs35l35->i2s_mode = true;
cs35l35->pdm_mode = false;
break;
- case SND_SOC_DAIFMT_PDM:
cs35l35->pdm_mode = true;
cs35l35->i2s_mode = false;
Feels a bit redundant to have both of these if they are only ever a logical inversion of each other.
+static int cs35l35_get_clk_config(int sysclk, int srate) +{
- int i;
- for (i = 0; i < ARRAY_SIZE(cs35l35_clk_ctl); i++) {
if (cs35l35_clk_ctl[i].sysclk == sysclk &&
cs35l35_clk_ctl[i].srate == srate)
return cs35l35_clk_ctl[i].clk_cfg;
- }
- return -EINVAL;
+}
+static int cs35l35_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct snd_soc_codec *codec = dai->codec;
- struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
- struct classh_cfg *classh = &cs35l35->pdata.classh_algo;
- int srate = params_rate(params);
- int ret = 0;
- u8 sp_sclks;
- int audin_format;
- int errata_chk;
- int clk_ctl = cs35l35_get_clk_config(cs35l35->sysclk, srate);
- if (clk_ctl < 0) {
dev_err(codec->dev, "Invalid CLK:Rate %d:%d\n",
cs35l35->sysclk, srate);
return -EINVAL;
- }
It would normally be slightly better to set constraints in startup based on the SYSCLK rather than returning an error in hw_params. This allows user-space to negotiate a rate that is actually supported and do any sample rate conversion required.
- ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL2,
CS35L35_CLK_CTL2_MASK, clk_ctl);
- if (ret != 0) {
dev_err(codec->dev, "Failed to set port config %d\n", ret);
return ret;
- }
- /* Rev A0 Errata
*
* When configured for the weak-drive detection path (CH_WKFET_DIS = 0)
* the Class H algorithm does not enable weak-drive operation for
* nonzero values of CH_WKFET_DELAY if SP_RATE = 01 or 10
*
*/
- errata_chk = clk_ctl & CS35L35_SP_RATE_MASK;
- if (classh->classh_wk_fet_disable == 0x00 &&
(errata_chk == 0x01 || errata_chk == 0x03)) {
ret = regmap_update_bits(cs35l35->regmap,
CS35L35_CLASS_H_FET_DRIVE_CTL, CS35L35_CH_WKFET_DEL_MASK,
0 << CS35L35_CH_WKFET_DEL_SHIFT);
if (ret != 0) {
dev_err(codec->dev, "Failed to set fet config %d\n",
ret);
return ret;
}
- }
+/*
- You can pull more Monitor data from the SDOUT pin than going to SDIN
- Just make sure your SCLK is fast enough to fill the frame
- */
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (params_width(params)) {
case 8:
audin_format = CS35L35_SDIN_DEPTH_8;
break;
case 16:
audin_format = CS35L35_SDIN_DEPTH_16;
break;
case 24:
audin_format = CS35L35_SDIN_DEPTH_24;
break;
default:
dev_err(codec->dev, "Unsupported Width %d\n",
params_width(params));
}
regmap_update_bits(cs35l35->regmap,
CS35L35_AUDIN_DEPTH_CTL, CS35L35_AUDIN_DEPTH_MASK,
audin_format << CS35L35_AUDIN_DEPTH_SHIFT);
if (cs35l35->pdata.stereo) {
regmap_update_bits(cs35l35->regmap,
CS35L35_AUDIN_DEPTH_CTL, CS35L35_ADVIN_DEPTH_MASK,
audin_format << CS35L35_ADVIN_DEPTH_SHIFT);
}
- }
+/* We have to take the SCLK to derive num sclks
- to configure the CLOCK_CTL3 register correctly
- */
- if ((cs35l35->sclk / srate) % 4) {
dev_err(codec->dev, "Unsupported sclk/fs ratio %d:%d\n",
cs35l35->sclk, srate);
return -EINVAL;
- }
Again here it might be slightly better to constraints in startup.
- sp_sclks = ((cs35l35->sclk / srate) / 4) - 1;
- if (cs35l35->i2s_mode) {
/* Only certain ratios are supported in I2S Slave Mode */
if (cs35l35->slave_mode) {
switch (sp_sclks) {
case CS35L35_SP_SCLKS_32FS:
case CS35L35_SP_SCLKS_48FS:
case CS35L35_SP_SCLKS_64FS:
break;
default:
dev_err(codec->dev, "ratio not supported\n");
return -EINVAL;
};
} else {
/* Only certain ratios supported in I2S MASTER Mode */
switch (sp_sclks) {
case CS35L35_SP_SCLKS_32FS:
case CS35L35_SP_SCLKS_64FS:
break;
default:
dev_err(codec->dev, "ratio not supported\n");
return -EINVAL;
};
}
ret = regmap_update_bits(cs35l35->regmap,
CS35L35_CLK_CTL3, CS35L35_SP_SCLKS_MASK,
sp_sclks << CS35L35_SP_SCLKS_SHIFT);
if (ret != 0) {
dev_err(codec->dev, "Failed to set fsclk %d\n", ret);
return ret;
}
- }
- if (cs35l35->pdm_mode) {
regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL,
CS35L35_PDM_MODE_MASK, 1 << CS35L35_PDM_MODE_SHIFT);
- } else {
regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL,
CS35L35_PDM_MODE_MASK, 0 << CS35L35_PDM_MODE_SHIFT);
- }
This if could be combined with the one above since pdm_mode == !i2s_mode.
- return ret;
+}
+static const struct snd_soc_dai_ops cs35l35_ops = {
- .startup = cs35l35_pcm_startup,
- .set_fmt = cs35l35_set_dai_fmt,
- .hw_params = cs35l35_pcm_hw_params,
- .set_sysclk = cs35l35_dai_set_sysclk,
+};
+static const struct snd_soc_dai_ops cs35l35_pdm_ops = {
- .startup = cs35l35_pdm_startup,
- .set_fmt = cs35l35_set_dai_fmt,
- .hw_params = cs35l35_pcm_hw_params,
I would be tempted to rename the function to just cs35l35_hw_params if it is shared between both PCM and PDM.
- .set_sysclk = cs35l35_dai_set_sysclk,
+};
+static int cs35l35_codec_probe(struct snd_soc_codec *codec) +{
- struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
- struct classh_cfg *classh = &cs35l35->pdata.classh_algo;
- struct monitor_cfg *monitor_config = &cs35l35->pdata.mon_cfg;
- int ret;
- cs35l35->codec = codec;
- /* Set Platform Data */
- if (cs35l35->pdata.bst_vctl)
regmap_update_bits(cs35l35->regmap, CS35L35_BST_CVTR_V_CTL,
CS35L35_BST_CTL_MASK, cs35l35->pdata.bst_vctl);
- if (cs35l35->pdata.bst_ipk)
regmap_update_bits(cs35l35->regmap, CS35L35_BST_PEAK_I,
CS35L35_BST_IPK_MASK,
cs35l35->pdata.bst_ipk << CS35L35_BST_IPK_SHIFT);
I believe zero is a valid value for this field, but not the default. Are we happy that the user can never set this value?
- if (cs35l35->pdata.gain_zc)
regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL,
CS35L35_AMP_GAIN_ZC_MASK,
cs35l35->pdata.gain_zc << CS35L35_AMP_GAIN_ZC_SHIFT);
- if (cs35l35->pdata.aud_channel)
regmap_update_bits(cs35l35->regmap,
CS35L35_AUDIN_RXLOC_CTL,
CS35L35_AUD_IN_LR_MASK,
cs35l35->pdata.aud_channel << CS35L35_AUD_IN_LR_SHIFT);
- if (cs35l35->pdata.stereo) {
regmap_update_bits(cs35l35->regmap,
CS35L35_ADVIN_RXLOC_CTL, CS35L35_ADV_IN_LR_MASK,
cs35l35->pdata.adv_channel << CS35L35_ADV_IN_LR_SHIFT);
if (cs35l35->pdata.shared_bst)
regmap_update_bits(cs35l35->regmap, CS35L35_CLASS_H_CTL,
CS35L35_CH_STEREO_MASK, 1 << CS35L35_CH_STEREO_SHIFT);
ret = snd_soc_add_codec_controls(codec, cs35l35_adv_controls,
ARRAY_SIZE(cs35l35_adv_controls));
if (ret)
return ret;
- }
- if (cs35l35->pdata.sp_drv_str)
regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
CS35L35_SP_DRV_MASK,
cs35l35->pdata.sp_drv_str << CS35L35_SP_DRV_SHIFT);
- if (classh->classh_algo_enable) {
if (classh->classh_bst_override)
regmap_update_bits(cs35l35->regmap,
CS35L35_CLASS_H_CTL, CS35L35_CH_BST_OVR_MASK,
classh->classh_bst_override << CS35L35_CH_BST_OVR_SHIFT);
if (classh->classh_bst_max_limit)
regmap_update_bits(cs35l35->regmap,
CS35L35_CLASS_H_CTL, CS35L35_CH_BST_LIM_MASK,
classh->classh_bst_max_limit << CS35L35_CH_BST_LIM_SHIFT);
This is a single bit, but the default bit is 1, so this code can never change the value of the field.
if (classh->classh_mem_depth)
regmap_update_bits(cs35l35->regmap,
CS35L35_CLASS_H_CTL, CS35L35_CH_MEM_DEPTH_MASK,
classh->classh_mem_depth << CS35L35_CH_MEM_DEPTH_SHIFT);
Again zero is a valid value, and not the default.
if (classh->classh_headroom)
regmap_update_bits(cs35l35->regmap,
CS35L35_CLASS_H_HEADRM_CTL, CS35L35_CH_HDRM_CTL_MASK,
classh->classh_headroom << CS35L35_CH_HDRM_CTL_SHIFT);
if (classh->classh_release_rate)
regmap_update_bits(cs35l35->regmap,
CS35L35_CLASS_H_RELEASE_RATE, CS35L35_CH_REL_RATE_MASK,
classh->classh_release_rate << CS35L35_CH_REL_RATE_SHIFT);
if (classh->classh_wk_fet_disable)
regmap_update_bits(cs35l35->regmap,
CS35L35_CLASS_H_FET_DRIVE_CTL, CS35L35_CH_WKFET_DIS_MASK,
classh->classh_wk_fet_disable << CS35L35_CH_WKFET_DIS_SHIFT);
if (classh->classh_wk_fet_delay)
regmap_update_bits(cs35l35->regmap,
CS35L35_CLASS_H_FET_DRIVE_CTL, CS35L35_CH_WKFET_DEL_MASK,
classh->classh_wk_fet_delay << CS35L35_CH_WKFET_DEL_SHIFT);
Again zero is a valid value, and not the default.
if (classh->classh_wk_fet_thld)
regmap_update_bits(cs35l35->regmap,
CS35L35_CLASS_H_FET_DRIVE_CTL, CS35L35_CH_WKFET_THLD_MASK,
classh->classh_wk_fet_thld << CS35L35_CH_WKFET_THLD_SHIFT);
if (classh->classh_vpch_auto)
regmap_update_bits(cs35l35->regmap,
CS35L35_CLASS_H_VP_CTL, CS35L35_CH_VP_AUTO_MASK,
classh->classh_vpch_auto << CS35L35_CH_VP_AUTO_SHIFT);
Again single bit with a default of 1.
if (classh->classh_vpch_rate)
regmap_update_bits(cs35l35->regmap,
CS35L35_CLASS_H_VP_CTL, CS35L35_CH_VP_RATE_MASK,
classh->classh_vpch_rate << CS35L35_CH_VP_RATE_SHIFT);
Again zero is a valid value, and not the default.
if (classh->classh_vpch_man)
regmap_update_bits(cs35l35->regmap,
CS35L35_CLASS_H_VP_CTL, CS35L35_CH_VP_MAN_MASK,
classh->classh_vpch_man << CS35L35_CH_VP_MAN_SHIFT);
- }
<snip>
+static int cs35l35_i2c_probe(struct i2c_client *i2c_client,
const struct i2c_device_id *id)
+{
- struct cs35l35_private *cs35l35;
- struct cs35l35_platform_data *pdata =
dev_get_platdata(&i2c_client->dev);
- int i;
- int ret;
- unsigned int devid = 0;
- unsigned int reg;
- cs35l35 = devm_kzalloc(&i2c_client->dev,
sizeof(struct cs35l35_private),
GFP_KERNEL);
- if (!cs35l35) {
dev_err(&i2c_client->dev, "could not allocate codec\n");
return -ENOMEM;
- }
- i2c_set_clientdata(i2c_client, cs35l35);
- cs35l35->regmap = devm_regmap_init_i2c(i2c_client, &cs35l35_regmap);
- if (IS_ERR(cs35l35->regmap)) {
ret = PTR_ERR(cs35l35->regmap);
dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret);
goto err;
- }
- for (i = 0; i < ARRAY_SIZE(cs35l35_supplies); i++)
cs35l35->supplies[i].supply = cs35l35_supplies[i];
cs35l35->num_supplies = ARRAY_SIZE(cs35l35_supplies);
- ret = devm_regulator_bulk_get(&i2c_client->dev,
cs35l35->num_supplies,
cs35l35->supplies);
- if (ret != 0) {
dev_err(&i2c_client->dev,
"Failed to request core supplies: %d\n",
ret);
return ret;
- }
- if (pdata) {
cs35l35->pdata = *pdata;
- } else {
pdata = devm_kzalloc(&i2c_client->dev,
sizeof(struct cs35l35_platform_data),
GFP_KERNEL);
if (!pdata) {
dev_err(&i2c_client->dev,
"could not allocate pdata\n");
return -ENOMEM;
}
if (i2c_client->dev.of_node) {
ret = cs35l35_handle_of_data(i2c_client, pdata);
if (ret != 0)
return ret;
}
cs35l35->pdata = *pdata;
- }
- ret = regulator_bulk_enable(cs35l35->num_supplies,
cs35l35->supplies);
- if (ret != 0) {
dev_err(&i2c_client->dev,
"Failed to enable core supplies: %d\n",
ret);
return ret;
- }
- /* returning NULL can be an option if in stereo mode */
- cs35l35->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev,
"reset", GPIOD_OUT_LOW);
- if (IS_ERR(cs35l35->reset_gpio))
return PTR_ERR(cs35l35->reset_gpio);
This should be a goto err;
- if (cs35l35->reset_gpio)
gpiod_set_value_cansleep(cs35l35->reset_gpio, 1);
gpiod_set_value_can_sleep does an internal NULL check on the GPIO desc I would be tempted to just rely on that one.
- init_completion(&cs35l35->pdn_done);
- ret = regmap_register_patch(cs35l35->regmap, cs35l35_errata_patch,
ARRAY_SIZE(cs35l35_errata_patch));
- if (ret < 0) {
dev_err(&i2c_client->dev, "Failed to apply errata patch\n");
return ret;
This should be a goto err;
- }
- ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq, NULL,
cs35l35_irq, IRQF_ONESHOT | IRQF_TRIGGER_LOW,
"cs35l35", cs35l35);
- if (ret != 0) {
dev_err(&i2c_client->dev, "Failed to request IRQ: %d\n", ret);
goto err;
- }
- /* initialize codec */
- ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_AB, ®);
- devid = (reg & 0xFF) << 12;
- ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_CD, ®);
- devid |= (reg & 0xFF) << 4;
- ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_E, ®);
- devid |= (reg & 0xF0) >> 4;
- if (devid != CS35L35_CHIP_ID) {
dev_err(&i2c_client->dev,
"CS35L35 Device ID (%X). Expected ID %X\n",
devid, CS35L35_CHIP_ID);
ret = -ENODEV;
goto err;
- }
- ret = regmap_read(cs35l35->regmap, CS35L35_REV_ID, ®);
- if (ret < 0) {
dev_err(&i2c_client->dev, "Get Revision ID failed\n");
goto err;
- }
- dev_info(&i2c_client->dev,
"Cirrus Logic CS35L35 (%x), Revision: %02X\n", devid,
ret & 0xFF);
- /* Set the INT Masks for critical errors */
- regmap_write(cs35l35->regmap, CS35L35_INT_MASK_1, CS35L35_INT1_CRIT_MASK);
- regmap_write(cs35l35->regmap, CS35L35_INT_MASK_2, CS35L35_INT2_CRIT_MASK);
- regmap_write(cs35l35->regmap, CS35L35_INT_MASK_3, CS35L35_INT3_CRIT_MASK);
- regmap_write(cs35l35->regmap, CS35L35_INT_MASK_4, CS35L35_INT4_CRIT_MASK);
- regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
CS35L35_PWR2_PDN_MASK, CS35L35_PWR2_PDN_MASK);
- if (cs35l35->pdata.bst_pdn_fet_on)
regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
CS35L35_PDN_BST_MASK, 1 << CS35L35_PDN_BST_FETON_SHIFT);
- else
regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
CS35L35_PDN_BST_MASK, 1 << CS35L35_PDN_BST_FETOFF_SHIFT);
- regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL3,
CS35L35_PWR3_PDN_MASK, CS35L35_PWR3_PDN_MASK);
- regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL,
CS35L35_AMP_MUTE_MASK, 1 << CS35L35_AMP_MUTE_SHIFT);
- ret = snd_soc_register_codec(&i2c_client->dev,
&soc_codec_dev_cs35l35, cs35l35_dai,
ARRAY_SIZE(cs35l35_dai));
- if (ret < 0) {
dev_err(&i2c_client->dev,
"%s: Register codec failed\n", __func__);
goto err;
- }
+err:
- regulator_bulk_disable(cs35l35->num_supplies,
cs35l35->supplies);
- return ret;
+}
+static int cs35l35_i2c_remove(struct i2c_client *client) +{
- snd_soc_unregister_codec(&client->dev);
- kfree(i2c_get_clientdata(client));
clientdata was allocated with devm this kfree will cause a double free.
- return 0;
+}
Thanks, Charles