1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) STMicroelectronics SA 2015 |
4 | * Authors: Arnaud Pouliquen <arnaud.pouliquen@st.com> |
5 | * for STMicroelectronics. |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/pinctrl/consumer.h> |
10 | #include <linux/delay.h> |
11 | |
12 | #include "uniperif.h" |
13 | |
14 | /* |
15 | * User frame size shall be 2, 4, 6 or 8 32-bits words length |
16 | * (i.e. 8, 16, 24 or 32 bytes) |
17 | * This constraint comes from allowed values for |
18 | * UNIPERIF_I2S_FMT_NUM_CH register |
19 | */ |
20 | #define UNIPERIF_MAX_FRAME_SZ 0x20 |
21 | #define UNIPERIF_ALLOWED_FRAME_SZ (0x08 | 0x10 | 0x18 | UNIPERIF_MAX_FRAME_SZ) |
22 | |
23 | struct sti_uniperiph_dev_data { |
24 | unsigned int id; /* Nb available player instances */ |
25 | unsigned int version; /* player IP version */ |
26 | unsigned int stream; |
27 | const char *dai_names; |
28 | enum uniperif_type type; |
29 | }; |
30 | |
31 | static const struct sti_uniperiph_dev_data sti_uniplayer_hdmi = { |
32 | .id = 0, |
33 | .version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0, |
34 | .stream = SNDRV_PCM_STREAM_PLAYBACK, |
35 | .dai_names = "Uni Player #0 (HDMI)" , |
36 | .type = SND_ST_UNIPERIF_TYPE_HDMI |
37 | }; |
38 | |
39 | static const struct sti_uniperiph_dev_data sti_uniplayer_pcm_out = { |
40 | .id = 1, |
41 | .version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0, |
42 | .stream = SNDRV_PCM_STREAM_PLAYBACK, |
43 | .dai_names = "Uni Player #1 (PCM OUT)" , |
44 | .type = SND_ST_UNIPERIF_TYPE_PCM | SND_ST_UNIPERIF_TYPE_TDM, |
45 | }; |
46 | |
47 | static const struct sti_uniperiph_dev_data sti_uniplayer_dac = { |
48 | .id = 2, |
49 | .version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0, |
50 | .stream = SNDRV_PCM_STREAM_PLAYBACK, |
51 | .dai_names = "Uni Player #2 (DAC)" , |
52 | .type = SND_ST_UNIPERIF_TYPE_PCM, |
53 | }; |
54 | |
55 | static const struct sti_uniperiph_dev_data sti_uniplayer_spdif = { |
56 | .id = 3, |
57 | .version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0, |
58 | .stream = SNDRV_PCM_STREAM_PLAYBACK, |
59 | .dai_names = "Uni Player #3 (SPDIF)" , |
60 | .type = SND_ST_UNIPERIF_TYPE_SPDIF |
61 | }; |
62 | |
63 | static const struct sti_uniperiph_dev_data sti_unireader_pcm_in = { |
64 | .id = 0, |
65 | .version = SND_ST_UNIPERIF_VERSION_UNI_RDR_1_0, |
66 | .stream = SNDRV_PCM_STREAM_CAPTURE, |
67 | .dai_names = "Uni Reader #0 (PCM IN)" , |
68 | .type = SND_ST_UNIPERIF_TYPE_PCM | SND_ST_UNIPERIF_TYPE_TDM, |
69 | }; |
70 | |
71 | static const struct sti_uniperiph_dev_data sti_unireader_hdmi_in = { |
72 | .id = 1, |
73 | .version = SND_ST_UNIPERIF_VERSION_UNI_RDR_1_0, |
74 | .stream = SNDRV_PCM_STREAM_CAPTURE, |
75 | .dai_names = "Uni Reader #1 (HDMI IN)" , |
76 | .type = SND_ST_UNIPERIF_TYPE_PCM, |
77 | }; |
78 | |
79 | static const struct of_device_id snd_soc_sti_match[] = { |
80 | { .compatible = "st,stih407-uni-player-hdmi" , |
81 | .data = &sti_uniplayer_hdmi |
82 | }, |
83 | { .compatible = "st,stih407-uni-player-pcm-out" , |
84 | .data = &sti_uniplayer_pcm_out |
85 | }, |
86 | { .compatible = "st,stih407-uni-player-dac" , |
87 | .data = &sti_uniplayer_dac |
88 | }, |
89 | { .compatible = "st,stih407-uni-player-spdif" , |
90 | .data = &sti_uniplayer_spdif |
91 | }, |
92 | { .compatible = "st,stih407-uni-reader-pcm_in" , |
93 | .data = &sti_unireader_pcm_in |
94 | }, |
95 | { .compatible = "st,stih407-uni-reader-hdmi" , |
96 | .data = &sti_unireader_hdmi_in |
97 | }, |
98 | {}, |
99 | }; |
100 | MODULE_DEVICE_TABLE(of, snd_soc_sti_match); |
101 | |
102 | int sti_uniperiph_reset(struct uniperif *uni) |
103 | { |
104 | int count = 10; |
105 | |
106 | /* Reset uniperipheral uni */ |
107 | SET_UNIPERIF_SOFT_RST_SOFT_RST(uni); |
108 | |
109 | if (uni->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) { |
110 | while (GET_UNIPERIF_SOFT_RST_SOFT_RST(uni) && count) { |
111 | udelay(5); |
112 | count--; |
113 | } |
114 | } |
115 | |
116 | if (!count) { |
117 | dev_err(uni->dev, "Failed to reset uniperif\n" ); |
118 | return -EIO; |
119 | } |
120 | |
121 | return 0; |
122 | } |
123 | |
124 | int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, |
125 | unsigned int rx_mask, int slots, |
126 | int slot_width) |
127 | { |
128 | struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); |
129 | struct uniperif *uni = priv->dai_data.uni; |
130 | int i, frame_size, avail_slots; |
131 | |
132 | if (!UNIPERIF_TYPE_IS_TDM(uni)) { |
133 | dev_err(uni->dev, "cpu dai not in tdm mode\n" ); |
134 | return -EINVAL; |
135 | } |
136 | |
137 | /* store info in unip context */ |
138 | uni->tdm_slot.slots = slots; |
139 | uni->tdm_slot.slot_width = slot_width; |
140 | /* unip is unidirectionnal */ |
141 | uni->tdm_slot.mask = (tx_mask != 0) ? tx_mask : rx_mask; |
142 | |
143 | /* number of available timeslots */ |
144 | for (i = 0, avail_slots = 0; i < uni->tdm_slot.slots; i++) { |
145 | if ((uni->tdm_slot.mask >> i) & 0x01) |
146 | avail_slots++; |
147 | } |
148 | uni->tdm_slot.avail_slots = avail_slots; |
149 | |
150 | /* frame size in bytes */ |
151 | frame_size = uni->tdm_slot.avail_slots * uni->tdm_slot.slot_width / 8; |
152 | |
153 | /* check frame size is allowed */ |
154 | if ((frame_size > UNIPERIF_MAX_FRAME_SZ) || |
155 | (frame_size & ~(int)UNIPERIF_ALLOWED_FRAME_SZ)) { |
156 | dev_err(uni->dev, "frame size not allowed: %d bytes\n" , |
157 | frame_size); |
158 | return -EINVAL; |
159 | } |
160 | |
161 | return 0; |
162 | } |
163 | |
164 | int sti_uniperiph_fix_tdm_chan(struct snd_pcm_hw_params *params, |
165 | struct snd_pcm_hw_rule *rule) |
166 | { |
167 | struct uniperif *uni = rule->private; |
168 | struct snd_interval t; |
169 | |
170 | t.min = uni->tdm_slot.avail_slots; |
171 | t.max = uni->tdm_slot.avail_slots; |
172 | t.openmin = 0; |
173 | t.openmax = 0; |
174 | t.integer = 0; |
175 | |
176 | return snd_interval_refine(i: hw_param_interval(params, var: rule->var), v: &t); |
177 | } |
178 | |
179 | int sti_uniperiph_fix_tdm_format(struct snd_pcm_hw_params *params, |
180 | struct snd_pcm_hw_rule *rule) |
181 | { |
182 | struct uniperif *uni = rule->private; |
183 | struct snd_mask *maskp = hw_param_mask(params, var: rule->var); |
184 | u64 format; |
185 | |
186 | switch (uni->tdm_slot.slot_width) { |
187 | case 16: |
188 | format = SNDRV_PCM_FMTBIT_S16_LE; |
189 | break; |
190 | case 32: |
191 | format = SNDRV_PCM_FMTBIT_S32_LE; |
192 | break; |
193 | default: |
194 | dev_err(uni->dev, "format not supported: %d bits\n" , |
195 | uni->tdm_slot.slot_width); |
196 | return -EINVAL; |
197 | } |
198 | |
199 | maskp->bits[0] &= (u_int32_t)format; |
200 | maskp->bits[1] &= (u_int32_t)(format >> 32); |
201 | /* clear remaining indexes */ |
202 | memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX - 64) / 8); |
203 | |
204 | if (!maskp->bits[0] && !maskp->bits[1]) |
205 | return -EINVAL; |
206 | |
207 | return 0; |
208 | } |
209 | |
210 | int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni, |
211 | unsigned int *word_pos) |
212 | { |
213 | int slot_width = uni->tdm_slot.slot_width / 8; |
214 | int slots_num = uni->tdm_slot.slots; |
215 | unsigned int slots_mask = uni->tdm_slot.mask; |
216 | int i, j, k; |
217 | unsigned int word16_pos[4]; |
218 | |
219 | /* word16_pos: |
220 | * word16_pos[0] = WORDX_LSB |
221 | * word16_pos[1] = WORDX_MSB, |
222 | * word16_pos[2] = WORDX+1_LSB |
223 | * word16_pos[3] = WORDX+1_MSB |
224 | */ |
225 | |
226 | /* set unip word position */ |
227 | for (i = 0, j = 0, k = 0; (i < slots_num) && (k < WORD_MAX); i++) { |
228 | if ((slots_mask >> i) & 0x01) { |
229 | word16_pos[j] = i * slot_width; |
230 | |
231 | if (slot_width == 4) { |
232 | word16_pos[j + 1] = word16_pos[j] + 2; |
233 | j++; |
234 | } |
235 | j++; |
236 | |
237 | if (j > 3) { |
238 | word_pos[k] = word16_pos[1] | |
239 | (word16_pos[0] << 8) | |
240 | (word16_pos[3] << 16) | |
241 | (word16_pos[2] << 24); |
242 | j = 0; |
243 | k++; |
244 | } |
245 | } |
246 | } |
247 | |
248 | return 0; |
249 | } |
250 | |
251 | /* |
252 | * sti_uniperiph_dai_create_ctrl |
253 | * This function is used to create Ctrl associated to DAI but also pcm device. |
254 | * Request is done by front end to associate ctrl with pcm device id |
255 | */ |
256 | static int sti_uniperiph_dai_create_ctrl(struct snd_soc_dai *dai) |
257 | { |
258 | struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); |
259 | struct uniperif *uni = priv->dai_data.uni; |
260 | struct snd_kcontrol_new *ctrl; |
261 | int i; |
262 | |
263 | if (!uni->num_ctrls) |
264 | return 0; |
265 | |
266 | for (i = 0; i < uni->num_ctrls; i++) { |
267 | /* |
268 | * Several Control can have same name. Controls are indexed on |
269 | * Uniperipheral instance ID |
270 | */ |
271 | ctrl = &uni->snd_ctrls[i]; |
272 | ctrl->index = uni->id; |
273 | ctrl->device = uni->id; |
274 | } |
275 | |
276 | return snd_soc_add_dai_controls(dai, controls: uni->snd_ctrls, num_controls: uni->num_ctrls); |
277 | } |
278 | |
279 | /* |
280 | * DAI |
281 | */ |
282 | int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream, |
283 | struct snd_pcm_hw_params *params, |
284 | struct snd_soc_dai *dai) |
285 | { |
286 | struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); |
287 | struct uniperif *uni = priv->dai_data.uni; |
288 | struct snd_dmaengine_dai_dma_data *dma_data; |
289 | int transfer_size; |
290 | |
291 | if (uni->type == SND_ST_UNIPERIF_TYPE_TDM) |
292 | /* transfer size = user frame size (in 32-bits FIFO cell) */ |
293 | transfer_size = snd_soc_params_to_frame_size(params) / 32; |
294 | else |
295 | transfer_size = params_channels(p: params) * UNIPERIF_FIFO_FRAMES; |
296 | |
297 | dma_data = snd_soc_dai_get_dma_data(dai, substream); |
298 | dma_data->maxburst = transfer_size; |
299 | |
300 | return 0; |
301 | } |
302 | |
303 | int sti_uniperiph_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
304 | { |
305 | struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); |
306 | |
307 | priv->dai_data.uni->daifmt = fmt; |
308 | |
309 | return 0; |
310 | } |
311 | |
312 | static int sti_uniperiph_suspend(struct snd_soc_component *component) |
313 | { |
314 | struct sti_uniperiph_data *priv = snd_soc_component_get_drvdata(c: component); |
315 | struct uniperif *uni = priv->dai_data.uni; |
316 | int ret; |
317 | |
318 | /* The uniperipheral should be in stopped state */ |
319 | if (uni->state != UNIPERIF_STATE_STOPPED) { |
320 | dev_err(uni->dev, "%s: invalid uni state( %d)\n" , |
321 | __func__, (int)uni->state); |
322 | return -EBUSY; |
323 | } |
324 | |
325 | /* Pinctrl: switch pinstate to sleep */ |
326 | ret = pinctrl_pm_select_sleep_state(dev: uni->dev); |
327 | if (ret) |
328 | dev_err(uni->dev, "%s: failed to select pinctrl state\n" , |
329 | __func__); |
330 | |
331 | return ret; |
332 | } |
333 | |
334 | static int sti_uniperiph_resume(struct snd_soc_component *component) |
335 | { |
336 | struct sti_uniperiph_data *priv = snd_soc_component_get_drvdata(c: component); |
337 | struct uniperif *uni = priv->dai_data.uni; |
338 | int ret; |
339 | |
340 | if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) { |
341 | ret = uni_player_resume(player: uni); |
342 | if (ret) |
343 | return ret; |
344 | } |
345 | |
346 | /* pinctrl: switch pinstate to default */ |
347 | ret = pinctrl_pm_select_default_state(dev: uni->dev); |
348 | if (ret) |
349 | dev_err(uni->dev, "%s: failed to select pinctrl state\n" , |
350 | __func__); |
351 | |
352 | return ret; |
353 | } |
354 | |
355 | static int sti_uniperiph_dai_probe(struct snd_soc_dai *dai) |
356 | { |
357 | struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); |
358 | struct sti_uniperiph_dai *dai_data = &priv->dai_data; |
359 | |
360 | /* DMA settings*/ |
361 | if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) |
362 | snd_soc_dai_init_dma_data(dai, playback: &dai_data->dma_data, NULL); |
363 | else |
364 | snd_soc_dai_init_dma_data(dai, NULL, capture: &dai_data->dma_data); |
365 | |
366 | dai_data->dma_data.addr = dai_data->uni->fifo_phys_address; |
367 | dai_data->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
368 | |
369 | return sti_uniperiph_dai_create_ctrl(dai); |
370 | } |
371 | |
372 | static const struct snd_soc_dai_ops sti_uniperiph_dai_ops = { |
373 | .probe = sti_uniperiph_dai_probe, |
374 | }; |
375 | |
376 | static const struct snd_soc_dai_driver sti_uniperiph_dai_template = { |
377 | .ops = &sti_uniperiph_dai_ops, |
378 | }; |
379 | |
380 | static const struct snd_soc_component_driver sti_uniperiph_dai_component = { |
381 | .name = "sti_cpu_dai" , |
382 | .suspend = sti_uniperiph_suspend, |
383 | .resume = sti_uniperiph_resume, |
384 | .legacy_dai_naming = 1, |
385 | }; |
386 | |
387 | static int sti_uniperiph_cpu_dai_of(struct device_node *node, |
388 | struct sti_uniperiph_data *priv) |
389 | { |
390 | struct device *dev = &priv->pdev->dev; |
391 | struct sti_uniperiph_dai *dai_data = &priv->dai_data; |
392 | struct snd_soc_dai_driver *dai = priv->dai; |
393 | struct snd_soc_pcm_stream *stream; |
394 | struct uniperif *uni; |
395 | const struct of_device_id *of_id; |
396 | const struct sti_uniperiph_dev_data *dev_data; |
397 | const char *mode; |
398 | int ret; |
399 | |
400 | /* Populate data structure depending on compatibility */ |
401 | of_id = of_match_node(matches: snd_soc_sti_match, node); |
402 | if (!of_id->data) { |
403 | dev_err(dev, "data associated to device is missing\n" ); |
404 | return -EINVAL; |
405 | } |
406 | dev_data = (struct sti_uniperiph_dev_data *)of_id->data; |
407 | |
408 | uni = devm_kzalloc(dev, size: sizeof(*uni), GFP_KERNEL); |
409 | if (!uni) |
410 | return -ENOMEM; |
411 | |
412 | uni->id = dev_data->id; |
413 | uni->ver = dev_data->version; |
414 | |
415 | *dai = sti_uniperiph_dai_template; |
416 | dai->name = dev_data->dai_names; |
417 | |
418 | /* Get resources and base address */ |
419 | uni->base = devm_platform_get_and_ioremap_resource(pdev: priv->pdev, index: 0, res: &uni->mem_region); |
420 | if (IS_ERR(ptr: uni->base)) |
421 | return PTR_ERR(ptr: uni->base); |
422 | |
423 | uni->fifo_phys_address = uni->mem_region->start + |
424 | UNIPERIF_FIFO_DATA_OFFSET(uni); |
425 | |
426 | uni->irq = platform_get_irq(priv->pdev, 0); |
427 | if (uni->irq < 0) |
428 | return -ENXIO; |
429 | |
430 | uni->type = dev_data->type; |
431 | |
432 | /* check if player should be configured for tdm */ |
433 | if (dev_data->type & SND_ST_UNIPERIF_TYPE_TDM) { |
434 | if (!of_property_read_string(np: node, propname: "st,tdm-mode" , out_string: &mode)) |
435 | uni->type = SND_ST_UNIPERIF_TYPE_TDM; |
436 | else |
437 | uni->type = SND_ST_UNIPERIF_TYPE_PCM; |
438 | } |
439 | |
440 | dai_data->uni = uni; |
441 | dai_data->stream = dev_data->stream; |
442 | |
443 | if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) { |
444 | ret = uni_player_init(pdev: priv->pdev, player: uni); |
445 | stream = &dai->playback; |
446 | } else { |
447 | ret = uni_reader_init(pdev: priv->pdev, reader: uni); |
448 | stream = &dai->capture; |
449 | } |
450 | if (ret < 0) |
451 | return ret; |
452 | |
453 | dai->ops = uni->dai_ops; |
454 | |
455 | stream->stream_name = dai->name; |
456 | stream->channels_min = uni->hw->channels_min; |
457 | stream->channels_max = uni->hw->channels_max; |
458 | stream->rates = uni->hw->rates; |
459 | stream->formats = uni->hw->formats; |
460 | |
461 | return 0; |
462 | } |
463 | |
464 | static int sti_uniperiph_probe(struct platform_device *pdev) |
465 | { |
466 | struct sti_uniperiph_data *priv; |
467 | struct device_node *node = pdev->dev.of_node; |
468 | int ret; |
469 | |
470 | /* Allocate the private data and the CPU_DAI array */ |
471 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
472 | if (!priv) |
473 | return -ENOMEM; |
474 | priv->dai = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv->dai), GFP_KERNEL); |
475 | if (!priv->dai) |
476 | return -ENOMEM; |
477 | |
478 | priv->pdev = pdev; |
479 | |
480 | ret = sti_uniperiph_cpu_dai_of(node, priv); |
481 | if (ret < 0) |
482 | return ret; |
483 | |
484 | dev_set_drvdata(dev: &pdev->dev, data: priv); |
485 | |
486 | ret = devm_snd_soc_register_component(dev: &pdev->dev, |
487 | component_driver: &sti_uniperiph_dai_component, |
488 | dai_drv: priv->dai, num_dai: 1); |
489 | if (ret < 0) |
490 | return ret; |
491 | |
492 | return devm_snd_dmaengine_pcm_register(dev: &pdev->dev, NULL, flags: 0); |
493 | } |
494 | |
495 | static struct platform_driver sti_uniperiph_driver = { |
496 | .driver = { |
497 | .name = "sti-uniperiph-dai" , |
498 | .of_match_table = snd_soc_sti_match, |
499 | }, |
500 | .probe = sti_uniperiph_probe, |
501 | }; |
502 | module_platform_driver(sti_uniperiph_driver); |
503 | |
504 | MODULE_DESCRIPTION("uniperipheral DAI driver" ); |
505 | MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>" ); |
506 | MODULE_LICENSE("GPL v2" ); |
507 | |