332 lines · c
1// SPDX-License-Identifier: GPL-2.02//3// Copyright (c) 2020 BayLibre, SAS.4// Author: Jerome Brunet <jbrunet@baylibre.com>5 6#include <linux/bitfield.h>7#include <linux/clk.h>8#include <sound/pcm_params.h>9#include <sound/soc.h>10#include <sound/soc-dai.h>11 12#include "aiu.h"13 14#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0)15#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5)16#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9)17#define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11)18#define AIU_RST_SOFT_I2S_FAST BIT(0)19 20#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2)21#define AIU_CLK_CTRL_I2S_DIV_EN BIT(0)22#define AIU_CLK_CTRL_I2S_DIV GENMASK(3, 2)23#define AIU_CLK_CTRL_AOCLK_INVERT BIT(6)24#define AIU_CLK_CTRL_LRCLK_INVERT BIT(7)25#define AIU_CLK_CTRL_LRCLK_SKEW GENMASK(9, 8)26#define AIU_CLK_CTRL_MORE_HDMI_AMCLK BIT(6)27#define AIU_CLK_CTRL_MORE_I2S_DIV GENMASK(5, 0)28#define AIU_CODEC_DAC_LRCLK_CTRL_DIV GENMASK(11, 0)29 30static void aiu_encoder_i2s_divider_enable(struct snd_soc_component *component,31 bool enable)32{33 snd_soc_component_update_bits(component, AIU_CLK_CTRL,34 AIU_CLK_CTRL_I2S_DIV_EN,35 enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0);36}37 38static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component,39 struct snd_pcm_hw_params *params)40{41 /* Always operate in split (classic interleaved) mode */42 unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT;43 44 /* Reset required to update the pipeline */45 snd_soc_component_write(component, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST);46 snd_soc_component_read(component, AIU_I2S_SYNC);47 48 switch (params_physical_width(params)) {49 case 16: /* Nothing to do */50 break;51 52 case 32:53 desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT |54 AIU_I2S_SOURCE_DESC_MODE_32BIT);55 break;56 57 default:58 return -EINVAL;59 }60 61 switch (params_channels(params)) {62 case 2: /* Nothing to do */63 break;64 case 8:65 desc |= AIU_I2S_SOURCE_DESC_MODE_8CH;66 break;67 default:68 return -EINVAL;69 }70 71 snd_soc_component_update_bits(component, AIU_I2S_SOURCE_DESC,72 AIU_I2S_SOURCE_DESC_MODE_8CH |73 AIU_I2S_SOURCE_DESC_MODE_24BIT |74 AIU_I2S_SOURCE_DESC_MODE_32BIT |75 AIU_I2S_SOURCE_DESC_MODE_SPLIT,76 desc);77 78 return 0;79}80 81static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component,82 struct snd_pcm_hw_params *params,83 unsigned int bs)84{85 switch (bs) {86 case 1:87 case 2:88 case 4:89 case 8:90 /* These are the only valid legacy dividers */91 break;92 93 default:94 dev_err(component->dev, "Unsupported i2s divider: %u\n", bs);95 return -EINVAL;96 }97 98 snd_soc_component_update_bits(component, AIU_CLK_CTRL,99 AIU_CLK_CTRL_I2S_DIV,100 FIELD_PREP(AIU_CLK_CTRL_I2S_DIV,101 __ffs(bs)));102 103 snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,104 AIU_CLK_CTRL_MORE_I2S_DIV,105 FIELD_PREP(AIU_CLK_CTRL_MORE_I2S_DIV,106 0));107 108 return 0;109}110 111static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component,112 struct snd_pcm_hw_params *params,113 unsigned int bs)114{115 /*116 * NOTE: this HW is odd.117 * In most configuration, the i2s divider is 'mclk / blck'.118 * However, in 16 bits - 8ch mode, this factor needs to be119 * increased by 50% to get the correct output rate.120 * No idea why !121 */122 if (params_width(params) == 16 && params_channels(params) == 8) {123 if (bs % 2) {124 dev_err(component->dev,125 "Cannot increase i2s divider by 50%%\n");126 return -EINVAL;127 }128 bs += bs / 2;129 }130 131 /* Use CLK_MORE for mclk to bclk divider */132 snd_soc_component_update_bits(component, AIU_CLK_CTRL,133 AIU_CLK_CTRL_I2S_DIV,134 FIELD_PREP(AIU_CLK_CTRL_I2S_DIV, 0));135 136 snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,137 AIU_CLK_CTRL_MORE_I2S_DIV,138 FIELD_PREP(AIU_CLK_CTRL_MORE_I2S_DIV,139 bs - 1));140 141 return 0;142}143 144static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component,145 struct snd_pcm_hw_params *params)146{147 struct aiu *aiu = snd_soc_component_get_drvdata(component);148 unsigned int srate = params_rate(params);149 unsigned int fs, bs;150 int ret;151 152 /* Get the oversampling factor */153 fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate);154 155 if (fs % 64)156 return -EINVAL;157 158 /* Send data MSB first */159 snd_soc_component_update_bits(component, AIU_I2S_DAC_CFG,160 AIU_I2S_DAC_CFG_MSB_FIRST,161 AIU_I2S_DAC_CFG_MSB_FIRST);162 163 /* Set bclk to lrlck ratio */164 snd_soc_component_update_bits(component, AIU_CODEC_DAC_LRCLK_CTRL,165 AIU_CODEC_DAC_LRCLK_CTRL_DIV,166 FIELD_PREP(AIU_CODEC_DAC_LRCLK_CTRL_DIV,167 64 - 1));168 169 bs = fs / 64;170 171 if (aiu->platform->has_clk_ctrl_more_i2s_div)172 ret = aiu_encoder_i2s_set_more_div(component, params, bs);173 else174 ret = aiu_encoder_i2s_set_legacy_div(component, params, bs);175 176 if (ret)177 return ret;178 179 /* Make sure amclk is used for HDMI i2s as well */180 snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,181 AIU_CLK_CTRL_MORE_HDMI_AMCLK,182 AIU_CLK_CTRL_MORE_HDMI_AMCLK);183 184 return 0;185}186 187static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream,188 struct snd_pcm_hw_params *params,189 struct snd_soc_dai *dai)190{191 struct snd_soc_component *component = dai->component;192 int ret;193 194 /* Disable the clock while changing the settings */195 aiu_encoder_i2s_divider_enable(component, false);196 197 ret = aiu_encoder_i2s_setup_desc(component, params);198 if (ret) {199 dev_err(dai->dev, "setting i2s desc failed\n");200 return ret;201 }202 203 ret = aiu_encoder_i2s_set_clocks(component, params);204 if (ret) {205 dev_err(dai->dev, "setting i2s clocks failed\n");206 return ret;207 }208 209 aiu_encoder_i2s_divider_enable(component, true);210 211 return 0;212}213 214static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream,215 struct snd_soc_dai *dai)216{217 struct snd_soc_component *component = dai->component;218 219 aiu_encoder_i2s_divider_enable(component, false);220 221 return 0;222}223 224static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)225{226 struct snd_soc_component *component = dai->component;227 unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK;228 unsigned int val = 0;229 unsigned int skew;230 231 /* Only CPU Master / Codec Slave supported ATM */232 if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_BP_FP)233 return -EINVAL;234 235 if (inv == SND_SOC_DAIFMT_NB_IF ||236 inv == SND_SOC_DAIFMT_IB_IF)237 val |= AIU_CLK_CTRL_LRCLK_INVERT;238 239 if (inv == SND_SOC_DAIFMT_IB_NF ||240 inv == SND_SOC_DAIFMT_IB_IF)241 val |= AIU_CLK_CTRL_AOCLK_INVERT;242 243 /* Signal skew */244 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {245 case SND_SOC_DAIFMT_I2S:246 /* Invert sample clock for i2s */247 val ^= AIU_CLK_CTRL_LRCLK_INVERT;248 skew = 1;249 break;250 case SND_SOC_DAIFMT_LEFT_J:251 skew = 0;252 break;253 default:254 return -EINVAL;255 }256 257 val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew);258 snd_soc_component_update_bits(component, AIU_CLK_CTRL,259 AIU_CLK_CTRL_LRCLK_INVERT |260 AIU_CLK_CTRL_AOCLK_INVERT |261 AIU_CLK_CTRL_LRCLK_SKEW,262 val);263 264 return 0;265}266 267static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,268 unsigned int freq, int dir)269{270 struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);271 int ret;272 273 if (WARN_ON(clk_id != 0))274 return -EINVAL;275 276 if (dir == SND_SOC_CLOCK_IN)277 return 0;278 279 ret = clk_set_rate(aiu->i2s.clks[MCLK].clk, freq);280 if (ret)281 dev_err(dai->dev, "Failed to set sysclk to %uHz", freq);282 283 return ret;284}285 286static const unsigned int hw_channels[] = {2, 8};287static const struct snd_pcm_hw_constraint_list hw_channel_constraints = {288 .list = hw_channels,289 .count = ARRAY_SIZE(hw_channels),290 .mask = 0,291};292 293static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream,294 struct snd_soc_dai *dai)295{296 struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);297 int ret;298 299 /* Make sure the encoder gets either 2 or 8 channels */300 ret = snd_pcm_hw_constraint_list(substream->runtime, 0,301 SNDRV_PCM_HW_PARAM_CHANNELS,302 &hw_channel_constraints);303 if (ret) {304 dev_err(dai->dev, "adding channels constraints failed\n");305 return ret;306 }307 308 ret = clk_bulk_prepare_enable(aiu->i2s.clk_num, aiu->i2s.clks);309 if (ret)310 dev_err(dai->dev, "failed to enable i2s clocks\n");311 312 return ret;313}314 315static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream,316 struct snd_soc_dai *dai)317{318 struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);319 320 clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks);321}322 323const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = {324 .hw_params = aiu_encoder_i2s_hw_params,325 .hw_free = aiu_encoder_i2s_hw_free,326 .set_fmt = aiu_encoder_i2s_set_fmt,327 .set_sysclk = aiu_encoder_i2s_set_sysclk,328 .startup = aiu_encoder_i2s_startup,329 .shutdown = aiu_encoder_i2s_shutdown,330};331 332