@@ -121,16 +121,18 @@ struct sun8i_codec_quirks {
bool lrck_inversion : 1;
};
struct sun8i_codec {
struct regmap *regmap;
struct clk *clk_module;
const struct sun8i_codec_quirks *quirks;
struct sun8i_codec_aif aifs[SUN8I_CODEC_NAIFS];
+ unsigned int sysclk_rate;
+ int sysclk_refcnt;
};
static int sun8i_codec_runtime_resume(struct device *dev)
{
struct sun8i_codec *scodec = dev_get_drvdata(dev);
int ret;
regcache_cache_only(scodec->regmap, false);
@@ -319,16 +321,57 @@ static int sun8i_codec_set_tdm_slot(struct snd_soc_dai *dai,
return -EINVAL;
aif->slots = slots;
aif->slot_width = slot_width;
return 0;
}
+static const unsigned int sun8i_codec_rates[] = {
+ 7350, 8000, 11025, 12000, 14700, 16000, 22050, 24000,
+ 29400, 32000, 44100, 48000, 88200, 96000, 176400, 192000,
+};
+
+static const struct snd_pcm_hw_constraint_list sun8i_codec_all_rates = {
+ .list = sun8i_codec_rates,
+ .count = ARRAY_SIZE(sun8i_codec_rates),
+};
+
+static const struct snd_pcm_hw_constraint_list sun8i_codec_22M_rates = {
+ .list = sun8i_codec_rates,
+ .count = ARRAY_SIZE(sun8i_codec_rates),
+ .mask = 0x5555,
+};
+
+static const struct snd_pcm_hw_constraint_list sun8i_codec_24M_rates = {
+ .list = sun8i_codec_rates,
+ .count = ARRAY_SIZE(sun8i_codec_rates),
+ .mask = 0xaaaa,
+};
+
+static int sun8i_codec_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sun8i_codec *scodec = snd_soc_dai_get_drvdata(dai);
+ const struct snd_pcm_hw_constraint_list *list;
+
+ if (!scodec->sysclk_refcnt)
+ list = &sun8i_codec_all_rates;
+ else if (scodec->sysclk_rate == 22579200)
+ list = &sun8i_codec_22M_rates;
+ else if (scodec->sysclk_rate == 24576000)
+ list = &sun8i_codec_24M_rates;
+ else
+ return -EINVAL;
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, list);
+}
+
struct sun8i_codec_clk_div {
u8 div;
u8 val;
};
static const struct sun8i_codec_clk_div sun8i_codec_bclk_div[] = {
{ .div = 1, .val = 0 },
{ .div = 2, .val = 1 },
@@ -341,22 +384,21 @@ static const struct sun8i_codec_clk_div sun8i_codec_bclk_div[] = {
{ .div = 32, .val = 8 },
{ .div = 48, .val = 9 },
{ .div = 64, .val = 10 },
{ .div = 96, .val = 11 },
{ .div = 128, .val = 12 },
{ .div = 192, .val = 13 },
};
-static u8 sun8i_codec_get_bclk_div(struct sun8i_codec *scodec,
+static u8 sun8i_codec_get_bclk_div(unsigned int sysclk_rate,
unsigned int lrck_div_order,
unsigned int sample_rate)
{
- unsigned long clk_rate = clk_get_rate(scodec->clk_module);
- unsigned int div = clk_rate / sample_rate >> lrck_div_order;
+ unsigned int div = sysclk_rate / sample_rate >> lrck_div_order;
unsigned int best_val = 0, best_diff = ~0;
int i;
for (i = 0; i < ARRAY_SIZE(sun8i_codec_bclk_div); i++) {
const struct sun8i_codec_clk_div *bdiv = &sun8i_codec_bclk_div[i];
unsigned int diff = abs(bdiv->div - div);
if (diff < best_diff) {
@@ -383,16 +425,17 @@ static int sun8i_codec_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct sun8i_codec *scodec = snd_soc_dai_get_drvdata(dai);
struct sun8i_codec_aif *aif = &scodec->aifs[dai->id];
unsigned int sample_rate = params_rate(params);
unsigned int slots = aif->slots ?: params_channels(params);
unsigned int slot_width = aif->slot_width ?: params_width(params);
+ unsigned int sysclk_rate = clk_get_rate(scodec->clk_module);
int lrck_div_order, word_size;
u8 bclk_div;
/* word size */
switch (params_width(params)) {
case 8:
word_size = 0x0;
break;
@@ -418,46 +461,52 @@ static int sun8i_codec_hw_params(struct snd_pcm_substream *substream,
if (lrck_div_order < 0)
return lrck_div_order;
regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL,
SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_MASK,
(lrck_div_order - 4) << SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV);
/* BCLK divider (SYSCLK/BCLK ratio) */
- bclk_div = sun8i_codec_get_bclk_div(scodec, lrck_div_order, sample_rate);
+ bclk_div = sun8i_codec_get_bclk_div(sysclk_rate, lrck_div_order, sample_rate);
regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL,
SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV_MASK,
bclk_div << SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV);
+ if (!aif->open_streams)
+ scodec->sysclk_refcnt++;
+ scodec->sysclk_rate = sysclk_rate;
+
aif->sample_rate = sample_rate;
aif->open_streams |= BIT(substream->stream);
return sun8i_codec_update_sample_rate(scodec);
}
static int sun8i_codec_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct sun8i_codec *scodec = snd_soc_dai_get_drvdata(dai);
struct sun8i_codec_aif *aif = &scodec->aifs[dai->id];
if (aif->open_streams != BIT(substream->stream))
goto done;
+ scodec->sysclk_refcnt--;
aif->sample_rate = 0;
done:
aif->open_streams &= ~BIT(substream->stream);
return 0;
}
static const struct snd_soc_dai_ops sun8i_codec_dai_ops = {
.set_fmt = sun8i_codec_set_fmt,
.set_tdm_slot = sun8i_codec_set_tdm_slot,
+ .startup = sun8i_codec_startup,
.hw_params = sun8i_codec_hw_params,
.hw_free = sun8i_codec_hw_free,
};
static struct snd_soc_dai_driver sun8i_codec_dais[] = {
{
.name = "sun8i-codec-aif1",
.id = SUN8I_CODEC_AIF1,