File Index
Files and directories
Sign in
FILE

loongson1_ac97.c

File preview with syntax highlighting.

linux-6.19.14/sound/soc/loongson/loongson1_ac97.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * AC97 Controller Driver for Loongson-1 SoC
  4 *
  5 * Copyright (C) 2025 Keguang Zhang <keguang.zhang@gmail.com>
  6 */
  7
  8#include <linux/bitfield.h>
  9#include <linux/dma-mapping.h>
 10#include <linux/init.h>
 11#include <linux/module.h>
 12#include <linux/platform_device.h>
 13#include <linux/regmap.h>
 14
 15#include <sound/dmaengine_pcm.h>
 16#include <sound/pcm.h>
 17#include <sound/pcm_params.h>
 18#include <sound/soc.h>
 19
 20/* Loongson-1 AC97 Controller Registers */
 21#define AC97_CSR		0x0
 22#define AC97_OCC0		0x4
 23#define AC97_ICC		0x10
 24#define AC97_CRAC		0x18
 25#define AC97_INTRAW		0x54
 26#define AC97_INTM		0x58
 27#define AC97_INT_CW_CLR		0x68
 28#define AC97_INT_CR_CLR		0x6c
 29
 30/* Control Status Register Bits (CSR) */
 31#define CSR_RESUME		BIT(1)
 32#define CSR_RST_FORCE		BIT(0)
 33
 34/* MIC Channel Configuration Bits */
 35#define M_DMA_EN		BIT(22)
 36#define M_FIFO_THRES		GENMASK(21, 20)
 37#define M_FIFO_THRES_FULL	FIELD_PREP(M_FIFO_THRES, 3)
 38#define M_FIFO_THRES_HALF	FIELD_PREP(M_FIFO_THRES, 1)
 39#define M_FIFO_THRES_QUARTER	FIELD_PREP(M_FIFO_THRES, 0)
 40#define M_SW			GENMASK(19, 18)
 41#define M_SW_16_BITS		FIELD_PREP(M_SW, 2)
 42#define M_SW_8_BITS		FIELD_PREP(M_SW, 0)
 43#define M_VSR			BIT(17)
 44#define M_CH_EN			BIT(16)
 45/* Right Channel Configuration Bits */
 46#define R_DMA_EN		BIT(14)
 47#define R_FIFO_THRES		GENMASK(13, 12)
 48#define R_FIFO_THRES_EMPTY	FIELD_PREP(R_FIFO_THRES, 3)
 49#define R_FIFO_THRES_HALF	FIELD_PREP(R_FIFO_THRES, 1)
 50#define R_FIFO_THRES_QUARTER	FIELD_PREP(R_FIFO_THRES, 0)
 51#define R_SW			GENMASK(11, 10)
 52#define R_SW_16_BITS		FIELD_PREP(R_SW, 2)
 53#define R_SW_8_BITS		FIELD_PREP(R_SW, 0)
 54#define R_VSR			BIT(9)
 55#define R_CH_EN			BIT(8)
 56/* Left Channel Configuration Bits */
 57#define L_DMA_EN		BIT(6)
 58#define L_FIFO_THRES		GENMASK(5, 4)
 59#define L_FIFO_THRES_EMPTY	FIELD_PREP(L_FIFO_THRES, 3)
 60#define L_FIFO_THRES_HALF	FIELD_PREP(L_FIFO_THRES, 1)
 61#define L_FIFO_THRES_QUARTER	FIELD_PREP(L_FIFO_THRES, 0)
 62#define L_SW			GENMASK(3, 2)
 63#define L_SW_16_BITS		FIELD_PREP(L_SW, 2)
 64#define L_SW_8_BITS		FIELD_PREP(L_SW, 0)
 65#define L_VSR			BIT(1)
 66#define L_CH_EN			BIT(0)
 67
 68/* Codec Register Access Command Bits (CRAC) */
 69#define CODEC_WR		BIT(31)
 70#define CODEC_ADR		GENMASK(22, 16)
 71#define CODEC_DAT		GENMASK(15, 0)
 72
 73/* Interrupt Register (INTRAW) */
 74#define CW_DONE			BIT(1)
 75#define CR_DONE			BIT(0)
 76
 77#define LS1X_AC97_DMA_TX_EN		BIT(31)
 78#define LS1X_AC97_DMA_STEREO		BIT(30)
 79#define LS1X_AC97_DMA_TX_BYTES		GENMASK(29, 28)
 80#define LS1X_AC97_DMA_TX_4_BYTES	FIELD_PREP(LS1X_AC97_DMA_TX_BYTES, 2)
 81#define LS1X_AC97_DMA_TX_2_BYTES	FIELD_PREP(LS1X_AC97_DMA_TX_BYTES, 1)
 82#define LS1X_AC97_DMA_TX_1_BYTE		FIELD_PREP(LS1X_AC97_DMA_TX_BYTES, 0)
 83#define LS1X_AC97_DMA_DADDR_MASK	GENMASK(27, 0)
 84
 85#define LS1X_AC97_DMA_FIFO_SIZE		128
 86
 87#define LS1X_AC97_TIMEOUT		3000
 88
 89struct ls1x_ac97 {
 90	void __iomem *reg_base;
 91	struct regmap *regmap;
 92	dma_addr_t tx_dma_base;
 93	dma_addr_t rx_dma_base;
 94	struct snd_dmaengine_dai_dma_data capture_dma_data;
 95	struct snd_dmaengine_dai_dma_data playback_dma_data;
 96};
 97
 98static struct ls1x_ac97 *ls1x_ac97;
 99
100static const struct regmap_config ls1x_ac97_regmap_config = {
101	.reg_bits = 32,
102	.val_bits = 32,
103	.reg_stride = 4,
104};
105
106static void ls1x_ac97_reset(struct snd_ac97 *ac97)
107{
108	int val;
109
110	regmap_write(ls1x_ac97->regmap, AC97_CSR, CSR_RST_FORCE);
111	regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_CSR, val,
112				 !(val & CSR_RESUME), 0, LS1X_AC97_TIMEOUT);
113}
114
115static void ls1x_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val)
116{
117	int tmp, ret;
118
119	tmp = FIELD_PREP(CODEC_ADR, reg) | FIELD_PREP(CODEC_DAT, val);
120	regmap_write(ls1x_ac97->regmap, AC97_CRAC, tmp);
121	ret = regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_INTRAW, tmp,
122				       (tmp & CW_DONE), 0, LS1X_AC97_TIMEOUT);
123	if (ret)
124		pr_err("timeout on AC97 write! %d\n", ret);
125
126	regmap_read(ls1x_ac97->regmap, AC97_INT_CW_CLR, &ret);
127}
128
129static unsigned short ls1x_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
130{
131	int val, ret;
132
133	val = CODEC_WR | FIELD_PREP(CODEC_ADR, reg);
134	regmap_write(ls1x_ac97->regmap, AC97_CRAC, val);
135	ret = regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_INTRAW, val,
136				       (val & CR_DONE), 0, LS1X_AC97_TIMEOUT);
137	if (ret) {
138		pr_err("timeout on AC97 read! %d\n", ret);
139		return ret;
140	}
141
142	regmap_read(ls1x_ac97->regmap, AC97_INT_CR_CLR, &ret);
143	regmap_read(ls1x_ac97->regmap, AC97_CRAC, &ret);
144
145	return (ret & CODEC_DAT);
146}
147
148static void ls1x_ac97_init(struct snd_ac97 *ac97)
149{
150	writel(0, ls1x_ac97->reg_base + AC97_INTRAW);
151	writel(0, ls1x_ac97->reg_base + AC97_INTM);
152
153	/* Config output channels */
154	regmap_update_bits(ls1x_ac97->regmap, AC97_OCC0,
155			   R_DMA_EN | R_FIFO_THRES | R_CH_EN |
156			   L_DMA_EN | L_FIFO_THRES | L_CH_EN,
157			   R_DMA_EN | R_FIFO_THRES_EMPTY | R_CH_EN |
158			   L_DMA_EN | L_FIFO_THRES_EMPTY | L_CH_EN);
159
160	/* Config inputs channel */
161	regmap_update_bits(ls1x_ac97->regmap, AC97_ICC,
162			   M_DMA_EN | M_FIFO_THRES | M_CH_EN |
163			   R_DMA_EN | R_FIFO_THRES | R_CH_EN |
164			   L_DMA_EN | L_FIFO_THRES | L_CH_EN,
165			   M_DMA_EN | M_FIFO_THRES_FULL | M_CH_EN |
166			   R_DMA_EN | R_FIFO_THRES_EMPTY | R_CH_EN |
167			   L_DMA_EN | L_FIFO_THRES_EMPTY | L_CH_EN);
168
169	if (ac97->ext_id & AC97_EI_VRA) {
170		regmap_update_bits(ls1x_ac97->regmap, AC97_OCC0, R_VSR | L_VSR, R_VSR | L_VSR);
171		regmap_update_bits(ls1x_ac97->regmap, AC97_ICC, M_VSR, M_VSR);
172	}
173}
174
175static struct snd_ac97_bus_ops ls1x_ac97_ops = {
176	.reset	= ls1x_ac97_reset,
177	.write	= ls1x_ac97_write,
178	.read	= ls1x_ac97_read,
179	.init	= ls1x_ac97_init,
180};
181
182static int ls1x_ac97_hw_params(struct snd_pcm_substream *substream,
183			       struct snd_pcm_hw_params *params,
184			       struct snd_soc_dai *cpu_dai)
185{
186	struct ls1x_ac97 *ac97 = dev_get_drvdata(cpu_dai->dev);
187	struct snd_dmaengine_dai_dma_data *dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream);
188
189	switch (params_channels(params)) {
190	case 1:
191		dma_data->addr &= ~LS1X_AC97_DMA_STEREO;
192		break;
193	case 2:
194		dma_data->addr |= LS1X_AC97_DMA_STEREO;
195		break;
196	default:
197		dev_err(cpu_dai->dev, "unsupported channels! %d\n", params_channels(params));
198		return -EINVAL;
199	}
200
201	switch (params_format(params)) {
202	case SNDRV_PCM_FORMAT_S8:
203	case SNDRV_PCM_FORMAT_U8:
204		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
205			regmap_update_bits(ac97->regmap, AC97_OCC0,
206					   R_SW | L_SW,
207					   R_SW_8_BITS | L_SW_8_BITS);
208		else
209			regmap_update_bits(ac97->regmap, AC97_ICC,
210					   M_SW | R_SW | L_SW,
211					   M_SW_8_BITS | R_SW_8_BITS | L_SW_8_BITS);
212		break;
213	case SNDRV_PCM_FORMAT_S16_LE:
214	case SNDRV_PCM_FORMAT_U16_LE:
215	case SNDRV_PCM_FORMAT_S16_BE:
216	case SNDRV_PCM_FORMAT_U16_BE:
217		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
218			regmap_update_bits(ac97->regmap, AC97_OCC0,
219					   R_SW | L_SW,
220					   R_SW_16_BITS | L_SW_16_BITS);
221		else
222			regmap_update_bits(ac97->regmap, AC97_ICC,
223					   M_SW | R_SW | L_SW,
224					   M_SW_16_BITS | R_SW_16_BITS | L_SW_16_BITS);
225		break;
226	default:
227		dev_err(cpu_dai->dev, "unsupported format! %d\n", params_format(params));
228		return -EINVAL;
229	}
230
231	return 0;
232}
233
234static int ls1x_ac97_dai_probe(struct snd_soc_dai *cpu_dai)
235{
236	struct ls1x_ac97 *ac97 = dev_get_drvdata(cpu_dai->dev);
237
238	ac97->capture_dma_data.addr = ac97->rx_dma_base & LS1X_AC97_DMA_DADDR_MASK;
239	ac97->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
240	ac97->capture_dma_data.fifo_size = LS1X_AC97_DMA_FIFO_SIZE;
241
242	ac97->playback_dma_data.addr = ac97->tx_dma_base & LS1X_AC97_DMA_DADDR_MASK;
243	ac97->playback_dma_data.addr |= LS1X_AC97_DMA_TX_4_BYTES;
244	ac97->playback_dma_data.addr |= LS1X_AC97_DMA_TX_EN;
245	ac97->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
246	ac97->playback_dma_data.fifo_size = LS1X_AC97_DMA_FIFO_SIZE;
247
248	snd_soc_dai_init_dma_data(cpu_dai, &ac97->playback_dma_data, &ac97->capture_dma_data);
249	snd_soc_dai_set_drvdata(cpu_dai, ac97);
250
251	return 0;
252}
253
254static const struct snd_soc_dai_ops ls1x_ac97_dai_ops = {
255	.probe		= ls1x_ac97_dai_probe,
256	.hw_params	= ls1x_ac97_hw_params,
257};
258
259#define LS1X_AC97_FMTS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |\
260	SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\
261	SNDRV_PCM_FMTBIT_U16_LE	| SNDRV_PCM_FMTBIT_U16_BE)
262
263static struct snd_soc_dai_driver ls1x_ac97_dai[] = {
264	{
265		.name = "ls1x-ac97",
266		.playback = {
267			.stream_name = "AC97 Playback",
268			.channels_min = 1,
269			.channels_max = 2,
270			.rates = SNDRV_PCM_RATE_8000_48000,
271			.formats = LS1X_AC97_FMTS,
272		},
273		.capture = {
274			.stream_name = "AC97 Capture",
275			.channels_min = 1,
276			.channels_max = 2,
277			.rates = SNDRV_PCM_RATE_8000_48000,
278			.formats = LS1X_AC97_FMTS,
279		},
280		.ops = &ls1x_ac97_dai_ops,
281	},
282};
283
284static const struct snd_soc_component_driver ls1x_ac97_component = {
285	.name = KBUILD_MODNAME,
286	.legacy_dai_naming = 1,
287};
288
289static int ls1x_ac97_probe(struct platform_device *pdev)
290{
291	struct device *dev = &pdev->dev;
292	struct ls1x_ac97 *ac97;
293	struct resource *res;
294	int ret;
295
296	ac97 = devm_kzalloc(dev, sizeof(struct ls1x_ac97), GFP_KERNEL);
297	if (!ac97)
298		return -ENOMEM;
299	ls1x_ac97 = ac97;
300	platform_set_drvdata(pdev, ac97);
301
302	ac97->reg_base = devm_platform_ioremap_resource(pdev, 0);
303	if (IS_ERR(ac97->reg_base))
304		return PTR_ERR(ac97->reg_base);
305
306	ac97->regmap = devm_regmap_init_mmio(dev, ac97->reg_base, &ls1x_ac97_regmap_config);
307	if (IS_ERR(ac97->regmap))
308		return dev_err_probe(dev, PTR_ERR(ac97->regmap), "devm_regmap_init_mmio failed\n");
309
310	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audio-tx");
311	if (!res)
312		return dev_err_probe(dev, -EINVAL, "Missing 'audio-tx' in reg-names property\n");
313
314	ac97->tx_dma_base = dma_map_resource(dev, res->start, resource_size(res),
315					     DMA_TO_DEVICE, 0);
316	if (dma_mapping_error(dev, ac97->tx_dma_base))
317		return -ENXIO;
318
319	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audio-rx");
320	if (!res)
321		return dev_err_probe(dev, -EINVAL, "Missing 'audio-rx' in reg-names property\n");
322
323	ac97->rx_dma_base = dma_map_resource(dev, res->start, resource_size(res),
324					     DMA_FROM_DEVICE, 0);
325	if (dma_mapping_error(dev, ac97->rx_dma_base))
326		return -ENXIO;
327
328	ret = devm_snd_dmaengine_pcm_register(dev, NULL, 0);
329	if (ret)
330		dev_err_probe(dev, ret, "failed to register PCM\n");
331
332	ret = devm_snd_soc_register_component(dev, &ls1x_ac97_component,
333					      ls1x_ac97_dai, ARRAY_SIZE(ls1x_ac97_dai));
334	if (ret)
335		dev_err_probe(dev, ret, "failed to register DAI\n");
336
337	return snd_soc_set_ac97_ops(&ls1x_ac97_ops);
338}
339
340static void ls1x_ac97_remove(struct platform_device *pdev)
341{
342	ls1x_ac97 = NULL;
343	snd_soc_set_ac97_ops(NULL);
344}
345
346#ifdef CONFIG_PM_SLEEP
347static int ls1x_ac97_suspend(struct device *dev)
348{
349	int val;
350
351	regmap_clear_bits(ls1x_ac97->regmap, AC97_OCC0, R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN);
352	regmap_clear_bits(ls1x_ac97->regmap, AC97_ICC,
353			  M_DMA_EN | M_CH_EN | R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN);
354	regmap_set_bits(ls1x_ac97->regmap, AC97_CSR, CSR_RESUME);
355
356	return regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_CSR, val,
357					(val & CSR_RESUME), 0, LS1X_AC97_TIMEOUT);
358}
359
360static int ls1x_ac97_resume(struct device *dev)
361{
362	int val;
363
364	regmap_set_bits(ls1x_ac97->regmap, AC97_OCC0, R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN);
365	regmap_set_bits(ls1x_ac97->regmap, AC97_ICC,
366			M_DMA_EN | M_CH_EN | R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN);
367	regmap_set_bits(ls1x_ac97->regmap, AC97_CSR, CSR_RESUME);
368
369	return regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_CSR, val,
370					!(val & CSR_RESUME), 0, LS1X_AC97_TIMEOUT);
371}
372#endif
373
374static const struct dev_pm_ops ls1x_ac97_pm_ops = {
375	SET_SYSTEM_SLEEP_PM_OPS(ls1x_ac97_suspend, ls1x_ac97_resume)
376};
377
378static const struct of_device_id ls1x_ac97_match[] = {
379	{ .compatible = "loongson,ls1b-ac97" },
380	{ /* sentinel */ }
381};
382MODULE_DEVICE_TABLE(of, ls1x_ac97_match);
383
384static struct platform_driver ls1x_ac97_driver = {
385	.probe		= ls1x_ac97_probe,
386	.remove		= ls1x_ac97_remove,
387	.driver		= {
388		.name	= KBUILD_MODNAME,
389		.of_match_table = ls1x_ac97_match,
390		.pm = &ls1x_ac97_pm_ops,
391	},
392};
393
394module_platform_driver(ls1x_ac97_driver);
395
396MODULE_AUTHOR("Keguang Zhang <keguang.zhang@gmail.com>");
397MODULE_DESCRIPTION("Loongson-1 AC97 Controller Driver");
398MODULE_LICENSE("GPL");
~/ Read-only file preview.