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 | 110250, |
71 | 220500, |
72 | 441000, |
73 | 882000, |
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(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 | |
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; |
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 | |
224 | if (cfg->rate % ctrl->a_framer->superfreq) { |
225 | /* |
226 | * data rate not exactly multiple of super frame, |
227 | * use PUSH/PULL protocol |
228 | */ |
229 | if (cfg->direction == SNDRV_PCM_STREAM_PLAYBACK) |
230 | rt->prot = SLIM_PROTO_PUSH; |
231 | else |
232 | rt->prot = SLIM_PROTO_PULL; |
233 | } else { |
234 | rt->prot = SLIM_PROTO_ISO; |
235 | } |
236 | |
237 | rt->ratem = cfg->rate/ctrl->a_framer->superfreq; |
238 | |
239 | i = 0; |
240 | for_each_set_bit(port_id, &cfg->port_mask, SLIM_DEVICE_MAX_PORTS) { |
241 | port = &rt->ports[i]; |
242 | port->state = SLIM_PORT_DISCONNECTED; |
243 | port->id = port_id; |
244 | port->ch.prrate = slim_get_prate_code(cfg->rate); |
245 | port->ch.id = cfg->chs[i]; |
246 | port->ch.data_fmt = SLIM_CH_DATA_FMT_NOT_DEFINED; |
247 | port->ch.aux_fmt = SLIM_CH_AUX_FMT_NOT_APPLICABLE; |
248 | port->ch.state = SLIM_CH_STATE_ALLOCATED; |
249 | |
250 | if (cfg->direction == SNDRV_PCM_STREAM_PLAYBACK) |
251 | port->direction = SLIM_PORT_SINK; |
252 | else |
253 | port->direction = SLIM_PORT_SOURCE; |
254 | |
255 | slim_connect_port_channel(rt, port); |
256 | i++; |
257 | } |
258 | |
259 | return 0; |
260 | } |
261 | EXPORT_SYMBOL_GPL(slim_stream_prepare); |
262 | |
263 | static int slim_define_channel_content(struct slim_stream_runtime *stream, |
264 | struct slim_port *port) |
265 | { |
266 | struct slim_device *sdev = stream->dev; |
267 | u8 wbuf[4]; |
268 | struct slim_val_inf msg = {0, 4, NULL, wbuf, NULL}; |
269 | u8 mc = SLIM_MSG_MC_NEXT_DEFINE_CONTENT; |
270 | DEFINE_SLIM_LDEST_TXN(txn, mc, 8, stream->dev->laddr, &msg); |
271 | |
272 | wbuf[0] = port->ch.id; |
273 | wbuf[1] = port->ch.prrate; |
274 | |
275 | /* Frequency Locked for ISO Protocol */ |
276 | if (stream->prot != SLIM_PROTO_ISO) |
277 | wbuf[1] |= SLIM_CHANNEL_CONTENT_FL; |
278 | |
279 | wbuf[2] = port->ch.data_fmt | (port->ch.aux_fmt << 4); |
280 | wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS; |
281 | port->ch.state = SLIM_CH_STATE_CONTENT_DEFINED; |
282 | |
283 | return slim_do_transfer(sdev->ctrl, &txn); |
284 | } |
285 | |
286 | static int slim_get_segdist_code(int ratem) |
287 | { |
288 | int i; |
289 | |
290 | for (i = 0; i < ARRAY_SIZE(segdist_codes); i++) { |
291 | if (segdist_codes[i].ratem == ratem) |
292 | return segdist_codes[i].segdist_code; |
293 | } |
294 | |
295 | return -EINVAL; |
296 | } |
297 | |
298 | static int slim_define_channel(struct slim_stream_runtime *stream, |
299 | struct slim_port *port) |
300 | { |
301 | struct slim_device *sdev = stream->dev; |
302 | u8 wbuf[4]; |
303 | struct slim_val_inf msg = {0, 4, NULL, wbuf, NULL}; |
304 | u8 mc = SLIM_MSG_MC_NEXT_DEFINE_CHANNEL; |
305 | DEFINE_SLIM_LDEST_TXN(txn, mc, 8, stream->dev->laddr, &msg); |
306 | |
307 | port->ch.seg_dist = slim_get_segdist_code(stream->ratem); |
308 | |
309 | wbuf[0] = port->ch.id; |
310 | wbuf[1] = port->ch.seg_dist & 0xFF; |
311 | wbuf[2] = (stream->prot << 4) | ((port->ch.seg_dist & 0xF00) >> 8); |
312 | if (stream->prot == SLIM_PROTO_ISO) |
313 | wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS; |
314 | else |
315 | wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS + 1; |
316 | |
317 | port->ch.state = SLIM_CH_STATE_DEFINED; |
318 | |
319 | return slim_do_transfer(sdev->ctrl, &txn); |
320 | } |
321 | |
322 | static int slim_activate_channel(struct slim_stream_runtime *stream, |
323 | struct slim_port *port) |
324 | { |
325 | struct slim_device *sdev = stream->dev; |
326 | u8 wbuf[1]; |
327 | struct slim_val_inf msg = {0, 1, NULL, wbuf, NULL}; |
328 | u8 mc = SLIM_MSG_MC_NEXT_ACTIVATE_CHANNEL; |
329 | DEFINE_SLIM_LDEST_TXN(txn, mc, 5, stream->dev->laddr, &msg); |
330 | |
331 | txn.msg->num_bytes = 1; |
332 | txn.msg->wbuf = wbuf; |
333 | wbuf[0] = port->ch.id; |
334 | port->ch.state = SLIM_CH_STATE_ACTIVE; |
335 | |
336 | return slim_do_transfer(sdev->ctrl, &txn); |
337 | } |
338 | |
339 | /* |
340 | * slim_stream_enable() - Enable a prepared SLIMbus Stream |
341 | * |
342 | * @stream: instance of slim stream runtime to enable |
343 | * |
344 | * This API will enable all the ports and channels associated with |
345 | * SLIMbus stream |
346 | * |
347 | * Return: zero on success and error code on failure. From ASoC DPCM framework, |
348 | * this state is linked to trigger() start operation. |
349 | */ |
350 | int slim_stream_enable(struct slim_stream_runtime *stream) |
351 | { |
352 | DEFINE_SLIM_BCAST_TXN(txn, SLIM_MSG_MC_BEGIN_RECONFIGURATION, |
353 | 3, SLIM_LA_MANAGER, NULL); |
354 | struct slim_controller *ctrl = stream->dev->ctrl; |
355 | int ret, i; |
356 | |
357 | if (ctrl->enable_stream) { |
358 | ret = ctrl->enable_stream(stream); |
359 | if (ret) |
360 | return ret; |
361 | |
362 | for (i = 0; i < stream->num_ports; i++) |
363 | stream->ports[i].ch.state = SLIM_CH_STATE_ACTIVE; |
364 | |
365 | return ret; |
366 | } |
367 | |
368 | ret = slim_do_transfer(ctrl, &txn); |
369 | if (ret) |
370 | return ret; |
371 | |
372 | /* define channels first before activating them */ |
373 | for (i = 0; i < stream->num_ports; i++) { |
374 | struct slim_port *port = &stream->ports[i]; |
375 | |
376 | slim_define_channel(stream, port); |
377 | slim_define_channel_content(stream, port); |
378 | } |
379 | |
380 | for (i = 0; i < stream->num_ports; i++) { |
381 | struct slim_port *port = &stream->ports[i]; |
382 | |
383 | slim_activate_channel(stream, port); |
384 | port->state = SLIM_PORT_CONFIGURED; |
385 | } |
386 | txn.mc = SLIM_MSG_MC_RECONFIGURE_NOW; |
387 | |
388 | return slim_do_transfer(ctrl, &txn); |
389 | } |
390 | EXPORT_SYMBOL_GPL(slim_stream_enable); |
391 | |
392 | /* |
393 | * slim_stream_disable() - Disable a SLIMbus Stream |
394 | * |
395 | * @stream: instance of slim stream runtime to disable |
396 | * |
397 | * This API will disable all the ports and channels associated with |
398 | * SLIMbus stream |
399 | * |
400 | * Return: zero on success and error code on failure. From ASoC DPCM framework, |
401 | * this state is linked to trigger() pause operation. |
402 | */ |
403 | int slim_stream_disable(struct slim_stream_runtime *stream) |
404 | { |
405 | DEFINE_SLIM_BCAST_TXN(txn, SLIM_MSG_MC_BEGIN_RECONFIGURATION, |
406 | 3, SLIM_LA_MANAGER, NULL); |
407 | struct slim_controller *ctrl = stream->dev->ctrl; |
408 | int ret, i; |
409 | |
410 | if (ctrl->disable_stream) |
411 | ctrl->disable_stream(stream); |
412 | |
413 | ret = slim_do_transfer(ctrl, &txn); |
414 | if (ret) |
415 | return ret; |
416 | |
417 | for (i = 0; i < stream->num_ports; i++) |
418 | slim_deactivate_remove_channel(stream, &stream->ports[i]); |
419 | |
420 | txn.mc = SLIM_MSG_MC_RECONFIGURE_NOW; |
421 | |
422 | return slim_do_transfer(ctrl, &txn); |
423 | } |
424 | EXPORT_SYMBOL_GPL(slim_stream_disable); |
425 | |
426 | /* |
427 | * slim_stream_unprepare() - Un-prepare a SLIMbus Stream |
428 | * |
429 | * @stream: instance of slim stream runtime to unprepare |
430 | * |
431 | * This API will un allocate all the ports and channels associated with |
432 | * SLIMbus stream |
433 | * |
434 | * Return: zero on success and error code on failure. From ASoC DPCM framework, |
435 | * this state is linked to trigger() stop operation. |
436 | */ |
437 | int slim_stream_unprepare(struct slim_stream_runtime *stream) |
438 | { |
439 | int i; |
440 | |
441 | for (i = 0; i < stream->num_ports; i++) |
442 | slim_disconnect_port(stream, &stream->ports[i]); |
443 | |
444 | kfree(stream->ports); |
445 | stream->ports = NULL; |
446 | stream->num_ports = 0; |
447 | |
448 | return 0; |
449 | } |
450 | EXPORT_SYMBOL_GPL(slim_stream_unprepare); |
451 | |
452 | /* |
453 | * slim_stream_free() - Free a SLIMbus Stream |
454 | * |
455 | * @stream: instance of slim stream runtime to free |
456 | * |
457 | * This API will un allocate all the memory associated with |
458 | * slim stream runtime, user is not allowed to make an dereference |
459 | * to stream after this call. |
460 | * |
461 | * Return: zero on success and error code on failure. From ASoC DPCM framework, |
462 | * this state is linked to shutdown() operation. |
463 | */ |
464 | int slim_stream_free(struct slim_stream_runtime *stream) |
465 | { |
466 | struct slim_device *sdev = stream->dev; |
467 | |
468 | spin_lock(&sdev->stream_list_lock); |
469 | list_del(&stream->node); |
470 | spin_unlock(&sdev->stream_list_lock); |
471 | |
472 | kfree(stream->name); |
473 | kfree(stream); |
474 | |
475 | return 0; |
476 | } |
477 | EXPORT_SYMBOL_GPL(slim_stream_free); |
478 | |