1 // SPDX-License-Identifier: GPL-2.0 2 // 3 // CS40L50 Advanced Haptic Driver with waveform memory, 4 // integrated DSP, and closed-loop algorithms 5 // 6 // Copyright 2024 Cirrus Logic, Inc. 7 // 8 // Author: James Ogletree <james.ogletree@cirrus.com> 9 10 #include <linux/bitfield.h> 11 #include <linux/mfd/cs40l50.h> 12 #include <sound/pcm_params.h> 13 #include <sound/soc.h> 14 15 #define CS40L50_REFCLK_INPUT 0x2C04 16 #define CS40L50_ASP_CONTROL2 0x4808 17 #define CS40L50_ASP_DATA_CONTROL5 0x4840 18 19 /* PLL Config */ 20 #define CS40L50_PLL_REFCLK_BCLK 0x0 21 #define CS40L50_PLL_REFCLK_MCLK 0x5 22 #define CS40L50_PLL_REEFCLK_MCLK_CFG 0x00 23 #define CS40L50_PLL_REFCLK_LOOP_MASK BIT(11) 24 #define CS40L50_PLL_REFCLK_OPEN_LOOP 1 25 #define CS40L50_PLL_REFCLK_CLOSED_LOOP 0 26 #define CS40L50_PLL_REFCLK_LOOP_SHIFT 11 27 #define CS40L50_PLL_REFCLK_FREQ_MASK GENMASK(10, 5) 28 #define CS40L50_PLL_REFCLK_FREQ_SHIFT 5 29 #define CS40L50_PLL_REFCLK_SEL_MASK GENMASK(2, 0) 30 #define CS40L50_BCLK_RATIO_DEFAULT 32 31 32 /* ASP Config */ 33 #define CS40L50_ASP_RX_WIDTH_SHIFT 24 34 #define CS40L50_ASP_RX_WIDTH_MASK GENMASK(31, 24) 35 #define CS40L50_ASP_RX_WL_MASK GENMASK(5, 0) 36 #define CS40L50_ASP_FSYNC_INV_MASK BIT(2) 37 #define CS40L50_ASP_BCLK_INV_MASK BIT(6) 38 #define CS40L50_ASP_FMT_MASK GENMASK(10, 8) 39 #define CS40L50_ASP_FMT_I2S 0x2 40 41 struct cs40l50_pll_config { 42 unsigned int freq; 43 unsigned int cfg; 44 }; 45 46 struct cs40l50_codec { 47 struct device *dev; 48 struct regmap *regmap; 49 unsigned int daifmt; 50 unsigned int bclk_ratio; 51 unsigned int rate; 52 }; 53 54 static const struct cs40l50_pll_config cs40l50_pll_cfg[] = { 55 { 32768, 0x00 }, 56 { 1536000, 0x1B }, 57 { 3072000, 0x21 }, 58 { 6144000, 0x28 }, 59 { 9600000, 0x30 }, 60 { 12288000, 0x33 }, 61 }; 62 63 static int cs40l50_get_clk_config(const unsigned int freq, unsigned int *cfg) 64 { 65 int i; 66 67 for (i = 0; i < ARRAY_SIZE(cs40l50_pll_cfg); i++) { 68 if (cs40l50_pll_cfg[i].freq == freq) { 69 *cfg = cs40l50_pll_cfg[i].cfg; 70 return 0; 71 } 72 } 73 74 return -EINVAL; 75 } 76 77 static int cs40l50_swap_ext_clk(struct cs40l50_codec *codec, const unsigned int clk_src) 78 { 79 unsigned int cfg; 80 int ret; 81 82 switch (clk_src) { 83 case CS40L50_PLL_REFCLK_BCLK: 84 ret = cs40l50_get_clk_config(codec->bclk_ratio * codec->rate, &cfg); 85 if (ret) 86 return ret; 87 break; 88 case CS40L50_PLL_REFCLK_MCLK: 89 cfg = CS40L50_PLL_REEFCLK_MCLK_CFG; 90 break; 91 default: 92 return -EINVAL; 93 } 94 95 ret = regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT, 96 CS40L50_PLL_REFCLK_LOOP_MASK, 97 CS40L50_PLL_REFCLK_OPEN_LOOP << 98 CS40L50_PLL_REFCLK_LOOP_SHIFT); 99 if (ret) 100 return ret; 101 102 ret = regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT, 103 CS40L50_PLL_REFCLK_FREQ_MASK | 104 CS40L50_PLL_REFCLK_SEL_MASK, 105 (cfg << CS40L50_PLL_REFCLK_FREQ_SHIFT) | clk_src); 106 if (ret) 107 return ret; 108 109 return regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT, 110 CS40L50_PLL_REFCLK_LOOP_MASK, 111 CS40L50_PLL_REFCLK_CLOSED_LOOP << 112 CS40L50_PLL_REFCLK_LOOP_SHIFT); 113 } 114 115 static int cs40l50_clk_en(struct snd_soc_dapm_widget *w, 116 struct snd_kcontrol *kcontrol, 117 int event) 118 { 119 struct snd_soc_component *comp = snd_soc_dapm_to_component(w->dapm); 120 struct cs40l50_codec *codec = snd_soc_component_get_drvdata(comp); 121 int ret; 122 123 switch (event) { 124 case SND_SOC_DAPM_POST_PMU: 125 ret = cs40l50_dsp_write(codec->dev, codec->regmap, CS40L50_STOP_PLAYBACK); 126 if (ret) 127 return ret; 128 129 ret = cs40l50_dsp_write(codec->dev, codec->regmap, CS40L50_START_I2S); 130 if (ret) 131 return ret; 132 133 ret = cs40l50_swap_ext_clk(codec, CS40L50_PLL_REFCLK_BCLK); 134 if (ret) 135 return ret; 136 break; 137 case SND_SOC_DAPM_PRE_PMD: 138 ret = cs40l50_swap_ext_clk(codec, CS40L50_PLL_REFCLK_MCLK); 139 if (ret) 140 return ret; 141 break; 142 default: 143 return -EINVAL; 144 } 145 146 return 0; 147 } 148 149 static const struct snd_soc_dapm_widget cs40l50_dapm_widgets[] = { 150 SND_SOC_DAPM_SUPPLY_S("ASP PLL", 0, SND_SOC_NOPM, 0, 0, cs40l50_clk_en, 151 SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), 152 SND_SOC_DAPM_AIF_IN("ASPRX1", NULL, 0, SND_SOC_NOPM, 0, 0), 153 SND_SOC_DAPM_AIF_IN("ASPRX2", NULL, 0, SND_SOC_NOPM, 0, 0), 154 SND_SOC_DAPM_OUTPUT("OUT"), 155 }; 156 157 static const struct snd_soc_dapm_route cs40l50_dapm_routes[] = { 158 { "ASP Playback", NULL, "ASP PLL" }, 159 { "ASPRX1", NULL, "ASP Playback" }, 160 { "ASPRX2", NULL, "ASP Playback" }, 161 162 { "OUT", NULL, "ASPRX1" }, 163 { "OUT", NULL, "ASPRX2" }, 164 }; 165 166 static int cs40l50_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) 167 { 168 struct cs40l50_codec *codec = snd_soc_component_get_drvdata(codec_dai->component); 169 170 if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBC_CFC) 171 return -EINVAL; 172 173 switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 174 case SND_SOC_DAIFMT_NB_NF: 175 codec->daifmt = 0; 176 break; 177 case SND_SOC_DAIFMT_NB_IF: 178 codec->daifmt = CS40L50_ASP_FSYNC_INV_MASK; 179 break; 180 case SND_SOC_DAIFMT_IB_NF: 181 codec->daifmt = CS40L50_ASP_BCLK_INV_MASK; 182 break; 183 case SND_SOC_DAIFMT_IB_IF: 184 codec->daifmt = CS40L50_ASP_FSYNC_INV_MASK | CS40L50_ASP_BCLK_INV_MASK; 185 break; 186 default: 187 dev_err(codec->dev, "Invalid clock invert\n"); 188 return -EINVAL; 189 } 190 191 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 192 case SND_SOC_DAIFMT_I2S: 193 codec->daifmt |= FIELD_PREP(CS40L50_ASP_FMT_MASK, CS40L50_ASP_FMT_I2S); 194 break; 195 default: 196 dev_err(codec->dev, "Unsupported DAI format\n"); 197 return -EINVAL; 198 } 199 200 return 0; 201 } 202 203 static int cs40l50_hw_params(struct snd_pcm_substream *substream, 204 struct snd_pcm_hw_params *params, 205 struct snd_soc_dai *dai) 206 { 207 struct cs40l50_codec *codec = snd_soc_component_get_drvdata(dai->component); 208 unsigned int asp_rx_wl = params_width(params); 209 int ret; 210 211 codec->rate = params_rate(params); 212 213 ret = regmap_update_bits(codec->regmap, CS40L50_ASP_DATA_CONTROL5, 214 CS40L50_ASP_RX_WL_MASK, asp_rx_wl); 215 if (ret) 216 return ret; 217 218 codec->daifmt |= (asp_rx_wl << CS40L50_ASP_RX_WIDTH_SHIFT); 219 220 return regmap_update_bits(codec->regmap, CS40L50_ASP_CONTROL2, 221 CS40L50_ASP_FSYNC_INV_MASK | 222 CS40L50_ASP_BCLK_INV_MASK | 223 CS40L50_ASP_FMT_MASK | 224 CS40L50_ASP_RX_WIDTH_MASK, codec->daifmt); 225 } 226 227 static int cs40l50_set_dai_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) 228 { 229 struct cs40l50_codec *codec = snd_soc_component_get_drvdata(dai->component); 230 231 codec->bclk_ratio = ratio; 232 233 return 0; 234 } 235 236 static const struct snd_soc_dai_ops cs40l50_dai_ops = { 237 .set_fmt = cs40l50_set_dai_fmt, 238 .set_bclk_ratio = cs40l50_set_dai_bclk_ratio, 239 .hw_params = cs40l50_hw_params, 240 }; 241 242 static struct snd_soc_dai_driver cs40l50_dai[] = { 243 { 244 .name = "cs40l50-pcm", 245 .id = 0, 246 .playback = { 247 .stream_name = "ASP Playback", 248 .channels_min = 1, 249 .channels_max = 2, 250 .rates = SNDRV_PCM_RATE_48000, 251 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, 252 }, 253 .ops = &cs40l50_dai_ops, 254 }, 255 }; 256 257 static int cs40l50_codec_probe(struct snd_soc_component *component) 258 { 259 struct cs40l50_codec *codec = snd_soc_component_get_drvdata(component); 260 261 codec->bclk_ratio = CS40L50_BCLK_RATIO_DEFAULT; 262 263 return 0; 264 } 265 266 static const struct snd_soc_component_driver soc_codec_dev_cs40l50 = { 267 .probe = cs40l50_codec_probe, 268 .dapm_widgets = cs40l50_dapm_widgets, 269 .num_dapm_widgets = ARRAY_SIZE(cs40l50_dapm_widgets), 270 .dapm_routes = cs40l50_dapm_routes, 271 .num_dapm_routes = ARRAY_SIZE(cs40l50_dapm_routes), 272 }; 273 274 static int cs40l50_codec_driver_probe(struct platform_device *pdev) 275 { 276 struct cs40l50 *cs40l50 = dev_get_drvdata(pdev->dev.parent); 277 struct cs40l50_codec *codec; 278 279 codec = devm_kzalloc(&pdev->dev, sizeof(*codec), GFP_KERNEL); 280 if (!codec) 281 return -ENOMEM; 282 283 codec->regmap = cs40l50->regmap; 284 codec->dev = &pdev->dev; 285 286 return devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_cs40l50, 287 cs40l50_dai, ARRAY_SIZE(cs40l50_dai)); 288 } 289 290 static const struct platform_device_id cs40l50_id[] = { 291 { "cs40l50-codec", }, 292 {} 293 }; 294 MODULE_DEVICE_TABLE(platform, cs40l50_id); 295 296 static struct platform_driver cs40l50_codec_driver = { 297 .probe = cs40l50_codec_driver_probe, 298 .id_table = cs40l50_id, 299 .driver = { 300 .name = "cs40l50-codec", 301 }, 302 }; 303 module_platform_driver(cs40l50_codec_driver); 304 305 MODULE_DESCRIPTION("ASoC CS40L50 driver"); 306 MODULE_AUTHOR("James Ogletree <james.ogletree@cirrus.com>"); 307 MODULE_LICENSE("GPL"); 308