Linux Audio

Check our new training course

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");