]>
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 */ | |
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 | ||
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; | |
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 | ||
2f0f2441 | 339 | /** |
abb9c9b8 SK |
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 | ||
2f0f2441 | 392 | /** |
abb9c9b8 SK |
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 | ||
2f0f2441 | 426 | /** |
abb9c9b8 SK |
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 | ||
2f0f2441 | 452 | /** |
abb9c9b8 SK |
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); |