diff options
Diffstat (limited to 'sound/soc/codecs/tas2552.c')
-rw-r--r-- | sound/soc/codecs/tas2552.c | 182 |
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; } |