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 + dev->l_reg); \ |
35 | iowrite32(p[tx_ptr][1], dev->i2s_base + dev->r_reg); \ |
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 | |
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 + dev->l_reg); \ |
55 | p[rx_ptr][1] = ioread32(dev->i2s_base + dev->r_reg); \ |
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 | |
64 | dw_pcm_tx_fn(16); |
65 | dw_pcm_tx_fn(32); |
66 | dw_pcm_rx_fn(16); |
67 | dw_pcm_rx_fn(32); |
68 | |
69 | #undef dw_pcm_tx_fn |
70 | #undef dw_pcm_rx_fn |
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 | |
83 | SNDRV_PCM_FMTBIT_S24_LE | |
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 | |
95 | static void dw_pcm_transfer(struct dw_i2s_dev *dev, bool push) |
96 | { |
97 | struct snd_pcm_substream *substream; |
98 | bool active, period_elapsed; |
99 | |
100 | rcu_read_lock(); |
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 | } |
121 | |
122 | if (period_elapsed) |
123 | snd_pcm_period_elapsed(substream); |
124 | } |
125 | rcu_read_unlock(); |
126 | } |
127 | |
128 | void dw_pcm_push_tx(struct dw_i2s_dev *dev) |
129 | { |
130 | dw_pcm_transfer(dev, push: true); |
131 | } |
132 | |
133 | void dw_pcm_pop_rx(struct dw_i2s_dev *dev) |
134 | { |
135 | dw_pcm_transfer(dev, push: false); |
136 | } |
137 | |
138 | static int dw_pcm_open(struct snd_soc_component *component, |
139 | struct snd_pcm_substream *substream) |
140 | { |
141 | struct snd_pcm_runtime *runtime = substream->runtime; |
142 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
143 | struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); |
144 | |
145 | snd_soc_set_runtime_hwparams(substream, hw: &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 | |
152 | static int dw_pcm_close(struct snd_soc_component *component, |
153 | struct snd_pcm_substream *substream) |
154 | { |
155 | synchronize_rcu(); |
156 | return 0; |
157 | } |
158 | |
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) |
162 | { |
163 | struct snd_pcm_runtime *runtime = substream->runtime; |
164 | struct dw_i2s_dev *dev = runtime->private_data; |
165 | |
166 | switch (params_channels(p: hw_params)) { |
167 | case 2: |
168 | break; |
169 | default: |
170 | dev_err(dev->dev, "invalid channels number\n" ); |
171 | return -EINVAL; |
172 | } |
173 | |
174 | switch (params_format(p: hw_params)) { |
175 | case SNDRV_PCM_FORMAT_S16_LE: |
176 | dev->tx_fn = dw_pcm_tx_16; |
177 | dev->rx_fn = dw_pcm_rx_16; |
178 | break; |
179 | case SNDRV_PCM_FORMAT_S24_LE: |
180 | case SNDRV_PCM_FORMAT_S32_LE: |
181 | dev->tx_fn = dw_pcm_tx_32; |
182 | dev->rx_fn = dw_pcm_rx_32; |
183 | break; |
184 | default: |
185 | dev_err(dev->dev, "invalid format\n" ); |
186 | return -EINVAL; |
187 | } |
188 | |
189 | return 0; |
190 | } |
191 | |
192 | static int dw_pcm_trigger(struct snd_soc_component *component, |
193 | struct snd_pcm_substream *substream, int cmd) |
194 | { |
195 | struct snd_pcm_runtime *runtime = substream->runtime; |
196 | struct dw_i2s_dev *dev = runtime->private_data; |
197 | int ret = 0; |
198 | |
199 | switch (cmd) { |
200 | case SNDRV_PCM_TRIGGER_START: |
201 | case SNDRV_PCM_TRIGGER_RESUME: |
202 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
203 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
204 | WRITE_ONCE(dev->tx_ptr, 0); |
205 | rcu_assign_pointer(dev->tx_substream, substream); |
206 | } else { |
207 | WRITE_ONCE(dev->rx_ptr, 0); |
208 | rcu_assign_pointer(dev->rx_substream, substream); |
209 | } |
210 | break; |
211 | case SNDRV_PCM_TRIGGER_STOP: |
212 | case SNDRV_PCM_TRIGGER_SUSPEND: |
213 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
214 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
215 | rcu_assign_pointer(dev->tx_substream, NULL); |
216 | else |
217 | rcu_assign_pointer(dev->rx_substream, NULL); |
218 | break; |
219 | default: |
220 | ret = -EINVAL; |
221 | break; |
222 | } |
223 | |
224 | return ret; |
225 | } |
226 | |
227 | static snd_pcm_uframes_t dw_pcm_pointer(struct snd_soc_component *component, |
228 | struct snd_pcm_substream *substream) |
229 | { |
230 | struct snd_pcm_runtime *runtime = substream->runtime; |
231 | struct dw_i2s_dev *dev = runtime->private_data; |
232 | snd_pcm_uframes_t pos; |
233 | |
234 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
235 | pos = READ_ONCE(dev->tx_ptr); |
236 | else |
237 | pos = READ_ONCE(dev->rx_ptr); |
238 | |
239 | return pos < runtime->buffer_size ? pos : 0; |
240 | } |
241 | |
242 | static int dw_pcm_new(struct snd_soc_component *component, |
243 | struct snd_soc_pcm_runtime *rtd) |
244 | { |
245 | size_t size = dw_pcm_hardware.buffer_bytes_max; |
246 | |
247 | snd_pcm_set_managed_buffer_all(pcm: rtd->pcm, |
248 | SNDRV_DMA_TYPE_CONTINUOUS, |
249 | NULL, size, max: size); |
250 | return 0; |
251 | } |
252 | |
253 | static const struct snd_soc_component_driver dw_pcm_component = { |
254 | .open = dw_pcm_open, |
255 | .close = dw_pcm_close, |
256 | .hw_params = dw_pcm_hw_params, |
257 | .trigger = dw_pcm_trigger, |
258 | .pointer = dw_pcm_pointer, |
259 | .pcm_construct = dw_pcm_new, |
260 | }; |
261 | |
262 | int dw_pcm_register(struct platform_device *pdev) |
263 | { |
264 | return devm_snd_soc_register_component(dev: &pdev->dev, component_driver: &dw_pcm_component, |
265 | NULL, num_dai: 0); |
266 | } |
267 | |