xref: /openbmc/linux/sound/soc/adi/axi-spdif.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
1fda8d26eSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2429e4374SLars-Peter Clausen /*
3429e4374SLars-Peter Clausen  * Copyright (C) 2012-2013, Analog Devices Inc.
4429e4374SLars-Peter Clausen  * Author: Lars-Peter Clausen <lars@metafoo.de>
5429e4374SLars-Peter Clausen  */
6429e4374SLars-Peter Clausen 
7429e4374SLars-Peter Clausen #include <linux/init.h>
8429e4374SLars-Peter Clausen #include <linux/kernel.h>
9429e4374SLars-Peter Clausen #include <linux/module.h>
10429e4374SLars-Peter Clausen #include <linux/platform_device.h>
11429e4374SLars-Peter Clausen #include <linux/slab.h>
12429e4374SLars-Peter Clausen #include <linux/of.h>
13429e4374SLars-Peter Clausen #include <linux/clk.h>
14429e4374SLars-Peter Clausen #include <linux/regmap.h>
15429e4374SLars-Peter Clausen 
16429e4374SLars-Peter Clausen #include <sound/core.h>
17429e4374SLars-Peter Clausen #include <sound/pcm.h>
18429e4374SLars-Peter Clausen #include <sound/pcm_params.h>
19429e4374SLars-Peter Clausen #include <sound/soc.h>
20429e4374SLars-Peter Clausen #include <sound/initval.h>
21429e4374SLars-Peter Clausen #include <sound/dmaengine_pcm.h>
22429e4374SLars-Peter Clausen 
23429e4374SLars-Peter Clausen #define AXI_SPDIF_REG_CTRL	0x0
24429e4374SLars-Peter Clausen #define AXI_SPDIF_REG_STAT	0x4
25429e4374SLars-Peter Clausen #define AXI_SPDIF_REG_TX_FIFO	0xc
26429e4374SLars-Peter Clausen 
27429e4374SLars-Peter Clausen #define AXI_SPDIF_CTRL_TXDATA BIT(1)
28429e4374SLars-Peter Clausen #define AXI_SPDIF_CTRL_TXEN BIT(0)
29429e4374SLars-Peter Clausen #define AXI_SPDIF_CTRL_CLKDIV_OFFSET 8
30429e4374SLars-Peter Clausen #define AXI_SPDIF_CTRL_CLKDIV_MASK (0xff << 8)
31429e4374SLars-Peter Clausen 
32429e4374SLars-Peter Clausen #define AXI_SPDIF_FREQ_44100	(0x0 << 6)
33429e4374SLars-Peter Clausen #define AXI_SPDIF_FREQ_48000	(0x1 << 6)
34429e4374SLars-Peter Clausen #define AXI_SPDIF_FREQ_32000	(0x2 << 6)
35429e4374SLars-Peter Clausen #define AXI_SPDIF_FREQ_NA	(0x3 << 6)
36429e4374SLars-Peter Clausen 
37429e4374SLars-Peter Clausen struct axi_spdif {
38429e4374SLars-Peter Clausen 	struct regmap *regmap;
39429e4374SLars-Peter Clausen 	struct clk *clk;
40429e4374SLars-Peter Clausen 	struct clk *clk_ref;
41429e4374SLars-Peter Clausen 
42429e4374SLars-Peter Clausen 	struct snd_dmaengine_dai_dma_data dma_data;
43429e4374SLars-Peter Clausen 
44429e4374SLars-Peter Clausen 	struct snd_ratnum ratnum;
45429e4374SLars-Peter Clausen 	struct snd_pcm_hw_constraint_ratnums rate_constraints;
46429e4374SLars-Peter Clausen };
47429e4374SLars-Peter Clausen 
axi_spdif_trigger(struct snd_pcm_substream * substream,int cmd,struct snd_soc_dai * dai)48429e4374SLars-Peter Clausen static int axi_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
49429e4374SLars-Peter Clausen 	struct snd_soc_dai *dai)
50429e4374SLars-Peter Clausen {
51429e4374SLars-Peter Clausen 	struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
52429e4374SLars-Peter Clausen 	unsigned int val;
53429e4374SLars-Peter Clausen 
54429e4374SLars-Peter Clausen 	switch (cmd) {
55429e4374SLars-Peter Clausen 	case SNDRV_PCM_TRIGGER_START:
56429e4374SLars-Peter Clausen 	case SNDRV_PCM_TRIGGER_RESUME:
57429e4374SLars-Peter Clausen 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
58429e4374SLars-Peter Clausen 		val = AXI_SPDIF_CTRL_TXDATA;
59429e4374SLars-Peter Clausen 		break;
60429e4374SLars-Peter Clausen 	case SNDRV_PCM_TRIGGER_STOP:
61429e4374SLars-Peter Clausen 	case SNDRV_PCM_TRIGGER_SUSPEND:
62429e4374SLars-Peter Clausen 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
63429e4374SLars-Peter Clausen 		val = 0;
64429e4374SLars-Peter Clausen 		break;
65429e4374SLars-Peter Clausen 	default:
66429e4374SLars-Peter Clausen 		return -EINVAL;
67429e4374SLars-Peter Clausen 	}
68429e4374SLars-Peter Clausen 
69429e4374SLars-Peter Clausen 	regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
70429e4374SLars-Peter Clausen 		AXI_SPDIF_CTRL_TXDATA, val);
71429e4374SLars-Peter Clausen 
72429e4374SLars-Peter Clausen 	return 0;
73429e4374SLars-Peter Clausen }
74429e4374SLars-Peter Clausen 
axi_spdif_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params,struct snd_soc_dai * dai)75429e4374SLars-Peter Clausen static int axi_spdif_hw_params(struct snd_pcm_substream *substream,
76429e4374SLars-Peter Clausen 	struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
77429e4374SLars-Peter Clausen {
78429e4374SLars-Peter Clausen 	struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
79429e4374SLars-Peter Clausen 	unsigned int rate = params_rate(params);
80429e4374SLars-Peter Clausen 	unsigned int clkdiv, stat;
81429e4374SLars-Peter Clausen 
82429e4374SLars-Peter Clausen 	switch (params_rate(params)) {
83429e4374SLars-Peter Clausen 	case 32000:
84429e4374SLars-Peter Clausen 		stat = AXI_SPDIF_FREQ_32000;
85429e4374SLars-Peter Clausen 		break;
86429e4374SLars-Peter Clausen 	case 44100:
87429e4374SLars-Peter Clausen 		stat = AXI_SPDIF_FREQ_44100;
88429e4374SLars-Peter Clausen 		break;
89429e4374SLars-Peter Clausen 	case 48000:
90429e4374SLars-Peter Clausen 		stat = AXI_SPDIF_FREQ_48000;
91429e4374SLars-Peter Clausen 		break;
92429e4374SLars-Peter Clausen 	default:
93429e4374SLars-Peter Clausen 		stat = AXI_SPDIF_FREQ_NA;
94429e4374SLars-Peter Clausen 		break;
95429e4374SLars-Peter Clausen 	}
96429e4374SLars-Peter Clausen 
97429e4374SLars-Peter Clausen 	clkdiv = DIV_ROUND_CLOSEST(clk_get_rate(spdif->clk_ref),
98429e4374SLars-Peter Clausen 			rate * 64 * 2) - 1;
99429e4374SLars-Peter Clausen 	clkdiv <<= AXI_SPDIF_CTRL_CLKDIV_OFFSET;
100429e4374SLars-Peter Clausen 
101429e4374SLars-Peter Clausen 	regmap_write(spdif->regmap, AXI_SPDIF_REG_STAT, stat);
102429e4374SLars-Peter Clausen 	regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
103429e4374SLars-Peter Clausen 		AXI_SPDIF_CTRL_CLKDIV_MASK, clkdiv);
104429e4374SLars-Peter Clausen 
105429e4374SLars-Peter Clausen 	return 0;
106429e4374SLars-Peter Clausen }
107429e4374SLars-Peter Clausen 
axi_spdif_dai_probe(struct snd_soc_dai * dai)108429e4374SLars-Peter Clausen static int axi_spdif_dai_probe(struct snd_soc_dai *dai)
109429e4374SLars-Peter Clausen {
110429e4374SLars-Peter Clausen 	struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
111429e4374SLars-Peter Clausen 
112429e4374SLars-Peter Clausen 	snd_soc_dai_init_dma_data(dai, &spdif->dma_data, NULL);
113429e4374SLars-Peter Clausen 
114429e4374SLars-Peter Clausen 	return 0;
115429e4374SLars-Peter Clausen }
116429e4374SLars-Peter Clausen 
axi_spdif_startup(struct snd_pcm_substream * substream,struct snd_soc_dai * dai)117429e4374SLars-Peter Clausen static int axi_spdif_startup(struct snd_pcm_substream *substream,
118429e4374SLars-Peter Clausen 	struct snd_soc_dai *dai)
119429e4374SLars-Peter Clausen {
120429e4374SLars-Peter Clausen 	struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
121429e4374SLars-Peter Clausen 	int ret;
122429e4374SLars-Peter Clausen 
123429e4374SLars-Peter Clausen 	ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
124429e4374SLars-Peter Clausen 			   SNDRV_PCM_HW_PARAM_RATE,
125429e4374SLars-Peter Clausen 			   &spdif->rate_constraints);
126429e4374SLars-Peter Clausen 	if (ret)
127429e4374SLars-Peter Clausen 		return ret;
128429e4374SLars-Peter Clausen 
129429e4374SLars-Peter Clausen 	ret = clk_prepare_enable(spdif->clk_ref);
130429e4374SLars-Peter Clausen 	if (ret)
131429e4374SLars-Peter Clausen 		return ret;
132429e4374SLars-Peter Clausen 
133429e4374SLars-Peter Clausen 	regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
134429e4374SLars-Peter Clausen 		AXI_SPDIF_CTRL_TXEN, AXI_SPDIF_CTRL_TXEN);
135429e4374SLars-Peter Clausen 
136429e4374SLars-Peter Clausen 	return 0;
137429e4374SLars-Peter Clausen }
138429e4374SLars-Peter Clausen 
axi_spdif_shutdown(struct snd_pcm_substream * substream,struct snd_soc_dai * dai)139429e4374SLars-Peter Clausen static void axi_spdif_shutdown(struct snd_pcm_substream *substream,
140429e4374SLars-Peter Clausen 	struct snd_soc_dai *dai)
141429e4374SLars-Peter Clausen {
142429e4374SLars-Peter Clausen 	struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
143429e4374SLars-Peter Clausen 
144429e4374SLars-Peter Clausen 	regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
145429e4374SLars-Peter Clausen 		AXI_SPDIF_CTRL_TXEN, 0);
146429e4374SLars-Peter Clausen 
147429e4374SLars-Peter Clausen 	clk_disable_unprepare(spdif->clk_ref);
148429e4374SLars-Peter Clausen }
149429e4374SLars-Peter Clausen 
150429e4374SLars-Peter Clausen static const struct snd_soc_dai_ops axi_spdif_dai_ops = {
151*7baf6b1eSKuninori Morimoto 	.probe = axi_spdif_dai_probe,
152429e4374SLars-Peter Clausen 	.startup = axi_spdif_startup,
153429e4374SLars-Peter Clausen 	.shutdown = axi_spdif_shutdown,
154429e4374SLars-Peter Clausen 	.trigger = axi_spdif_trigger,
155429e4374SLars-Peter Clausen 	.hw_params = axi_spdif_hw_params,
156429e4374SLars-Peter Clausen };
157429e4374SLars-Peter Clausen 
158429e4374SLars-Peter Clausen static struct snd_soc_dai_driver axi_spdif_dai = {
159429e4374SLars-Peter Clausen 	.playback = {
160429e4374SLars-Peter Clausen 		.channels_min = 2,
161429e4374SLars-Peter Clausen 		.channels_max = 2,
162429e4374SLars-Peter Clausen 		.rates = SNDRV_PCM_RATE_KNOT,
163429e4374SLars-Peter Clausen 		.formats = SNDRV_PCM_FMTBIT_S16_LE,
164429e4374SLars-Peter Clausen 	},
165429e4374SLars-Peter Clausen 	.ops = &axi_spdif_dai_ops,
166429e4374SLars-Peter Clausen };
167429e4374SLars-Peter Clausen 
168429e4374SLars-Peter Clausen static const struct snd_soc_component_driver axi_spdif_component = {
169429e4374SLars-Peter Clausen 	.name = "axi-spdif",
1709a34161aSCharles Keepax 	.legacy_dai_naming = 1,
171429e4374SLars-Peter Clausen };
172429e4374SLars-Peter Clausen 
173429e4374SLars-Peter Clausen static const struct regmap_config axi_spdif_regmap_config = {
174429e4374SLars-Peter Clausen 	.reg_bits = 32,
175429e4374SLars-Peter Clausen 	.reg_stride = 4,
176429e4374SLars-Peter Clausen 	.val_bits = 32,
177429e4374SLars-Peter Clausen 	.max_register = AXI_SPDIF_REG_STAT,
178429e4374SLars-Peter Clausen };
179429e4374SLars-Peter Clausen 
axi_spdif_probe(struct platform_device * pdev)180429e4374SLars-Peter Clausen static int axi_spdif_probe(struct platform_device *pdev)
181429e4374SLars-Peter Clausen {
182429e4374SLars-Peter Clausen 	struct axi_spdif *spdif;
183429e4374SLars-Peter Clausen 	struct resource *res;
184429e4374SLars-Peter Clausen 	void __iomem *base;
185429e4374SLars-Peter Clausen 	int ret;
186429e4374SLars-Peter Clausen 
187429e4374SLars-Peter Clausen 	spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL);
188429e4374SLars-Peter Clausen 	if (!spdif)
189429e4374SLars-Peter Clausen 		return -ENOMEM;
190429e4374SLars-Peter Clausen 
191429e4374SLars-Peter Clausen 	platform_set_drvdata(pdev, spdif);
192429e4374SLars-Peter Clausen 
19368912ebfSYang Yingliang 	base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
194bbe58030SFengguang Wu 	if (IS_ERR(base))
195bbe58030SFengguang Wu 		return PTR_ERR(base);
196429e4374SLars-Peter Clausen 
197429e4374SLars-Peter Clausen 	spdif->regmap = devm_regmap_init_mmio(&pdev->dev, base,
198429e4374SLars-Peter Clausen 					    &axi_spdif_regmap_config);
199429e4374SLars-Peter Clausen 	if (IS_ERR(spdif->regmap))
200429e4374SLars-Peter Clausen 		return PTR_ERR(spdif->regmap);
201429e4374SLars-Peter Clausen 
202429e4374SLars-Peter Clausen 	spdif->clk = devm_clk_get(&pdev->dev, "axi");
203429e4374SLars-Peter Clausen 	if (IS_ERR(spdif->clk))
204429e4374SLars-Peter Clausen 		return PTR_ERR(spdif->clk);
205429e4374SLars-Peter Clausen 
206429e4374SLars-Peter Clausen 	spdif->clk_ref = devm_clk_get(&pdev->dev, "ref");
207429e4374SLars-Peter Clausen 	if (IS_ERR(spdif->clk_ref))
208429e4374SLars-Peter Clausen 		return PTR_ERR(spdif->clk_ref);
209429e4374SLars-Peter Clausen 
210429e4374SLars-Peter Clausen 	ret = clk_prepare_enable(spdif->clk);
211429e4374SLars-Peter Clausen 	if (ret)
212429e4374SLars-Peter Clausen 		return ret;
213429e4374SLars-Peter Clausen 
214429e4374SLars-Peter Clausen 	spdif->dma_data.addr = res->start + AXI_SPDIF_REG_TX_FIFO;
215429e4374SLars-Peter Clausen 	spdif->dma_data.addr_width = 4;
216429e4374SLars-Peter Clausen 	spdif->dma_data.maxburst = 1;
217429e4374SLars-Peter Clausen 
218429e4374SLars-Peter Clausen 	spdif->ratnum.num = clk_get_rate(spdif->clk_ref) / 128;
219429e4374SLars-Peter Clausen 	spdif->ratnum.den_step = 1;
220429e4374SLars-Peter Clausen 	spdif->ratnum.den_min = 1;
221429e4374SLars-Peter Clausen 	spdif->ratnum.den_max = 64;
222429e4374SLars-Peter Clausen 
223429e4374SLars-Peter Clausen 	spdif->rate_constraints.rats = &spdif->ratnum;
224429e4374SLars-Peter Clausen 	spdif->rate_constraints.nrats = 1;
225429e4374SLars-Peter Clausen 
226429e4374SLars-Peter Clausen 	ret = devm_snd_soc_register_component(&pdev->dev, &axi_spdif_component,
227429e4374SLars-Peter Clausen 					 &axi_spdif_dai, 1);
228429e4374SLars-Peter Clausen 	if (ret)
229429e4374SLars-Peter Clausen 		goto err_clk_disable;
230429e4374SLars-Peter Clausen 
231153e66f5SLars-Peter Clausen 	ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
232429e4374SLars-Peter Clausen 	if (ret)
233429e4374SLars-Peter Clausen 		goto err_clk_disable;
234429e4374SLars-Peter Clausen 
235429e4374SLars-Peter Clausen 	return 0;
236429e4374SLars-Peter Clausen 
237429e4374SLars-Peter Clausen err_clk_disable:
238429e4374SLars-Peter Clausen 	clk_disable_unprepare(spdif->clk);
239429e4374SLars-Peter Clausen 	return ret;
240429e4374SLars-Peter Clausen }
241429e4374SLars-Peter Clausen 
axi_spdif_dev_remove(struct platform_device * pdev)242a0d18db0SUwe Kleine-König static void axi_spdif_dev_remove(struct platform_device *pdev)
243429e4374SLars-Peter Clausen {
244429e4374SLars-Peter Clausen 	struct axi_spdif *spdif = platform_get_drvdata(pdev);
245429e4374SLars-Peter Clausen 
246429e4374SLars-Peter Clausen 	clk_disable_unprepare(spdif->clk);
247429e4374SLars-Peter Clausen }
248429e4374SLars-Peter Clausen 
249429e4374SLars-Peter Clausen static const struct of_device_id axi_spdif_of_match[] = {
250429e4374SLars-Peter Clausen 	{ .compatible = "adi,axi-spdif-tx-1.00.a", },
251429e4374SLars-Peter Clausen 	{},
252429e4374SLars-Peter Clausen };
253429e4374SLars-Peter Clausen MODULE_DEVICE_TABLE(of, axi_spdif_of_match);
254429e4374SLars-Peter Clausen 
255429e4374SLars-Peter Clausen static struct platform_driver axi_spdif_driver = {
256429e4374SLars-Peter Clausen 	.driver = {
257429e4374SLars-Peter Clausen 		.name = "axi-spdif",
258429e4374SLars-Peter Clausen 		.of_match_table = axi_spdif_of_match,
259429e4374SLars-Peter Clausen 	},
260429e4374SLars-Peter Clausen 	.probe = axi_spdif_probe,
261a0d18db0SUwe Kleine-König 	.remove_new = axi_spdif_dev_remove,
262429e4374SLars-Peter Clausen };
263429e4374SLars-Peter Clausen module_platform_driver(axi_spdif_driver);
264429e4374SLars-Peter Clausen 
265429e4374SLars-Peter Clausen MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
266429e4374SLars-Peter Clausen MODULE_DESCRIPTION("AXI SPDIF driver");
267429e4374SLars-Peter Clausen MODULE_LICENSE("GPL");
268