[alsa-devel] [PATCH v3 1/2] ASoC: cs35l35: Add support for Cirrus CS35L35 Amplifier
Charles Keepax
ckeepax at opensource.wolfsonmicro.com
Wed Dec 21 11:53:50 CET 2016
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 at 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
More information about the Alsa-devel
mailing list