diff options
Diffstat (limited to 'sound/soc/intel/sst-atom-controls.c')
-rw-r--r-- | sound/soc/intel/sst-atom-controls.c | 1208 |
1 files changed, 1206 insertions, 2 deletions
diff --git a/sound/soc/intel/sst-atom-controls.c b/sound/soc/intel/sst-atom-controls.c index 7104a34181a9..90aa5c0476f3 100644 --- a/sound/soc/intel/sst-atom-controls.c +++ b/sound/soc/intel/sst-atom-controls.c | |||
@@ -15,6 +15,9 @@ | |||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | * General Public License for more details. | 16 | * General Public License for more details. |
17 | * | 17 | * |
18 | * In the dpcm driver modelling when a particular FE/BE/Mixer/Pipe is active | ||
19 | * we forward the settings and parameters, rest we keep the values in | ||
20 | * driver and forward when DAPM enables them | ||
18 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 21 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
19 | */ | 22 | */ |
20 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 23 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
@@ -81,6 +84,183 @@ static int sst_fill_and_send_cmd(struct sst_data *drv, | |||
81 | return ret; | 84 | return ret; |
82 | } | 85 | } |
83 | 86 | ||
87 | /** | ||
88 | * tx map value is a bitfield where each bit represents a FW channel | ||
89 | * | ||
90 | * 3 2 1 0 # 0 = codec0, 1 = codec1 | ||
91 | * RLRLRLRL # 3, 4 = reserved | ||
92 | * | ||
93 | * e.g. slot 0 rx map = 00001100b -> data from slot 0 goes into codec_in1 L,R | ||
94 | */ | ||
95 | static u8 sst_ssp_tx_map[SST_MAX_TDM_SLOTS] = { | ||
96 | 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default rx map */ | ||
97 | }; | ||
98 | |||
99 | /** | ||
100 | * rx map value is a bitfield where each bit represents a slot | ||
101 | * | ||
102 | * 76543210 # 0 = slot 0, 1 = slot 1 | ||
103 | * | ||
104 | * e.g. codec1_0 tx map = 00000101b -> data from codec_out1_0 goes into slot 0, 2 | ||
105 | */ | ||
106 | static u8 sst_ssp_rx_map[SST_MAX_TDM_SLOTS] = { | ||
107 | 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default tx map */ | ||
108 | }; | ||
109 | |||
110 | /** | ||
111 | * NOTE: this is invoked with lock held | ||
112 | */ | ||
113 | static int sst_send_slot_map(struct sst_data *drv) | ||
114 | { | ||
115 | struct sst_param_sba_ssp_slot_map cmd; | ||
116 | |||
117 | SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); | ||
118 | cmd.header.command_id = SBA_SET_SSP_SLOT_MAP; | ||
119 | cmd.header.length = sizeof(struct sst_param_sba_ssp_slot_map) | ||
120 | - sizeof(struct sst_dsp_header); | ||
121 | |||
122 | cmd.param_id = SBA_SET_SSP_SLOT_MAP; | ||
123 | cmd.param_len = sizeof(cmd.rx_slot_map) + sizeof(cmd.tx_slot_map) | ||
124 | + sizeof(cmd.ssp_index); | ||
125 | cmd.ssp_index = SSP_CODEC; | ||
126 | |||
127 | memcpy(cmd.rx_slot_map, &sst_ssp_tx_map[0], sizeof(cmd.rx_slot_map)); | ||
128 | memcpy(cmd.tx_slot_map, &sst_ssp_rx_map[0], sizeof(cmd.tx_slot_map)); | ||
129 | |||
130 | return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS, | ||
131 | SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, | ||
132 | sizeof(cmd.header) + cmd.header.length); | ||
133 | } | ||
134 | |||
135 | int sst_slot_enum_info(struct snd_kcontrol *kcontrol, | ||
136 | struct snd_ctl_elem_info *uinfo) | ||
137 | { | ||
138 | struct sst_enum *e = (struct sst_enum *)kcontrol->private_value; | ||
139 | |||
140 | uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; | ||
141 | uinfo->count = 1; | ||
142 | uinfo->value.enumerated.items = e->max; | ||
143 | |||
144 | if (uinfo->value.enumerated.item > e->max - 1) | ||
145 | uinfo->value.enumerated.item = e->max - 1; | ||
146 | strcpy(uinfo->value.enumerated.name, | ||
147 | e->texts[uinfo->value.enumerated.item]); | ||
148 | |||
149 | return 0; | ||
150 | } | ||
151 | |||
152 | /** | ||
153 | * sst_slot_get - get the status of the interleaver/deinterleaver control | ||
154 | * | ||
155 | * Searches the map where the control status is stored, and gets the | ||
156 | * channel/slot which is currently set for this enumerated control. Since it is | ||
157 | * an enumerated control, there is only one possible value. | ||
158 | */ | ||
159 | static int sst_slot_get(struct snd_kcontrol *kcontrol, | ||
160 | struct snd_ctl_elem_value *ucontrol) | ||
161 | { | ||
162 | struct sst_enum *e = (void *)kcontrol->private_value; | ||
163 | struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); | ||
164 | struct sst_data *drv = snd_soc_component_get_drvdata(c); | ||
165 | unsigned int ctl_no = e->reg; | ||
166 | unsigned int is_tx = e->tx; | ||
167 | unsigned int val, mux; | ||
168 | u8 *map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; | ||
169 | |||
170 | mutex_lock(&drv->lock); | ||
171 | val = 1 << ctl_no; | ||
172 | /* search which slot/channel has this bit set - there should be only one */ | ||
173 | for (mux = e->max; mux > 0; mux--) | ||
174 | if (map[mux - 1] & val) | ||
175 | break; | ||
176 | |||
177 | ucontrol->value.enumerated.item[0] = mux; | ||
178 | mutex_unlock(&drv->lock); | ||
179 | |||
180 | dev_dbg(c->dev, "%s - %s map = %#x\n", | ||
181 | is_tx ? "tx channel" : "rx slot", | ||
182 | e->texts[mux], mux ? map[mux - 1] : -1); | ||
183 | return 0; | ||
184 | } | ||
185 | |||
186 | /* sst_check_and_send_slot_map - helper for checking power state and sending | ||
187 | * slot map cmd | ||
188 | * | ||
189 | * called with lock held | ||
190 | */ | ||
191 | static int sst_check_and_send_slot_map(struct sst_data *drv, struct snd_kcontrol *kcontrol) | ||
192 | { | ||
193 | struct sst_enum *e = (void *)kcontrol->private_value; | ||
194 | int ret = 0; | ||
195 | |||
196 | if (e->w && e->w->power) | ||
197 | ret = sst_send_slot_map(drv); | ||
198 | else | ||
199 | dev_err(&drv->pdev->dev, "Slot control: %s doesn't have DAPM widget!!!\n", | ||
200 | kcontrol->id.name); | ||
201 | return ret; | ||
202 | } | ||
203 | |||
204 | /** | ||
205 | * sst_slot_put - set the status of interleaver/deinterleaver control | ||
206 | * | ||
207 | * (de)interleaver controls are defined in opposite sense to be user-friendly | ||
208 | * | ||
209 | * Instead of the enum value being the value written to the register, it is the | ||
210 | * register address; and the kcontrol number (register num) is the value written | ||
211 | * to the register. This is so that there can be only one value for each | ||
212 | * slot/channel since there is only one control for each slot/channel. | ||
213 | * | ||
214 | * This means that whenever an enum is set, we need to clear the bit | ||
215 | * for that kcontrol_no for all the interleaver OR deinterleaver registers | ||
216 | */ | ||
217 | static int sst_slot_put(struct snd_kcontrol *kcontrol, | ||
218 | struct snd_ctl_elem_value *ucontrol) | ||
219 | { | ||
220 | struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); | ||
221 | struct sst_data *drv = snd_soc_component_get_drvdata(c); | ||
222 | struct sst_enum *e = (void *)kcontrol->private_value; | ||
223 | int i, ret = 0; | ||
224 | unsigned int ctl_no = e->reg; | ||
225 | unsigned int is_tx = e->tx; | ||
226 | unsigned int slot_channel_no; | ||
227 | unsigned int val, mux; | ||
228 | u8 *map; | ||
229 | |||
230 | map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; | ||
231 | |||
232 | val = 1 << ctl_no; | ||
233 | mux = ucontrol->value.enumerated.item[0]; | ||
234 | if (mux > e->max - 1) | ||
235 | return -EINVAL; | ||
236 | |||
237 | mutex_lock(&drv->lock); | ||
238 | /* first clear all registers of this bit */ | ||
239 | for (i = 0; i < e->max; i++) | ||
240 | map[i] &= ~val; | ||
241 | |||
242 | if (mux == 0) { | ||
243 | /* kctl set to 'none' and we reset the bits so send IPC */ | ||
244 | ret = sst_check_and_send_slot_map(drv, kcontrol); | ||
245 | |||
246 | mutex_unlock(&drv->lock); | ||
247 | return ret; | ||
248 | } | ||
249 | |||
250 | /* offset by one to take "None" into account */ | ||
251 | slot_channel_no = mux - 1; | ||
252 | map[slot_channel_no] |= val; | ||
253 | |||
254 | dev_dbg(c->dev, "%s %s map = %#x\n", | ||
255 | is_tx ? "tx channel" : "rx slot", | ||
256 | e->texts[mux], map[slot_channel_no]); | ||
257 | |||
258 | ret = sst_check_and_send_slot_map(drv, kcontrol); | ||
259 | |||
260 | mutex_unlock(&drv->lock); | ||
261 | return ret; | ||
262 | } | ||
263 | |||
84 | static int sst_send_algo_cmd(struct sst_data *drv, | 264 | static int sst_send_algo_cmd(struct sst_data *drv, |
85 | struct sst_algo_control *bc) | 265 | struct sst_algo_control *bc) |
86 | { | 266 | { |
@@ -104,6 +284,34 @@ static int sst_send_algo_cmd(struct sst_data *drv, | |||
104 | return ret; | 284 | return ret; |
105 | } | 285 | } |
106 | 286 | ||
287 | /** | ||
288 | * sst_find_and_send_pipe_algo - send all the algo parameters for a pipe | ||
289 | * | ||
290 | * The algos which are in each pipeline are sent to the firmware one by one | ||
291 | * | ||
292 | * Called with lock held | ||
293 | */ | ||
294 | static int sst_find_and_send_pipe_algo(struct sst_data *drv, | ||
295 | const char *pipe, struct sst_ids *ids) | ||
296 | { | ||
297 | int ret = 0; | ||
298 | struct sst_algo_control *bc; | ||
299 | struct sst_module *algo = NULL; | ||
300 | |||
301 | dev_dbg(&drv->pdev->dev, "Enter: widget=%s\n", pipe); | ||
302 | |||
303 | list_for_each_entry(algo, &ids->algo_list, node) { | ||
304 | bc = (void *)algo->kctl->private_value; | ||
305 | |||
306 | dev_dbg(&drv->pdev->dev, "Found algo control name=%s pipe=%s\n", | ||
307 | algo->kctl->id.name, pipe); | ||
308 | ret = sst_send_algo_cmd(drv, bc); | ||
309 | if (ret) | ||
310 | return ret; | ||
311 | } | ||
312 | return ret; | ||
313 | } | ||
314 | |||
107 | static int sst_algo_bytes_ctl_info(struct snd_kcontrol *kcontrol, | 315 | static int sst_algo_bytes_ctl_info(struct snd_kcontrol *kcontrol, |
108 | struct snd_ctl_elem_info *uinfo) | 316 | struct snd_ctl_elem_info *uinfo) |
109 | { | 317 | { |
@@ -162,6 +370,743 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol, | |||
162 | return ret; | 370 | return ret; |
163 | } | 371 | } |
164 | 372 | ||
373 | static int sst_gain_ctl_info(struct snd_kcontrol *kcontrol, | ||
374 | struct snd_ctl_elem_info *uinfo) | ||
375 | { | ||
376 | struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; | ||
377 | |||
378 | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | ||
379 | uinfo->count = mc->stereo ? 2 : 1; | ||
380 | uinfo->value.integer.min = mc->min; | ||
381 | uinfo->value.integer.max = mc->max; | ||
382 | |||
383 | return 0; | ||
384 | } | ||
385 | |||
386 | /** | ||
387 | * sst_send_gain_cmd - send the gain algorithm IPC to the FW | ||
388 | * @gv: the stored value of gain (also contains rampduration) | ||
389 | * @mute: flag that indicates whether this was called from the | ||
390 | * digital_mute callback or directly. If called from the | ||
391 | * digital_mute callback, module will be muted/unmuted based on this | ||
392 | * flag. The flag is always 0 if called directly. | ||
393 | * | ||
394 | * Called with sst_data.lock held | ||
395 | * | ||
396 | * The user-set gain value is sent only if the user-controllable 'mute' control | ||
397 | * is OFF (indicated by gv->mute). Otherwise, the mute value (MIN value) is | ||
398 | * sent. | ||
399 | */ | ||
400 | static int sst_send_gain_cmd(struct sst_data *drv, struct sst_gain_value *gv, | ||
401 | u16 task_id, u16 loc_id, u16 module_id, int mute) | ||
402 | { | ||
403 | struct sst_cmd_set_gain_dual cmd; | ||
404 | |||
405 | dev_dbg(&drv->pdev->dev, "Enter\n"); | ||
406 | |||
407 | cmd.header.command_id = MMX_SET_GAIN; | ||
408 | SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); | ||
409 | cmd.gain_cell_num = 1; | ||
410 | |||
411 | if (mute || gv->mute) { | ||
412 | cmd.cell_gains[0].cell_gain_left = SST_GAIN_MIN_VALUE; | ||
413 | cmd.cell_gains[0].cell_gain_right = SST_GAIN_MIN_VALUE; | ||
414 | } else { | ||
415 | cmd.cell_gains[0].cell_gain_left = gv->l_gain; | ||
416 | cmd.cell_gains[0].cell_gain_right = gv->r_gain; | ||
417 | } | ||
418 | |||
419 | SST_FILL_DESTINATION(2, cmd.cell_gains[0].dest, | ||
420 | loc_id, module_id); | ||
421 | cmd.cell_gains[0].gain_time_constant = gv->ramp_duration; | ||
422 | |||
423 | cmd.header.length = sizeof(struct sst_cmd_set_gain_dual) | ||
424 | - sizeof(struct sst_dsp_header); | ||
425 | |||
426 | /* we are with lock held, so call the unlocked api to send */ | ||
427 | return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS, | ||
428 | SST_FLAG_BLOCKED, task_id, 0, &cmd, | ||
429 | sizeof(cmd.header) + cmd.header.length); | ||
430 | } | ||
431 | |||
432 | static int sst_gain_get(struct snd_kcontrol *kcontrol, | ||
433 | struct snd_ctl_elem_value *ucontrol) | ||
434 | { | ||
435 | struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); | ||
436 | struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; | ||
437 | struct sst_gain_value *gv = mc->gain_val; | ||
438 | |||
439 | switch (mc->type) { | ||
440 | case SST_GAIN_TLV: | ||
441 | ucontrol->value.integer.value[0] = gv->l_gain; | ||
442 | ucontrol->value.integer.value[1] = gv->r_gain; | ||
443 | break; | ||
444 | |||
445 | case SST_GAIN_MUTE: | ||
446 | ucontrol->value.integer.value[0] = gv->mute ? 1 : 0; | ||
447 | break; | ||
448 | |||
449 | case SST_GAIN_RAMP_DURATION: | ||
450 | ucontrol->value.integer.value[0] = gv->ramp_duration; | ||
451 | break; | ||
452 | |||
453 | default: | ||
454 | dev_err(component->dev, "Invalid Input- gain type:%d\n", | ||
455 | mc->type); | ||
456 | return -EINVAL; | ||
457 | } | ||
458 | |||
459 | return 0; | ||
460 | } | ||
461 | |||
462 | static int sst_gain_put(struct snd_kcontrol *kcontrol, | ||
463 | struct snd_ctl_elem_value *ucontrol) | ||
464 | { | ||
465 | int ret = 0; | ||
466 | struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); | ||
467 | struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt); | ||
468 | struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; | ||
469 | struct sst_gain_value *gv = mc->gain_val; | ||
470 | |||
471 | mutex_lock(&drv->lock); | ||
472 | |||
473 | switch (mc->type) { | ||
474 | case SST_GAIN_TLV: | ||
475 | gv->l_gain = ucontrol->value.integer.value[0]; | ||
476 | gv->r_gain = ucontrol->value.integer.value[1]; | ||
477 | dev_dbg(cmpnt->dev, "%s: Volume %d, %d\n", | ||
478 | mc->pname, gv->l_gain, gv->r_gain); | ||
479 | break; | ||
480 | |||
481 | case SST_GAIN_MUTE: | ||
482 | gv->mute = !!ucontrol->value.integer.value[0]; | ||
483 | dev_dbg(cmpnt->dev, "%s: Mute %d\n", mc->pname, gv->mute); | ||
484 | break; | ||
485 | |||
486 | case SST_GAIN_RAMP_DURATION: | ||
487 | gv->ramp_duration = ucontrol->value.integer.value[0]; | ||
488 | dev_dbg(cmpnt->dev, "%s: Ramp Delay%d\n", | ||
489 | mc->pname, gv->ramp_duration); | ||
490 | break; | ||
491 | |||
492 | default: | ||
493 | mutex_unlock(&drv->lock); | ||
494 | dev_err(cmpnt->dev, "Invalid Input- gain type:%d\n", | ||
495 | mc->type); | ||
496 | return -EINVAL; | ||
497 | } | ||
498 | |||
499 | if (mc->w && mc->w->power) | ||
500 | ret = sst_send_gain_cmd(drv, gv, mc->task_id, | ||
501 | mc->pipe_id | mc->instance_id, mc->module_id, 0); | ||
502 | mutex_unlock(&drv->lock); | ||
503 | |||
504 | return ret; | ||
505 | } | ||
506 | |||
507 | static int sst_set_pipe_gain(struct sst_ids *ids, | ||
508 | struct sst_data *drv, int mute); | ||
509 | |||
510 | static int sst_send_pipe_module_params(struct snd_soc_dapm_widget *w, | ||
511 | struct snd_kcontrol *kcontrol) | ||
512 | { | ||
513 | struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); | ||
514 | struct sst_data *drv = snd_soc_component_get_drvdata(c); | ||
515 | struct sst_ids *ids = w->priv; | ||
516 | |||
517 | mutex_lock(&drv->lock); | ||
518 | sst_find_and_send_pipe_algo(drv, w->name, ids); | ||
519 | sst_set_pipe_gain(ids, drv, 0); | ||
520 | mutex_unlock(&drv->lock); | ||
521 | |||
522 | return 0; | ||
523 | } | ||
524 | |||
525 | static int sst_generic_modules_event(struct snd_soc_dapm_widget *w, | ||
526 | struct snd_kcontrol *k, int event) | ||
527 | { | ||
528 | if (SND_SOC_DAPM_EVENT_ON(event)) | ||
529 | return sst_send_pipe_module_params(w, k); | ||
530 | return 0; | ||
531 | } | ||
532 | |||
533 | static const DECLARE_TLV_DB_SCALE(sst_gain_tlv_common, SST_GAIN_MIN_VALUE * 10, 10, 0); | ||
534 | |||
535 | /* Look up table to convert MIXER SW bit regs to SWM inputs */ | ||
536 | static const uint swm_mixer_input_ids[SST_SWM_INPUT_COUNT] = { | ||
537 | [SST_IP_CODEC0] = SST_SWM_IN_CODEC0, | ||
538 | [SST_IP_CODEC1] = SST_SWM_IN_CODEC1, | ||
539 | [SST_IP_LOOP0] = SST_SWM_IN_SPROT_LOOP, | ||
540 | [SST_IP_LOOP1] = SST_SWM_IN_MEDIA_LOOP1, | ||
541 | [SST_IP_LOOP2] = SST_SWM_IN_MEDIA_LOOP2, | ||
542 | [SST_IP_PCM0] = SST_SWM_IN_PCM0, | ||
543 | [SST_IP_PCM1] = SST_SWM_IN_PCM1, | ||
544 | [SST_IP_MEDIA0] = SST_SWM_IN_MEDIA0, | ||
545 | [SST_IP_MEDIA1] = SST_SWM_IN_MEDIA1, | ||
546 | [SST_IP_MEDIA2] = SST_SWM_IN_MEDIA2, | ||
547 | [SST_IP_MEDIA3] = SST_SWM_IN_MEDIA3, | ||
548 | }; | ||
549 | |||
550 | /** | ||
551 | * fill_swm_input - fill in the SWM input ids given the register | ||
552 | * | ||
553 | * The register value is a bit-field inicated which mixer inputs are ON. Use the | ||
554 | * lookup table to get the input-id and fill it in the structure. | ||
555 | */ | ||
556 | static int fill_swm_input(struct snd_soc_component *cmpnt, | ||
557 | struct swm_input_ids *swm_input, unsigned int reg) | ||
558 | { | ||
559 | uint i, is_set, nb_inputs = 0; | ||
560 | u16 input_loc_id; | ||
561 | |||
562 | dev_dbg(cmpnt->dev, "reg: %#x\n", reg); | ||
563 | for (i = 0; i < SST_SWM_INPUT_COUNT; i++) { | ||
564 | is_set = reg & BIT(i); | ||
565 | if (!is_set) | ||
566 | continue; | ||
567 | |||
568 | input_loc_id = swm_mixer_input_ids[i]; | ||
569 | SST_FILL_DESTINATION(2, swm_input->input_id, | ||
570 | input_loc_id, SST_DEFAULT_MODULE_ID); | ||
571 | nb_inputs++; | ||
572 | swm_input++; | ||
573 | dev_dbg(cmpnt->dev, "input id: %#x, nb_inputs: %d\n", | ||
574 | input_loc_id, nb_inputs); | ||
575 | |||
576 | if (nb_inputs == SST_CMD_SWM_MAX_INPUTS) { | ||
577 | dev_warn(cmpnt->dev, "SET_SWM cmd max inputs reached"); | ||
578 | break; | ||
579 | } | ||
580 | } | ||
581 | return nb_inputs; | ||
582 | } | ||
583 | |||
584 | |||
585 | /** | ||
586 | * called with lock held | ||
587 | */ | ||
588 | static int sst_set_pipe_gain(struct sst_ids *ids, | ||
589 | struct sst_data *drv, int mute) | ||
590 | { | ||
591 | int ret = 0; | ||
592 | struct sst_gain_mixer_control *mc; | ||
593 | struct sst_gain_value *gv; | ||
594 | struct sst_module *gain = NULL; | ||
595 | |||
596 | list_for_each_entry(gain, &ids->gain_list, node) { | ||
597 | struct snd_kcontrol *kctl = gain->kctl; | ||
598 | |||
599 | dev_dbg(&drv->pdev->dev, "control name=%s\n", kctl->id.name); | ||
600 | mc = (void *)kctl->private_value; | ||
601 | gv = mc->gain_val; | ||
602 | |||
603 | ret = sst_send_gain_cmd(drv, gv, mc->task_id, | ||
604 | mc->pipe_id | mc->instance_id, mc->module_id, mute); | ||
605 | if (ret) | ||
606 | return ret; | ||
607 | } | ||
608 | return ret; | ||
609 | } | ||
610 | |||
611 | static int sst_swm_mixer_event(struct snd_soc_dapm_widget *w, | ||
612 | struct snd_kcontrol *k, int event) | ||
613 | { | ||
614 | struct sst_cmd_set_swm cmd; | ||
615 | struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); | ||
616 | struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt); | ||
617 | struct sst_ids *ids = w->priv; | ||
618 | bool set_mixer = false; | ||
619 | struct soc_mixer_control *mc; | ||
620 | int val = 0; | ||
621 | int i = 0; | ||
622 | |||
623 | dev_dbg(cmpnt->dev, "widget = %s\n", w->name); | ||
624 | /* | ||
625 | * Identify which mixer input is on and send the bitmap of the | ||
626 | * inputs as an IPC to the DSP. | ||
627 | */ | ||
628 | for (i = 0; i < w->num_kcontrols; i++) { | ||
629 | if (dapm_kcontrol_get_value(w->kcontrols[i])) { | ||
630 | mc = (struct soc_mixer_control *)(w->kcontrols[i])->private_value; | ||
631 | val |= 1 << mc->shift; | ||
632 | } | ||
633 | } | ||
634 | dev_dbg(cmpnt->dev, "val = %#x\n", val); | ||
635 | |||
636 | switch (event) { | ||
637 | case SND_SOC_DAPM_PRE_PMU: | ||
638 | case SND_SOC_DAPM_POST_PMD: | ||
639 | set_mixer = true; | ||
640 | break; | ||
641 | case SND_SOC_DAPM_POST_REG: | ||
642 | if (w->power) | ||
643 | set_mixer = true; | ||
644 | break; | ||
645 | default: | ||
646 | set_mixer = false; | ||
647 | } | ||
648 | |||
649 | if (set_mixer == false) | ||
650 | return 0; | ||
651 | |||
652 | if (SND_SOC_DAPM_EVENT_ON(event) || | ||
653 | event == SND_SOC_DAPM_POST_REG) | ||
654 | cmd.switch_state = SST_SWM_ON; | ||
655 | else | ||
656 | cmd.switch_state = SST_SWM_OFF; | ||
657 | |||
658 | SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); | ||
659 | /* MMX_SET_SWM == SBA_SET_SWM */ | ||
660 | cmd.header.command_id = SBA_SET_SWM; | ||
661 | |||
662 | SST_FILL_DESTINATION(2, cmd.output_id, | ||
663 | ids->location_id, SST_DEFAULT_MODULE_ID); | ||
664 | cmd.nb_inputs = fill_swm_input(cmpnt, &cmd.input[0], val); | ||
665 | cmd.header.length = offsetof(struct sst_cmd_set_swm, input) | ||
666 | - sizeof(struct sst_dsp_header) | ||
667 | + (cmd.nb_inputs * sizeof(cmd.input[0])); | ||
668 | |||
669 | return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, | ||
670 | ids->task_id, 0, &cmd, | ||
671 | sizeof(cmd.header) + cmd.header.length); | ||
672 | } | ||
673 | |||
674 | /* SBA mixers - 16 inputs */ | ||
675 | #define SST_SBA_DECLARE_MIX_CONTROLS(kctl_name) \ | ||
676 | static const struct snd_kcontrol_new kctl_name[] = { \ | ||
677 | SOC_DAPM_SINGLE("codec_in0 Switch", SND_SOC_NOPM, SST_IP_CODEC0, 1, 0), \ | ||
678 | SOC_DAPM_SINGLE("codec_in1 Switch", SND_SOC_NOPM, SST_IP_CODEC1, 1, 0), \ | ||
679 | SOC_DAPM_SINGLE("sprot_loop_in Switch", SND_SOC_NOPM, SST_IP_LOOP0, 1, 0), \ | ||
680 | SOC_DAPM_SINGLE("media_loop1_in Switch", SND_SOC_NOPM, SST_IP_LOOP1, 1, 0), \ | ||
681 | SOC_DAPM_SINGLE("media_loop2_in Switch", SND_SOC_NOPM, SST_IP_LOOP2, 1, 0), \ | ||
682 | SOC_DAPM_SINGLE("pcm0_in Switch", SND_SOC_NOPM, SST_IP_PCM0, 1, 0), \ | ||
683 | SOC_DAPM_SINGLE("pcm1_in Switch", SND_SOC_NOPM, SST_IP_PCM1, 1, 0), \ | ||
684 | } | ||
685 | |||
686 | #define SST_SBA_MIXER_GRAPH_MAP(mix_name) \ | ||
687 | { mix_name, "codec_in0 Switch", "codec_in0" }, \ | ||
688 | { mix_name, "codec_in1 Switch", "codec_in1" }, \ | ||
689 | { mix_name, "sprot_loop_in Switch", "sprot_loop_in" }, \ | ||
690 | { mix_name, "media_loop1_in Switch", "media_loop1_in" }, \ | ||
691 | { mix_name, "media_loop2_in Switch", "media_loop2_in" }, \ | ||
692 | { mix_name, "pcm0_in Switch", "pcm0_in" }, \ | ||
693 | { mix_name, "pcm1_in Switch", "pcm1_in" } | ||
694 | |||
695 | #define SST_MMX_DECLARE_MIX_CONTROLS(kctl_name) \ | ||
696 | static const struct snd_kcontrol_new kctl_name[] = { \ | ||
697 | SOC_DAPM_SINGLE("media0_in Switch", SND_SOC_NOPM, SST_IP_MEDIA0, 1, 0), \ | ||
698 | SOC_DAPM_SINGLE("media1_in Switch", SND_SOC_NOPM, SST_IP_MEDIA1, 1, 0), \ | ||
699 | SOC_DAPM_SINGLE("media2_in Switch", SND_SOC_NOPM, SST_IP_MEDIA2, 1, 0), \ | ||
700 | SOC_DAPM_SINGLE("media3_in Switch", SND_SOC_NOPM, SST_IP_MEDIA3, 1, 0), \ | ||
701 | } | ||
702 | |||
703 | SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media0_controls); | ||
704 | SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media1_controls); | ||
705 | |||
706 | /* 18 SBA mixers */ | ||
707 | SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm0_controls); | ||
708 | SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm1_controls); | ||
709 | SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm2_controls); | ||
710 | SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_sprot_l0_controls); | ||
711 | SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l1_controls); | ||
712 | SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l2_controls); | ||
713 | SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_voip_controls); | ||
714 | SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec0_controls); | ||
715 | SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec1_controls); | ||
716 | |||
717 | /* | ||
718 | * sst_handle_vb_timer - Start/Stop the DSP scheduler | ||
719 | * | ||
720 | * The DSP expects first cmd to be SBA_VB_START, so at first startup send | ||
721 | * that. | ||
722 | * DSP expects last cmd to be SBA_VB_IDLE, so at last shutdown send that. | ||
723 | * | ||
724 | * Do refcount internally so that we send command only at first start | ||
725 | * and last end. Since SST driver does its own ref count, invoke sst's | ||
726 | * power ops always! | ||
727 | */ | ||
728 | int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable) | ||
729 | { | ||
730 | int ret = 0; | ||
731 | struct sst_cmd_generic cmd; | ||
732 | struct sst_data *drv = snd_soc_dai_get_drvdata(dai); | ||
733 | static int timer_usage; | ||
734 | |||
735 | if (enable) | ||
736 | cmd.header.command_id = SBA_VB_START; | ||
737 | else | ||
738 | cmd.header.command_id = SBA_IDLE; | ||
739 | dev_dbg(dai->dev, "enable=%u, usage=%d\n", enable, timer_usage); | ||
740 | |||
741 | SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); | ||
742 | cmd.header.length = 0; | ||
743 | |||
744 | if (enable) { | ||
745 | ret = sst->ops->power(sst->dev, true); | ||
746 | if (ret < 0) | ||
747 | return ret; | ||
748 | } | ||
749 | |||
750 | mutex_lock(&drv->lock); | ||
751 | if (enable) | ||
752 | timer_usage++; | ||
753 | else | ||
754 | timer_usage--; | ||
755 | |||
756 | /* | ||
757 | * Send the command only if this call is the first enable or last | ||
758 | * disable | ||
759 | */ | ||
760 | if ((enable && (timer_usage == 1)) || | ||
761 | (!enable && (timer_usage == 0))) { | ||
762 | ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, | ||
763 | SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, | ||
764 | sizeof(cmd.header) + cmd.header.length); | ||
765 | if (ret && enable) { | ||
766 | timer_usage--; | ||
767 | enable = false; | ||
768 | } | ||
769 | } | ||
770 | mutex_unlock(&drv->lock); | ||
771 | |||
772 | if (!enable) | ||
773 | sst->ops->power(sst->dev, false); | ||
774 | return ret; | ||
775 | } | ||
776 | |||
777 | /** | ||
778 | * sst_ssp_config - contains SSP configuration for media UC | ||
779 | */ | ||
780 | static const struct sst_ssp_config sst_ssp_configs = { | ||
781 | .ssp_id = SSP_CODEC, | ||
782 | .bits_per_slot = 24, | ||
783 | .slots = 4, | ||
784 | .ssp_mode = SSP_MODE_MASTER, | ||
785 | .pcm_mode = SSP_PCM_MODE_NETWORK, | ||
786 | .duplex = SSP_DUPLEX, | ||
787 | .ssp_protocol = SSP_MODE_PCM, | ||
788 | .fs_width = 1, | ||
789 | .fs_frequency = SSP_FS_48_KHZ, | ||
790 | .active_slot_map = 0xF, | ||
791 | .start_delay = 0, | ||
792 | }; | ||
793 | |||
794 | int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable) | ||
795 | { | ||
796 | struct sst_cmd_sba_hw_set_ssp cmd; | ||
797 | struct sst_data *drv = snd_soc_dai_get_drvdata(dai); | ||
798 | const struct sst_ssp_config *config; | ||
799 | |||
800 | dev_info(dai->dev, "Enter: enable=%d port_name=%s\n", enable, id); | ||
801 | |||
802 | SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); | ||
803 | cmd.header.command_id = SBA_HW_SET_SSP; | ||
804 | cmd.header.length = sizeof(struct sst_cmd_sba_hw_set_ssp) | ||
805 | - sizeof(struct sst_dsp_header); | ||
806 | |||
807 | config = &sst_ssp_configs; | ||
808 | dev_dbg(dai->dev, "ssp_id: %u\n", config->ssp_id); | ||
809 | |||
810 | if (enable) | ||
811 | cmd.switch_state = SST_SWITCH_ON; | ||
812 | else | ||
813 | cmd.switch_state = SST_SWITCH_OFF; | ||
814 | |||
815 | cmd.selection = config->ssp_id; | ||
816 | cmd.nb_bits_per_slots = config->bits_per_slot; | ||
817 | cmd.nb_slots = config->slots; | ||
818 | cmd.mode = config->ssp_mode | (config->pcm_mode << 1); | ||
819 | cmd.duplex = config->duplex; | ||
820 | cmd.active_tx_slot_map = config->active_slot_map; | ||
821 | cmd.active_rx_slot_map = config->active_slot_map; | ||
822 | cmd.frame_sync_frequency = config->fs_frequency; | ||
823 | cmd.frame_sync_polarity = SSP_FS_ACTIVE_HIGH; | ||
824 | cmd.data_polarity = 1; | ||
825 | cmd.frame_sync_width = config->fs_width; | ||
826 | cmd.ssp_protocol = config->ssp_protocol; | ||
827 | cmd.start_delay = config->start_delay; | ||
828 | cmd.reserved1 = cmd.reserved2 = 0xFF; | ||
829 | |||
830 | return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, | ||
831 | SST_TASK_SBA, 0, &cmd, | ||
832 | sizeof(cmd.header) + cmd.header.length); | ||
833 | } | ||
834 | |||
835 | static int sst_set_be_modules(struct snd_soc_dapm_widget *w, | ||
836 | struct snd_kcontrol *k, int event) | ||
837 | { | ||
838 | int ret = 0; | ||
839 | struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); | ||
840 | struct sst_data *drv = snd_soc_component_get_drvdata(c); | ||
841 | |||
842 | dev_dbg(c->dev, "Enter: widget=%s\n", w->name); | ||
843 | |||
844 | if (SND_SOC_DAPM_EVENT_ON(event)) { | ||
845 | ret = sst_send_slot_map(drv); | ||
846 | if (ret) | ||
847 | return ret; | ||
848 | ret = sst_send_pipe_module_params(w, k); | ||
849 | } | ||
850 | return ret; | ||
851 | } | ||
852 | |||
853 | static int sst_set_media_path(struct snd_soc_dapm_widget *w, | ||
854 | struct snd_kcontrol *k, int event) | ||
855 | { | ||
856 | int ret = 0; | ||
857 | struct sst_cmd_set_media_path cmd; | ||
858 | struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); | ||
859 | struct sst_data *drv = snd_soc_component_get_drvdata(c); | ||
860 | struct sst_ids *ids = w->priv; | ||
861 | |||
862 | dev_dbg(c->dev, "widget=%s\n", w->name); | ||
863 | dev_dbg(c->dev, "task=%u, location=%#x\n", | ||
864 | ids->task_id, ids->location_id); | ||
865 | |||
866 | if (SND_SOC_DAPM_EVENT_ON(event)) | ||
867 | cmd.switch_state = SST_PATH_ON; | ||
868 | else | ||
869 | cmd.switch_state = SST_PATH_OFF; | ||
870 | |||
871 | SST_FILL_DESTINATION(2, cmd.header.dst, | ||
872 | ids->location_id, SST_DEFAULT_MODULE_ID); | ||
873 | |||
874 | /* MMX_SET_MEDIA_PATH == SBA_SET_MEDIA_PATH */ | ||
875 | cmd.header.command_id = MMX_SET_MEDIA_PATH; | ||
876 | cmd.header.length = sizeof(struct sst_cmd_set_media_path) | ||
877 | - sizeof(struct sst_dsp_header); | ||
878 | |||
879 | ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, | ||
880 | ids->task_id, 0, &cmd, | ||
881 | sizeof(cmd.header) + cmd.header.length); | ||
882 | if (ret) | ||
883 | return ret; | ||
884 | |||
885 | if (SND_SOC_DAPM_EVENT_ON(event)) | ||
886 | ret = sst_send_pipe_module_params(w, k); | ||
887 | return ret; | ||
888 | } | ||
889 | |||
890 | static int sst_set_media_loop(struct snd_soc_dapm_widget *w, | ||
891 | struct snd_kcontrol *k, int event) | ||
892 | { | ||
893 | int ret = 0; | ||
894 | struct sst_cmd_sba_set_media_loop_map cmd; | ||
895 | struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); | ||
896 | struct sst_data *drv = snd_soc_component_get_drvdata(c); | ||
897 | struct sst_ids *ids = w->priv; | ||
898 | |||
899 | dev_dbg(c->dev, "Enter:widget=%s\n", w->name); | ||
900 | if (SND_SOC_DAPM_EVENT_ON(event)) | ||
901 | cmd.switch_state = SST_SWITCH_ON; | ||
902 | else | ||
903 | cmd.switch_state = SST_SWITCH_OFF; | ||
904 | |||
905 | SST_FILL_DESTINATION(2, cmd.header.dst, | ||
906 | ids->location_id, SST_DEFAULT_MODULE_ID); | ||
907 | |||
908 | cmd.header.command_id = SBA_SET_MEDIA_LOOP_MAP; | ||
909 | cmd.header.length = sizeof(struct sst_cmd_sba_set_media_loop_map) | ||
910 | - sizeof(struct sst_dsp_header); | ||
911 | cmd.param.part.cfg.rate = 2; /* 48khz */ | ||
912 | |||
913 | cmd.param.part.cfg.format = ids->format; /* stereo/Mono */ | ||
914 | cmd.param.part.cfg.s_length = 1; /* 24bit left justified */ | ||
915 | cmd.map = 0; /* Algo sequence: Gain - DRP - FIR - IIR */ | ||
916 | |||
917 | ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, | ||
918 | SST_TASK_SBA, 0, &cmd, | ||
919 | sizeof(cmd.header) + cmd.header.length); | ||
920 | if (ret) | ||
921 | return ret; | ||
922 | |||
923 | if (SND_SOC_DAPM_EVENT_ON(event)) | ||
924 | ret = sst_send_pipe_module_params(w, k); | ||
925 | return ret; | ||
926 | } | ||
927 | |||
928 | static const struct snd_soc_dapm_widget sst_dapm_widgets[] = { | ||
929 | SST_AIF_IN("codec_in0", sst_set_be_modules), | ||
930 | SST_AIF_IN("codec_in1", sst_set_be_modules), | ||
931 | SST_AIF_OUT("codec_out0", sst_set_be_modules), | ||
932 | SST_AIF_OUT("codec_out1", sst_set_be_modules), | ||
933 | |||
934 | /* Media Paths */ | ||
935 | /* MediaX IN paths are set via ALLOC, so no SET_MEDIA_PATH command */ | ||
936 | SST_PATH_INPUT("media0_in", SST_TASK_MMX, SST_SWM_IN_MEDIA0, sst_generic_modules_event), | ||
937 | SST_PATH_INPUT("media1_in", SST_TASK_MMX, SST_SWM_IN_MEDIA1, NULL), | ||
938 | SST_PATH_INPUT("media2_in", SST_TASK_MMX, SST_SWM_IN_MEDIA2, sst_set_media_path), | ||
939 | SST_PATH_INPUT("media3_in", SST_TASK_MMX, SST_SWM_IN_MEDIA3, NULL), | ||
940 | SST_PATH_OUTPUT("media0_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA0, sst_set_media_path), | ||
941 | SST_PATH_OUTPUT("media1_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA1, sst_set_media_path), | ||
942 | |||
943 | /* SBA PCM Paths */ | ||
944 | SST_PATH_INPUT("pcm0_in", SST_TASK_SBA, SST_SWM_IN_PCM0, sst_set_media_path), | ||
945 | SST_PATH_INPUT("pcm1_in", SST_TASK_SBA, SST_SWM_IN_PCM1, sst_set_media_path), | ||
946 | SST_PATH_OUTPUT("pcm0_out", SST_TASK_SBA, SST_SWM_OUT_PCM0, sst_set_media_path), | ||
947 | SST_PATH_OUTPUT("pcm1_out", SST_TASK_SBA, SST_SWM_OUT_PCM1, sst_set_media_path), | ||
948 | SST_PATH_OUTPUT("pcm2_out", SST_TASK_SBA, SST_SWM_OUT_PCM2, sst_set_media_path), | ||
949 | |||
950 | /* SBA Loops */ | ||
951 | SST_PATH_INPUT("sprot_loop_in", SST_TASK_SBA, SST_SWM_IN_SPROT_LOOP, NULL), | ||
952 | SST_PATH_INPUT("media_loop1_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP1, NULL), | ||
953 | SST_PATH_INPUT("media_loop2_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP2, NULL), | ||
954 | SST_PATH_MEDIA_LOOP_OUTPUT("sprot_loop_out", SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP, SST_FMT_MONO, sst_set_media_loop), | ||
955 | SST_PATH_MEDIA_LOOP_OUTPUT("media_loop1_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1, SST_FMT_MONO, sst_set_media_loop), | ||
956 | SST_PATH_MEDIA_LOOP_OUTPUT("media_loop2_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2, SST_FMT_STEREO, sst_set_media_loop), | ||
957 | |||
958 | /* Media Mixers */ | ||
959 | SST_SWM_MIXER("media0_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA0, | ||
960 | sst_mix_media0_controls, sst_swm_mixer_event), | ||
961 | SST_SWM_MIXER("media1_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA1, | ||
962 | sst_mix_media1_controls, sst_swm_mixer_event), | ||
963 | |||
964 | /* SBA PCM mixers */ | ||
965 | SST_SWM_MIXER("pcm0_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM0, | ||
966 | sst_mix_pcm0_controls, sst_swm_mixer_event), | ||
967 | SST_SWM_MIXER("pcm1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM1, | ||
968 | sst_mix_pcm1_controls, sst_swm_mixer_event), | ||
969 | SST_SWM_MIXER("pcm2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM2, | ||
970 | sst_mix_pcm2_controls, sst_swm_mixer_event), | ||
971 | |||
972 | /* SBA Loop mixers */ | ||
973 | SST_SWM_MIXER("sprot_loop_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP, | ||
974 | sst_mix_sprot_l0_controls, sst_swm_mixer_event), | ||
975 | SST_SWM_MIXER("media_loop1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1, | ||
976 | sst_mix_media_l1_controls, sst_swm_mixer_event), | ||
977 | SST_SWM_MIXER("media_loop2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2, | ||
978 | sst_mix_media_l2_controls, sst_swm_mixer_event), | ||
979 | |||
980 | /* SBA Backend mixers */ | ||
981 | SST_SWM_MIXER("codec_out0 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC0, | ||
982 | sst_mix_codec0_controls, sst_swm_mixer_event), | ||
983 | SST_SWM_MIXER("codec_out1 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC1, | ||
984 | sst_mix_codec1_controls, sst_swm_mixer_event), | ||
985 | }; | ||
986 | |||
987 | static const struct snd_soc_dapm_route intercon[] = { | ||
988 | {"media0_in", NULL, "Compress Playback"}, | ||
989 | {"media1_in", NULL, "Headset Playback"}, | ||
990 | {"media2_in", NULL, "pcm0_out"}, | ||
991 | |||
992 | {"media0_out mix 0", "media0_in Switch", "media0_in"}, | ||
993 | {"media0_out mix 0", "media1_in Switch", "media1_in"}, | ||
994 | {"media0_out mix 0", "media2_in Switch", "media2_in"}, | ||
995 | {"media0_out mix 0", "media3_in Switch", "media3_in"}, | ||
996 | {"media1_out mix 0", "media0_in Switch", "media0_in"}, | ||
997 | {"media1_out mix 0", "media1_in Switch", "media1_in"}, | ||
998 | {"media1_out mix 0", "media2_in Switch", "media2_in"}, | ||
999 | {"media1_out mix 0", "media3_in Switch", "media3_in"}, | ||
1000 | |||
1001 | {"media0_out", NULL, "media0_out mix 0"}, | ||
1002 | {"media1_out", NULL, "media1_out mix 0"}, | ||
1003 | {"pcm0_in", NULL, "media0_out"}, | ||
1004 | {"pcm1_in", NULL, "media1_out"}, | ||
1005 | |||
1006 | {"Headset Capture", NULL, "pcm1_out"}, | ||
1007 | {"Headset Capture", NULL, "pcm2_out"}, | ||
1008 | {"pcm0_out", NULL, "pcm0_out mix 0"}, | ||
1009 | SST_SBA_MIXER_GRAPH_MAP("pcm0_out mix 0"), | ||
1010 | {"pcm1_out", NULL, "pcm1_out mix 0"}, | ||
1011 | SST_SBA_MIXER_GRAPH_MAP("pcm1_out mix 0"), | ||
1012 | {"pcm2_out", NULL, "pcm2_out mix 0"}, | ||
1013 | SST_SBA_MIXER_GRAPH_MAP("pcm2_out mix 0"), | ||
1014 | |||
1015 | {"media_loop1_in", NULL, "media_loop1_out"}, | ||
1016 | {"media_loop1_out", NULL, "media_loop1_out mix 0"}, | ||
1017 | SST_SBA_MIXER_GRAPH_MAP("media_loop1_out mix 0"), | ||
1018 | {"media_loop2_in", NULL, "media_loop2_out"}, | ||
1019 | {"media_loop2_out", NULL, "media_loop2_out mix 0"}, | ||
1020 | SST_SBA_MIXER_GRAPH_MAP("media_loop2_out mix 0"), | ||
1021 | {"sprot_loop_in", NULL, "sprot_loop_out"}, | ||
1022 | {"sprot_loop_out", NULL, "sprot_loop_out mix 0"}, | ||
1023 | SST_SBA_MIXER_GRAPH_MAP("sprot_loop_out mix 0"), | ||
1024 | |||
1025 | {"codec_out0", NULL, "codec_out0 mix 0"}, | ||
1026 | SST_SBA_MIXER_GRAPH_MAP("codec_out0 mix 0"), | ||
1027 | {"codec_out1", NULL, "codec_out1 mix 0"}, | ||
1028 | SST_SBA_MIXER_GRAPH_MAP("codec_out1 mix 0"), | ||
1029 | |||
1030 | }; | ||
1031 | static const char * const slot_names[] = { | ||
1032 | "none", | ||
1033 | "slot 0", "slot 1", "slot 2", "slot 3", | ||
1034 | "slot 4", "slot 5", "slot 6", "slot 7", /* not supported by FW */ | ||
1035 | }; | ||
1036 | |||
1037 | static const char * const channel_names[] = { | ||
1038 | "none", | ||
1039 | "codec_out0_0", "codec_out0_1", "codec_out1_0", "codec_out1_1", | ||
1040 | "codec_out2_0", "codec_out2_1", "codec_out3_0", "codec_out3_1", /* not supported by FW */ | ||
1041 | }; | ||
1042 | |||
1043 | #define SST_INTERLEAVER(xpname, slot_name, slotno) \ | ||
1044 | SST_SSP_SLOT_CTL(xpname, "tx interleaver", slot_name, slotno, true, \ | ||
1045 | channel_names, sst_slot_get, sst_slot_put) | ||
1046 | |||
1047 | #define SST_DEINTERLEAVER(xpname, channel_name, channel_no) \ | ||
1048 | SST_SSP_SLOT_CTL(xpname, "rx deinterleaver", channel_name, channel_no, false, \ | ||
1049 | slot_names, sst_slot_get, sst_slot_put) | ||
1050 | |||
1051 | static const struct snd_kcontrol_new sst_slot_controls[] = { | ||
1052 | SST_INTERLEAVER("codec_out", "slot 0", 0), | ||
1053 | SST_INTERLEAVER("codec_out", "slot 1", 1), | ||
1054 | SST_INTERLEAVER("codec_out", "slot 2", 2), | ||
1055 | SST_INTERLEAVER("codec_out", "slot 3", 3), | ||
1056 | SST_DEINTERLEAVER("codec_in", "codec_in0_0", 0), | ||
1057 | SST_DEINTERLEAVER("codec_in", "codec_in0_1", 1), | ||
1058 | SST_DEINTERLEAVER("codec_in", "codec_in1_0", 2), | ||
1059 | SST_DEINTERLEAVER("codec_in", "codec_in1_1", 3), | ||
1060 | }; | ||
1061 | |||
1062 | /* Gain helper with min/max set */ | ||
1063 | #define SST_GAIN(name, path_id, task_id, instance, gain_var) \ | ||
1064 | SST_GAIN_KCONTROLS(name, "Gain", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \ | ||
1065 | SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \ | ||
1066 | sst_gain_get, sst_gain_put, \ | ||
1067 | SST_MODULE_ID_GAIN_CELL, path_id, instance, task_id, \ | ||
1068 | sst_gain_tlv_common, gain_var) | ||
1069 | |||
1070 | #define SST_VOLUME(name, path_id, task_id, instance, gain_var) \ | ||
1071 | SST_GAIN_KCONTROLS(name, "Volume", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \ | ||
1072 | SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \ | ||
1073 | sst_gain_get, sst_gain_put, \ | ||
1074 | SST_MODULE_ID_VOLUME, path_id, instance, task_id, \ | ||
1075 | sst_gain_tlv_common, gain_var) | ||
1076 | |||
1077 | static struct sst_gain_value sst_gains[]; | ||
1078 | |||
1079 | static const struct snd_kcontrol_new sst_gain_controls[] = { | ||
1080 | SST_GAIN("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[0]), | ||
1081 | SST_GAIN("media1_in", SST_PATH_INDEX_MEDIA1_IN, SST_TASK_MMX, 0, &sst_gains[1]), | ||
1082 | SST_GAIN("media2_in", SST_PATH_INDEX_MEDIA2_IN, SST_TASK_MMX, 0, &sst_gains[2]), | ||
1083 | SST_GAIN("media3_in", SST_PATH_INDEX_MEDIA3_IN, SST_TASK_MMX, 0, &sst_gains[3]), | ||
1084 | |||
1085 | SST_GAIN("pcm0_in", SST_PATH_INDEX_PCM0_IN, SST_TASK_SBA, 0, &sst_gains[4]), | ||
1086 | SST_GAIN("pcm1_in", SST_PATH_INDEX_PCM1_IN, SST_TASK_SBA, 0, &sst_gains[5]), | ||
1087 | SST_GAIN("pcm1_out", SST_PATH_INDEX_PCM1_OUT, SST_TASK_SBA, 0, &sst_gains[6]), | ||
1088 | SST_GAIN("pcm2_out", SST_PATH_INDEX_PCM2_OUT, SST_TASK_SBA, 0, &sst_gains[7]), | ||
1089 | |||
1090 | SST_GAIN("codec_in0", SST_PATH_INDEX_CODEC_IN0, SST_TASK_SBA, 0, &sst_gains[8]), | ||
1091 | SST_GAIN("codec_in1", SST_PATH_INDEX_CODEC_IN1, SST_TASK_SBA, 0, &sst_gains[9]), | ||
1092 | SST_GAIN("codec_out0", SST_PATH_INDEX_CODEC_OUT0, SST_TASK_SBA, 0, &sst_gains[10]), | ||
1093 | SST_GAIN("codec_out1", SST_PATH_INDEX_CODEC_OUT1, SST_TASK_SBA, 0, &sst_gains[11]), | ||
1094 | SST_GAIN("media_loop1_out", SST_PATH_INDEX_MEDIA_LOOP1_OUT, SST_TASK_SBA, 0, &sst_gains[12]), | ||
1095 | SST_GAIN("media_loop2_out", SST_PATH_INDEX_MEDIA_LOOP2_OUT, SST_TASK_SBA, 0, &sst_gains[13]), | ||
1096 | SST_GAIN("sprot_loop_out", SST_PATH_INDEX_SPROT_LOOP_OUT, SST_TASK_SBA, 0, &sst_gains[14]), | ||
1097 | SST_VOLUME("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[15]), | ||
1098 | }; | ||
1099 | |||
1100 | #define SST_GAIN_NUM_CONTROLS 3 | ||
1101 | /* the SST_GAIN macro above will create three alsa controls for each | ||
1102 | * instance invoked, gain, mute and ramp duration, which use the same gain | ||
1103 | * cell sst_gain to keep track of data | ||
1104 | * To calculate number of gain cell instances we need to device by 3 in | ||
1105 | * below caulcation for gain cell memory. | ||
1106 | * This gets rid of static number and issues while adding new controls | ||
1107 | */ | ||
1108 | static struct sst_gain_value sst_gains[ARRAY_SIZE(sst_gain_controls)/SST_GAIN_NUM_CONTROLS]; | ||
1109 | |||
165 | static const struct snd_kcontrol_new sst_algo_controls[] = { | 1110 | static const struct snd_kcontrol_new sst_algo_controls[] = { |
166 | SST_ALGO_KCONTROL_BYTES("media_loop1_out", "fir", 272, SST_MODULE_ID_FIR_24, | 1111 | SST_ALGO_KCONTROL_BYTES("media_loop1_out", "fir", 272, SST_MODULE_ID_FIR_24, |
167 | SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_VB_SET_FIR), | 1112 | SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_VB_SET_FIR), |
@@ -198,21 +1143,280 @@ static int sst_algo_control_init(struct device *dev) | |||
198 | return 0; | 1143 | return 0; |
199 | } | 1144 | } |
200 | 1145 | ||
201 | int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform) | 1146 | static bool is_sst_dapm_widget(struct snd_soc_dapm_widget *w) |
1147 | { | ||
1148 | switch (w->id) { | ||
1149 | case snd_soc_dapm_pga: | ||
1150 | case snd_soc_dapm_aif_in: | ||
1151 | case snd_soc_dapm_aif_out: | ||
1152 | case snd_soc_dapm_input: | ||
1153 | case snd_soc_dapm_output: | ||
1154 | case snd_soc_dapm_mixer: | ||
1155 | return true; | ||
1156 | default: | ||
1157 | return false; | ||
1158 | } | ||
1159 | } | ||
1160 | |||
1161 | /** | ||
1162 | * sst_send_pipe_gains - send gains for the front-end DAIs | ||
1163 | * | ||
1164 | * The gains in the pipes connected to the front-ends are muted/unmuted | ||
1165 | * automatically via the digital_mute() DAPM callback. This function sends the | ||
1166 | * gains for the front-end pipes. | ||
1167 | */ | ||
1168 | int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute) | ||
1169 | { | ||
1170 | struct sst_data *drv = snd_soc_dai_get_drvdata(dai); | ||
1171 | struct snd_soc_dapm_widget *w; | ||
1172 | struct snd_soc_dapm_path *p = NULL; | ||
1173 | |||
1174 | dev_dbg(dai->dev, "enter, dai-name=%s dir=%d\n", dai->name, stream); | ||
1175 | |||
1176 | if (stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
1177 | dev_dbg(dai->dev, "Stream name=%s\n", | ||
1178 | dai->playback_widget->name); | ||
1179 | w = dai->playback_widget; | ||
1180 | list_for_each_entry(p, &w->sinks, list_source) { | ||
1181 | if (p->connected && !p->connected(w, p->sink)) | ||
1182 | continue; | ||
1183 | |||
1184 | if (p->connect && p->sink->power && | ||
1185 | is_sst_dapm_widget(p->sink)) { | ||
1186 | struct sst_ids *ids = p->sink->priv; | ||
1187 | |||
1188 | dev_dbg(dai->dev, "send gains for widget=%s\n", | ||
1189 | p->sink->name); | ||
1190 | mutex_lock(&drv->lock); | ||
1191 | sst_set_pipe_gain(ids, drv, mute); | ||
1192 | mutex_unlock(&drv->lock); | ||
1193 | } | ||
1194 | } | ||
1195 | } else { | ||
1196 | dev_dbg(dai->dev, "Stream name=%s\n", | ||
1197 | dai->capture_widget->name); | ||
1198 | w = dai->capture_widget; | ||
1199 | list_for_each_entry(p, &w->sources, list_sink) { | ||
1200 | if (p->connected && !p->connected(w, p->sink)) | ||
1201 | continue; | ||
1202 | |||
1203 | if (p->connect && p->source->power && | ||
1204 | is_sst_dapm_widget(p->source)) { | ||
1205 | struct sst_ids *ids = p->source->priv; | ||
1206 | |||
1207 | dev_dbg(dai->dev, "send gain for widget=%s\n", | ||
1208 | p->source->name); | ||
1209 | mutex_lock(&drv->lock); | ||
1210 | sst_set_pipe_gain(ids, drv, mute); | ||
1211 | mutex_unlock(&drv->lock); | ||
1212 | } | ||
1213 | } | ||
1214 | } | ||
1215 | return 0; | ||
1216 | } | ||
1217 | |||
1218 | /** | ||
1219 | * sst_fill_module_list - populate the list of modules/gains for a pipe | ||
1220 | * | ||
1221 | * | ||
1222 | * Fills the widget pointer in the kcontrol private data, and also fills the | ||
1223 | * kcontrol pointer in the widget private data. | ||
1224 | * | ||
1225 | * Widget pointer is used to send the algo/gain in the .put() handler if the | ||
1226 | * widget is powerd on. | ||
1227 | * | ||
1228 | * Kcontrol pointer is used to send the algo/gain in the widget power ON/OFF | ||
1229 | * event handler. Each widget (pipe) has multiple algos stored in the algo_list. | ||
1230 | */ | ||
1231 | static int sst_fill_module_list(struct snd_kcontrol *kctl, | ||
1232 | struct snd_soc_dapm_widget *w, int type) | ||
202 | { | 1233 | { |
1234 | struct sst_module *module = NULL; | ||
1235 | struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); | ||
1236 | struct sst_ids *ids = w->priv; | ||
203 | int ret = 0; | 1237 | int ret = 0; |
1238 | |||
1239 | module = devm_kzalloc(c->dev, sizeof(*module), GFP_KERNEL); | ||
1240 | if (!module) | ||
1241 | return -ENOMEM; | ||
1242 | |||
1243 | if (type == SST_MODULE_GAIN) { | ||
1244 | struct sst_gain_mixer_control *mc = (void *)kctl->private_value; | ||
1245 | |||
1246 | mc->w = w; | ||
1247 | module->kctl = kctl; | ||
1248 | list_add_tail(&module->node, &ids->gain_list); | ||
1249 | } else if (type == SST_MODULE_ALGO) { | ||
1250 | struct sst_algo_control *bc = (void *)kctl->private_value; | ||
1251 | |||
1252 | bc->w = w; | ||
1253 | module->kctl = kctl; | ||
1254 | list_add_tail(&module->node, &ids->algo_list); | ||
1255 | } else { | ||
1256 | dev_err(c->dev, "invoked for unknown type %d module %s", | ||
1257 | type, kctl->id.name); | ||
1258 | ret = -EINVAL; | ||
1259 | } | ||
1260 | |||
1261 | return ret; | ||
1262 | } | ||
1263 | |||
1264 | /** | ||
1265 | * sst_fill_widget_module_info - fill list of gains/algos for the pipe | ||
1266 | * @widget: pipe modelled as a DAPM widget | ||
1267 | * | ||
1268 | * Fill the list of gains/algos for the widget by looking at all the card | ||
1269 | * controls and comparing the name of the widget with the first part of control | ||
1270 | * name. First part of control name contains the pipe name (widget name). | ||
1271 | */ | ||
1272 | static int sst_fill_widget_module_info(struct snd_soc_dapm_widget *w, | ||
1273 | struct snd_soc_platform *platform) | ||
1274 | { | ||
1275 | struct snd_kcontrol *kctl; | ||
1276 | int index, ret = 0; | ||
1277 | struct snd_card *card = platform->component.card->snd_card; | ||
1278 | char *idx; | ||
1279 | |||
1280 | down_read(&card->controls_rwsem); | ||
1281 | |||
1282 | list_for_each_entry(kctl, &card->controls, list) { | ||
1283 | idx = strstr(kctl->id.name, " "); | ||
1284 | if (idx == NULL) | ||
1285 | continue; | ||
1286 | index = strlen(kctl->id.name) - strlen(idx); | ||
1287 | |||
1288 | if (strstr(kctl->id.name, "Volume") && | ||
1289 | !strncmp(kctl->id.name, w->name, index)) | ||
1290 | ret = sst_fill_module_list(kctl, w, SST_MODULE_GAIN); | ||
1291 | |||
1292 | else if (strstr(kctl->id.name, "params") && | ||
1293 | !strncmp(kctl->id.name, w->name, index)) | ||
1294 | ret = sst_fill_module_list(kctl, w, SST_MODULE_ALGO); | ||
1295 | |||
1296 | else if (strstr(kctl->id.name, "Switch") && | ||
1297 | !strncmp(kctl->id.name, w->name, index) && | ||
1298 | strstr(kctl->id.name, "Gain")) { | ||
1299 | struct sst_gain_mixer_control *mc = | ||
1300 | (void *)kctl->private_value; | ||
1301 | |||
1302 | mc->w = w; | ||
1303 | |||
1304 | } else if (strstr(kctl->id.name, "interleaver") && | ||
1305 | !strncmp(kctl->id.name, w->name, index)) { | ||
1306 | struct sst_enum *e = (void *)kctl->private_value; | ||
1307 | |||
1308 | e->w = w; | ||
1309 | |||
1310 | } else if (strstr(kctl->id.name, "deinterleaver") && | ||
1311 | !strncmp(kctl->id.name, w->name, index)) { | ||
1312 | |||
1313 | struct sst_enum *e = (void *)kctl->private_value; | ||
1314 | |||
1315 | e->w = w; | ||
1316 | } | ||
1317 | |||
1318 | if (ret < 0) { | ||
1319 | up_read(&card->controls_rwsem); | ||
1320 | return ret; | ||
1321 | } | ||
1322 | } | ||
1323 | |||
1324 | up_read(&card->controls_rwsem); | ||
1325 | return 0; | ||
1326 | } | ||
1327 | |||
1328 | /** | ||
1329 | * sst_fill_linked_widgets - fill the parent pointer for the linked widget | ||
1330 | */ | ||
1331 | static void sst_fill_linked_widgets(struct snd_soc_platform *platform, | ||
1332 | struct sst_ids *ids) | ||
1333 | { | ||
1334 | struct snd_soc_dapm_widget *w; | ||
1335 | unsigned int len = strlen(ids->parent_wname); | ||
1336 | |||
1337 | list_for_each_entry(w, &platform->component.card->widgets, list) { | ||
1338 | if (!strncmp(ids->parent_wname, w->name, len)) { | ||
1339 | ids->parent_w = w; | ||
1340 | break; | ||
1341 | } | ||
1342 | } | ||
1343 | } | ||
1344 | |||
1345 | /** | ||
1346 | * sst_map_modules_to_pipe - fill algo/gains list for all pipes | ||
1347 | */ | ||
1348 | static int sst_map_modules_to_pipe(struct snd_soc_platform *platform) | ||
1349 | { | ||
1350 | struct snd_soc_dapm_widget *w; | ||
1351 | int ret = 0; | ||
1352 | |||
1353 | list_for_each_entry(w, &platform->component.card->widgets, list) { | ||
1354 | if (is_sst_dapm_widget(w) && (w->priv)) { | ||
1355 | struct sst_ids *ids = w->priv; | ||
1356 | |||
1357 | dev_dbg(platform->dev, "widget type=%d name=%s\n", | ||
1358 | w->id, w->name); | ||
1359 | INIT_LIST_HEAD(&ids->algo_list); | ||
1360 | INIT_LIST_HEAD(&ids->gain_list); | ||
1361 | ret = sst_fill_widget_module_info(w, platform); | ||
1362 | |||
1363 | if (ret < 0) | ||
1364 | return ret; | ||
1365 | |||
1366 | /* fill linked widgets */ | ||
1367 | if (ids->parent_wname != NULL) | ||
1368 | sst_fill_linked_widgets(platform, ids); | ||
1369 | } | ||
1370 | } | ||
1371 | return 0; | ||
1372 | } | ||
1373 | |||
1374 | int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform) | ||
1375 | { | ||
1376 | int i, ret = 0; | ||
1377 | struct snd_soc_dapm_context *dapm = | ||
1378 | snd_soc_component_get_dapm(&platform->component); | ||
204 | struct sst_data *drv = snd_soc_platform_get_drvdata(platform); | 1379 | struct sst_data *drv = snd_soc_platform_get_drvdata(platform); |
1380 | unsigned int gains = ARRAY_SIZE(sst_gain_controls)/3; | ||
205 | 1381 | ||
206 | drv->byte_stream = devm_kzalloc(platform->dev, | 1382 | drv->byte_stream = devm_kzalloc(platform->dev, |
207 | SST_MAX_BIN_BYTES, GFP_KERNEL); | 1383 | SST_MAX_BIN_BYTES, GFP_KERNEL); |
208 | if (!drv->byte_stream) | 1384 | if (!drv->byte_stream) |
209 | return -ENOMEM; | 1385 | return -ENOMEM; |
210 | 1386 | ||
211 | /*Initialize algo control params*/ | 1387 | snd_soc_dapm_new_controls(dapm, sst_dapm_widgets, |
1388 | ARRAY_SIZE(sst_dapm_widgets)); | ||
1389 | snd_soc_dapm_add_routes(dapm, intercon, | ||
1390 | ARRAY_SIZE(intercon)); | ||
1391 | snd_soc_dapm_new_widgets(dapm->card); | ||
1392 | |||
1393 | for (i = 0; i < gains; i++) { | ||
1394 | sst_gains[i].mute = SST_GAIN_MUTE_DEFAULT; | ||
1395 | sst_gains[i].l_gain = SST_GAIN_VOLUME_DEFAULT; | ||
1396 | sst_gains[i].r_gain = SST_GAIN_VOLUME_DEFAULT; | ||
1397 | sst_gains[i].ramp_duration = SST_GAIN_RAMP_DURATION_DEFAULT; | ||
1398 | } | ||
1399 | |||
1400 | ret = snd_soc_add_platform_controls(platform, sst_gain_controls, | ||
1401 | ARRAY_SIZE(sst_gain_controls)); | ||
1402 | if (ret) | ||
1403 | return ret; | ||
1404 | |||
1405 | /* Initialize algo control params */ | ||
212 | ret = sst_algo_control_init(platform->dev); | 1406 | ret = sst_algo_control_init(platform->dev); |
213 | if (ret) | 1407 | if (ret) |
214 | return ret; | 1408 | return ret; |
215 | ret = snd_soc_add_platform_controls(platform, sst_algo_controls, | 1409 | ret = snd_soc_add_platform_controls(platform, sst_algo_controls, |
216 | ARRAY_SIZE(sst_algo_controls)); | 1410 | ARRAY_SIZE(sst_algo_controls)); |
1411 | if (ret) | ||
1412 | return ret; | ||
1413 | |||
1414 | ret = snd_soc_add_platform_controls(platform, sst_slot_controls, | ||
1415 | ARRAY_SIZE(sst_slot_controls)); | ||
1416 | if (ret) | ||
1417 | return ret; | ||
1418 | |||
1419 | ret = sst_map_modules_to_pipe(platform); | ||
1420 | |||
217 | return ret; | 1421 | return ret; |
218 | } | 1422 | } |