]>
Commit | Line | Data |
---|---|---|
f3bd847e AP |
1 | /* |
2 | * Copyright (C) STMicroelectronics SA 2015 | |
3 | * Authors: Arnaud Pouliquen <arnaud.pouliquen@st.com> | |
4 | * for STMicroelectronics. | |
5 | * License terms: GNU General Public License (GPL), version 2 | |
6 | */ | |
7 | ||
8 | #include <linux/module.h> | |
bf9185dd | 9 | #include <linux/pinctrl/consumer.h> |
4c88f89f | 10 | #include <linux/delay.h> |
f3bd847e AP |
11 | |
12 | #include "uniperif.h" | |
13 | ||
44f948bd MG |
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 | ||
5a4326d1 AP |
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 | ||
4c88f89f AP |
101 | int sti_uniperiph_reset(struct uniperif *uni) |
102 | { | |
103 | int count = 10; | |
104 | ||
105 | /* Reset uniperipheral uni */ | |
106 | SET_UNIPERIF_SOFT_RST_SOFT_RST(uni); | |
107 | ||
108 | if (uni->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) { | |
109 | while (GET_UNIPERIF_SOFT_RST_SOFT_RST(uni) && count) { | |
110 | udelay(5); | |
111 | count--; | |
112 | } | |
113 | } | |
114 | ||
115 | if (!count) { | |
116 | dev_err(uni->dev, "Failed to reset uniperif\n"); | |
117 | return -EIO; | |
118 | } | |
119 | ||
120 | return 0; | |
121 | } | |
122 | ||
44f948bd MG |
123 | int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, |
124 | unsigned int rx_mask, int slots, | |
125 | int slot_width) | |
126 | { | |
127 | struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); | |
128 | struct uniperif *uni = priv->dai_data.uni; | |
129 | int i, frame_size, avail_slots; | |
130 | ||
131 | if (!UNIPERIF_TYPE_IS_TDM(uni)) { | |
132 | dev_err(uni->dev, "cpu dai not in tdm mode\n"); | |
133 | return -EINVAL; | |
134 | } | |
135 | ||
136 | /* store info in unip context */ | |
137 | uni->tdm_slot.slots = slots; | |
138 | uni->tdm_slot.slot_width = slot_width; | |
139 | /* unip is unidirectionnal */ | |
140 | uni->tdm_slot.mask = (tx_mask != 0) ? tx_mask : rx_mask; | |
141 | ||
142 | /* number of available timeslots */ | |
143 | for (i = 0, avail_slots = 0; i < uni->tdm_slot.slots; i++) { | |
144 | if ((uni->tdm_slot.mask >> i) & 0x01) | |
145 | avail_slots++; | |
146 | } | |
147 | uni->tdm_slot.avail_slots = avail_slots; | |
148 | ||
149 | /* frame size in bytes */ | |
150 | frame_size = uni->tdm_slot.avail_slots * uni->tdm_slot.slot_width / 8; | |
151 | ||
152 | /* check frame size is allowed */ | |
153 | if ((frame_size > UNIPERIF_MAX_FRAME_SZ) || | |
154 | (frame_size & ~(int)UNIPERIF_ALLOWED_FRAME_SZ)) { | |
155 | dev_err(uni->dev, "frame size not allowed: %d bytes\n", | |
156 | frame_size); | |
157 | return -EINVAL; | |
158 | } | |
159 | ||
72199787 MG |
160 | return 0; |
161 | } | |
162 | ||
163 | int sti_uniperiph_fix_tdm_chan(struct snd_pcm_hw_params *params, | |
164 | struct snd_pcm_hw_rule *rule) | |
165 | { | |
166 | struct uniperif *uni = rule->private; | |
167 | struct snd_interval t; | |
168 | ||
169 | t.min = uni->tdm_slot.avail_slots; | |
170 | t.max = uni->tdm_slot.avail_slots; | |
171 | t.openmin = 0; | |
172 | t.openmax = 0; | |
173 | t.integer = 0; | |
174 | ||
175 | return snd_interval_refine(hw_param_interval(params, rule->var), &t); | |
176 | } | |
177 | ||
178 | int sti_uniperiph_fix_tdm_format(struct snd_pcm_hw_params *params, | |
179 | struct snd_pcm_hw_rule *rule) | |
180 | { | |
181 | struct uniperif *uni = rule->private; | |
182 | struct snd_mask *maskp = hw_param_mask(params, rule->var); | |
183 | u64 format; | |
184 | ||
185 | switch (uni->tdm_slot.slot_width) { | |
186 | case 16: | |
187 | format = SNDRV_PCM_FMTBIT_S16_LE; | |
188 | break; | |
189 | case 32: | |
190 | format = SNDRV_PCM_FMTBIT_S32_LE; | |
191 | break; | |
192 | default: | |
193 | dev_err(uni->dev, "format not supported: %d bits\n", | |
194 | uni->tdm_slot.slot_width); | |
195 | return -EINVAL; | |
196 | } | |
197 | ||
198 | maskp->bits[0] &= (u_int32_t)format; | |
199 | maskp->bits[1] &= (u_int32_t)(format >> 32); | |
200 | /* clear remaining indexes */ | |
201 | memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX - 64) / 8); | |
202 | ||
203 | if (!maskp->bits[0] && !maskp->bits[1]) | |
204 | return -EINVAL; | |
205 | ||
44f948bd MG |
206 | return 0; |
207 | } | |
208 | ||
209 | int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni, | |
210 | unsigned int *word_pos) | |
211 | { | |
212 | int slot_width = uni->tdm_slot.slot_width / 8; | |
213 | int slots_num = uni->tdm_slot.slots; | |
214 | unsigned int slots_mask = uni->tdm_slot.mask; | |
215 | int i, j, k; | |
216 | unsigned int word16_pos[4]; | |
217 | ||
218 | /* word16_pos: | |
219 | * word16_pos[0] = WORDX_LSB | |
220 | * word16_pos[1] = WORDX_MSB, | |
221 | * word16_pos[2] = WORDX+1_LSB | |
222 | * word16_pos[3] = WORDX+1_MSB | |
223 | */ | |
224 | ||
225 | /* set unip word position */ | |
226 | for (i = 0, j = 0, k = 0; (i < slots_num) && (k < WORD_MAX); i++) { | |
227 | if ((slots_mask >> i) & 0x01) { | |
228 | word16_pos[j] = i * slot_width; | |
229 | ||
230 | if (slot_width == 4) { | |
231 | word16_pos[j + 1] = word16_pos[j] + 2; | |
232 | j++; | |
233 | } | |
234 | j++; | |
235 | ||
236 | if (j > 3) { | |
237 | word_pos[k] = word16_pos[1] | | |
238 | (word16_pos[0] << 8) | | |
239 | (word16_pos[3] << 16) | | |
240 | (word16_pos[2] << 24); | |
241 | j = 0; | |
242 | k++; | |
243 | } | |
244 | } | |
245 | } | |
246 | ||
247 | return 0; | |
248 | } | |
249 | ||
f3bd847e AP |
250 | /* |
251 | * sti_uniperiph_dai_create_ctrl | |
252 | * This function is used to create Ctrl associated to DAI but also pcm device. | |
253 | * Request is done by front end to associate ctrl with pcm device id | |
254 | */ | |
f2da4542 | 255 | static int sti_uniperiph_dai_create_ctrl(struct snd_soc_dai *dai) |
f3bd847e AP |
256 | { |
257 | struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); | |
258 | struct uniperif *uni = priv->dai_data.uni; | |
259 | struct snd_kcontrol_new *ctrl; | |
260 | int i; | |
261 | ||
262 | if (!uni->num_ctrls) | |
263 | return 0; | |
264 | ||
265 | for (i = 0; i < uni->num_ctrls; i++) { | |
266 | /* | |
267 | * Several Control can have same name. Controls are indexed on | |
268 | * Uniperipheral instance ID | |
269 | */ | |
270 | ctrl = &uni->snd_ctrls[i]; | |
5a4326d1 AP |
271 | ctrl->index = uni->id; |
272 | ctrl->device = uni->id; | |
f3bd847e AP |
273 | } |
274 | ||
275 | return snd_soc_add_dai_controls(dai, uni->snd_ctrls, uni->num_ctrls); | |
276 | } | |
277 | ||
278 | /* | |
279 | * DAI | |
280 | */ | |
281 | int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream, | |
282 | struct snd_pcm_hw_params *params, | |
283 | struct snd_soc_dai *dai) | |
284 | { | |
8d8b1e2e MG |
285 | struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); |
286 | struct uniperif *uni = priv->dai_data.uni; | |
f3bd847e AP |
287 | struct snd_dmaengine_dai_dma_data *dma_data; |
288 | int transfer_size; | |
289 | ||
5a4326d1 | 290 | if (uni->type == SND_ST_UNIPERIF_TYPE_TDM) |
8d8b1e2e MG |
291 | /* transfer size = user frame size (in 32-bits FIFO cell) */ |
292 | transfer_size = snd_soc_params_to_frame_size(params) / 32; | |
293 | else | |
294 | transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES; | |
f3bd847e AP |
295 | |
296 | dma_data = snd_soc_dai_get_dma_data(dai, substream); | |
297 | dma_data->maxburst = transfer_size; | |
298 | ||
299 | return 0; | |
300 | } | |
301 | ||
302 | int sti_uniperiph_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) | |
303 | { | |
304 | struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); | |
305 | ||
306 | priv->dai_data.uni->daifmt = fmt; | |
307 | ||
308 | return 0; | |
309 | } | |
310 | ||
311 | static int sti_uniperiph_dai_suspend(struct snd_soc_dai *dai) | |
312 | { | |
313 | struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); | |
314 | struct uniperif *uni = priv->dai_data.uni; | |
315 | int ret; | |
316 | ||
317 | /* The uniperipheral should be in stopped state */ | |
318 | if (uni->state != UNIPERIF_STATE_STOPPED) { | |
748abba8 | 319 | dev_err(uni->dev, "%s: invalid uni state( %d)\n", |
f3bd847e AP |
320 | __func__, (int)uni->state); |
321 | return -EBUSY; | |
322 | } | |
323 | ||
324 | /* Pinctrl: switch pinstate to sleep */ | |
325 | ret = pinctrl_pm_select_sleep_state(uni->dev); | |
326 | if (ret) | |
748abba8 | 327 | dev_err(uni->dev, "%s: failed to select pinctrl state\n", |
f3bd847e AP |
328 | __func__); |
329 | ||
330 | return ret; | |
331 | } | |
332 | ||
333 | static int sti_uniperiph_dai_resume(struct snd_soc_dai *dai) | |
334 | { | |
335 | struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); | |
336 | struct uniperif *uni = priv->dai_data.uni; | |
337 | int ret; | |
338 | ||
5a4326d1 | 339 | if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) { |
f3bd847e AP |
340 | ret = uni_player_resume(uni); |
341 | if (ret) | |
342 | return ret; | |
343 | } | |
344 | ||
345 | /* pinctrl: switch pinstate to default */ | |
346 | ret = pinctrl_pm_select_default_state(uni->dev); | |
347 | if (ret) | |
748abba8 | 348 | dev_err(uni->dev, "%s: failed to select pinctrl state\n", |
f3bd847e AP |
349 | __func__); |
350 | ||
351 | return ret; | |
352 | } | |
353 | ||
354 | static int sti_uniperiph_dai_probe(struct snd_soc_dai *dai) | |
355 | { | |
356 | struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); | |
357 | struct sti_uniperiph_dai *dai_data = &priv->dai_data; | |
358 | ||
359 | /* DMA settings*/ | |
5a4326d1 | 360 | if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) |
f3bd847e AP |
361 | snd_soc_dai_init_dma_data(dai, &dai_data->dma_data, NULL); |
362 | else | |
363 | snd_soc_dai_init_dma_data(dai, NULL, &dai_data->dma_data); | |
364 | ||
365 | dai_data->dma_data.addr = dai_data->uni->fifo_phys_address; | |
366 | dai_data->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; | |
367 | ||
368 | return sti_uniperiph_dai_create_ctrl(dai); | |
369 | } | |
370 | ||
371 | static const struct snd_soc_dai_driver sti_uniperiph_dai_template = { | |
372 | .probe = sti_uniperiph_dai_probe, | |
373 | .suspend = sti_uniperiph_dai_suspend, | |
374 | .resume = sti_uniperiph_dai_resume | |
375 | }; | |
376 | ||
377 | static const struct snd_soc_component_driver sti_uniperiph_dai_component = { | |
378 | .name = "sti_cpu_dai", | |
379 | }; | |
380 | ||
381 | static int sti_uniperiph_cpu_dai_of(struct device_node *node, | |
382 | struct sti_uniperiph_data *priv) | |
383 | { | |
f3bd847e AP |
384 | struct device *dev = &priv->pdev->dev; |
385 | struct sti_uniperiph_dai *dai_data = &priv->dai_data; | |
386 | struct snd_soc_dai_driver *dai = priv->dai; | |
387 | struct snd_soc_pcm_stream *stream; | |
388 | struct uniperif *uni; | |
5a4326d1 AP |
389 | const struct of_device_id *of_id; |
390 | const struct sti_uniperiph_dev_data *dev_data; | |
391 | const char *mode; | |
748abba8 | 392 | int ret; |
5a4326d1 AP |
393 | |
394 | /* Populate data structure depending on compatibility */ | |
395 | of_id = of_match_node(snd_soc_sti_match, node); | |
396 | if (!of_id->data) { | |
748abba8 | 397 | dev_err(dev, "data associated to device is missing\n"); |
5a4326d1 AP |
398 | return -EINVAL; |
399 | } | |
400 | dev_data = (struct sti_uniperiph_dev_data *)of_id->data; | |
f3bd847e AP |
401 | |
402 | uni = devm_kzalloc(dev, sizeof(*uni), GFP_KERNEL); | |
403 | if (!uni) | |
404 | return -ENOMEM; | |
405 | ||
5a4326d1 AP |
406 | uni->id = dev_data->id; |
407 | uni->ver = dev_data->version; | |
408 | ||
f3bd847e | 409 | *dai = sti_uniperiph_dai_template; |
5a4326d1 | 410 | dai->name = dev_data->dai_names; |
f3bd847e AP |
411 | |
412 | /* Get resources */ | |
413 | uni->mem_region = platform_get_resource(priv->pdev, IORESOURCE_MEM, 0); | |
414 | ||
415 | if (!uni->mem_region) { | |
748abba8 | 416 | dev_err(dev, "Failed to get memory resource\n"); |
f3bd847e AP |
417 | return -ENODEV; |
418 | } | |
419 | ||
420 | uni->base = devm_ioremap_resource(dev, uni->mem_region); | |
421 | ||
422 | if (IS_ERR(uni->base)) | |
423 | return PTR_ERR(uni->base); | |
424 | ||
425 | uni->fifo_phys_address = uni->mem_region->start + | |
426 | UNIPERIF_FIFO_DATA_OFFSET(uni); | |
427 | ||
428 | uni->irq = platform_get_irq(priv->pdev, 0); | |
a4642e99 | 429 | if (uni->irq < 0) { |
748abba8 | 430 | dev_err(dev, "Failed to get IRQ resource\n"); |
f3bd847e AP |
431 | return -ENXIO; |
432 | } | |
433 | ||
5a4326d1 AP |
434 | uni->type = dev_data->type; |
435 | ||
436 | /* check if player should be configured for tdm */ | |
437 | if (dev_data->type & SND_ST_UNIPERIF_TYPE_TDM) { | |
438 | if (!of_property_read_string(node, "st,tdm-mode", &mode)) | |
439 | uni->type = SND_ST_UNIPERIF_TYPE_TDM; | |
440 | else | |
441 | uni->type = SND_ST_UNIPERIF_TYPE_PCM; | |
442 | } | |
443 | ||
f3bd847e | 444 | dai_data->uni = uni; |
5a4326d1 | 445 | dai_data->stream = dev_data->stream; |
f3bd847e | 446 | |
5a4326d1 | 447 | if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) { |
748abba8 | 448 | ret = uni_player_init(priv->pdev, uni); |
f3bd847e AP |
449 | stream = &dai->playback; |
450 | } else { | |
748abba8 | 451 | ret = uni_reader_init(priv->pdev, uni); |
f3bd847e AP |
452 | stream = &dai->capture; |
453 | } | |
748abba8 AP |
454 | if (ret < 0) |
455 | return ret; | |
456 | ||
f3bd847e AP |
457 | dai->ops = uni->dai_ops; |
458 | ||
459 | stream->stream_name = dai->name; | |
460 | stream->channels_min = uni->hw->channels_min; | |
461 | stream->channels_max = uni->hw->channels_max; | |
462 | stream->rates = uni->hw->rates; | |
463 | stream->formats = uni->hw->formats; | |
464 | ||
465 | return 0; | |
466 | } | |
467 | ||
468 | static const struct snd_dmaengine_pcm_config dmaengine_pcm_config = { | |
469 | .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, | |
470 | }; | |
471 | ||
472 | static int sti_uniperiph_probe(struct platform_device *pdev) | |
473 | { | |
474 | struct sti_uniperiph_data *priv; | |
475 | struct device_node *node = pdev->dev.of_node; | |
476 | int ret; | |
477 | ||
478 | /* Allocate the private data and the CPU_DAI array */ | |
479 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | |
480 | if (!priv) | |
481 | return -ENOMEM; | |
482 | priv->dai = devm_kzalloc(&pdev->dev, sizeof(*priv->dai), GFP_KERNEL); | |
483 | if (!priv->dai) | |
484 | return -ENOMEM; | |
485 | ||
486 | priv->pdev = pdev; | |
487 | ||
488 | ret = sti_uniperiph_cpu_dai_of(node, priv); | |
489 | ||
490 | dev_set_drvdata(&pdev->dev, priv); | |
491 | ||
e1290910 AL |
492 | ret = devm_snd_soc_register_component(&pdev->dev, |
493 | &sti_uniperiph_dai_component, | |
494 | priv->dai, 1); | |
f3bd847e AP |
495 | if (ret < 0) |
496 | return ret; | |
497 | ||
498 | return devm_snd_dmaengine_pcm_register(&pdev->dev, | |
499 | &dmaengine_pcm_config, 0); | |
500 | } | |
501 | ||
f3bd847e AP |
502 | static struct platform_driver sti_uniperiph_driver = { |
503 | .driver = { | |
504 | .name = "sti-uniperiph-dai", | |
505 | .of_match_table = snd_soc_sti_match, | |
506 | }, | |
507 | .probe = sti_uniperiph_probe, | |
508 | }; | |
509 | module_platform_driver(sti_uniperiph_driver); | |
510 | ||
511 | MODULE_DESCRIPTION("uniperipheral DAI driver"); | |
512 | MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>"); | |
513 | MODULE_LICENSE("GPL v2"); |