// SPDX-License-Identifier: GPL-2.0 // // ALSA SoC Texas Instruments PCM6240 Family Audio ADC/DAC Device // // Copyright (C) 2022 - 2024 Texas Instruments Incorporated // https://www.ti.com // // The PCM6240 driver implements a flexible and configurable // algo coefficient setting for one, two, or even multiple // PCM6240 Family chips. // // Author: Shenghao Ding // #include #include #include #include #include #include #include #include #include #include #include "pcm6240.h" static const struct i2c_device_id pcmdevice_i2c_id[] = { { "adc3120", ADC3120 }, { "adc5120", ADC5120 }, { "adc6120", ADC6120 }, { "dix4192", DIX4192 }, { "pcm1690", PCM1690 }, { "pcm3120", PCM3120 }, { "pcm3140", PCM3140 }, { "pcm5120", PCM5120 }, { "pcm5140", PCM5140 }, { "pcm6120", PCM6120 }, { "pcm6140", PCM6140 }, { "pcm6240", PCM6240 }, { "pcm6260", PCM6260 }, { "pcm9211", PCM9211 }, { "pcmd3140", PCMD3140 }, { "pcmd3180", PCMD3180 }, { "pcmd512x", PCMD512X }, { "taa5212", TAA5212 }, { "taa5412", TAA5412 }, { "tad5212", TAD5212 }, { "tad5412", TAD5412 }, {} }; MODULE_DEVICE_TABLE(i2c, pcmdevice_i2c_id); static const char *const pcmdev_ctrl_name[] = { "%s i2c%d Dev%d Ch%d Ana Volume", "%s i2c%d Dev%d Ch%d Digi Volume", "%s i2c%d Dev%d Ch%d Fine Volume", }; static const char *const pcmdev_ctrl_name_with_prefix[] = { "%s Dev%d Ch%d Ana Volume", "%s Dev%d Ch%d Digi Volume", "%s Dev%d Ch%d Fine Volume", }; static const struct pcmdevice_mixer_control adc5120_analog_gain_ctl[] = { { .shift = 1, .reg = ADC5120_REG_CH1_ANALOG_GAIN, .max = 0x54, .invert = 0, }, { .shift = 1, .reg = ADC5120_REG_CH2_ANALOG_GAIN, .max = 0x54, .invert = 0, } }; static const struct pcmdevice_mixer_control adc5120_digi_gain_ctl[] = { { .shift = 0, .reg = ADC5120_REG_CH1_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = ADC5120_REG_CH2_DIGITAL_GAIN, .max = 0xff, .invert = 0, } }; static const struct pcmdevice_mixer_control pcm1690_digi_gain_ctl[] = { { .shift = 0, .reg = PCM1690_REG_CH1_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM1690_REG_CH2_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM1690_REG_CH3_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM1690_REG_CH4_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM1690_REG_CH5_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM1690_REG_CH6_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM1690_REG_CH7_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM1690_REG_CH8_DIGITAL_GAIN, .max = 0xff, .invert = 0, } }; static const struct pcmdevice_mixer_control pcm6240_analog_gain_ctl[] = { { .shift = 2, .reg = PCM6240_REG_CH1_ANALOG_GAIN, .max = 0x42, .invert = 0, }, { .shift = 2, .reg = PCM6240_REG_CH2_ANALOG_GAIN, .max = 0x42, .invert = 0, }, { .shift = 2, .reg = PCM6240_REG_CH3_ANALOG_GAIN, .max = 0x42, .invert = 0, }, { .shift = 2, .reg = PCM6240_REG_CH4_ANALOG_GAIN, .max = 0x42, .invert = 0, } }; static const struct pcmdevice_mixer_control pcm6240_digi_gain_ctl[] = { { .shift = 0, .reg = PCM6240_REG_CH1_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM6240_REG_CH2_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM6240_REG_CH3_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM6240_REG_CH4_DIGITAL_GAIN, .max = 0xff, .invert = 0, } }; static const struct pcmdevice_mixer_control pcm6260_analog_gain_ctl[] = { { .shift = 2, .reg = PCM6260_REG_CH1_ANALOG_GAIN, .max = 0x42, .invert = 0, }, { .shift = 2, .reg = PCM6260_REG_CH2_ANALOG_GAIN, .max = 0x42, .invert = 0, }, { .shift = 2, .reg = PCM6260_REG_CH3_ANALOG_GAIN, .max = 0x42, .invert = 0, }, { .shift = 2, .reg = PCM6260_REG_CH4_ANALOG_GAIN, .max = 0x42, .invert = 0, }, { .shift = 2, .reg = PCM6260_REG_CH5_ANALOG_GAIN, .max = 0x42, .invert = 0, }, { .shift = 2, .reg = PCM6260_REG_CH6_ANALOG_GAIN, .max = 0x42, .invert = 0, } }; static const struct pcmdevice_mixer_control pcm6260_digi_gain_ctl[] = { { .shift = 0, .reg = PCM6260_REG_CH1_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM6260_REG_CH2_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM6260_REG_CH3_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM6260_REG_CH4_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM6260_REG_CH5_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM6260_REG_CH6_DIGITAL_GAIN, .max = 0xff, .invert = 0, } }; static const struct pcmdevice_mixer_control pcm9211_digi_gain_ctl[] = { { .shift = 0, .reg = PCM9211_REG_CH1_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCM9211_REG_CH2_DIGITAL_GAIN, .max = 0xff, .invert = 0, } }; static const struct pcmdevice_mixer_control pcmd3140_digi_gain_ctl[] = { { .shift = 0, .reg = PCMD3140_REG_CH1_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCMD3140_REG_CH2_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCMD3140_REG_CH3_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCMD3140_REG_CH4_DIGITAL_GAIN, .max = 0xff, .invert = 0, } }; static const struct pcmdevice_mixer_control pcmd3140_fine_gain_ctl[] = { { .shift = 4, .reg = PCMD3140_REG_CH1_FINE_GAIN, .max = 0xf, .invert = 0, }, { .shift = 4, .reg = PCMD3140_REG_CH2_FINE_GAIN, .max = 0xf, .invert = 0, }, { .shift = 4, .reg = PCMD3140_REG_CH3_FINE_GAIN, .max = 0xf, .invert = 0, }, { .shift = 4, .reg = PCMD3140_REG_CH4_FINE_GAIN, .max = 0xf, .invert = 0, } }; static const struct pcmdevice_mixer_control pcmd3180_digi_gain_ctl[] = { { .shift = 0, .reg = PCMD3180_REG_CH1_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCMD3180_REG_CH2_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCMD3180_REG_CH3_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCMD3180_REG_CH4_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCMD3180_REG_CH5_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCMD3180_REG_CH6_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCMD3180_REG_CH7_DIGITAL_GAIN, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = PCMD3180_REG_CH8_DIGITAL_GAIN, .max = 0xff, .invert = 0, } }; static const struct pcmdevice_mixer_control pcmd3180_fine_gain_ctl[] = { { .shift = 4, .reg = PCMD3180_REG_CH1_FINE_GAIN, .max = 0xf, .invert = 0, }, { .shift = 4, .reg = PCMD3180_REG_CH2_FINE_GAIN, .max = 0xf, .invert = 0, }, { .shift = 4, .reg = PCMD3180_REG_CH3_FINE_GAIN, .max = 0xf, .invert = 0, }, { .shift = 4, .reg = PCMD3180_REG_CH4_FINE_GAIN, .max = 0xf, .invert = 0, }, { .shift = 4, .reg = PCMD3180_REG_CH5_FINE_GAIN, .max = 0xf, .invert = 0, }, { .shift = 4, .reg = PCMD3180_REG_CH6_FINE_GAIN, .max = 0xf, .invert = 0, }, { .shift = 4, .reg = PCMD3180_REG_CH7_FINE_GAIN, .max = 0xf, .invert = 0, }, { .shift = 4, .reg = PCMD3180_REG_CH8_FINE_GAIN, .max = 0xf, .invert = 0, } }; static const struct pcmdevice_mixer_control taa5412_digi_vol_ctl[] = { { .shift = 0, .reg = TAA5412_REG_CH1_DIGITAL_VOLUME, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = TAA5412_REG_CH2_DIGITAL_VOLUME, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = TAA5412_REG_CH3_DIGITAL_VOLUME, .max = 0xff, .invert = 0, }, { .shift = 0, .reg = TAA5412_REG_CH4_DIGITAL_VOLUME, .max = 0xff, .invert = 0, } }; static const struct pcmdevice_mixer_control taa5412_fine_gain_ctl[] = { { .shift = 4, .reg = TAA5412_REG_CH1_FINE_GAIN, .max = 0xf, .invert = 0, }, { .shift = 4, .reg = TAA5412_REG_CH2_FINE_GAIN, .max = 0xf, .invert = 0, }, { .shift = 4, .reg = TAA5412_REG_CH3_FINE_GAIN, .max = 0xf, .invert = 4, }, { .shift = 0, .reg = TAA5412_REG_CH4_FINE_GAIN, .max = 0xf, .invert = 4, } }; static const DECLARE_TLV_DB_MINMAX_MUTE(pcmd3140_dig_gain_tlv, -10000, 2700); static const DECLARE_TLV_DB_MINMAX_MUTE(pcm1690_fine_dig_gain_tlv, -12750, 0); static const DECLARE_TLV_DB_MINMAX_MUTE(pcm1690_dig_gain_tlv, -25500, 0); static const DECLARE_TLV_DB_MINMAX_MUTE(pcm9211_dig_gain_tlv, -11450, 2000); static const DECLARE_TLV_DB_MINMAX_MUTE(adc5120_fgain_tlv, -10050, 2700); static const DECLARE_TLV_DB_LINEAR(adc5120_chgain_tlv, 0, 4200); static const DECLARE_TLV_DB_MINMAX_MUTE(pcm6260_fgain_tlv, -10000, 2700); static const DECLARE_TLV_DB_LINEAR(pcm6260_chgain_tlv, 0, 4200); static const DECLARE_TLV_DB_MINMAX_MUTE(taa5412_dig_vol_tlv, -8050, 4700); static const DECLARE_TLV_DB_LINEAR(taa5412_fine_gain_tlv, -80, 70); static int pcmdev_change_dev(struct pcmdevice_priv *pcm_priv, unsigned short dev_no) { struct i2c_client *client = (struct i2c_client *)pcm_priv->client; struct regmap *map = pcm_priv->regmap; int ret; if (client->addr == pcm_priv->addr[dev_no]) return 0; client->addr = pcm_priv->addr[dev_no]; /* All pcmdevices share the same regmap, clear the page * inside regmap once switching to another pcmdevice. * Register 0 at any pages inside pcmdevice is the same * one for page-switching. */ ret = regmap_write(map, PCMDEVICE_PAGE_SELECT, 0); if (ret < 0) dev_err(pcm_priv->dev, "%s: err = %d\n", __func__, ret); return ret; } static int pcmdev_dev_read(struct pcmdevice_priv *pcm_dev, unsigned int dev_no, unsigned int reg, unsigned int *val) { struct regmap *map = pcm_dev->regmap; int ret; if (dev_no >= pcm_dev->ndev) { dev_err(pcm_dev->dev, "%s: no such channel(%d)\n", __func__, dev_no); return -EINVAL; } ret = pcmdev_change_dev(pcm_dev, dev_no); if (ret < 0) { dev_err(pcm_dev->dev, "%s: chg dev err = %d\n", __func__, ret); return ret; } ret = regmap_read(map, reg, val); if (ret < 0) dev_err(pcm_dev->dev, "%s: err = %d\n", __func__, ret); return ret; } static int pcmdev_dev_update_bits(struct pcmdevice_priv *pcm_dev, unsigned int dev_no, unsigned int reg, unsigned int mask, unsigned int value) { struct regmap *map = pcm_dev->regmap; int ret; if (dev_no >= pcm_dev->ndev) { dev_err(pcm_dev->dev, "%s: no such channel(%d)\n", __func__, dev_no); return -EINVAL; } ret = pcmdev_change_dev(pcm_dev, dev_no); if (ret < 0) { dev_err(pcm_dev->dev, "%s: chg dev err = %d\n", __func__, ret); return ret; } ret = regmap_update_bits(map, reg, mask, value); if (ret < 0) dev_err(pcm_dev->dev, "%s: update_bits err=%d\n", __func__, ret); return ret; } static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol, int vol_ctrl_type) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(component); struct pcmdevice_mixer_control *mc = (struct pcmdevice_mixer_control *)kcontrol->private_value; int max = mc->max, ret; unsigned int mask = BIT(fls(max)) - 1; unsigned int dev_no = mc->dev_no; unsigned int shift = mc->shift; unsigned int reg = mc->reg; unsigned int val; mutex_lock(&pcm_dev->codec_lock); if (pcm_dev->chip_id == PCM1690) { ret = pcmdev_dev_read(pcm_dev, dev_no, PCM1690_REG_MODE_CTRL, &val); if (ret) { dev_err(pcm_dev->dev, "%s: read mode err=%d\n", __func__, ret); goto out; } val &= PCM1690_REG_MODE_CTRL_DAMS_MSK; /* Set to wide-range mode, before using vol ctrl. */ if (!val && vol_ctrl_type == PCMDEV_PCM1690_VOL_CTRL) { ucontrol->value.integer.value[0] = -25500; goto out; } /* Set to fine mode, before using fine vol ctrl. */ if (val && vol_ctrl_type == PCMDEV_PCM1690_FINE_VOL_CTRL) { ucontrol->value.integer.value[0] = -12750; goto out; } } ret = pcmdev_dev_read(pcm_dev, dev_no, reg, &val); if (ret) { dev_err(pcm_dev->dev, "%s: read err=%d\n", __func__, ret); goto out; } val = (val >> shift) & mask; val = (val > max) ? max : val; val = mc->invert ? max - val : val; ucontrol->value.integer.value[0] = val; out: mutex_unlock(&pcm_dev->codec_lock); return ret; } static int pcmdevice_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { return pcmdev_get_volsw(kcontrol, ucontrol, PCMDEV_GENERIC_VOL_CTRL); } static int pcm1690_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { return pcmdev_get_volsw(kcontrol, ucontrol, PCMDEV_PCM1690_VOL_CTRL); } static int pcm1690_get_finevolsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { return pcmdev_get_volsw(kcontrol, ucontrol, PCMDEV_PCM1690_FINE_VOL_CTRL); } static int pcmdev_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol, int vol_ctrl_type) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(component); struct pcmdevice_mixer_control *mc = (struct pcmdevice_mixer_control *)kcontrol->private_value; int max = mc->max, rc; unsigned int mask = BIT(fls(max)) - 1; unsigned int dev_no = mc->dev_no; unsigned int shift = mc->shift; unsigned int val, val_mask; unsigned int reg = mc->reg; mutex_lock(&pcm_dev->codec_lock); val = ucontrol->value.integer.value[0] & mask; val = (val > max) ? max : val; val = mc->invert ? max - val : val; val_mask = mask << shift; val = val << shift; switch (vol_ctrl_type) { case PCMDEV_PCM1690_VOL_CTRL: val_mask |= PCM1690_REG_MODE_CTRL_DAMS_MSK; val |= PCM1690_REG_MODE_CTRL_DAMS_WIDE_RANGE; break; case PCMDEV_PCM1690_FINE_VOL_CTRL: val_mask |= PCM1690_REG_MODE_CTRL_DAMS_MSK; val |= PCM1690_REG_MODE_CTRL_DAMS_FINE_STEP; break; } rc = pcmdev_dev_update_bits(pcm_dev, dev_no, reg, val_mask, val); if (rc < 0) dev_err(pcm_dev->dev, "%s: update_bits err = %d\n", __func__, rc); else rc = 1; mutex_unlock(&pcm_dev->codec_lock); return rc; } static int pcmdevice_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { return pcmdev_put_volsw(kcontrol, ucontrol, PCMDEV_GENERIC_VOL_CTRL); } static int pcm1690_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { return pcmdev_put_volsw(kcontrol, ucontrol, PCMDEV_PCM1690_VOL_CTRL); } static int pcm1690_put_finevolsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { return pcmdev_put_volsw(kcontrol, ucontrol, PCMDEV_PCM1690_FINE_VOL_CTRL); } static const struct pcmdev_ctrl_info pcmdev_gain_ctl_info[][2] = { // ADC3120 { { .gain = adc5120_chgain_tlv, .pcmdev_ctrl = adc5120_analog_gain_ctl, .ctrl_array_size = ARRAY_SIZE(adc5120_analog_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 0, }, { .gain = adc5120_fgain_tlv, .pcmdev_ctrl = adc5120_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(adc5120_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // ADC5120 { { .gain = adc5120_chgain_tlv, .pcmdev_ctrl = adc5120_analog_gain_ctl, .ctrl_array_size = ARRAY_SIZE(adc5120_analog_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 0, }, { .gain = adc5120_fgain_tlv, .pcmdev_ctrl = adc5120_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(adc5120_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // ADC6120 { { .gain = adc5120_chgain_tlv, .pcmdev_ctrl = adc5120_analog_gain_ctl, .ctrl_array_size = ARRAY_SIZE(adc5120_analog_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 0, }, { .gain = adc5120_fgain_tlv, .pcmdev_ctrl = adc5120_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(adc5120_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // DIX4192 { { .ctrl_array_size = 0, }, { .ctrl_array_size = 0, }, }, // PCM1690 { { .gain = pcm1690_fine_dig_gain_tlv, .pcmdev_ctrl = pcm1690_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcm1690_digi_gain_ctl), .get = pcm1690_get_volsw, .put = pcm1690_put_volsw, .pcmdev_ctrl_name_id = 1, }, { .gain = pcm1690_dig_gain_tlv, .pcmdev_ctrl = pcm1690_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcm1690_digi_gain_ctl), .get = pcm1690_get_finevolsw, .put = pcm1690_put_finevolsw, .pcmdev_ctrl_name_id = 2, }, }, // PCM3120 { { .gain = adc5120_chgain_tlv, .pcmdev_ctrl = adc5120_analog_gain_ctl, .ctrl_array_size = ARRAY_SIZE(adc5120_analog_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 0, }, { .gain = adc5120_fgain_tlv, .pcmdev_ctrl = adc5120_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(adc5120_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // PCM3140 { { .gain = pcm6260_chgain_tlv, .pcmdev_ctrl = pcm6240_analog_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcm6240_analog_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 0, }, { .gain = pcm6260_fgain_tlv, .pcmdev_ctrl = pcm6240_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcm6240_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // PCM5120 { { .gain = adc5120_chgain_tlv, .pcmdev_ctrl = adc5120_analog_gain_ctl, .ctrl_array_size = ARRAY_SIZE(adc5120_analog_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 0, }, { .gain = adc5120_fgain_tlv, .pcmdev_ctrl = adc5120_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(adc5120_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // PCM5140 { { .gain = pcm6260_chgain_tlv, .pcmdev_ctrl = pcm6240_analog_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcm6240_analog_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 0, }, { .gain = pcm6260_fgain_tlv, .pcmdev_ctrl = pcm6240_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcm6240_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // PCM6120 { { .gain = adc5120_chgain_tlv, .pcmdev_ctrl = adc5120_analog_gain_ctl, .ctrl_array_size = ARRAY_SIZE(adc5120_analog_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 0, }, { .gain = adc5120_fgain_tlv, .pcmdev_ctrl = adc5120_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(adc5120_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // PCM6140 { { .gain = pcm6260_chgain_tlv, .pcmdev_ctrl = pcm6240_analog_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcm6240_analog_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 0, }, { .gain = pcm6260_fgain_tlv, .pcmdev_ctrl = pcm6240_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcm6240_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // PCM6240 { { .gain = pcm6260_chgain_tlv, .pcmdev_ctrl = pcm6240_analog_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcm6240_analog_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 0, }, { .gain = pcm6260_fgain_tlv, .pcmdev_ctrl = pcm6240_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcm6240_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // PCM6260 { { .gain = pcm6260_chgain_tlv, .pcmdev_ctrl = pcm6260_analog_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcm6260_analog_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 0, }, { .gain = pcm6260_fgain_tlv, .pcmdev_ctrl = pcm6260_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcm6260_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // PCM9211 { { .ctrl_array_size = 0, }, { .gain = pcm9211_dig_gain_tlv, .pcmdev_ctrl = pcm9211_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcm9211_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // PCMD3140 { { .gain = taa5412_fine_gain_tlv, .pcmdev_ctrl = pcmd3140_fine_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcmd3140_fine_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 2, }, { .gain = pcmd3140_dig_gain_tlv, .pcmdev_ctrl = pcmd3140_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcmd3140_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // PCMD3180 { { .gain = taa5412_fine_gain_tlv, .pcmdev_ctrl = pcmd3180_fine_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcmd3180_fine_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 2, }, { .gain = pcmd3140_dig_gain_tlv, .pcmdev_ctrl = pcmd3180_digi_gain_ctl, .ctrl_array_size = ARRAY_SIZE(pcmd3180_digi_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // PCMD512X { { .ctrl_array_size = 0, }, { .ctrl_array_size = 0, }, }, // TAA5212 { { .gain = taa5412_fine_gain_tlv, .pcmdev_ctrl = taa5412_fine_gain_ctl, .ctrl_array_size = ARRAY_SIZE(taa5412_fine_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 2, }, { .gain = taa5412_dig_vol_tlv, .pcmdev_ctrl = taa5412_digi_vol_ctl, .ctrl_array_size = ARRAY_SIZE(taa5412_digi_vol_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // TAA5412 { { .gain = taa5412_fine_gain_tlv, .pcmdev_ctrl = taa5412_fine_gain_ctl, .ctrl_array_size = ARRAY_SIZE(taa5412_fine_gain_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 2, }, { .gain = taa5412_dig_vol_tlv, .pcmdev_ctrl = taa5412_digi_vol_ctl, .ctrl_array_size = ARRAY_SIZE(taa5412_digi_vol_ctl), .get = pcmdevice_get_volsw, .put = pcmdevice_put_volsw, .pcmdev_ctrl_name_id = 1, }, }, // TAD5212 { { .ctrl_array_size = 0, }, { .ctrl_array_size = 0, }, }, // TAD5412 { { .ctrl_array_size = 0, }, { .ctrl_array_size = 0, }, }, }; static int pcmdev_dev_bulk_write(struct pcmdevice_priv *pcm_dev, unsigned int dev_no, unsigned int reg, unsigned char *data, unsigned int len) { struct regmap *map = pcm_dev->regmap; int ret; if (dev_no >= pcm_dev->ndev) { dev_err(pcm_dev->dev, "%s: no such channel(%d)\n", __func__, dev_no); return -EINVAL; } ret = pcmdev_change_dev(pcm_dev, dev_no); if (ret < 0) { dev_err(pcm_dev->dev, "%s: chg dev err = %d\n", __func__, ret); return ret; } ret = regmap_bulk_write(map, reg, data, len); if (ret < 0) dev_err(pcm_dev->dev, "%s: bulk_write err = %d\n", __func__, ret); return ret; } static int pcmdev_dev_write(struct pcmdevice_priv *pcm_dev, unsigned int dev_no, unsigned int reg, unsigned int value) { struct regmap *map = pcm_dev->regmap; int ret; if (dev_no >= pcm_dev->ndev) { dev_err(pcm_dev->dev, "%s: no such channel(%d)\n", __func__, dev_no); return -EINVAL; } ret = pcmdev_change_dev(pcm_dev, dev_no); if (ret < 0) { dev_err(pcm_dev->dev, "%s: chg dev err = %d\n", __func__, ret); return ret; } ret = regmap_write(map, reg, value); if (ret < 0) dev_err(pcm_dev->dev, "%s: err = %d\n", __func__, ret); return ret; } static int pcmdevice_info_profile( struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(codec); uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; uinfo->value.integer.min = 0; uinfo->value.integer.max = max(0, pcm_dev->regbin.ncfgs - 1); return 0; } static int pcmdevice_get_profile_id( struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(codec); ucontrol->value.integer.value[0] = pcm_dev->cur_conf; return 0; } static int pcmdevice_set_profile_id( struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(codec); int nr_profile = ucontrol->value.integer.value[0]; int max = pcm_dev->regbin.ncfgs - 1; int ret = 0; nr_profile = clamp(nr_profile, 0, max); if (pcm_dev->cur_conf != nr_profile) { pcm_dev->cur_conf = nr_profile; ret = 1; } return ret; } static int pcmdevice_info_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct pcmdevice_mixer_control *mc = (struct pcmdevice_mixer_control *)kcontrol->private_value; uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; uinfo->value.integer.min = 0; uinfo->value.integer.max = mc->max; return 0; } static void pcm9211_sw_rst(struct pcmdevice_priv *pcm_dev) { int ret, i; for (i = 0; i < pcm_dev->ndev; i++) { ret = pcmdev_dev_update_bits(pcm_dev, i, PCM9211_REG_SW_CTRL, PCM9211_REG_SW_CTRL_MRST_MSK, PCM9211_REG_SW_CTRL_MRST); if (ret < 0) dev_err(pcm_dev->dev, "%s: dev %d swreset fail %d\n", __func__, i, ret); } } static void pcmdevice_sw_rst(struct pcmdevice_priv *pcm_dev) { int ret, i; for (i = 0; i < pcm_dev->ndev; i++) { ret = pcmdev_dev_write(pcm_dev, i, PCMDEVICE_REG_SWRESET, PCMDEVICE_REG_SWRESET_RESET); if (ret < 0) dev_err(pcm_dev->dev, "%s: dev %d swreset fail %d\n", __func__, i, ret); } } static struct pcmdevice_config_info *pcmdevice_add_config(void *ctxt, const unsigned char *config_data, unsigned int config_size, int *status) { struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *)ctxt; struct pcmdevice_config_info *cfg_info; struct pcmdevice_block_data **bk_da; unsigned int config_offset = 0, i; cfg_info = kzalloc(sizeof(struct pcmdevice_config_info), GFP_KERNEL); if (!cfg_info) { *status = -ENOMEM; goto out; } if (pcm_dev->regbin.fw_hdr.binary_version_num >= 0x105) { if (config_offset + 64 > (int)config_size) { *status = -EINVAL; dev_err(pcm_dev->dev, "%s: cfg_name out of boundary\n", __func__); goto out; } memcpy(cfg_info->cfg_name, &config_data[config_offset], 64); config_offset += 64; } if (config_offset + 4 > config_size) { *status = -EINVAL; dev_err(pcm_dev->dev, "%s: nblocks out of boundary\n", __func__); goto out; } cfg_info->nblocks = get_unaligned_be32(&config_data[config_offset]); config_offset += 4; bk_da = cfg_info->blk_data = kcalloc(cfg_info->nblocks, sizeof(struct pcmdevice_block_data *), GFP_KERNEL); if (!bk_da) { *status = -ENOMEM; goto out; } cfg_info->real_nblocks = 0; for (i = 0; i < cfg_info->nblocks; i++) { if (config_offset + 12 > config_size) { *status = -EINVAL; dev_err(pcm_dev->dev, "%s: out of boundary i = %d nblocks = %u\n", __func__, i, cfg_info->nblocks); break; } bk_da[i] = kzalloc(sizeof(struct pcmdevice_block_data), GFP_KERNEL); if (!bk_da[i]) { *status = -ENOMEM; break; } bk_da[i]->dev_idx = config_data[config_offset]; config_offset++; bk_da[i]->block_type = config_data[config_offset]; config_offset++; if (bk_da[i]->block_type == PCMDEVICE_BIN_BLK_PRE_POWER_UP) { if (bk_da[i]->dev_idx == 0) cfg_info->active_dev = (1 << pcm_dev->ndev) - 1; else cfg_info->active_dev = 1 << (bk_da[i]->dev_idx - 1); } bk_da[i]->yram_checksum = get_unaligned_be16(&config_data[config_offset]); config_offset += 2; bk_da[i]->block_size = get_unaligned_be32(&config_data[config_offset]); config_offset += 4; bk_da[i]->n_subblks = get_unaligned_be32(&config_data[config_offset]); config_offset += 4; if (config_offset + bk_da[i]->block_size > config_size) { *status = -EINVAL; dev_err(pcm_dev->dev, "%s: out of boundary: i = %d blks = %u\n", __func__, i, cfg_info->nblocks); break; } bk_da[i]->regdata = kmemdup(&config_data[config_offset], bk_da[i]->block_size, GFP_KERNEL); if (!bk_da[i]->regdata) { *status = -ENOMEM; goto out; } config_offset += bk_da[i]->block_size; cfg_info->real_nblocks += 1; } out: return cfg_info; } static int pcmdev_gain_ctrl_add(struct pcmdevice_priv *pcm_dev, int dev_no, int ctl_id) { struct i2c_adapter *adap = pcm_dev->client->adapter; struct snd_soc_component *comp = pcm_dev->component; struct pcmdevice_mixer_control *pcmdev_ctrl; struct snd_kcontrol_new *pcmdev_controls; int ret, mix_index = 0, name_id, chn; unsigned int id = pcm_dev->chip_id; const int nr_chn = pcmdev_gain_ctl_info[id][ctl_id].ctrl_array_size; const char *ctrl_name; char *name; if (!nr_chn) { dev_dbg(pcm_dev->dev, "%s: no gain ctrl for %s\n", __func__, pcm_dev->dev_name); return 0; } pcmdev_controls = devm_kzalloc(pcm_dev->dev, nr_chn * sizeof(struct snd_kcontrol_new), GFP_KERNEL); if (!pcmdev_controls) return -ENOMEM; name_id = pcmdev_gain_ctl_info[id][ctl_id].pcmdev_ctrl_name_id; if (comp->name_prefix) ctrl_name = pcmdev_ctrl_name_with_prefix[name_id]; else ctrl_name = pcmdev_ctrl_name[name_id]; for (chn = 1; chn <= nr_chn; chn++) { name = devm_kzalloc(pcm_dev->dev, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL); if (!name) { ret = -ENOMEM; goto out; } if (comp->name_prefix) scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, ctrl_name, comp->name_prefix, dev_no, chn); else scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, ctrl_name, pcm_dev->upper_dev_name, adap->nr, dev_no, chn); pcmdev_controls[mix_index].tlv.p = pcmdev_gain_ctl_info[id][ctl_id].gain; pcmdev_ctrl = devm_kmemdup(pcm_dev->dev, &pcmdev_gain_ctl_info[id][ctl_id].pcmdev_ctrl[chn - 1], sizeof(*pcmdev_ctrl), GFP_KERNEL); if (!pcmdev_ctrl) { ret = -ENOMEM; goto out; } pcmdev_ctrl->dev_no = dev_no; pcmdev_controls[mix_index].private_value = (unsigned long)pcmdev_ctrl; pcmdev_controls[mix_index].name = name; pcmdev_controls[mix_index].access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE; pcmdev_controls[mix_index].iface = SNDRV_CTL_ELEM_IFACE_MIXER; pcmdev_controls[mix_index].info = pcmdevice_info_volsw; pcmdev_controls[mix_index].get = pcmdev_gain_ctl_info[id][ctl_id].get; pcmdev_controls[mix_index].put = pcmdev_gain_ctl_info[id][ctl_id].put; mix_index++; } ret = snd_soc_add_component_controls(comp, pcmdev_controls, mix_index); if (ret) dev_err(pcm_dev->dev, "%s: add_controls err = %d\n", __func__, ret); out: return ret; } static int pcmdev_profile_ctrl_add(struct pcmdevice_priv *pcm_dev) { struct snd_soc_component *comp = pcm_dev->component; struct i2c_adapter *adap = pcm_dev->client->adapter; struct snd_kcontrol_new *pcmdev_ctrl; char *name; int ret; pcmdev_ctrl = devm_kzalloc(pcm_dev->dev, sizeof(struct snd_kcontrol_new), GFP_KERNEL); if (!pcmdev_ctrl) return -ENOMEM; /* Create a mixer item for selecting the active profile */ name = devm_kzalloc(pcm_dev->dev, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL); if (!name) return -ENOMEM; if (comp->name_prefix) scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s Profile id", comp->name_prefix); else scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s i2c%d Profile id", pcm_dev->upper_dev_name, adap->nr); pcmdev_ctrl->name = name; pcmdev_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; pcmdev_ctrl->info = pcmdevice_info_profile; pcmdev_ctrl->get = pcmdevice_get_profile_id; pcmdev_ctrl->put = pcmdevice_set_profile_id; ret = snd_soc_add_component_controls(comp, pcmdev_ctrl, 1); if (ret) dev_err(pcm_dev->dev, "%s: add_controls err = %d\n", __func__, ret); return ret; } static void pcmdevice_config_info_remove(void *ctxt) { struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *) ctxt; struct pcmdevice_regbin *regbin = &(pcm_dev->regbin); struct pcmdevice_config_info **cfg_info = regbin->cfg_info; int i, j; if (!cfg_info) return; for (i = 0; i < regbin->ncfgs; i++) { if (!cfg_info[i]) continue; if (cfg_info[i]->blk_data) { for (j = 0; j < (int)cfg_info[i]->real_nblocks; j++) { if (!cfg_info[i]->blk_data[j]) continue; kfree(cfg_info[i]->blk_data[j]->regdata); kfree(cfg_info[i]->blk_data[j]); } kfree(cfg_info[i]->blk_data); } kfree(cfg_info[i]); } kfree(cfg_info); } static int pcmdev_regbin_ready(const struct firmware *fmw, void *ctxt) { struct pcmdevice_config_info **cfg_info; struct pcmdevice_priv *pcm_dev = ctxt; struct pcmdevice_regbin_hdr *fw_hdr; struct pcmdevice_regbin *regbin; unsigned int total_config_sz = 0; int offset = 0, ret = 0, i; unsigned char *buf; regbin = &(pcm_dev->regbin); fw_hdr = &(regbin->fw_hdr); if (!fmw || !fmw->data) { dev_err(pcm_dev->dev, "%s: failed to read %s\n", __func__, pcm_dev->bin_name); pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED; ret = -EINVAL; goto out; } buf = (unsigned char *)fmw->data; fw_hdr->img_sz = get_unaligned_be32(&buf[offset]); offset += 4; if (fw_hdr->img_sz != fmw->size) { dev_err(pcm_dev->dev, "%s: file size(%d) not match %u", __func__, (int)fmw->size, fw_hdr->img_sz); pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED; ret = -EINVAL; goto out; } fw_hdr->checksum = get_unaligned_be32(&buf[offset]); offset += 4; fw_hdr->binary_version_num = get_unaligned_be32(&buf[offset]); if (fw_hdr->binary_version_num < 0x103) { dev_err(pcm_dev->dev, "%s: bin version 0x%04x is out of date", __func__, fw_hdr->binary_version_num); pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED; ret = -EINVAL; goto out; } offset += 4; fw_hdr->drv_fw_version = get_unaligned_be32(&buf[offset]); offset += 8; fw_hdr->plat_type = buf[offset]; offset += 1; fw_hdr->dev_family = buf[offset]; offset += 1; fw_hdr->reserve = buf[offset]; offset += 1; fw_hdr->ndev = buf[offset]; offset += 1; if (fw_hdr->ndev != pcm_dev->ndev) { dev_err(pcm_dev->dev, "%s: invalid ndev(%u)\n", __func__, fw_hdr->ndev); pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED; ret = -EINVAL; goto out; } if (offset + PCMDEVICE_MAX_REGBIN_DEVICES > fw_hdr->img_sz) { dev_err(pcm_dev->dev, "%s: devs out of boundary!\n", __func__); pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED; ret = -EINVAL; goto out; } for (i = 0; i < PCMDEVICE_MAX_REGBIN_DEVICES; i++, offset++) fw_hdr->devs[i] = buf[offset]; fw_hdr->nconfig = get_unaligned_be32(&buf[offset]); offset += 4; for (i = 0; i < PCMDEVICE_CONFIG_SUM; i++) { fw_hdr->config_size[i] = get_unaligned_be32(&buf[offset]); offset += 4; total_config_sz += fw_hdr->config_size[i]; } if (fw_hdr->img_sz - total_config_sz != (unsigned int)offset) { dev_err(pcm_dev->dev, "%s: bin file error!\n", __func__); pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED; ret = -EINVAL; goto out; } cfg_info = kcalloc(fw_hdr->nconfig, sizeof(*cfg_info), GFP_KERNEL); if (!cfg_info) { pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED; ret = -ENOMEM; goto out; } regbin->cfg_info = cfg_info; regbin->ncfgs = 0; for (i = 0; i < (int)fw_hdr->nconfig; i++) { cfg_info[i] = pcmdevice_add_config(ctxt, &buf[offset], fw_hdr->config_size[i], &ret); if (ret) { /* In case the bin file is partially destroyed. */ if (regbin->ncfgs == 0) pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED; break; } offset += (int)fw_hdr->config_size[i]; regbin->ncfgs += 1; } out: if (pcm_dev->fw_state == PCMDEVICE_FW_LOAD_FAILED) { dev_err(pcm_dev->dev, "%s: remove config due to fw load error!\n", __func__); pcmdevice_config_info_remove(pcm_dev); } return ret; } static int pcmdevice_comp_probe(struct snd_soc_component *comp) { struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(comp); struct i2c_adapter *adap = pcm_dev->client->adapter; const struct firmware *fw_entry = NULL; int ret, i, j; mutex_lock(&pcm_dev->codec_lock); pcm_dev->component = comp; for (i = 0; i < pcm_dev->ndev; i++) { for (j = 0; j < 2; j++) { ret = pcmdev_gain_ctrl_add(pcm_dev, i, j); if (ret < 0) goto out; } } if (comp->name_prefix) { /* There's name_prefix defined in DTS. Bin file name will be * name_prefix.bin stores the firmware including register * setting and params for different filters inside chips, it * must be copied into firmware folder. The same types of * pcmdevices sitting on the same i2c bus will be aggregated as * one single codec, all of them share the same bin file. */ scnprintf(pcm_dev->bin_name, PCMDEVICE_BIN_FILENAME_LEN, "%s.bin", comp->name_prefix); } else { /* There's NO name_prefix defined in DTS. Bin file name will be * device-name[defined in pcmdevice_i2c_id]-i2c-bus_id * [0,1,...,N]-sum[1,...,4]dev.bin stores the firmware * including register setting and params for different filters * inside chips, it must be copied into firmware folder. The * same types of pcmdevices sitting on the same i2c bus will be * aggregated as one single codec, all of them share the same * bin file. */ scnprintf(pcm_dev->bin_name, PCMDEVICE_BIN_FILENAME_LEN, "%s-i2c-%d-%udev.bin", pcm_dev->dev_name, adap->nr, pcm_dev->ndev); } ret = request_firmware(&fw_entry, pcm_dev->bin_name, pcm_dev->dev); if (ret) { dev_err(pcm_dev->dev, "%s: request %s err = %d\n", __func__, pcm_dev->bin_name, ret); goto out; } ret = pcmdev_regbin_ready(fw_entry, pcm_dev); if (ret) { dev_err(pcm_dev->dev, "%s: %s parse err = %d\n", __func__, pcm_dev->bin_name, ret); goto out; } ret = pcmdev_profile_ctrl_add(pcm_dev); out: if (fw_entry) release_firmware(fw_entry); mutex_unlock(&pcm_dev->codec_lock); return ret; } static void pcmdevice_comp_remove(struct snd_soc_component *codec) { struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(codec); if (!pcm_dev) return; mutex_lock(&pcm_dev->codec_lock); pcmdevice_config_info_remove(pcm_dev); mutex_unlock(&pcm_dev->codec_lock); } static const struct snd_soc_dapm_widget pcmdevice_dapm_widgets[] = { SND_SOC_DAPM_AIF_IN("ASI", "ASI Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("ASI1 OUT", "ASI1 Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_OUTPUT("OUT"), SND_SOC_DAPM_INPUT("MIC"), }; static const struct snd_soc_dapm_route pcmdevice_audio_map[] = { {"OUT", NULL, "ASI"}, {"ASI1 OUT", NULL, "MIC"}, }; static const struct snd_soc_component_driver soc_codec_driver_pcmdevice = { .probe = pcmdevice_comp_probe, .remove = pcmdevice_comp_remove, .dapm_widgets = pcmdevice_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(pcmdevice_dapm_widgets), .dapm_routes = pcmdevice_audio_map, .num_dapm_routes = ARRAY_SIZE(pcmdevice_audio_map), .suspend_bias_off = 1, .idle_bias_on = 0, .use_pmdown_time = 1, .endianness = 1, }; static int pcmdev_single_byte_wr(struct pcmdevice_priv *pcm_dev, unsigned char *data, int devn, int sublocksize) { unsigned short len = get_unaligned_be16(&data[2]); int offset = 2; int i, ret; offset += 2; if (offset + 4 * len > sublocksize) { dev_err(pcm_dev->dev, "%s: dev-%d byt wr out of boundary\n", __func__, devn); return -EINVAL; } for (i = 0; i < len; i++) { ret = pcmdev_dev_write(pcm_dev, devn, PCMDEVICE_REG(data[offset + 1], data[offset + 2]), data[offset + 3]); /* skip this error for next operation or next devices */ if (ret < 0) dev_err(pcm_dev->dev, "%s: dev-%d single write err\n", __func__, devn); offset += 4; } return offset; } static int pcmdev_burst_wr(struct pcmdevice_priv *pcm_dev, unsigned char *data, int devn, int sublocksize) { unsigned short len = get_unaligned_be16(&data[2]); int offset = 2; int ret; offset += 2; if (offset + 4 + len > sublocksize) { dev_err(pcm_dev->dev, "%s: dev-%d burst Out of boundary\n", __func__, devn); return -EINVAL; } if (len % 4) { dev_err(pcm_dev->dev, "%s: dev-%d bst-len(%u) not div by 4\n", __func__, devn, len); return -EINVAL; } ret = pcmdev_dev_bulk_write(pcm_dev, devn, PCMDEVICE_REG(data[offset + 1], data[offset + 2]), &(data[offset + 4]), len); /* skip this error for next devices */ if (ret < 0) dev_err(pcm_dev->dev, "%s: dev-%d bulk_write err = %d\n", __func__, devn, ret); offset += (len + 4); return offset; } static int pcmdev_delay(struct pcmdevice_priv *pcm_dev, unsigned char *data, int devn, int sublocksize) { unsigned int delay_time = 0; int offset = 2; if (offset + 2 > sublocksize) { dev_err(pcm_dev->dev, "%s: dev-%d delay out of boundary\n", __func__, devn); return -EINVAL; } delay_time = get_unaligned_be16(&data[2]) * 1000; usleep_range(delay_time, delay_time + 50); offset += 2; return offset; } static int pcmdev_bits_wr(struct pcmdevice_priv *pcm_dev, unsigned char *data, int devn, int sublocksize) { int offset = 2; int ret; if (offset + 6 > sublocksize) { dev_err(pcm_dev->dev, "%s: dev-%d bit write out of memory\n", __func__, devn); return -EINVAL; } ret = pcmdev_dev_update_bits(pcm_dev, devn, PCMDEVICE_REG(data[offset + 3], data[offset + 4]), data[offset + 1], data[offset + 5]); /* skip this error for next devices */ if (ret < 0) dev_err(pcm_dev->dev, "%s: dev-%d update_bits err = %d\n", __func__, devn, ret); offset += 6; return offset; } static int pcmdevice_process_block(void *ctxt, unsigned char *data, unsigned char dev_idx, int sublocksize) { struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *)ctxt; int devn, dev_end, ret = 0; unsigned char subblk_typ = data[1]; if (dev_idx) { devn = dev_idx - 1; dev_end = dev_idx; } else { devn = 0; dev_end = pcm_dev->ndev; } /* loop in case of several devices sharing the same sub-block */ for (; devn < dev_end; devn++) { switch (subblk_typ) { case PCMDEVICE_CMD_SING_W: ret = pcmdev_single_byte_wr(pcm_dev, data, devn, sublocksize); break; case PCMDEVICE_CMD_BURST: ret = pcmdev_burst_wr(pcm_dev, data, devn, sublocksize); break; case PCMDEVICE_CMD_DELAY: ret = pcmdev_delay(pcm_dev, data, devn, sublocksize); break; case PCMDEVICE_CMD_FIELD_W: ret = pcmdev_bits_wr(pcm_dev, data, devn, sublocksize); break; default: break; } /* * In case of sub-block error, break the loop for the rest of * devices. */ if (ret < 0) break; } return ret; } static void pcmdevice_select_cfg_blk(void *ctxt, int conf_no, unsigned char block_type) { struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *)ctxt; struct pcmdevice_regbin *regbin = &(pcm_dev->regbin); struct pcmdevice_config_info **cfg_info = regbin->cfg_info; struct pcmdevice_block_data **blk_data; int j, k; if (conf_no >= regbin->ncfgs || conf_no < 0 || NULL == cfg_info) { dev_err(pcm_dev->dev, "%s: conf_no should be less than %u\n", __func__, regbin->ncfgs); goto out; } blk_data = cfg_info[conf_no]->blk_data; for (j = 0; j < (int)cfg_info[conf_no]->real_nblocks; j++) { unsigned int length = 0, ret; if (block_type > 5 || block_type < 2) { dev_err(pcm_dev->dev, "%s: block_type should be out of range\n", __func__); goto out; } if (block_type != blk_data[j]->block_type) continue; for (k = 0; k < (int)blk_data[j]->n_subblks; k++) { ret = pcmdevice_process_block(pcm_dev, blk_data[j]->regdata + length, blk_data[j]->dev_idx, blk_data[j]->block_size - length); length += ret; if (blk_data[j]->block_size < length) { dev_err(pcm_dev->dev, "%s: %u %u out of boundary\n", __func__, length, blk_data[j]->block_size); break; } } if (length != blk_data[j]->block_size) dev_err(pcm_dev->dev, "%s: %u %u size is not same\n", __func__, length, blk_data[j]->block_size); } out: return; } static int pcmdevice_mute(struct snd_soc_dai *dai, int mute, int stream) { struct snd_soc_component *codec = dai->component; struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(codec); unsigned char block_type; if (pcm_dev->fw_state == PCMDEVICE_FW_LOAD_FAILED) { dev_err(pcm_dev->dev, "%s: bin file not loaded\n", __func__); return -EINVAL; } if (mute) block_type = PCMDEVICE_BIN_BLK_PRE_SHUTDOWN; else block_type = PCMDEVICE_BIN_BLK_PRE_POWER_UP; mutex_lock(&pcm_dev->codec_lock); pcmdevice_select_cfg_blk(pcm_dev, pcm_dev->cur_conf, block_type); mutex_unlock(&pcm_dev->codec_lock); return 0; } static int pcmdevice_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct pcmdevice_priv *pcm_dev = snd_soc_dai_get_drvdata(dai); unsigned int fsrate; unsigned int slot_width; int bclk_rate; int ret = 0; fsrate = params_rate(params); switch (fsrate) { case 48000: break; case 44100: break; default: dev_err(pcm_dev->dev, "%s: incorrect sample rate = %u\n", __func__, fsrate); ret = -EINVAL; goto out; } slot_width = params_width(params); switch (slot_width) { case 16: break; case 20: break; case 24: break; case 32: break; default: dev_err(pcm_dev->dev, "%s: incorrect slot width = %u\n", __func__, slot_width); ret = -EINVAL; goto out; } bclk_rate = snd_soc_params_to_bclk(params); if (bclk_rate < 0) { dev_err(pcm_dev->dev, "%s: incorrect bclk rate = %d\n", __func__, bclk_rate); ret = bclk_rate; } out: return ret; } static const struct snd_soc_dai_ops pcmdevice_dai_ops = { .mute_stream = pcmdevice_mute, .hw_params = pcmdevice_hw_params, }; static struct snd_soc_dai_driver pcmdevice_dai_driver[] = { { .name = "pcmdevice-codec", .capture = { .stream_name = "Capture", .channels_min = 2, .channels_max = PCMDEVICE_MAX_CHANNELS, .rates = PCMDEVICE_RATES, .formats = PCMDEVICE_FORMATS, }, .playback = { .stream_name = "Playback", .channels_min = 2, .channels_max = PCMDEVICE_MAX_CHANNELS, .rates = PCMDEVICE_RATES, .formats = PCMDEVICE_FORMATS, }, .ops = &pcmdevice_dai_ops, .symmetric_rate = 1, } }; #ifdef CONFIG_OF static const struct of_device_id pcmdevice_of_match[] = { { .compatible = "ti,adc3120" }, { .compatible = "ti,adc5120" }, { .compatible = "ti,adc6120" }, { .compatible = "ti,dix4192" }, { .compatible = "ti,pcm1690" }, { .compatible = "ti,pcm3120" }, { .compatible = "ti,pcm3140" }, { .compatible = "ti,pcm5120" }, { .compatible = "ti,pcm5140" }, { .compatible = "ti,pcm6120" }, { .compatible = "ti,pcm6140" }, { .compatible = "ti,pcm6240" }, { .compatible = "ti,pcm6260" }, { .compatible = "ti,pcm9211" }, { .compatible = "ti,pcmd3140" }, { .compatible = "ti,pcmd3180" }, { .compatible = "ti,pcmd512x" }, { .compatible = "ti,taa5212" }, { .compatible = "ti,taa5412" }, { .compatible = "ti,tad5212" }, { .compatible = "ti,tad5412" }, {}, }; MODULE_DEVICE_TABLE(of, pcmdevice_of_match); #endif static const struct regmap_range_cfg pcmdevice_ranges[] = { { .range_min = 0, .range_max = 256 * 128, .selector_reg = PCMDEVICE_PAGE_SELECT, .selector_mask = 0xff, .selector_shift = 0, .window_start = 0, .window_len = 128, }, }; static const struct regmap_config pcmdevice_i2c_regmap = { .reg_bits = 8, .val_bits = 8, .cache_type = REGCACHE_MAPLE, .ranges = pcmdevice_ranges, .num_ranges = ARRAY_SIZE(pcmdevice_ranges), .max_register = 256 * 128, }; static void pcmdevice_remove(struct pcmdevice_priv *pcm_dev) { if (gpio_is_valid(pcm_dev->irq_info.gpio)) { gpio_free(pcm_dev->irq_info.gpio); free_irq(pcm_dev->irq_info.nmb, pcm_dev); } mutex_destroy(&pcm_dev->codec_lock); } static char *str_to_upper(char *str) { char *orig = str; if (!str) return NULL; while (*str) { *str = toupper(*str); str++; } return orig; } static int pcmdevice_i2c_probe(struct i2c_client *i2c) { const struct i2c_device_id *id = i2c_match_id(pcmdevice_i2c_id, i2c); struct pcmdevice_priv *pcm_dev; struct device_node *np; unsigned int dev_addrs[PCMDEVICE_MAX_I2C_DEVICES]; int ret = 0, i = 0, ndev = 0; #ifdef CONFIG_OF const __be32 *reg, *reg_end; int len, sw, aw; #endif pcm_dev = devm_kzalloc(&i2c->dev, sizeof(*pcm_dev), GFP_KERNEL); if (!pcm_dev) return -ENOMEM; pcm_dev->chip_id = (id != NULL) ? id->driver_data : 0; pcm_dev->dev = &i2c->dev; pcm_dev->client = i2c; if (pcm_dev->chip_id >= MAX_DEVICE) pcm_dev->chip_id = 0; strscpy(pcm_dev->dev_name, pcmdevice_i2c_id[pcm_dev->chip_id].name, sizeof(pcm_dev->dev_name)); strscpy(pcm_dev->upper_dev_name, pcmdevice_i2c_id[pcm_dev->chip_id].name, sizeof(pcm_dev->upper_dev_name)); str_to_upper(pcm_dev->upper_dev_name); pcm_dev->regmap = devm_regmap_init_i2c(i2c, &pcmdevice_i2c_regmap); if (IS_ERR(pcm_dev->regmap)) { ret = PTR_ERR(pcm_dev->regmap); dev_err(&i2c->dev, "%s: failed to allocate register map: %d\n", __func__, ret); goto out; } i2c_set_clientdata(i2c, pcm_dev); mutex_init(&pcm_dev->codec_lock); np = pcm_dev->dev->of_node; #ifdef CONFIG_OF aw = of_n_addr_cells(np); sw = of_n_size_cells(np); if (sw == 0) { reg = (const __be32 *)of_get_property(np, "reg", &len); reg_end = reg + len/sizeof(*reg); ndev = 0; do { dev_addrs[ndev] = of_read_number(reg, aw); reg += aw; ndev++; } while (reg < reg_end); } else { ndev = 1; dev_addrs[0] = i2c->addr; } #else ndev = 1; dev_addrs[0] = i2c->addr; #endif pcm_dev->irq_info.gpio = of_irq_get(np, 0); for (i = 0; i < ndev; i++) pcm_dev->addr[i] = dev_addrs[i]; pcm_dev->ndev = ndev; pcm_dev->hw_rst = devm_gpiod_get_optional(&i2c->dev, "reset-gpios", GPIOD_OUT_HIGH); /* No reset GPIO, no side-effect */ if (IS_ERR(pcm_dev->hw_rst)) { if (pcm_dev->chip_id == PCM9211 || pcm_dev->chip_id == PCM1690) pcm9211_sw_rst(pcm_dev); else pcmdevice_sw_rst(pcm_dev); } else { gpiod_set_value_cansleep(pcm_dev->hw_rst, 0); usleep_range(500, 1000); gpiod_set_value_cansleep(pcm_dev->hw_rst, 1); } if (pcm_dev->chip_id == PCM1690) goto skip_interrupt; if (gpio_is_valid(pcm_dev->irq_info.gpio)) { dev_dbg(pcm_dev->dev, "irq-gpio = %d", pcm_dev->irq_info.gpio); ret = gpio_request(pcm_dev->irq_info.gpio, "PCMDEV-IRQ"); if (!ret) { int gpio = pcm_dev->irq_info.gpio; gpio_direction_input(gpio); pcm_dev->irq_info.nmb = gpio_to_irq(gpio); } else dev_err(pcm_dev->dev, "%s: GPIO %d request error\n", __func__, pcm_dev->irq_info.gpio); } else dev_err(pcm_dev->dev, "Looking up irq-gpio failed %d\n", pcm_dev->irq_info.gpio); skip_interrupt: ret = devm_snd_soc_register_component(&i2c->dev, &soc_codec_driver_pcmdevice, pcmdevice_dai_driver, ARRAY_SIZE(pcmdevice_dai_driver)); if (ret < 0) dev_err(&i2c->dev, "probe register comp failed %d\n", ret); out: if (ret < 0) pcmdevice_remove(pcm_dev); return ret; } static void pcmdevice_i2c_remove(struct i2c_client *i2c) { struct pcmdevice_priv *pcm_dev = i2c_get_clientdata(i2c); pcmdevice_remove(pcm_dev); } static struct i2c_driver pcmdevice_i2c_driver = { .driver = { .name = "pcmdevice-codec", .of_match_table = of_match_ptr(pcmdevice_of_match), }, .probe = pcmdevice_i2c_probe, .remove = pcmdevice_i2c_remove, .id_table = pcmdevice_i2c_id, }; module_i2c_driver(pcmdevice_i2c_driver); MODULE_AUTHOR("Shenghao Ding "); MODULE_DESCRIPTION("ASoC PCM6240 Family Audio ADC/DAC Driver"); MODULE_LICENSE("GPL");