aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars-Peter Clausen <lars@metafoo.de>2013-12-06 05:02:50 -0500
committerMark Brown <broonie@linaro.org>2013-12-09 12:57:35 -0500
commit8f2fe346822419ee729c081b71c8835b733e0884 (patch)
treeadfa3c1e6b195365a739bc919c66fbc275ece096
parent00e6cb2aed48a86e97a244c6f96ac1f934e2272c (diff)
ASoC: Add support for the Analog Devices AXI-I2S core
This patch adds support for the AXI-I2S softcore. The core implements a simple bidirectional I2S transceiver and is used by Analog Devices in some of their reference designs for various FPGA platforms. 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/Kconfig1
-rw-r--r--sound/soc/Makefile1
-rw-r--r--sound/soc/adi/Kconfig13
-rw-r--r--sound/soc/adi/Makefile3
-rw-r--r--sound/soc/adi/axi-i2s.c277
5 files changed, 295 insertions, 0 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 5138b8493051..866dfec4b6b5 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -31,6 +31,7 @@ config SND_SOC_GENERIC_DMAENGINE_PCM
31 select SND_DMAENGINE_PCM 31 select SND_DMAENGINE_PCM
32 32
33# All the supported SoCs 33# All the supported SoCs
34source "sound/soc/adi/Kconfig"
34source "sound/soc/atmel/Kconfig" 35source "sound/soc/atmel/Kconfig"
35source "sound/soc/au1x/Kconfig" 36source "sound/soc/au1x/Kconfig"
36source "sound/soc/blackfin/Kconfig" 37source "sound/soc/blackfin/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 8b9e70105dd2..c70c7f76d2df 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -8,6 +8,7 @@ endif
8obj-$(CONFIG_SND_SOC) += snd-soc-core.o 8obj-$(CONFIG_SND_SOC) += snd-soc-core.o
9obj-$(CONFIG_SND_SOC) += codecs/ 9obj-$(CONFIG_SND_SOC) += codecs/
10obj-$(CONFIG_SND_SOC) += generic/ 10obj-$(CONFIG_SND_SOC) += generic/
11obj-$(CONFIG_SND_SOC) += adi/
11obj-$(CONFIG_SND_SOC) += atmel/ 12obj-$(CONFIG_SND_SOC) += atmel/
12obj-$(CONFIG_SND_SOC) += au1x/ 13obj-$(CONFIG_SND_SOC) += au1x/
13obj-$(CONFIG_SND_SOC) += blackfin/ 14obj-$(CONFIG_SND_SOC) += blackfin/
diff --git a/sound/soc/adi/Kconfig b/sound/soc/adi/Kconfig
new file mode 100644
index 000000000000..46f4b79f7a87
--- /dev/null
+++ b/sound/soc/adi/Kconfig
@@ -0,0 +1,13 @@
1config SND_SOC_ADI
2 tristate "Audio support for Analog Devices reference designs"
3 depends on MICROBLAZE || ARCH_ZYNQ || COMPILE_TEST
4 help
5 Audio support for various reference designs by Analog Devices.
6
7config SND_SOC_ADI_AXI_I2S
8 tristate "AXI-I2S support"
9 depends on SND_SOC_ADI
10 select SND_SOC_GENERIC_DMAENGINE_PCM
11 select REGMAP_MMIO
12 help
13 ASoC driver for the Analog Devices AXI-I2S softcore peripheral.
diff --git a/sound/soc/adi/Makefile b/sound/soc/adi/Makefile
new file mode 100644
index 000000000000..d32c21ae14b6
--- /dev/null
+++ b/sound/soc/adi/Makefile
@@ -0,0 +1,3 @@
1snd-soc-adi-axi-i2s-objs := axi-i2s.o
2
3obj-$(CONFIG_SND_SOC_ADI_AXI_I2S) += snd-soc-adi-axi-i2s.o
diff --git a/sound/soc/adi/axi-i2s.c b/sound/soc/adi/axi-i2s.c
new file mode 100644
index 000000000000..0822c7713669
--- /dev/null
+++ b/sound/soc/adi/axi-i2s.c
@@ -0,0 +1,277 @@
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/clk.h>
9#include <linux/init.h>
10#include <linux/kernel.h>
11#include <linux/module.h>
12#include <linux/of.h>
13#include <linux/platform_device.h>
14#include <linux/regmap.h>
15#include <linux/slab.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/dmaengine_pcm.h>
22
23#define AXI_I2S_REG_RESET 0x00
24#define AXI_I2S_REG_CTRL 0x04
25#define AXI_I2S_REG_CLK_CTRL 0x08
26#define AXI_I2S_REG_STATUS 0x10
27
28#define AXI_I2S_REG_RX_FIFO 0x28
29#define AXI_I2S_REG_TX_FIFO 0x2C
30
31#define AXI_I2S_RESET_GLOBAL BIT(0)
32#define AXI_I2S_RESET_TX_FIFO BIT(1)
33#define AXI_I2S_RESET_RX_FIFO BIT(2)
34
35#define AXI_I2S_CTRL_TX_EN BIT(0)
36#define AXI_I2S_CTRL_RX_EN BIT(1)
37
38/* The frame size is configurable, but for now we always set it 64 bit */
39#define AXI_I2S_BITS_PER_FRAME 64
40
41struct axi_i2s {
42 struct regmap *regmap;
43 struct clk *clk;
44 struct clk *clk_ref;
45
46 struct snd_soc_dai_driver dai_driver;
47
48 struct snd_dmaengine_dai_dma_data capture_dma_data;
49 struct snd_dmaengine_dai_dma_data playback_dma_data;
50
51 struct snd_ratnum ratnum;
52 struct snd_pcm_hw_constraint_ratnums rate_constraints;
53};
54
55static int axi_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
56 struct snd_soc_dai *dai)
57{
58 struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
59 unsigned int mask, val;
60
61 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
62 mask = AXI_I2S_CTRL_RX_EN;
63 else
64 mask = AXI_I2S_CTRL_TX_EN;
65
66 switch (cmd) {
67 case SNDRV_PCM_TRIGGER_START:
68 case SNDRV_PCM_TRIGGER_RESUME:
69 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
70 val = mask;
71 break;
72 case SNDRV_PCM_TRIGGER_STOP:
73 case SNDRV_PCM_TRIGGER_SUSPEND:
74 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
75 val = 0;
76 break;
77 default:
78 return -EINVAL;
79 }
80
81 regmap_update_bits(i2s->regmap, AXI_I2S_REG_CTRL, mask, val);
82
83 return 0;
84}
85
86static int axi_i2s_hw_params(struct snd_pcm_substream *substream,
87 struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
88{
89 struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
90 unsigned int bclk_div, word_size;
91 unsigned int bclk_rate;
92
93 bclk_rate = params_rate(params) * AXI_I2S_BITS_PER_FRAME;
94
95 word_size = AXI_I2S_BITS_PER_FRAME / 2 - 1;
96 bclk_div = DIV_ROUND_UP(clk_get_rate(i2s->clk_ref), bclk_rate) / 2 - 1;
97
98 regmap_write(i2s->regmap, AXI_I2S_REG_CLK_CTRL, (word_size << 16) |
99 bclk_div);
100
101 return 0;
102}
103
104static int axi_i2s_startup(struct snd_pcm_substream *substream,
105 struct snd_soc_dai *dai)
106{
107 struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
108 uint32_t mask;
109 int ret;
110
111 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
112 mask = AXI_I2S_RESET_RX_FIFO;
113 else
114 mask = AXI_I2S_RESET_TX_FIFO;
115
116 regmap_write(i2s->regmap, AXI_I2S_REG_RESET, mask);
117
118 ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
119 SNDRV_PCM_HW_PARAM_RATE,
120 &i2s->rate_constraints);
121 if (ret)
122 return ret;
123
124 return clk_prepare_enable(i2s->clk_ref);
125}
126
127static void axi_i2s_shutdown(struct snd_pcm_substream *substream,
128 struct snd_soc_dai *dai)
129{
130 struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
131
132 clk_disable_unprepare(i2s->clk_ref);
133}
134
135static int axi_i2s_dai_probe(struct snd_soc_dai *dai)
136{
137 struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
138
139 snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data,
140 &i2s->capture_dma_data);
141
142 return 0;
143}
144
145static const struct snd_soc_dai_ops axi_i2s_dai_ops = {
146 .startup = axi_i2s_startup,
147 .shutdown = axi_i2s_shutdown,
148 .trigger = axi_i2s_trigger,
149 .hw_params = axi_i2s_hw_params,
150};
151
152static struct snd_soc_dai_driver axi_i2s_dai = {
153 .probe = axi_i2s_dai_probe,
154 .playback = {
155 .channels_min = 2,
156 .channels_max = 2,
157 .rates = SNDRV_PCM_RATE_KNOT,
158 .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE,
159 },
160 .capture = {
161 .channels_min = 2,
162 .channels_max = 2,
163 .rates = SNDRV_PCM_RATE_KNOT,
164 .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE,
165 },
166 .ops = &axi_i2s_dai_ops,
167 .symmetric_rates = 1,
168};
169
170static const struct snd_soc_component_driver axi_i2s_component = {
171 .name = "axi-i2s",
172};
173
174static const struct regmap_config axi_i2s_regmap_config = {
175 .reg_bits = 32,
176 .reg_stride = 4,
177 .val_bits = 32,
178 .max_register = AXI_I2S_REG_STATUS,
179};
180
181static int axi_i2s_probe(struct platform_device *pdev)
182{
183 struct resource *res;
184 struct axi_i2s *i2s;
185 void __iomem *base;
186 int ret;
187
188 i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
189 if (!i2s)
190 return -ENOMEM;
191
192 platform_set_drvdata(pdev, i2s);
193
194 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
195 base = devm_request_and_ioremap(&pdev->dev, res);
196 if (!base)
197 return -EBUSY;
198
199 i2s->regmap = devm_regmap_init_mmio(&pdev->dev, base,
200 &axi_i2s_regmap_config);
201 if (IS_ERR(i2s->regmap))
202 return PTR_ERR(i2s->regmap);
203
204 i2s->clk = devm_clk_get(&pdev->dev, "axi");
205 if (IS_ERR(i2s->clk))
206 return PTR_ERR(i2s->clk);
207
208 i2s->clk_ref = devm_clk_get(&pdev->dev, "ref");
209 if (IS_ERR(i2s->clk_ref))
210 return PTR_ERR(i2s->clk_ref);
211
212 ret = clk_prepare_enable(i2s->clk);
213 if (ret)
214 return ret;
215
216 i2s->playback_dma_data.addr = res->start + AXI_I2S_REG_TX_FIFO;
217 i2s->playback_dma_data.addr_width = 4;
218 i2s->playback_dma_data.maxburst = 1;
219
220 i2s->capture_dma_data.addr = res->start + AXI_I2S_REG_RX_FIFO;
221 i2s->capture_dma_data.addr_width = 4;
222 i2s->capture_dma_data.maxburst = 1;
223
224 i2s->ratnum.num = clk_get_rate(i2s->clk_ref) / 2 / AXI_I2S_BITS_PER_FRAME;
225 i2s->ratnum.den_step = 1;
226 i2s->ratnum.den_min = 1;
227 i2s->ratnum.den_max = 64;
228
229 i2s->rate_constraints.rats = &i2s->ratnum;
230 i2s->rate_constraints.nrats = 1;
231
232 regmap_write(i2s->regmap, AXI_I2S_REG_RESET, AXI_I2S_RESET_GLOBAL);
233
234 ret = devm_snd_soc_register_component(&pdev->dev, &axi_i2s_component,
235 &axi_i2s_dai, 1);
236 if (ret)
237 goto err_clk_disable;
238
239 ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
240 SND_DMAENGINE_PCM_FLAG_NO_RESIDUE);
241 if (ret)
242 goto err_clk_disable;
243
244err_clk_disable:
245 clk_disable_unprepare(i2s->clk);
246 return ret;
247}
248
249static int axi_i2s_dev_remove(struct platform_device *pdev)
250{
251 struct axi_i2s *i2s = platform_get_drvdata(pdev);
252
253 clk_disable_unprepare(i2s->clk);
254
255 return 0;
256}
257
258static const struct of_device_id axi_i2s_of_match[] = {
259 { .compatible = "adi,axi-i2s-1.00.a", },
260 {},
261};
262MODULE_DEVICE_TABLE(of, axi_i2s_of_match);
263
264static struct platform_driver axi_i2s_driver = {
265 .driver = {
266 .name = "axi-i2s",
267 .owner = THIS_MODULE,
268 .of_match_table = axi_i2s_of_match,
269 },
270 .probe = axi_i2s_probe,
271 .remove = axi_i2s_dev_remove,
272};
273module_platform_driver(axi_i2s_driver);
274
275MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
276MODULE_DESCRIPTION("AXI I2S driver");
277MODULE_LICENSE("GPL");