xref: /linux/sound/soc/codecs/cs40l50-codec.c (revision da5b2ad1c2f18834cb1ce429e2e5a5cf5cbdf21b)
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