aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars-Peter Clausen <lars@metafoo.de>2013-12-06 05:02:52 -0500
committerMark Brown <broonie@linaro.org>2013-12-09 12:57:36 -0500
commit429e4374cc007f260c7d0e2d0df5247deeaf8fbe (patch)
tree0683f51b5d2deb4a2bd4cc24eefd4656dd59b9eb
parentd7b528eff9277b83b315500f44ade178035ed0d1 (diff)
ASoC: Add support for the Analog Devices AXI-SPDIF driver
This patch adds a ASoC driver for the AXI-SPDIF softcore. The core implements a simple SPDIF transmitter and is used on some Analog Devices' reference designs for various FPGA platforms. For now the driver only support the PL330 as the the DMA controller. The driver uses the generic PCM dmaengine driver for its PCM. The only restriction is that we need to set the SND_DMAENGINE_PCM_FLAG_NO_RESIDUE flag as the dmaengine driver for the DMA core (PL330) that is used with this core has no residue reporting capabilities yet. This will be fixed in the future though. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Signed-off-by: Mark Brown <broonie@linaro.org>
-rw-r--r--sound/soc/adi/Kconfig8
-rw-r--r--sound/soc/adi/Makefile2
-rw-r--r--sound/soc/adi/axi-spdif.c272
3 files changed, 282 insertions, 0 deletions
diff --git a/sound/soc/adi/Kconfig b/sound/soc/adi/Kconfig
index 46f4b79f7a87..dd763f55edac 100644
--- a/sound/soc/adi/Kconfig
+++ b/sound/soc/adi/Kconfig
@@ -11,3 +11,11 @@ config SND_SOC_ADI_AXI_I2S
11 select REGMAP_MMIO 11 select REGMAP_MMIO
12 help 12 help
13 ASoC driver for the Analog Devices AXI-I2S softcore peripheral. 13 ASoC driver for the Analog Devices AXI-I2S softcore peripheral.
14
15config SND_SOC_ADI_AXI_SPDIF
16 tristate "AXI-SPDIF support"
17 depends on SND_SOC_ADI
18 select SND_SOC_GENERIC_DMAENGINE_PCM
19 select REGMAP_MMIO
20 help
21 ASoC driver for the Analog Devices AXI-SPDIF softcore peripheral.
diff --git a/sound/soc/adi/Makefile b/sound/soc/adi/Makefile
index d32c21ae14b6..64456c1e5347 100644
--- a/sound/soc/adi/Makefile
+++ b/sound/soc/adi/Makefile
@@ -1,3 +1,5 @@
1snd-soc-adi-axi-i2s-objs := axi-i2s.o 1snd-soc-adi-axi-i2s-objs := axi-i2s.o
2snd-soc-adi-axi-spdif-objs := axi-spdif.o
2 3
3obj-$(CONFIG_SND_SOC_ADI_AXI_I2S) += snd-soc-adi-axi-i2s.o 4obj-$(CONFIG_SND_SOC_ADI_AXI_I2S) += snd-soc-adi-axi-i2s.o
5obj-$(CONFIG_SND_SOC_ADI_AXI_SPDIF) += snd-soc-adi-axi-spdif.o
diff --git a/sound/soc/adi/axi-spdif.c b/sound/soc/adi/axi-spdif.c
new file mode 100644
index 000000000000..d5408d23b438
--- /dev/null
+++ b/sound/soc/adi/axi-spdif.c
@@ -0,0 +1,272 @@
1/*
2 * Copyright (C) 2012-2013, Analog Devices Inc.
3 * Author: Lars-Peter Clausen <lars@metafoo.de>
4 *
5 * Licensed under the GPL-2.
6 */
7
8#include <linux/init.h>
9#include <linux/kernel.h>
10#include <linux/module.h>
11#include <linux/platform_device.h>
12#include <linux/slab.h>
13#include <linux/of.h>
14#include <linux/clk.h>
15#include <linux/regmap.h>
16
17#include <sound/core.h>
18#include <sound/pcm.h>
19#include <sound/pcm_params.h>
20#include <sound/soc.h>
21#include <sound/initval.h>
22#include <sound/dmaengine_pcm.h>
23
24#define AXI_SPDIF_REG_CTRL 0x0
25#define AXI_SPDIF_REG_STAT 0x4
26#define AXI_SPDIF_REG_TX_FIFO 0xc
27
28#define AXI_SPDIF_CTRL_TXDATA BIT(1)
29#define AXI_SPDIF_CTRL_TXEN BIT(0)
30#define AXI_SPDIF_CTRL_CLKDIV_OFFSET 8
31#define AXI_SPDIF_CTRL_CLKDIV_MASK (0xff << 8)
32
33#define AXI_SPDIF_FREQ_44100 (0x0 << 6)
34#define AXI_SPDIF_FREQ_48000 (0x1 << 6)
35#define AXI_SPDIF_FREQ_32000 (0x2 << 6)
36#define AXI_SPDIF_FREQ_NA (0x3 << 6)
37
38struct axi_spdif {
39 struct regmap *regmap;
40 struct clk *clk;
41 struct clk *clk_ref;
42
43 struct snd_dmaengine_dai_dma_data dma_data;
44
45 struct snd_ratnum ratnum;
46 struct snd_pcm_hw_constraint_ratnums rate_constraints;
47};
48
49static int axi_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
50 struct snd_soc_dai *dai)
51{
52 struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
53 unsigned int val;
54
55 switch (cmd) {
56 case SNDRV_PCM_TRIGGER_START:
57 case SNDRV_PCM_TRIGGER_RESUME:
58 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
59 val = AXI_SPDIF_CTRL_TXDATA;
60 break;
61 case SNDRV_PCM_TRIGGER_STOP:
62 case SNDRV_PCM_TRIGGER_SUSPEND:
63 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
64 val = 0;
65 break;
66 default:
67 return -EINVAL;
68 }
69
70 regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
71 AXI_SPDIF_CTRL_TXDATA, val);
72
73 return 0;
74}
75
76static int axi_spdif_hw_params(struct snd_pcm_substream *substream,
77 struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
78{
79 struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
80 unsigned int rate = params_rate(params);
81 unsigned int clkdiv, stat;
82
83 switch (params_rate(params)) {
84 case 32000:
85 stat = AXI_SPDIF_FREQ_32000;
86 break;
87 case 44100:
88 stat = AXI_SPDIF_FREQ_44100;
89 break;
90 case 48000:
91 stat = AXI_SPDIF_FREQ_48000;
92 break;
93 default:
94 stat = AXI_SPDIF_FREQ_NA;
95 break;
96 }
97
98 clkdiv = DIV_ROUND_CLOSEST(clk_get_rate(spdif->clk_ref),
99 rate * 64 * 2) - 1;
100 clkdiv <<= AXI_SPDIF_CTRL_CLKDIV_OFFSET;
101
102 regmap_write(spdif->regmap, AXI_SPDIF_REG_STAT, stat);
103 regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
104 AXI_SPDIF_CTRL_CLKDIV_MASK, clkdiv);
105
106 return 0;
107}
108
109static int axi_spdif_dai_probe(struct snd_soc_dai *dai)
110{
111 struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
112
113 snd_soc_dai_init_dma_data(dai, &spdif->dma_data, NULL);
114
115 return 0;
116}
117
118static int axi_spdif_startup(struct snd_pcm_substream *substream,
119 struct snd_soc_dai *dai)
120{
121 struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
122 int ret;
123
124 ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
125 SNDRV_PCM_HW_PARAM_RATE,
126 &spdif->rate_constraints);
127 if (ret)
128 return ret;
129
130 ret = clk_prepare_enable(spdif->clk_ref);
131 if (ret)
132 return ret;
133
134 regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
135 AXI_SPDIF_CTRL_TXEN, AXI_SPDIF_CTRL_TXEN);
136
137 return 0;
138}
139
140static void axi_spdif_shutdown(struct snd_pcm_substream *substream,
141 struct snd_soc_dai *dai)
142{
143 struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
144
145 regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
146 AXI_SPDIF_CTRL_TXEN, 0);
147
148 clk_disable_unprepare(spdif->clk_ref);
149}
150
151static const struct snd_soc_dai_ops axi_spdif_dai_ops = {
152 .startup = axi_spdif_startup,
153 .shutdown = axi_spdif_shutdown,
154 .trigger = axi_spdif_trigger,
155 .hw_params = axi_spdif_hw_params,
156};
157
158static struct snd_soc_dai_driver axi_spdif_dai = {
159 .probe = axi_spdif_dai_probe,
160 .playback = {
161 .channels_min = 2,
162 .channels_max = 2,
163 .rates = SNDRV_PCM_RATE_KNOT,
164 .formats = SNDRV_PCM_FMTBIT_S16_LE,
165 },
166 .ops = &axi_spdif_dai_ops,
167};
168
169static const struct snd_soc_component_driver axi_spdif_component = {
170 .name = "axi-spdif",
171};
172
173static const struct regmap_config axi_spdif_regmap_config = {
174 .reg_bits = 32,
175 .reg_stride = 4,
176 .val_bits = 32,
177 .max_register = AXI_SPDIF_REG_STAT,
178};
179
180static int axi_spdif_probe(struct platform_device *pdev)
181{
182 struct axi_spdif *spdif;
183 struct resource *res;
184 void __iomem *base;
185 int ret;
186
187 spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL);
188 if (!spdif)
189 return -ENOMEM;
190
191 platform_set_drvdata(pdev, spdif);
192
193 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
194 base = devm_request_and_ioremap(&pdev->dev, res);
195 if (!base)
196 return -EBUSY;
197
198 spdif->regmap = devm_regmap_init_mmio(&pdev->dev, base,
199 &axi_spdif_regmap_config);
200 if (IS_ERR(spdif->regmap))
201 return PTR_ERR(spdif->regmap);
202
203 spdif->clk = devm_clk_get(&pdev->dev, "axi");
204 if (IS_ERR(spdif->clk))
205 return PTR_ERR(spdif->clk);
206
207 spdif->clk_ref = devm_clk_get(&pdev->dev, "ref");
208 if (IS_ERR(spdif->clk_ref))
209 return PTR_ERR(spdif->clk_ref);
210
211 ret = clk_prepare_enable(spdif->clk);
212 if (ret)
213 return ret;
214
215 spdif->dma_data.addr = res->start + AXI_SPDIF_REG_TX_FIFO;
216 spdif->dma_data.addr_width = 4;
217 spdif->dma_data.maxburst = 1;
218
219 spdif->ratnum.num = clk_get_rate(spdif->clk_ref) / 128;
220 spdif->ratnum.den_step = 1;
221 spdif->ratnum.den_min = 1;
222 spdif->ratnum.den_max = 64;
223
224 spdif->rate_constraints.rats = &spdif->ratnum;
225 spdif->rate_constraints.nrats = 1;
226
227 ret = devm_snd_soc_register_component(&pdev->dev, &axi_spdif_component,
228 &axi_spdif_dai, 1);
229 if (ret)
230 goto err_clk_disable;
231
232 ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
233 SND_DMAENGINE_PCM_FLAG_NO_RESIDUE);
234 if (ret)
235 goto err_clk_disable;
236
237 return 0;
238
239err_clk_disable:
240 clk_disable_unprepare(spdif->clk);
241 return ret;
242}
243
244static int axi_spdif_dev_remove(struct platform_device *pdev)
245{
246 struct axi_spdif *spdif = platform_get_drvdata(pdev);
247
248 clk_disable_unprepare(spdif->clk);
249
250 return 0;
251}
252
253static const struct of_device_id axi_spdif_of_match[] = {
254 { .compatible = "adi,axi-spdif-tx-1.00.a", },
255 {},
256};
257MODULE_DEVICE_TABLE(of, axi_spdif_of_match);
258
259static struct platform_driver axi_spdif_driver = {
260 .driver = {
261 .name = "axi-spdif",
262 .owner = THIS_MODULE,
263 .of_match_table = axi_spdif_of_match,
264 },
265 .probe = axi_spdif_probe,
266 .remove = axi_spdif_dev_remove,
267};
268module_platform_driver(axi_spdif_driver);
269
270MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
271MODULE_DESCRIPTION("AXI SPDIF driver");
272MODULE_LICENSE("GPL");