Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ASoC: cs40l50: Support I2S streaming to CS40L50

Introduce support for Cirrus Logic Device CS40L50: a
haptic driver with waveform memory, integrated DSP,
and closed-loop algorithms.

The ASoC driver enables I2S streaming to the device.

Reviewed-by: David Rhodes <drhodes@opensource.cirrus.com>
Signed-off-by: James Ogletree <jogletre@opensource.cirrus.com>
Reviewed-by: Jeff LaBundy <jeff@labundy.com>
Reviewed-by: Ricardo Rivera-Matos <rriveram@opensource.cirrus.com>
Reviewed-by: Mark Brown <broonie@kernel.org>
Link: https://lore.kernel.org/r/20240620161745.2312359-6-jogletre@opensource.cirrus.com
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

James Ogletree and committed by
Lee Jones
c486def5 c38fe1bb

+321
+1
MAINTAINERS
··· 5216 5216 F: drivers/input/misc/cs40l* 5217 5217 F: drivers/mfd/cs40l* 5218 5218 F: include/linux/mfd/cs40l* 5219 + F: sound/soc/codecs/cs40l* 5219 5220 5220 5221 CIRRUS LOGIC DSP FIRMWARE DRIVER 5221 5222 M: Simon Trimmer <simont@opensource.cirrus.com>
+11
sound/soc/codecs/Kconfig
··· 75 75 imply SND_SOC_CS35L56_I2C 76 76 imply SND_SOC_CS35L56_SPI 77 77 imply SND_SOC_CS35L56_SDW 78 + imply SND_SOC_CS40L50 78 79 imply SND_SOC_CS42L42 79 80 imply SND_SOC_CS42L42_SDW 80 81 imply SND_SOC_CS42L43 ··· 847 846 select SND_SOC_CS35L56_SHARED 848 847 help 849 848 Enable support for Cirrus Logic CS35L56 boosted amplifier with SoundWire control 849 + 850 + config SND_SOC_CS40L50 851 + tristate "Cirrus Logic CS40L50 CODEC" 852 + depends on MFD_CS40L50_CORE 853 + help 854 + This option enables support for I2S streaming to Cirrus Logic CS40L50. 855 + 856 + CS40L50 is a haptic driver with waveform memory, an integrated 857 + DSP, and closed-loop algorithms. If built as a module, it will be 858 + called snd-soc-cs40l50. 850 859 851 860 config SND_SOC_CS42L42_CORE 852 861 tristate
+2
sound/soc/codecs/Makefile
··· 78 78 snd-soc-cs35l56-i2c-y := cs35l56-i2c.o 79 79 snd-soc-cs35l56-spi-y := cs35l56-spi.o 80 80 snd-soc-cs35l56-sdw-y := cs35l56-sdw.o 81 + snd-soc-cs40l50-objs := cs40l50-codec.o 81 82 snd-soc-cs42l42-y := cs42l42.o 82 83 snd-soc-cs42l42-i2c-y := cs42l42-i2c.o 83 84 snd-soc-cs42l42-sdw-y := cs42l42-sdw.o ··· 476 475 obj-$(CONFIG_SND_SOC_CS35L56_I2C) += snd-soc-cs35l56-i2c.o 477 476 obj-$(CONFIG_SND_SOC_CS35L56_SPI) += snd-soc-cs35l56-spi.o 478 477 obj-$(CONFIG_SND_SOC_CS35L56_SDW) += snd-soc-cs35l56-sdw.o 478 + obj-$(CONFIG_SND_SOC_CS40L50) += snd-soc-cs40l50.o 479 479 obj-$(CONFIG_SND_SOC_CS42L42_CORE) += snd-soc-cs42l42.o 480 480 obj-$(CONFIG_SND_SOC_CS42L42) += snd-soc-cs42l42-i2c.o 481 481 obj-$(CONFIG_SND_SOC_CS42L42_SDW) += snd-soc-cs42l42-sdw.o
+307
sound/soc/codecs/cs40l50-codec.c
··· 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");