Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/sound/soc/adi/axi-i2s.c
26428 views
1
// SPDX-License-Identifier: GPL-2.0-only
2
/*
3
* Copyright (C) 2012-2013, Analog Devices Inc.
4
* Author: Lars-Peter Clausen <[email protected]>
5
*/
6
7
#include <linux/clk.h>
8
#include <linux/init.h>
9
#include <linux/kernel.h>
10
#include <linux/module.h>
11
#include <linux/of.h>
12
#include <linux/platform_device.h>
13
#include <linux/regmap.h>
14
#include <linux/slab.h>
15
16
#include <sound/core.h>
17
#include <sound/pcm.h>
18
#include <sound/pcm_params.h>
19
#include <sound/soc.h>
20
#include <sound/dmaengine_pcm.h>
21
22
#define AXI_I2S_REG_RESET 0x00
23
#define AXI_I2S_REG_CTRL 0x04
24
#define AXI_I2S_REG_CLK_CTRL 0x08
25
#define AXI_I2S_REG_STATUS 0x10
26
27
#define AXI_I2S_REG_RX_FIFO 0x28
28
#define AXI_I2S_REG_TX_FIFO 0x2C
29
30
#define AXI_I2S_RESET_GLOBAL BIT(0)
31
#define AXI_I2S_RESET_TX_FIFO BIT(1)
32
#define AXI_I2S_RESET_RX_FIFO BIT(2)
33
34
#define AXI_I2S_CTRL_TX_EN BIT(0)
35
#define AXI_I2S_CTRL_RX_EN BIT(1)
36
37
/* The frame size is configurable, but for now we always set it 64 bit */
38
#define AXI_I2S_BITS_PER_FRAME 64
39
40
struct axi_i2s {
41
struct regmap *regmap;
42
struct clk *clk;
43
struct clk *clk_ref;
44
45
bool has_capture;
46
bool has_playback;
47
48
struct snd_soc_dai_driver dai_driver;
49
50
struct snd_dmaengine_dai_dma_data capture_dma_data;
51
struct snd_dmaengine_dai_dma_data playback_dma_data;
52
53
struct snd_ratnum ratnum;
54
struct snd_pcm_hw_constraint_ratnums rate_constraints;
55
};
56
57
static int axi_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
58
struct snd_soc_dai *dai)
59
{
60
struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
61
unsigned int mask, val;
62
63
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
64
mask = AXI_I2S_CTRL_RX_EN;
65
else
66
mask = AXI_I2S_CTRL_TX_EN;
67
68
switch (cmd) {
69
case SNDRV_PCM_TRIGGER_START:
70
case SNDRV_PCM_TRIGGER_RESUME:
71
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
72
val = mask;
73
break;
74
case SNDRV_PCM_TRIGGER_STOP:
75
case SNDRV_PCM_TRIGGER_SUSPEND:
76
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
77
val = 0;
78
break;
79
default:
80
return -EINVAL;
81
}
82
83
regmap_update_bits(i2s->regmap, AXI_I2S_REG_CTRL, mask, val);
84
85
return 0;
86
}
87
88
static int axi_i2s_hw_params(struct snd_pcm_substream *substream,
89
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
90
{
91
struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
92
unsigned int bclk_div, word_size;
93
unsigned int bclk_rate;
94
95
bclk_rate = params_rate(params) * AXI_I2S_BITS_PER_FRAME;
96
97
word_size = AXI_I2S_BITS_PER_FRAME / 2 - 1;
98
bclk_div = DIV_ROUND_UP(clk_get_rate(i2s->clk_ref), bclk_rate) / 2 - 1;
99
100
regmap_write(i2s->regmap, AXI_I2S_REG_CLK_CTRL, (word_size << 16) |
101
bclk_div);
102
103
return 0;
104
}
105
106
static int axi_i2s_startup(struct snd_pcm_substream *substream,
107
struct snd_soc_dai *dai)
108
{
109
struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
110
uint32_t mask;
111
int ret;
112
113
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
114
mask = AXI_I2S_RESET_RX_FIFO;
115
else
116
mask = AXI_I2S_RESET_TX_FIFO;
117
118
regmap_write(i2s->regmap, AXI_I2S_REG_RESET, mask);
119
120
ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
121
SNDRV_PCM_HW_PARAM_RATE,
122
&i2s->rate_constraints);
123
if (ret)
124
return ret;
125
126
return clk_prepare_enable(i2s->clk_ref);
127
}
128
129
static void axi_i2s_shutdown(struct snd_pcm_substream *substream,
130
struct snd_soc_dai *dai)
131
{
132
struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
133
134
clk_disable_unprepare(i2s->clk_ref);
135
}
136
137
static int axi_i2s_dai_probe(struct snd_soc_dai *dai)
138
{
139
struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
140
141
snd_soc_dai_init_dma_data(
142
dai,
143
i2s->has_playback ? &i2s->playback_dma_data : NULL,
144
i2s->has_capture ? &i2s->capture_dma_data : NULL);
145
146
return 0;
147
}
148
149
static const struct snd_soc_dai_ops axi_i2s_dai_ops = {
150
.probe = axi_i2s_dai_probe,
151
.startup = axi_i2s_startup,
152
.shutdown = axi_i2s_shutdown,
153
.trigger = axi_i2s_trigger,
154
.hw_params = axi_i2s_hw_params,
155
};
156
157
static struct snd_soc_dai_driver axi_i2s_dai = {
158
.ops = &axi_i2s_dai_ops,
159
.symmetric_rate = 1,
160
};
161
162
static const struct snd_soc_component_driver axi_i2s_component = {
163
.name = "axi-i2s",
164
.legacy_dai_naming = 1,
165
};
166
167
static const struct regmap_config axi_i2s_regmap_config = {
168
.reg_bits = 32,
169
.reg_stride = 4,
170
.val_bits = 32,
171
.max_register = AXI_I2S_REG_STATUS,
172
};
173
174
static void axi_i2s_parse_of(struct axi_i2s *i2s, const struct device_node *np)
175
{
176
struct property *dma_names;
177
const char *dma_name;
178
179
of_property_for_each_string(np, "dma-names", dma_names, dma_name) {
180
if (strcmp(dma_name, "rx") == 0)
181
i2s->has_capture = true;
182
if (strcmp(dma_name, "tx") == 0)
183
i2s->has_playback = true;
184
}
185
}
186
187
static int axi_i2s_probe(struct platform_device *pdev)
188
{
189
struct resource *res;
190
struct axi_i2s *i2s;
191
void __iomem *base;
192
int ret;
193
194
i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
195
if (!i2s)
196
return -ENOMEM;
197
198
platform_set_drvdata(pdev, i2s);
199
200
axi_i2s_parse_of(i2s, pdev->dev.of_node);
201
202
base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
203
if (IS_ERR(base))
204
return PTR_ERR(base);
205
206
i2s->regmap = devm_regmap_init_mmio(&pdev->dev, base,
207
&axi_i2s_regmap_config);
208
if (IS_ERR(i2s->regmap))
209
return PTR_ERR(i2s->regmap);
210
211
i2s->clk = devm_clk_get(&pdev->dev, "axi");
212
if (IS_ERR(i2s->clk))
213
return PTR_ERR(i2s->clk);
214
215
i2s->clk_ref = devm_clk_get(&pdev->dev, "ref");
216
if (IS_ERR(i2s->clk_ref))
217
return PTR_ERR(i2s->clk_ref);
218
219
ret = clk_prepare_enable(i2s->clk);
220
if (ret)
221
return ret;
222
223
if (i2s->has_playback) {
224
axi_i2s_dai.playback.channels_min = 2;
225
axi_i2s_dai.playback.channels_max = 2;
226
axi_i2s_dai.playback.rates = SNDRV_PCM_RATE_KNOT;
227
axi_i2s_dai.playback.formats =
228
SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE;
229
230
i2s->playback_dma_data.addr = res->start + AXI_I2S_REG_TX_FIFO;
231
i2s->playback_dma_data.addr_width = 4;
232
i2s->playback_dma_data.maxburst = 1;
233
}
234
235
if (i2s->has_capture) {
236
axi_i2s_dai.capture.channels_min = 2;
237
axi_i2s_dai.capture.channels_max = 2;
238
axi_i2s_dai.capture.rates = SNDRV_PCM_RATE_KNOT;
239
axi_i2s_dai.capture.formats =
240
SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE;
241
242
i2s->capture_dma_data.addr = res->start + AXI_I2S_REG_RX_FIFO;
243
i2s->capture_dma_data.addr_width = 4;
244
i2s->capture_dma_data.maxburst = 1;
245
}
246
247
i2s->ratnum.num = clk_get_rate(i2s->clk_ref) / 2 / AXI_I2S_BITS_PER_FRAME;
248
i2s->ratnum.den_step = 1;
249
i2s->ratnum.den_min = 1;
250
i2s->ratnum.den_max = 64;
251
252
i2s->rate_constraints.rats = &i2s->ratnum;
253
i2s->rate_constraints.nrats = 1;
254
255
regmap_write(i2s->regmap, AXI_I2S_REG_RESET, AXI_I2S_RESET_GLOBAL);
256
257
ret = devm_snd_soc_register_component(&pdev->dev, &axi_i2s_component,
258
&axi_i2s_dai, 1);
259
if (ret)
260
goto err_clk_disable;
261
262
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
263
if (ret)
264
goto err_clk_disable;
265
266
dev_info(&pdev->dev, "probed, capture %s, playback %s\n",
267
str_enabled_disabled(i2s->has_capture),
268
str_enabled_disabled(i2s->has_playback));
269
270
return 0;
271
272
err_clk_disable:
273
clk_disable_unprepare(i2s->clk);
274
return ret;
275
}
276
277
static void axi_i2s_dev_remove(struct platform_device *pdev)
278
{
279
struct axi_i2s *i2s = platform_get_drvdata(pdev);
280
281
clk_disable_unprepare(i2s->clk);
282
}
283
284
static const struct of_device_id axi_i2s_of_match[] = {
285
{ .compatible = "adi,axi-i2s-1.00.a", },
286
{},
287
};
288
MODULE_DEVICE_TABLE(of, axi_i2s_of_match);
289
290
static struct platform_driver axi_i2s_driver = {
291
.driver = {
292
.name = "axi-i2s",
293
.of_match_table = axi_i2s_of_match,
294
},
295
.probe = axi_i2s_probe,
296
.remove = axi_i2s_dev_remove,
297
};
298
module_platform_driver(axi_i2s_driver);
299
300
MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
301
MODULE_DESCRIPTION("AXI I2S driver");
302
MODULE_LICENSE("GPL");
303
304