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 */ |
70 | 11025, |
71 | 22050, |
72 | 44100, |
73 | 88200, |
74 | 176400, |
75 | 352800, |
76 | 705600, |
77 | 4000, |
78 | 8000, |
79 | 16000, |
80 | 32000, |
81 | 64000, |
82 | 128000, |
83 | 256000, |
84 | 512000, |
85 | }; |
86 | |
87 | /** |
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(size: sizeof(*rt), GFP_KERNEL); |
105 | if (!rt) |
106 | return ERR_PTR(error: -ENOMEM); |
107 | |
108 | rt->name = kasprintf(GFP_KERNEL, fmt: "slim-%s" , name); |
109 | if (!rt->name) { |
110 | kfree(objp: rt); |
111 | return ERR_PTR(error: -ENOMEM); |
112 | } |
113 | |
114 | rt->dev = dev; |
115 | spin_lock(lock: &dev->stream_list_lock); |
116 | list_add_tail(new: &rt->node, head: &dev->stream_list); |
117 | spin_unlock(lock: &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(ctrl: sdev->ctrl, txn: &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(ctrl: sdev->ctrl, txn: &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(ctrl: sdev->ctrl, txn: &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(ctrl: sdev->ctrl, txn: &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 | |
192 | /** |
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; |
207 | int num_ports, i, port_id, prrate; |
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(n: num_ports, size: 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 | |
224 | prrate = slim_get_prate_code(rate: 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 | |
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; |
251 | port->ch.prrate = prrate; |
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(stream: 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(ctrl: sdev->ctrl, txn: &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(ratem: 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(ctrl: sdev->ctrl, txn: &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(ctrl: sdev->ctrl, txn: &txn); |
344 | } |
345 | |
346 | /** |
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: &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: &txn); |
396 | } |
397 | EXPORT_SYMBOL_GPL(slim_stream_enable); |
398 | |
399 | /** |
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 | |
417 | if (!stream->ports || !stream->num_ports) |
418 | return -EINVAL; |
419 | |
420 | if (ctrl->disable_stream) |
421 | ctrl->disable_stream(stream); |
422 | |
423 | ret = slim_do_transfer(ctrl, txn: &txn); |
424 | if (ret) |
425 | return ret; |
426 | |
427 | for (i = 0; i < stream->num_ports; i++) |
428 | slim_deactivate_remove_channel(stream, port: &stream->ports[i]); |
429 | |
430 | txn.mc = SLIM_MSG_MC_RECONFIGURE_NOW; |
431 | |
432 | return slim_do_transfer(ctrl, txn: &txn); |
433 | } |
434 | EXPORT_SYMBOL_GPL(slim_stream_disable); |
435 | |
436 | /** |
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 | |
451 | if (!stream->ports || !stream->num_ports) |
452 | return -EINVAL; |
453 | |
454 | for (i = 0; i < stream->num_ports; i++) |
455 | slim_disconnect_port(stream, port: &stream->ports[i]); |
456 | |
457 | kfree(objp: stream->ports); |
458 | stream->ports = NULL; |
459 | stream->num_ports = 0; |
460 | |
461 | return 0; |
462 | } |
463 | EXPORT_SYMBOL_GPL(slim_stream_unprepare); |
464 | |
465 | /** |
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(lock: &sdev->stream_list_lock); |
482 | list_del(entry: &stream->node); |
483 | spin_unlock(lock: &sdev->stream_list_lock); |
484 | |
485 | kfree(objp: stream->name); |
486 | kfree(objp: stream); |
487 | |
488 | return 0; |
489 | } |
490 | EXPORT_SYMBOL_GPL(slim_stream_free); |
491 | |