File Index
Files and directories
Sign in
FILE

wm8524.c

File preview with syntax highlighting.

linux-6.19.14/sound/soc/codecs/wm8524.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * wm8524.c  --  WM8524 ALSA SoC Audio driver
  4 *
  5 * Copyright 2009 Wolfson Microelectronics plc
  6 * Copyright 2017 NXP
  7 *
  8 * Based on WM8523 ALSA SoC Audio driver written by Mark Brown
  9 */
 10
 11#include <linux/mod_devicetable.h>
 12#include <linux/module.h>
 13#include <linux/moduleparam.h>
 14#include <linux/init.h>
 15#include <linux/delay.h>
 16#include <linux/slab.h>
 17#include <linux/gpio/consumer.h>
 18#include <sound/core.h>
 19#include <sound/pcm.h>
 20#include <sound/pcm_params.h>
 21#include <sound/soc.h>
 22#include <sound/initval.h>
 23
 24#define WM8524_NUM_RATES 12
 25
 26/* codec private data */
 27struct wm8524_priv {
 28	struct gpio_desc *mute;
 29	unsigned int sysclk;
 30	unsigned int rate_constraint_list[WM8524_NUM_RATES];
 31	struct snd_pcm_hw_constraint_list rate_constraint;
 32};
 33
 34
 35static const struct snd_soc_dapm_widget wm8524_dapm_widgets[] = {
 36SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
 37SND_SOC_DAPM_OUTPUT("LINEVOUTL"),
 38SND_SOC_DAPM_OUTPUT("LINEVOUTR"),
 39};
 40
 41static const struct snd_soc_dapm_route wm8524_dapm_routes[] = {
 42	{ "LINEVOUTL", NULL, "DAC" },
 43	{ "LINEVOUTR", NULL, "DAC" },
 44};
 45
 46static const struct {
 47	int value;
 48	int ratio;
 49} lrclk_ratios[] = {
 50	{ 1, 128 },
 51	{ 2, 192 },
 52	{ 3, 256 },
 53	{ 4, 384 },
 54	{ 5, 512 },
 55	{ 6, 768 },
 56	{ 7, 1152 },
 57};
 58
 59static int wm8524_startup(struct snd_pcm_substream *substream,
 60			  struct snd_soc_dai *dai)
 61{
 62	struct snd_soc_component *component = dai->component;
 63	struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component);
 64
 65	/* The set of sample rates that can be supported depends on the
 66	 * MCLK supplied to the CODEC.
 67	 */
 68	if (wm8524->sysclk)
 69		snd_pcm_hw_constraint_list(substream->runtime, 0,
 70					   SNDRV_PCM_HW_PARAM_RATE,
 71					   &wm8524->rate_constraint);
 72
 73	gpiod_set_value_cansleep(wm8524->mute, 1);
 74
 75	return 0;
 76}
 77
 78static void wm8524_shutdown(struct snd_pcm_substream *substream,
 79			  struct snd_soc_dai *dai)
 80{
 81	struct snd_soc_component *component = dai->component;
 82	struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component);
 83
 84	gpiod_set_value_cansleep(wm8524->mute, 0);
 85}
 86
 87static int wm8524_set_dai_sysclk(struct snd_soc_dai *codec_dai,
 88		int clk_id, unsigned int freq, int dir)
 89{
 90	struct snd_soc_component *component = codec_dai->component;
 91	struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component);
 92	unsigned int val;
 93	int i, j = 0;
 94
 95	wm8524->rate_constraint.count = 0;
 96	wm8524->sysclk = freq;
 97	if (!wm8524->sysclk)
 98		return 0;
 99
100	for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) {
101		val = freq / lrclk_ratios[i].ratio;
102		/* Check that it's a standard rate since core can't
103		 * cope with others and having the odd rates confuses
104		 * constraint matching.
105		 */
106		switch (val) {
107		case 8000:
108		case 11025:
109		case 16000:
110		case 22050:
111		case 32000:
112		case 44100:
113		case 48000:
114		case 64000:
115		case 88200:
116		case 96000:
117		case 176400:
118		case 192000:
119			dev_dbg(component->dev, "Supported sample rate: %dHz\n",
120				val);
121			wm8524->rate_constraint_list[j++] = val;
122			wm8524->rate_constraint.count++;
123			break;
124		default:
125			dev_dbg(component->dev, "Skipping sample rate: %dHz\n",
126				val);
127		}
128	}
129
130	/* Need at least one supported rate... */
131	if (wm8524->rate_constraint.count == 0)
132		return -EINVAL;
133
134	return 0;
135}
136
137static int wm8524_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
138{
139	fmt &= (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_INV_MASK |
140		SND_SOC_DAIFMT_MASTER_MASK);
141
142	if (fmt != (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
143		    SND_SOC_DAIFMT_CBC_CFC)) {
144		dev_err(codec_dai->dev, "Invalid DAI format\n");
145		return -EINVAL;
146	}
147
148	return 0;
149}
150
151static int wm8524_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
152{
153	struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(dai->component);
154
155	if (wm8524->mute)
156		gpiod_set_value_cansleep(wm8524->mute, mute);
157
158	return 0;
159}
160
161static int wm8524_hw_params(struct snd_pcm_substream *substream,
162			    struct snd_pcm_hw_params *params,
163			    struct snd_soc_dai *dai)
164{
165	struct snd_soc_component *component = dai->component;
166	struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component);
167	int i;
168
169	/* If sysclk is not configured, no need to check the rate */
170	if (!wm8524->sysclk)
171		return 0;
172
173	/* Find a supported LRCLK rate */
174	for (i = 0; i < wm8524->rate_constraint.count; i++) {
175		if (wm8524->rate_constraint.list[i] == params_rate(params))
176			break;
177	}
178
179	if (i == wm8524->rate_constraint.count) {
180		dev_err(component->dev, "LRCLK %d unsupported with MCLK %d\n",
181			params_rate(params), wm8524->sysclk);
182		return -EINVAL;
183	}
184
185	return 0;
186}
187
188#define WM8524_RATES SNDRV_PCM_RATE_8000_192000
189
190#define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
191			SNDRV_PCM_FMTBIT_S24_LE |\
192			SNDRV_PCM_FMTBIT_S32_LE)
193
194static const struct snd_soc_dai_ops wm8524_dai_ops = {
195	.startup	= wm8524_startup,
196	.shutdown	= wm8524_shutdown,
197	.set_sysclk	= wm8524_set_dai_sysclk,
198	.set_fmt	= wm8524_set_fmt,
199	.mute_stream	= wm8524_mute_stream,
200	.hw_params	= wm8524_hw_params,
201};
202
203static struct snd_soc_dai_driver wm8524_dai = {
204	.name = "wm8524-hifi",
205	.playback = {
206		.stream_name = "Playback",
207		.channels_min = 2,
208		.channels_max = 2,
209		.rates = WM8524_RATES,
210		.formats = WM8524_FORMATS,
211	},
212	.ops = &wm8524_dai_ops,
213};
214
215static int wm8524_probe(struct snd_soc_component *component)
216{
217	struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component);
218
219	wm8524->rate_constraint.list = &wm8524->rate_constraint_list[0];
220	wm8524->rate_constraint.count =
221		ARRAY_SIZE(wm8524->rate_constraint_list);
222
223	return 0;
224}
225
226static const struct snd_soc_component_driver soc_component_dev_wm8524 = {
227	.probe			= wm8524_probe,
228	.dapm_widgets		= wm8524_dapm_widgets,
229	.num_dapm_widgets	= ARRAY_SIZE(wm8524_dapm_widgets),
230	.dapm_routes		= wm8524_dapm_routes,
231	.num_dapm_routes	= ARRAY_SIZE(wm8524_dapm_routes),
232	.idle_bias_on		= 1,
233	.use_pmdown_time	= 1,
234	.endianness		= 1,
235};
236
237static const struct of_device_id wm8524_of_match[] = {
238	{ .compatible = "wlf,wm8524" },
239	{ /* sentinel*/ }
240};
241MODULE_DEVICE_TABLE(of, wm8524_of_match);
242
243static int wm8524_codec_probe(struct platform_device *pdev)
244{
245	struct wm8524_priv *wm8524;
246	int ret;
247
248	wm8524 = devm_kzalloc(&pdev->dev, sizeof(struct wm8524_priv),
249						  GFP_KERNEL);
250	if (wm8524 == NULL)
251		return -ENOMEM;
252
253	platform_set_drvdata(pdev, wm8524);
254
255	wm8524->mute = devm_gpiod_get(&pdev->dev, "wlf,mute", GPIOD_OUT_LOW);
256	if (IS_ERR(wm8524->mute)) {
257		ret = PTR_ERR(wm8524->mute);
258		dev_err_probe(&pdev->dev, ret, "Failed to get mute line\n");
259		return ret;
260	}
261
262	ret = devm_snd_soc_register_component(&pdev->dev,
263			&soc_component_dev_wm8524, &wm8524_dai, 1);
264	if (ret < 0)
265		dev_err(&pdev->dev, "Failed to register component: %d\n", ret);
266
267	return ret;
268}
269
270static struct platform_driver wm8524_codec_driver = {
271	.probe		= wm8524_codec_probe,
272	.driver		= {
273		.name	= "wm8524-codec",
274		.of_match_table = wm8524_of_match,
275	},
276};
277module_platform_driver(wm8524_codec_driver);
278
279MODULE_DESCRIPTION("ASoC WM8524 driver");
280MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>");
281MODULE_ALIAS("platform:wm8524-codec");
282MODULE_LICENSE("GPL");
~/ Read-only file preview.