summaryrefslogtreecommitdiff
path: root/sound/soc/codecs/tas2552.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/codecs/tas2552.c')
-rw-r--r--sound/soc/codecs/tas2552.c182
1 files changed, 118 insertions, 64 deletions
diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c
index 891e2c529df3..4f25a7d0efa2 100644
--- a/sound/soc/codecs/tas2552.c
+++ b/sound/soc/codecs/tas2552.c
@@ -45,7 +45,7 @@ static struct reg_default tas2552_reg_defs[] = {
{TAS2552_OUTPUT_DATA, 0xc0},
{TAS2552_PDM_CFG, 0x01},
{TAS2552_PGA_GAIN, 0x00},
- {TAS2552_BOOST_PT_CTRL, 0x0f},
+ {TAS2552_BOOST_APT_CTRL, 0x0f},
{TAS2552_RESERVED_0D, 0xbe},
{TAS2552_LIMIT_RATE_HYS, 0x08},
{TAS2552_CFG_2, 0xef},
@@ -77,7 +77,9 @@ struct tas2552_data {
struct gpio_desc *enable_gpio;
unsigned char regs[TAS2552_VBAT_DATA];
unsigned int pll_clkin;
+ int pll_clk_id;
unsigned int pdm_clk;
+ int pdm_clk_id;
unsigned int dai_fmt;
unsigned int tdm_delay;
@@ -143,31 +145,105 @@ static const struct snd_soc_dapm_route tas2552_audio_map[] = {
};
#ifdef CONFIG_PM
-static void tas2552_sw_shutdown(struct tas2552_data *tas_data, int sw_shutdown)
+static void tas2552_sw_shutdown(struct tas2552_data *tas2552, int sw_shutdown)
{
u8 cfg1_reg = 0;
- if (!tas_data->codec)
+ if (!tas2552->codec)
return;
if (sw_shutdown)
cfg1_reg = TAS2552_SWS;
- snd_soc_update_bits(tas_data->codec, TAS2552_CFG_1, TAS2552_SWS,
+ snd_soc_update_bits(tas2552->codec, TAS2552_CFG_1, TAS2552_SWS,
cfg1_reg);
}
#endif
+static int tas2552_setup_pll(struct snd_soc_codec *codec,
+ struct snd_pcm_hw_params *params)
+{
+ struct tas2552_data *tas2552 = dev_get_drvdata(codec->dev);
+ bool bypass_pll = false;
+ unsigned int pll_clk = params_rate(params) * 512;
+ unsigned int pll_clkin = tas2552->pll_clkin;
+ u8 pll_enable;
+
+ if (!pll_clkin) {
+ if (tas2552->pll_clk_id != TAS2552_PLL_CLKIN_BCLK)
+ return -EINVAL;
+
+ pll_clkin = snd_soc_params_to_bclk(params);
+ pll_clkin += tas2552->tdm_delay;
+ }
+
+ pll_enable = snd_soc_read(codec, TAS2552_CFG_2) & TAS2552_PLL_ENABLE;
+ snd_soc_update_bits(codec, TAS2552_CFG_2, TAS2552_PLL_ENABLE, 0);
+
+ if (pll_clkin == pll_clk)
+ bypass_pll = true;
+
+ if (bypass_pll) {
+ /* By pass the PLL configuration */
+ snd_soc_update_bits(codec, TAS2552_PLL_CTRL_2,
+ TAS2552_PLL_BYPASS, TAS2552_PLL_BYPASS);
+ } else {
+ /* Fill in the PLL control registers for J & D
+ * pll_clk = (.5 * pll_clkin * J.D) / 2^p
+ * Need to fill in J and D here based on incoming freq
+ */
+ unsigned int d;
+ u8 j;
+ u8 pll_sel = (tas2552->pll_clk_id << 3) & TAS2552_PLL_SRC_MASK;
+ u8 p = snd_soc_read(codec, TAS2552_PLL_CTRL_1);
+
+ p = (p >> 7);
+
+recalc:
+ j = (pll_clk * 2 * (1 << p)) / pll_clkin;
+ d = (pll_clk * 2 * (1 << p)) % pll_clkin;
+ d /= (pll_clkin / 10000);
+
+ if (d && (pll_clkin < 512000 || pll_clkin > 9200000)) {
+ if (tas2552->pll_clk_id == TAS2552_PLL_CLKIN_BCLK) {
+ pll_clkin = 1800000;
+ pll_sel = (TAS2552_PLL_CLKIN_1_8_FIXED << 3) &
+ TAS2552_PLL_SRC_MASK;
+ } else {
+ pll_clkin = snd_soc_params_to_bclk(params);
+ pll_clkin += tas2552->tdm_delay;
+ pll_sel = (TAS2552_PLL_CLKIN_BCLK << 3) &
+ TAS2552_PLL_SRC_MASK;
+ }
+ goto recalc;
+ }
+
+ snd_soc_update_bits(codec, TAS2552_CFG_1, TAS2552_PLL_SRC_MASK,
+ pll_sel);
+
+ snd_soc_update_bits(codec, TAS2552_PLL_CTRL_1,
+ TAS2552_PLL_J_MASK, j);
+ /* Will clear the PLL_BYPASS bit */
+ snd_soc_write(codec, TAS2552_PLL_CTRL_2,
+ TAS2552_PLL_D_UPPER(d));
+ snd_soc_write(codec, TAS2552_PLL_CTRL_3,
+ TAS2552_PLL_D_LOWER(d));
+ }
+
+ /* Restore PLL status */
+ snd_soc_update_bits(codec, TAS2552_CFG_2, TAS2552_PLL_ENABLE,
+ pll_enable);
+
+ return 0;
+}
+
static int tas2552_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 tas2552_data *tas2552 = dev_get_drvdata(codec->dev);
- int sample_rate, pll_clk;
- int d;
int cpf;
- u8 p, j;
u8 ser_ctrl1_reg, wclk_rate;
switch (params_width(params)) {
@@ -245,49 +321,7 @@ static int tas2552_hw_params(struct snd_pcm_substream *substream,
snd_soc_update_bits(codec, TAS2552_CFG_3, TAS2552_WCLK_FREQ_MASK,
wclk_rate);
- if (!tas2552->pll_clkin)
- return -EINVAL;
-
- snd_soc_update_bits(codec, TAS2552_CFG_2, TAS2552_PLL_ENABLE, 0);
-
- if (tas2552->pll_clkin == TAS2552_245MHZ_CLK ||
- tas2552->pll_clkin == TAS2552_225MHZ_CLK) {
- /* By pass the PLL configuration */
- snd_soc_update_bits(codec, TAS2552_PLL_CTRL_2,
- TAS2552_PLL_BYPASS_MASK,
- TAS2552_PLL_BYPASS);
- } else {
- /* Fill in the PLL control registers for J & D
- * PLL_CLK = (.5 * freq * J.D) / 2^p
- * Need to fill in J and D here based on incoming freq
- */
- p = snd_soc_read(codec, TAS2552_PLL_CTRL_1);
- p = (p >> 7);
- sample_rate = params_rate(params);
-
- if (sample_rate == 48000)
- pll_clk = TAS2552_245MHZ_CLK;
- else if (sample_rate == 44100)
- pll_clk = TAS2552_225MHZ_CLK;
- else {
- dev_vdbg(codec->dev, "Substream sample rate is not found %i\n",
- params_rate(params));
- return -EINVAL;
- }
-
- j = (pll_clk * 2 * (1 << p)) / tas2552->pll_clkin;
- d = (pll_clk * 2 * (1 << p)) % tas2552->pll_clkin;
-
- snd_soc_update_bits(codec, TAS2552_PLL_CTRL_1,
- TAS2552_PLL_J_MASK, j);
- snd_soc_write(codec, TAS2552_PLL_CTRL_2,
- (d >> 7) & TAS2552_PLL_D_UPPER_MASK);
- snd_soc_write(codec, TAS2552_PLL_CTRL_3,
- d & TAS2552_PLL_D_LOWER_MASK);
-
- }
-
- return 0;
+ return tas2552_setup_pll(codec, params);
}
#define TAS2552_DAI_FMT_MASK (TAS2552_BCLKDIR | \
@@ -370,12 +404,21 @@ static int tas2552_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
switch (clk_id) {
case TAS2552_PLL_CLKIN_MCLK:
- case TAS2552_PLL_CLKIN_BCLK:
case TAS2552_PLL_CLKIN_IVCLKIN:
+ if (freq < 512000 || freq > 24576000) {
+ /* out of range PLL_CLKIN, fall back to use BCLK */
+ dev_warn(codec->dev, "Out of range PLL_CLKIN: %u\n",
+ freq);
+ clk_id = TAS2552_PLL_CLKIN_BCLK;
+ freq = 0;
+ }
+ /* fall through */
+ case TAS2552_PLL_CLKIN_BCLK:
case TAS2552_PLL_CLKIN_1_8_FIXED:
mask = TAS2552_PLL_SRC_MASK;
val = (clk_id << 3) & mask; /* bit 4:5 in the register */
reg = TAS2552_CFG_1;
+ tas2552->pll_clk_id = clk_id;
tas2552->pll_clkin = freq;
break;
case TAS2552_PDM_CLK_PLL:
@@ -385,6 +428,7 @@ static int tas2552_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
mask = TAS2552_PDM_CLK_SEL_MASK;
val = (clk_id >> 1) & mask; /* bit 0:1 in the register */
reg = TAS2552_PDM_CFG;
+ tas2552->pdm_clk_id = clk_id;
tas2552->pdm_clk = freq;
break;
default:
@@ -509,9 +553,20 @@ static struct snd_soc_dai_driver tas2552_dai[] = {
*/
static DECLARE_TLV_DB_SCALE(dac_tlv, -7, 100, 0);
+static const char * const tas2552_din_source_select[] = {
+ "Muted",
+ "Left",
+ "Right",
+ "Left + Right average",
+};
+static SOC_ENUM_SINGLE_DECL(tas2552_din_source_enum,
+ TAS2552_CFG_3, 3,
+ tas2552_din_source_select);
+
static const struct snd_kcontrol_new tas2552_snd_controls[] = {
SOC_SINGLE_TLV("Speaker Driver Playback Volume",
TAS2552_PGA_GAIN, 0, 0x1f, 0, dac_tlv),
+ SOC_ENUM("DIN source", tas2552_din_source_enum),
};
static int tas2552_codec_probe(struct snd_soc_codec *codec)
@@ -543,13 +598,14 @@ static int tas2552_codec_probe(struct snd_soc_codec *codec)
snd_soc_update_bits(codec, TAS2552_CFG_1, TAS2552_MUTE, TAS2552_MUTE);
snd_soc_write(codec, TAS2552_CFG_3, TAS2552_I2S_OUT_SEL |
TAS2552_DIN_SRC_SEL_AVG_L_R);
- snd_soc_write(codec, TAS2552_DOUT, TAS2552_PDM_DATA_I);
- snd_soc_write(codec, TAS2552_OUTPUT_DATA, TAS2552_PDM_DATA_V_I | 0x8);
- snd_soc_write(codec, TAS2552_BOOST_PT_CTRL, TAS2552_APT_DELAY_200 |
- TAS2552_APT_THRESH_2_1_7);
+ snd_soc_write(codec, TAS2552_OUTPUT_DATA,
+ TAS2552_PDM_DATA_SEL_V_I |
+ TAS2552_R_DATA_OUT(TAS2552_DATA_OUT_V_DATA));
+ snd_soc_write(codec, TAS2552_BOOST_APT_CTRL, TAS2552_APT_DELAY_200 |
+ TAS2552_APT_THRESH_20_17);
- snd_soc_write(codec, TAS2552_CFG_2, TAS2552_BOOST_EN |
- TAS2552_APT_EN | TAS2552_LIM_EN);
+ snd_soc_write(codec, TAS2552_CFG_2, TAS2552_BOOST_EN | TAS2552_APT_EN |
+ TAS2552_LIM_EN);
return 0;
@@ -647,13 +703,10 @@ static int tas2552_probe(struct i2c_client *client,
if (data == NULL)
return -ENOMEM;
- data->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
- if (IS_ERR(data->enable_gpio)) {
- if (PTR_ERR(data->enable_gpio) == -EPROBE_DEFER)
- return -EPROBE_DEFER;
-
- data->enable_gpio = NULL;;
- }
+ data->enable_gpio = devm_gpiod_get_optional(dev, "enable",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(data->enable_gpio))
+ return PTR_ERR(data->enable_gpio);
data->tas2552_client = client;
data->regmap = devm_regmap_init_i2c(client, &tas2552_regmap_config);
@@ -695,6 +748,7 @@ static int tas2552_probe(struct i2c_client *client,
static int tas2552_i2c_remove(struct i2c_client *client)
{
snd_soc_unregister_codec(&client->dev);
+ pm_runtime_disable(&client->dev);
return 0;
}