Commit | Line | Data |
---|---|---|
abb9c9b8 SK |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2018, Linaro Limited | |
3 | ||
4 | #include <linux/kernel.h> | |
5 | #include <linux/errno.h> | |
6 | #include <linux/slab.h> | |
7 | #include <linux/list.h> | |
8 | #include <linux/slimbus.h> | |
9 | #include <uapi/sound/asound.h> | |
10 | #include "slimbus.h" | |
11 | ||
12 | /** | |
13 | * struct segdist_code - Segment Distributions code from | |
14 | * Table 20 of SLIMbus Specs Version 2.0 | |
15 | * | |
16 | * @ratem: Channel Rate Multipler(Segments per Superframe) | |
17 | * @seg_interval: Number of slots between the first Slot of Segment | |
18 | * and the first slot of the next consecutive Segment. | |
19 | * @segdist_code: Segment Distribution Code SD[11:0] | |
20 | * @seg_offset_mask: Segment offset mask in SD[11:0] | |
21 | * @segdist_codes: List of all possible Segmet Distribution codes. | |
22 | */ | |
23 | static const struct segdist_code { | |
24 | int ratem; | |
25 | int seg_interval; | |
26 | int segdist_code; | |
27 | u32 seg_offset_mask; | |
28 | ||
29 | } segdist_codes[] = { | |
30 | {1, 1536, 0x200, 0xdff}, | |
31 | {2, 768, 0x100, 0xcff}, | |
32 | {4, 384, 0x080, 0xc7f}, | |
33 | {8, 192, 0x040, 0xc3f}, | |
34 | {16, 96, 0x020, 0xc1f}, | |
35 | {32, 48, 0x010, 0xc0f}, | |
36 | {64, 24, 0x008, 0xc07}, | |
37 | {128, 12, 0x004, 0xc03}, | |
38 | {256, 6, 0x002, 0xc01}, | |
39 | {512, 3, 0x001, 0xc00}, | |
40 | {3, 512, 0xe00, 0x1ff}, | |
41 | {6, 256, 0xd00, 0x0ff}, | |
42 | {12, 128, 0xc80, 0x07f}, | |
43 | {24, 64, 0xc40, 0x03f}, | |
44 | {48, 32, 0xc20, 0x01f}, | |
45 | {96, 16, 0xc10, 0x00f}, | |
46 | {192, 8, 0xc08, 0x007}, | |
47 | {364, 4, 0xc04, 0x003}, | |
48 | {768, 2, 0xc02, 0x001}, | |
49 | }; | |
50 | ||
51 | /* | |
52 | * Presence Rate table for all Natural Frequencies | |
53 | * The Presence rate of a constant bitrate stream is mean flow rate of the | |
54 | * stream expressed in occupied Segments of that Data Channel per second. | |
55 | * Table 66 from SLIMbus 2.0 Specs | |
56 | * | |
57 | * Index of the table corresponds to Presence rate code for the respective rate | |
58 | * in the table. | |
59 | */ | |
60 | static const int slim_presence_rate_table[] = { | |
61 | 0, /* Not Indicated */ | |
62 | 12000, | |
63 | 24000, | |
64 | 48000, | |
65 | 96000, | |
66 | 192000, | |
67 | 384000, | |
68 | 768000, | |
69 | 0, /* Reserved */ | |
b9c19396 KK |
70 | 11025, |
71 | 22050, | |
72 | 44100, | |
73 | 88200, | |
abb9c9b8 SK |
74 | 176400, |
75 | 352800, | |
76 | 705600, | |
77 | 4000, | |
78 | 8000, | |
79 | 16000, | |
80 | 32000, | |
81 | 64000, | |
82 | 128000, | |
83 | 256000, | |
84 | 512000, | |
85 | }; | |
86 | ||
2f0f2441 | 87 | /** |
abb9c9b8 SK |
88 | * slim_stream_allocate() - Allocate a new SLIMbus Stream |
89 | * @dev:Slim device to be associated with | |
90 | * @name: name of the stream | |
91 | * | |
92 | * This is very first call for SLIMbus streaming, this API will allocate | |
93 | * a new SLIMbus stream and return a valid stream runtime pointer for client | |
94 | * to use it in subsequent stream apis. state of stream is set to ALLOCATED | |
95 | * | |
96 | * Return: valid pointer on success and error code on failure. | |
97 | * From ASoC DPCM framework, this state is linked to startup() operation. | |
98 | */ | |
99 | struct slim_stream_runtime *slim_stream_allocate(struct slim_device *dev, | |
100 | const char *name) | |
101 | { | |
102 | struct slim_stream_runtime *rt; | |
103 | ||
104 | rt = kzalloc(sizeof(*rt), GFP_KERNEL); | |
105 | if (!rt) | |
106 | return ERR_PTR(-ENOMEM); | |
107 | ||
108 | rt->name = kasprintf(GFP_KERNEL, "slim-%s", name); | |
109 | if (!rt->name) { | |
110 | kfree(rt); | |
111 | return ERR_PTR(-ENOMEM); | |
112 | } | |
113 | ||
114 | rt->dev = dev; | |
115 | spin_lock(&dev->stream_list_lock); | |
116 | list_add_tail(&rt->node, &dev->stream_list); | |
117 | spin_unlock(&dev->stream_list_lock); | |
118 | ||
119 | return rt; | |
120 | } | |
121 | EXPORT_SYMBOL_GPL(slim_stream_allocate); | |
122 | ||
123 | static int slim_connect_port_channel(struct slim_stream_runtime *stream, | |
124 | struct slim_port *port) | |
125 | { | |
126 | struct slim_device *sdev = stream->dev; | |
127 | u8 wbuf[2]; | |
128 | struct slim_val_inf msg = {0, 2, NULL, wbuf, NULL}; | |
129 | u8 mc = SLIM_MSG_MC_CONNECT_SOURCE; | |
130 | DEFINE_SLIM_LDEST_TXN(txn, mc, 6, stream->dev->laddr, &msg); | |
131 | ||
132 | if (port->direction == SLIM_PORT_SINK) | |
133 | txn.mc = SLIM_MSG_MC_CONNECT_SINK; | |
134 | ||
135 | wbuf[0] = port->id; | |
136 | wbuf[1] = port->ch.id; | |
137 | port->ch.state = SLIM_CH_STATE_ASSOCIATED; | |
138 | port->state = SLIM_PORT_UNCONFIGURED; | |
139 | ||
140 | return slim_do_transfer(sdev->ctrl, &txn); | |
141 | } | |
142 | ||
143 | static int slim_disconnect_port(struct slim_stream_runtime *stream, | |
144 | struct slim_port *port) | |
145 | { | |
146 | struct slim_device *sdev = stream->dev; | |
147 | u8 wbuf[1]; | |
148 | struct slim_val_inf msg = {0, 1, NULL, wbuf, NULL}; | |
149 | u8 mc = SLIM_MSG_MC_DISCONNECT_PORT; | |
150 | DEFINE_SLIM_LDEST_TXN(txn, mc, 5, stream->dev->laddr, &msg); | |
151 | ||
152 | wbuf[0] = port->id; | |
153 | port->ch.state = SLIM_CH_STATE_DISCONNECTED; | |
154 | port->state = SLIM_PORT_DISCONNECTED; | |
155 | ||
156 | return slim_do_transfer(sdev->ctrl, &txn); | |
157 | } | |
158 | ||
159 | static int slim_deactivate_remove_channel(struct slim_stream_runtime *stream, | |
160 | struct slim_port *port) | |
161 | { | |
162 | struct slim_device *sdev = stream->dev; | |
163 | u8 wbuf[1]; | |
164 | struct slim_val_inf msg = {0, 1, NULL, wbuf, NULL}; | |
165 | u8 mc = SLIM_MSG_MC_NEXT_DEACTIVATE_CHANNEL; | |
166 | DEFINE_SLIM_LDEST_TXN(txn, mc, 5, stream->dev->laddr, &msg); | |
167 | int ret; | |
168 | ||
169 | wbuf[0] = port->ch.id; | |
170 | ret = slim_do_transfer(sdev->ctrl, &txn); | |
171 | if (ret) | |
172 | return ret; | |
173 | ||
174 | txn.mc = SLIM_MSG_MC_NEXT_REMOVE_CHANNEL; | |
175 | port->ch.state = SLIM_CH_STATE_REMOVED; | |
176 | ||
177 | return slim_do_transfer(sdev->ctrl, &txn); | |
178 | } | |
179 | ||
180 | static int slim_get_prate_code(int rate) | |
181 | { | |
182 | int i; | |
183 | ||
184 | for (i = 0; i < ARRAY_SIZE(slim_presence_rate_table); i++) { | |
185 | if (rate == slim_presence_rate_table[i]) | |
186 | return i; | |
187 | } | |
188 | ||
189 | return -EINVAL; | |
190 | } | |
191 | ||
2f0f2441 | 192 | /** |
abb9c9b8 SK |
193 | * slim_stream_prepare() - Prepare a SLIMbus Stream |
194 | * | |
195 | * @rt: instance of slim stream runtime to configure | |
196 | * @cfg: new configuration for the stream | |
197 | * | |
198 | * This API will configure SLIMbus stream with config parameters from cfg. | |
199 | * return zero on success and error code on failure. From ASoC DPCM framework, | |
200 | * this state is linked to hw_params() operation. | |
201 | */ | |
202 | int slim_stream_prepare(struct slim_stream_runtime *rt, | |
203 | struct slim_stream_config *cfg) | |
204 | { | |
205 | struct slim_controller *ctrl = rt->dev->ctrl; | |
206 | struct slim_port *port; | |
434d2572 | 207 | int num_ports, i, port_id, prrate; |
abb9c9b8 SK |
208 | |
209 | if (rt->ports) { | |
210 | dev_err(&rt->dev->dev, "Stream already Prepared\n"); | |
211 | return -EINVAL; | |
212 | } | |
213 | ||
214 | num_ports = hweight32(cfg->port_mask); | |
215 | rt->ports = kcalloc(num_ports, sizeof(*port), GFP_KERNEL); | |
216 | if (!rt->ports) | |
217 | return -ENOMEM; | |
218 | ||
219 | rt->num_ports = num_ports; | |
220 | rt->rate = cfg->rate; | |
221 | rt->bps = cfg->bps; | |
222 | rt->direction = cfg->direction; | |
223 | ||
434d2572 KK |
224 | prrate = slim_get_prate_code(cfg->rate); |
225 | if (prrate < 0) { | |
226 | dev_err(&rt->dev->dev, "Cannot get presence rate for rate %d Hz\n", | |
227 | cfg->rate); | |
228 | return prrate; | |
229 | } | |
230 | ||
abb9c9b8 SK |
231 | if (cfg->rate % ctrl->a_framer->superfreq) { |
232 | /* | |
233 | * data rate not exactly multiple of super frame, | |
234 | * use PUSH/PULL protocol | |
235 | */ | |
236 | if (cfg->direction == SNDRV_PCM_STREAM_PLAYBACK) | |
237 | rt->prot = SLIM_PROTO_PUSH; | |
238 | else | |
239 | rt->prot = SLIM_PROTO_PULL; | |
240 | } else { | |
241 | rt->prot = SLIM_PROTO_ISO; | |
242 | } | |
243 | ||
244 | rt->ratem = cfg->rate/ctrl->a_framer->superfreq; | |
245 | ||
246 | i = 0; | |
247 | for_each_set_bit(port_id, &cfg->port_mask, SLIM_DEVICE_MAX_PORTS) { | |
248 | port = &rt->ports[i]; | |
249 | port->state = SLIM_PORT_DISCONNECTED; | |
250 | port->id = port_id; | |
434d2572 | 251 | port->ch.prrate = prrate; |
abb9c9b8 SK |
252 | port->ch.id = cfg->chs[i]; |
253 | port->ch.data_fmt = SLIM_CH_DATA_FMT_NOT_DEFINED; | |
254 | port->ch.aux_fmt = SLIM_CH_AUX_FMT_NOT_APPLICABLE; | |
255 | port->ch.state = SLIM_CH_STATE_ALLOCATED; | |
256 | ||
257 | if (cfg->direction == SNDRV_PCM_STREAM_PLAYBACK) | |
258 | port->direction = SLIM_PORT_SINK; | |
259 | else | |
260 | port->direction = SLIM_PORT_SOURCE; | |
261 | ||
262 | slim_connect_port_channel(rt, port); | |
263 | i++; | |
264 | } | |
265 | ||
266 | return 0; | |
267 | } | |
268 | EXPORT_SYMBOL_GPL(slim_stream_prepare); | |
269 | ||
270 | static int slim_define_channel_content(struct slim_stream_runtime *stream, | |
271 | struct slim_port *port) | |
272 | { | |
273 | struct slim_device *sdev = stream->dev; | |
274 | u8 wbuf[4]; | |
275 | struct slim_val_inf msg = {0, 4, NULL, wbuf, NULL}; | |
276 | u8 mc = SLIM_MSG_MC_NEXT_DEFINE_CONTENT; | |
277 | DEFINE_SLIM_LDEST_TXN(txn, mc, 8, stream->dev->laddr, &msg); | |
278 | ||
279 | wbuf[0] = port->ch.id; | |
280 | wbuf[1] = port->ch.prrate; | |
281 | ||
282 | /* Frequency Locked for ISO Protocol */ | |
283 | if (stream->prot != SLIM_PROTO_ISO) | |
284 | wbuf[1] |= SLIM_CHANNEL_CONTENT_FL; | |
285 | ||
286 | wbuf[2] = port->ch.data_fmt | (port->ch.aux_fmt << 4); | |
287 | wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS; | |
288 | port->ch.state = SLIM_CH_STATE_CONTENT_DEFINED; | |
289 | ||
290 | return slim_do_transfer(sdev->ctrl, &txn); | |
291 | } | |
292 | ||
293 | static int slim_get_segdist_code(int ratem) | |
294 | { | |
295 | int i; | |
296 | ||
297 | for (i = 0; i < ARRAY_SIZE(segdist_codes); i++) { | |
298 | if (segdist_codes[i].ratem == ratem) | |
299 | return segdist_codes[i].segdist_code; | |
300 | } | |
301 | ||
302 | return -EINVAL; | |
303 | } | |
304 | ||
305 | static int slim_define_channel(struct slim_stream_runtime *stream, | |
306 | struct slim_port *port) | |
307 | { | |
308 | struct slim_device *sdev = stream->dev; | |
309 | u8 wbuf[4]; | |
310 | struct slim_val_inf msg = {0, 4, NULL, wbuf, NULL}; | |
311 | u8 mc = SLIM_MSG_MC_NEXT_DEFINE_CHANNEL; | |
312 | DEFINE_SLIM_LDEST_TXN(txn, mc, 8, stream->dev->laddr, &msg); | |
313 | ||
314 | port->ch.seg_dist = slim_get_segdist_code(stream->ratem); | |
315 | ||
316 | wbuf[0] = port->ch.id; | |
317 | wbuf[1] = port->ch.seg_dist & 0xFF; | |
318 | wbuf[2] = (stream->prot << 4) | ((port->ch.seg_dist & 0xF00) >> 8); | |
319 | if (stream->prot == SLIM_PROTO_ISO) | |
320 | wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS; | |
321 | else | |
322 | wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS + 1; | |
323 | ||
324 | port->ch.state = SLIM_CH_STATE_DEFINED; | |
325 | ||
326 | return slim_do_transfer(sdev->ctrl, &txn); | |
327 | } | |
328 | ||
329 | static int slim_activate_channel(struct slim_stream_runtime *stream, | |
330 | struct slim_port *port) | |
331 | { | |
332 | struct slim_device *sdev = stream->dev; | |
333 | u8 wbuf[1]; | |
334 | struct slim_val_inf msg = {0, 1, NULL, wbuf, NULL}; | |
335 | u8 mc = SLIM_MSG_MC_NEXT_ACTIVATE_CHANNEL; | |
336 | DEFINE_SLIM_LDEST_TXN(txn, mc, 5, stream->dev->laddr, &msg); | |
337 | ||
338 | txn.msg->num_bytes = 1; | |
339 | txn.msg->wbuf = wbuf; | |
340 | wbuf[0] = port->ch.id; | |
341 | port->ch.state = SLIM_CH_STATE_ACTIVE; | |
342 | ||
343 | return slim_do_transfer(sdev->ctrl, &txn); | |
344 | } | |
345 | ||
2f0f2441 | 346 | /** |
abb9c9b8 SK |
347 | * slim_stream_enable() - Enable a prepared SLIMbus Stream |
348 | * | |
349 | * @stream: instance of slim stream runtime to enable | |
350 | * | |
351 | * This API will enable all the ports and channels associated with | |
352 | * SLIMbus stream | |
353 | * | |
354 | * Return: zero on success and error code on failure. From ASoC DPCM framework, | |
355 | * this state is linked to trigger() start operation. | |
356 | */ | |
357 | int slim_stream_enable(struct slim_stream_runtime *stream) | |
358 | { | |
359 | DEFINE_SLIM_BCAST_TXN(txn, SLIM_MSG_MC_BEGIN_RECONFIGURATION, | |
360 | 3, SLIM_LA_MANAGER, NULL); | |
361 | struct slim_controller *ctrl = stream->dev->ctrl; | |
362 | int ret, i; | |
363 | ||
364 | if (ctrl->enable_stream) { | |
365 | ret = ctrl->enable_stream(stream); | |
366 | if (ret) | |
367 | return ret; | |
368 | ||
369 | for (i = 0; i < stream->num_ports; i++) | |
370 | stream->ports[i].ch.state = SLIM_CH_STATE_ACTIVE; | |
371 | ||
372 | return ret; | |
373 | } | |
374 | ||
375 | ret = slim_do_transfer(ctrl, &txn); | |
376 | if (ret) | |
377 | return ret; | |
378 | ||
379 | /* define channels first before activating them */ | |
380 | for (i = 0; i < stream->num_ports; i++) { | |
381 | struct slim_port *port = &stream->ports[i]; | |
382 | ||
383 | slim_define_channel(stream, port); | |
384 | slim_define_channel_content(stream, port); | |
385 | } | |
386 | ||
387 | for (i = 0; i < stream->num_ports; i++) { | |
388 | struct slim_port *port = &stream->ports[i]; | |
389 | ||
390 | slim_activate_channel(stream, port); | |
391 | port->state = SLIM_PORT_CONFIGURED; | |
392 | } | |
393 | txn.mc = SLIM_MSG_MC_RECONFIGURE_NOW; | |
394 | ||
395 | return slim_do_transfer(ctrl, &txn); | |
396 | } | |
397 | EXPORT_SYMBOL_GPL(slim_stream_enable); | |
398 | ||
2f0f2441 | 399 | /** |
abb9c9b8 SK |
400 | * slim_stream_disable() - Disable a SLIMbus Stream |
401 | * | |
402 | * @stream: instance of slim stream runtime to disable | |
403 | * | |
404 | * This API will disable all the ports and channels associated with | |
405 | * SLIMbus stream | |
406 | * | |
407 | * Return: zero on success and error code on failure. From ASoC DPCM framework, | |
408 | * this state is linked to trigger() pause operation. | |
409 | */ | |
410 | int slim_stream_disable(struct slim_stream_runtime *stream) | |
411 | { | |
412 | DEFINE_SLIM_BCAST_TXN(txn, SLIM_MSG_MC_BEGIN_RECONFIGURATION, | |
413 | 3, SLIM_LA_MANAGER, NULL); | |
414 | struct slim_controller *ctrl = stream->dev->ctrl; | |
415 | int ret, i; | |
416 | ||
a82b1ec3 KK |
417 | if (!stream->ports || !stream->num_ports) |
418 | return -EINVAL; | |
419 | ||
abb9c9b8 SK |
420 | if (ctrl->disable_stream) |
421 | ctrl->disable_stream(stream); | |
422 | ||
423 | ret = slim_do_transfer(ctrl, &txn); | |
424 | if (ret) | |
425 | return ret; | |
426 | ||
427 | for (i = 0; i < stream->num_ports; i++) | |
428 | slim_deactivate_remove_channel(stream, &stream->ports[i]); | |
429 | ||
430 | txn.mc = SLIM_MSG_MC_RECONFIGURE_NOW; | |
431 | ||
432 | return slim_do_transfer(ctrl, &txn); | |
433 | } | |
434 | EXPORT_SYMBOL_GPL(slim_stream_disable); | |
435 | ||
2f0f2441 | 436 | /** |
abb9c9b8 SK |
437 | * slim_stream_unprepare() - Un-prepare a SLIMbus Stream |
438 | * | |
439 | * @stream: instance of slim stream runtime to unprepare | |
440 | * | |
441 | * This API will un allocate all the ports and channels associated with | |
442 | * SLIMbus stream | |
443 | * | |
444 | * Return: zero on success and error code on failure. From ASoC DPCM framework, | |
445 | * this state is linked to trigger() stop operation. | |
446 | */ | |
447 | int slim_stream_unprepare(struct slim_stream_runtime *stream) | |
448 | { | |
449 | int i; | |
450 | ||
a82b1ec3 KK |
451 | if (!stream->ports || !stream->num_ports) |
452 | return -EINVAL; | |
453 | ||
abb9c9b8 SK |
454 | for (i = 0; i < stream->num_ports; i++) |
455 | slim_disconnect_port(stream, &stream->ports[i]); | |
456 | ||
457 | kfree(stream->ports); | |
458 | stream->ports = NULL; | |
459 | stream->num_ports = 0; | |
460 | ||
461 | return 0; | |
462 | } | |
463 | EXPORT_SYMBOL_GPL(slim_stream_unprepare); | |
464 | ||
2f0f2441 | 465 | /** |
abb9c9b8 SK |
466 | * slim_stream_free() - Free a SLIMbus Stream |
467 | * | |
468 | * @stream: instance of slim stream runtime to free | |
469 | * | |
470 | * This API will un allocate all the memory associated with | |
471 | * slim stream runtime, user is not allowed to make an dereference | |
472 | * to stream after this call. | |
473 | * | |
474 | * Return: zero on success and error code on failure. From ASoC DPCM framework, | |
475 | * this state is linked to shutdown() operation. | |
476 | */ | |
477 | int slim_stream_free(struct slim_stream_runtime *stream) | |
478 | { | |
479 | struct slim_device *sdev = stream->dev; | |
480 | ||
481 | spin_lock(&sdev->stream_list_lock); | |
482 | list_del(&stream->node); | |
483 | spin_unlock(&sdev->stream_list_lock); | |
484 | ||
485 | kfree(stream->name); | |
486 | kfree(stream); | |
487 | ||
488 | return 0; | |
489 | } | |
490 | EXPORT_SYMBOL_GPL(slim_stream_free); |