Commit | Line | Data |
---|---|---|
79361b2b JA |
1 | /* |
2 | * ALSA SoC Synopsys PIO PCM for I2S driver | |
3 | * | |
4 | * sound/soc/dwc/designware_pcm.c | |
5 | * | |
6 | * Copyright (C) 2016 Synopsys | |
7 | * Jose Abreu <joabreu@synopsys.com> | |
8 | * | |
9 | * This file is licensed under the terms of the GNU General Public | |
10 | * License version 2. This program is licensed "as is" without any | |
11 | * warranty of any kind, whether express or implied. | |
12 | */ | |
13 | ||
14 | #include <linux/io.h> | |
15 | #include <linux/rcupdate.h> | |
16 | #include <sound/pcm.h> | |
17 | #include <sound/pcm_params.h> | |
18 | #include "local.h" | |
19 | ||
20 | #define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN) | |
21 | #define PERIOD_BYTES_MIN 4096 | |
22 | #define PERIODS_MIN 2 | |
23 | ||
24 | #define dw_pcm_tx_fn(sample_bits) \ | |
25 | static unsigned int dw_pcm_tx_##sample_bits(struct dw_i2s_dev *dev, \ | |
26 | struct snd_pcm_runtime *runtime, unsigned int tx_ptr, \ | |
27 | bool *period_elapsed) \ | |
28 | { \ | |
29 | const u##sample_bits (*p)[2] = (void *)runtime->dma_area; \ | |
30 | unsigned int period_pos = tx_ptr % runtime->period_size; \ | |
31 | int i; \ | |
32 | \ | |
33 | for (i = 0; i < dev->fifo_th; i++) { \ | |
34 | iowrite32(p[tx_ptr][0], dev->i2s_base + LRBR_LTHR(0)); \ | |
35 | iowrite32(p[tx_ptr][1], dev->i2s_base + RRBR_RTHR(0)); \ | |
36 | period_pos++; \ | |
37 | if (++tx_ptr >= runtime->buffer_size) \ | |
38 | tx_ptr = 0; \ | |
39 | } \ | |
40 | *period_elapsed = period_pos >= runtime->period_size; \ | |
41 | return tx_ptr; \ | |
42 | } | |
43 | ||
e2f748e0 JA |
44 | #define dw_pcm_rx_fn(sample_bits) \ |
45 | static unsigned int dw_pcm_rx_##sample_bits(struct dw_i2s_dev *dev, \ | |
46 | struct snd_pcm_runtime *runtime, unsigned int rx_ptr, \ | |
47 | bool *period_elapsed) \ | |
48 | { \ | |
49 | u##sample_bits (*p)[2] = (void *)runtime->dma_area; \ | |
50 | unsigned int period_pos = rx_ptr % runtime->period_size; \ | |
51 | int i; \ | |
52 | \ | |
53 | for (i = 0; i < dev->fifo_th; i++) { \ | |
54 | p[rx_ptr][0] = ioread32(dev->i2s_base + LRBR_LTHR(0)); \ | |
55 | p[rx_ptr][1] = ioread32(dev->i2s_base + RRBR_RTHR(0)); \ | |
56 | period_pos++; \ | |
57 | if (++rx_ptr >= runtime->buffer_size) \ | |
58 | rx_ptr = 0; \ | |
59 | } \ | |
60 | *period_elapsed = period_pos >= runtime->period_size; \ | |
61 | return rx_ptr; \ | |
62 | } | |
63 | ||
79361b2b JA |
64 | dw_pcm_tx_fn(16); |
65 | dw_pcm_tx_fn(32); | |
e2f748e0 JA |
66 | dw_pcm_rx_fn(16); |
67 | dw_pcm_rx_fn(32); | |
79361b2b JA |
68 | |
69 | #undef dw_pcm_tx_fn | |
e2f748e0 | 70 | #undef dw_pcm_rx_fn |
79361b2b JA |
71 | |
72 | static const struct snd_pcm_hardware dw_pcm_hardware = { | |
73 | .info = SNDRV_PCM_INFO_INTERLEAVED | | |
74 | SNDRV_PCM_INFO_MMAP | | |
75 | SNDRV_PCM_INFO_MMAP_VALID | | |
76 | SNDRV_PCM_INFO_BLOCK_TRANSFER, | |
77 | .rates = SNDRV_PCM_RATE_32000 | | |
78 | SNDRV_PCM_RATE_44100 | | |
79 | SNDRV_PCM_RATE_48000, | |
80 | .rate_min = 32000, | |
81 | .rate_max = 48000, | |
82 | .formats = SNDRV_PCM_FMTBIT_S16_LE | | |
e21ab179 | 83 | SNDRV_PCM_FMTBIT_S24_LE | |
79361b2b JA |
84 | SNDRV_PCM_FMTBIT_S32_LE, |
85 | .channels_min = 2, | |
86 | .channels_max = 2, | |
87 | .buffer_bytes_max = BUFFER_BYTES_MAX, | |
88 | .period_bytes_min = PERIOD_BYTES_MIN, | |
89 | .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, | |
90 | .periods_min = PERIODS_MIN, | |
91 | .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, | |
92 | .fifo_size = 16, | |
93 | }; | |
94 | ||
e2f748e0 | 95 | static void dw_pcm_transfer(struct dw_i2s_dev *dev, bool push) |
79361b2b | 96 | { |
e2f748e0 JA |
97 | struct snd_pcm_substream *substream; |
98 | bool active, period_elapsed; | |
79361b2b JA |
99 | |
100 | rcu_read_lock(); | |
e2f748e0 JA |
101 | if (push) |
102 | substream = rcu_dereference(dev->tx_substream); | |
103 | else | |
104 | substream = rcu_dereference(dev->rx_substream); | |
105 | active = substream && snd_pcm_running(substream); | |
106 | if (active) { | |
107 | unsigned int ptr; | |
108 | unsigned int new_ptr; | |
109 | ||
110 | if (push) { | |
111 | ptr = READ_ONCE(dev->tx_ptr); | |
112 | new_ptr = dev->tx_fn(dev, substream->runtime, ptr, | |
113 | &period_elapsed); | |
114 | cmpxchg(&dev->tx_ptr, ptr, new_ptr); | |
115 | } else { | |
116 | ptr = READ_ONCE(dev->rx_ptr); | |
117 | new_ptr = dev->rx_fn(dev, substream->runtime, ptr, | |
118 | &period_elapsed); | |
119 | cmpxchg(&dev->rx_ptr, ptr, new_ptr); | |
120 | } | |
79361b2b JA |
121 | |
122 | if (period_elapsed) | |
e2f748e0 | 123 | snd_pcm_period_elapsed(substream); |
79361b2b JA |
124 | } |
125 | rcu_read_unlock(); | |
126 | } | |
e2f748e0 JA |
127 | |
128 | void dw_pcm_push_tx(struct dw_i2s_dev *dev) | |
129 | { | |
130 | dw_pcm_transfer(dev, true); | |
131 | } | |
79361b2b | 132 | |
e2f748e0 JA |
133 | void dw_pcm_pop_rx(struct dw_i2s_dev *dev) |
134 | { | |
135 | dw_pcm_transfer(dev, false); | |
136 | } | |
e2f748e0 | 137 | |
dfd00af5 KM |
138 | static int dw_pcm_open(struct snd_soc_component *component, |
139 | struct snd_pcm_substream *substream) | |
79361b2b JA |
140 | { |
141 | struct snd_pcm_runtime *runtime = substream->runtime; | |
142 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
143 | struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); | |
144 | ||
145 | snd_soc_set_runtime_hwparams(substream, &dw_pcm_hardware); | |
146 | snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); | |
147 | runtime->private_data = dev; | |
148 | ||
149 | return 0; | |
150 | } | |
151 | ||
dfd00af5 KM |
152 | static int dw_pcm_close(struct snd_soc_component *component, |
153 | struct snd_pcm_substream *substream) | |
79361b2b JA |
154 | { |
155 | synchronize_rcu(); | |
156 | return 0; | |
157 | } | |
158 | ||
dfd00af5 KM |
159 | static int dw_pcm_hw_params(struct snd_soc_component *component, |
160 | struct snd_pcm_substream *substream, | |
161 | struct snd_pcm_hw_params *hw_params) | |
79361b2b JA |
162 | { |
163 | struct snd_pcm_runtime *runtime = substream->runtime; | |
164 | struct dw_i2s_dev *dev = runtime->private_data; | |
165 | int ret; | |
166 | ||
167 | switch (params_channels(hw_params)) { | |
168 | case 2: | |
169 | break; | |
170 | default: | |
171 | dev_err(dev->dev, "invalid channels number\n"); | |
172 | return -EINVAL; | |
173 | } | |
174 | ||
175 | switch (params_format(hw_params)) { | |
176 | case SNDRV_PCM_FORMAT_S16_LE: | |
177 | dev->tx_fn = dw_pcm_tx_16; | |
e2f748e0 | 178 | dev->rx_fn = dw_pcm_rx_16; |
79361b2b | 179 | break; |
e21ab179 | 180 | case SNDRV_PCM_FORMAT_S24_LE: |
79361b2b JA |
181 | case SNDRV_PCM_FORMAT_S32_LE: |
182 | dev->tx_fn = dw_pcm_tx_32; | |
e2f748e0 | 183 | dev->rx_fn = dw_pcm_rx_32; |
79361b2b JA |
184 | break; |
185 | default: | |
186 | dev_err(dev->dev, "invalid format\n"); | |
187 | return -EINVAL; | |
188 | } | |
189 | ||
79361b2b JA |
190 | ret = snd_pcm_lib_malloc_pages(substream, |
191 | params_buffer_bytes(hw_params)); | |
192 | if (ret < 0) | |
193 | return ret; | |
194 | else | |
195 | return 0; | |
196 | } | |
197 | ||
dfd00af5 KM |
198 | static int dw_pcm_hw_free(struct snd_soc_component *component, |
199 | struct snd_pcm_substream *substream) | |
79361b2b JA |
200 | { |
201 | return snd_pcm_lib_free_pages(substream); | |
202 | } | |
203 | ||
dfd00af5 KM |
204 | static int dw_pcm_trigger(struct snd_soc_component *component, |
205 | struct snd_pcm_substream *substream, int cmd) | |
79361b2b JA |
206 | { |
207 | struct snd_pcm_runtime *runtime = substream->runtime; | |
208 | struct dw_i2s_dev *dev = runtime->private_data; | |
209 | int ret = 0; | |
210 | ||
211 | switch (cmd) { | |
212 | case SNDRV_PCM_TRIGGER_START: | |
213 | case SNDRV_PCM_TRIGGER_RESUME: | |
214 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | |
e2f748e0 JA |
215 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
216 | WRITE_ONCE(dev->tx_ptr, 0); | |
217 | rcu_assign_pointer(dev->tx_substream, substream); | |
218 | } else { | |
219 | WRITE_ONCE(dev->rx_ptr, 0); | |
220 | rcu_assign_pointer(dev->rx_substream, substream); | |
221 | } | |
79361b2b JA |
222 | break; |
223 | case SNDRV_PCM_TRIGGER_STOP: | |
224 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
225 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
e2f748e0 JA |
226 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
227 | rcu_assign_pointer(dev->tx_substream, NULL); | |
228 | else | |
229 | rcu_assign_pointer(dev->rx_substream, NULL); | |
79361b2b JA |
230 | break; |
231 | default: | |
232 | ret = -EINVAL; | |
233 | break; | |
234 | } | |
235 | ||
236 | return ret; | |
237 | } | |
238 | ||
dfd00af5 KM |
239 | static snd_pcm_uframes_t dw_pcm_pointer(struct snd_soc_component *component, |
240 | struct snd_pcm_substream *substream) | |
79361b2b JA |
241 | { |
242 | struct snd_pcm_runtime *runtime = substream->runtime; | |
243 | struct dw_i2s_dev *dev = runtime->private_data; | |
e2f748e0 JA |
244 | snd_pcm_uframes_t pos; |
245 | ||
246 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
247 | pos = READ_ONCE(dev->tx_ptr); | |
248 | else | |
249 | pos = READ_ONCE(dev->rx_ptr); | |
79361b2b JA |
250 | |
251 | return pos < runtime->buffer_size ? pos : 0; | |
252 | } | |
253 | ||
dfd00af5 KM |
254 | static int dw_pcm_new(struct snd_soc_component *component, |
255 | struct snd_soc_pcm_runtime *rtd) | |
79361b2b JA |
256 | { |
257 | size_t size = dw_pcm_hardware.buffer_bytes_max; | |
258 | ||
ad8ba770 | 259 | snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, |
79361b2b JA |
260 | SNDRV_DMA_TYPE_CONTINUOUS, |
261 | snd_dma_continuous_data(GFP_KERNEL), size, size); | |
ad8ba770 | 262 | return 0; |
79361b2b JA |
263 | } |
264 | ||
dfd00af5 KM |
265 | static void dw_pcm_free(struct snd_soc_component *component, |
266 | struct snd_pcm *pcm) | |
79361b2b JA |
267 | { |
268 | snd_pcm_lib_preallocate_free_for_all(pcm); | |
269 | } | |
270 | ||
ddce03ce | 271 | static const struct snd_soc_component_driver dw_pcm_component = { |
dfd00af5 KM |
272 | .open = dw_pcm_open, |
273 | .close = dw_pcm_close, | |
274 | .ioctl = snd_soc_pcm_lib_ioctl, | |
275 | .hw_params = dw_pcm_hw_params, | |
276 | .hw_free = dw_pcm_hw_free, | |
277 | .trigger = dw_pcm_trigger, | |
278 | .pointer = dw_pcm_pointer, | |
279 | .pcm_construct = dw_pcm_new, | |
280 | .pcm_destruct = dw_pcm_free, | |
79361b2b JA |
281 | }; |
282 | ||
283 | int dw_pcm_register(struct platform_device *pdev) | |
284 | { | |
ddce03ce KM |
285 | return devm_snd_soc_register_component(&pdev->dev, &dw_pcm_component, |
286 | NULL, 0); | |
79361b2b | 287 | } |