Loading...
Note: File does not exist in v3.1.
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
41struct cs40l50_pll_config {
42 unsigned int freq;
43 unsigned int cfg;
44};
45
46struct 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
54static 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
63static 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
77static 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
115static 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
149static 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
157static 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
166static 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
203static 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
227static 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
236static 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
242static 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
257static 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
266static 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
274static 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
290static const struct platform_device_id cs40l50_id[] = {
291 { "cs40l50-codec", },
292 {}
293};
294MODULE_DEVICE_TABLE(platform, cs40l50_id);
295
296static 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};
303module_platform_driver(cs40l50_codec_driver);
304
305MODULE_DESCRIPTION("ASoC CS40L50 driver");
306MODULE_AUTHOR("James Ogletree <james.ogletree@cirrus.com>");
307MODULE_LICENSE("GPL");