From 621fdf60ab22dd902d52a670e943371400b4ee20 Mon Sep 17 00:00:00 2001 From: Jia-Ju Bai Date: Mon, 9 Apr 2018 21:03:20 +0800 Subject: ALSA: ad1889: Replace mdelay with usleep_range in snd_ad1889_init snd_ad1889_init() is never called in atomic context. The call chain ending up at snd_ad1889_init() is: [1] snd_ad1889_init() <- snd_ad1889_create() <- snd_ad1889_probe() snd_ad1889_probe() is only set as ".probe" in struct pci_driver. This function is not called in atomic context. Despite never getting called from atomic context, snd_ad1889_init() calls mdelay for busy wait. This is not necessary and can be replaced with usleep_range to avoid busy waiting. This is found by a static analysis tool named DCNS written by myself. Signed-off-by: Jia-Ju Bai Signed-off-by: Takashi Iwai --- sound/pci/ad1889.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/ad1889.c b/sound/pci/ad1889.c index 0bf2c04eeada..d4965ebe967f 100644 --- a/sound/pci/ad1889.c +++ b/sound/pci/ad1889.c @@ -872,7 +872,7 @@ snd_ad1889_init(struct snd_ad1889 *chip) ad1889_writew(chip, AD_DS_CCS, AD_DS_CCS_CLKEN); /* turn on clock */ ad1889_readw(chip, AD_DS_CCS); /* flush posted write */ - mdelay(10); + usleep_range(10000, 11000); /* enable Master and Target abort interrupts */ ad1889_writel(chip, AD_DMA_DISR, AD_DMA_DISR_PMAE | AD_DMA_DISR_PTAE); -- cgit v1.2.3 From 1fa350b6e1a14edaeeb4e4f84010f5b0c53d2915 Mon Sep 17 00:00:00 2001 From: Jia-Ju Bai Date: Mon, 9 Apr 2018 21:03:38 +0800 Subject: ALSA: ad1889: Replace mdelay with usleep_range in snd_ad1889_ac97_ready snd_ad1889_ac97_ready() is never called in atomic context. The call chain ending up at snd_ad1889_ac97_ready() is: [1] snd_ad1889_ac97_ready() <- snd_ad1889_ac97_xinit() <- snd_ad1889_ac97_init() <- snd_ad1889_probe() snd_ad1889_probe() is only set as ".probe" in struct pci_driver. This function is not called in atomic context. Despite never getting called from atomic context, snd_ad1889_ac97_ready() calls mdelay for busy wait. This is not necessary and can be replaced with usleep_range to avoid busy waiting. This is found by a static analysis tool named DCNS written by myself. Signed-off-by: Jia-Ju Bai Signed-off-by: Takashi Iwai --- sound/pci/ad1889.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/ad1889.c b/sound/pci/ad1889.c index d4965ebe967f..d9c54c08e2db 100644 --- a/sound/pci/ad1889.c +++ b/sound/pci/ad1889.c @@ -258,7 +258,7 @@ snd_ad1889_ac97_ready(struct snd_ad1889 *chip) while (!(ad1889_readw(chip, AD_AC97_ACIC) & AD_AC97_ACIC_ACRDY) && --retry) - mdelay(1); + usleep_range(1000, 2000); if (!retry) { dev_err(chip->card->dev, "[%s] Link is not ready.\n", __func__); -- cgit v1.2.3 From 0be5168047c22ad6b2fa675f02e8090b192fbc8f Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 9 Apr 2018 22:09:27 +0200 Subject: ALSA: cmipci: Allocate with GFP_KERNEL instead of GFP_ATOMIC save_mixer_state() is called in a sleepable context, so it's safe to allocate with GFP_KERNEL instead of the current GFP_ATOMIC. The GFP_ATOMIC usage must have been based on an incorrect assumption in the very old code base. Signed-off-by: Takashi Iwai --- sound/pci/cmipci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c index 26a657870664..452cc79b44af 100644 --- a/sound/pci/cmipci.c +++ b/sound/pci/cmipci.c @@ -1139,7 +1139,7 @@ static int save_mixer_state(struct cmipci *cm) struct snd_ctl_elem_value *val; unsigned int i; - val = kmalloc(sizeof(*val), GFP_ATOMIC); + val = kmalloc(sizeof(*val), GFP_KERNEL); if (!val) return -ENOMEM; for (i = 0; i < CM_SAVED_MIXERS; i++) { -- cgit v1.2.3 From 057666b69b1d51feb389a17ec73722b001aaf3d0 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 9 Apr 2018 22:21:49 +0200 Subject: ALSA: emu10k1: Reduce GFP_ATOMIC allocation The emu10k1 fx8010 code allocates each irq resource dynamically and links to the list at PCM trigger callback. Due to the nature of trigger callback, the allocation is done with GFP_ATOMIC, hence it may fail more often. Moreover, the irq resource isn't big at all, and using the kmalloc for this won't save many bytes, either. This patch removes the dynamic allocation and embeds the irq resource into struct snd_emu10k1_fx8010_pcm.irq field instead of keeping a pointer. As a result, it simplifies the code and removes the unnecessary GFP_ATOMIC usage. Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 4 ++-- sound/pci/emu10k1/emufx.c | 9 +-------- sound/pci/emu10k1/emupcm.c | 2 +- 3 files changed, 4 insertions(+), 11 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 5ebcc51c0a6a..8c1572de44c5 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1610,7 +1610,7 @@ struct snd_emu10k1_fx8010_pcm { struct snd_pcm_indirect pcm_rec; unsigned int tram_pos; unsigned int tram_shift; - struct snd_emu10k1_fx8010_irq *irq; + struct snd_emu10k1_fx8010_irq irq; }; struct snd_emu10k1_fx8010 { @@ -1902,7 +1902,7 @@ int snd_emu10k1_fx8010_register_irq_handler(struct snd_emu10k1 *emu, snd_fx8010_irq_handler_t *handler, unsigned char gpr_running, void *private_data, - struct snd_emu10k1_fx8010_irq **r_irq); + struct snd_emu10k1_fx8010_irq *irq); int snd_emu10k1_fx8010_unregister_irq_handler(struct snd_emu10k1 *emu, struct snd_emu10k1_fx8010_irq *irq); diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index a2b56b188be4..608ff4857d70 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -421,14 +421,10 @@ int snd_emu10k1_fx8010_register_irq_handler(struct snd_emu10k1 *emu, snd_fx8010_irq_handler_t *handler, unsigned char gpr_running, void *private_data, - struct snd_emu10k1_fx8010_irq **r_irq) + struct snd_emu10k1_fx8010_irq *irq) { - struct snd_emu10k1_fx8010_irq *irq; unsigned long flags; - irq = kmalloc(sizeof(*irq), GFP_ATOMIC); - if (irq == NULL) - return -ENOMEM; irq->handler = handler; irq->gpr_running = gpr_running; irq->private_data = private_data; @@ -443,8 +439,6 @@ int snd_emu10k1_fx8010_register_irq_handler(struct snd_emu10k1 *emu, emu->fx8010.irq_handlers = irq; } spin_unlock_irqrestore(&emu->fx8010.irq_lock, flags); - if (r_irq) - *r_irq = irq; return 0; } @@ -468,7 +462,6 @@ int snd_emu10k1_fx8010_unregister_irq_handler(struct snd_emu10k1 *emu, tmp->next = tmp->next->next; } spin_unlock_irqrestore(&emu->fx8010.irq_lock, flags); - kfree(irq); return 0; } diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index cefe613ef7b7..d39458ab251f 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1724,7 +1724,7 @@ static int snd_emu10k1_fx8010_playback_trigger(struct snd_pcm_substream *substre case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_SUSPEND: - snd_emu10k1_fx8010_unregister_irq_handler(emu, pcm->irq); pcm->irq = NULL; + snd_emu10k1_fx8010_unregister_irq_handler(emu, &pcm->irq); snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 0); pcm->tram_pos = INITIAL_TRAM_POS(pcm->buffer_size); pcm->tram_shift = 0; -- cgit v1.2.3 From 763e5067aac91ce569a8b1212e6c31968bc7d325 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 11 Apr 2018 17:56:52 +0200 Subject: ALSA: pcm: Clean up with snd_pcm_avail() and snd_pcm_hw_avail() helpers Introduce two new direction-neutral helpers to calculate the avail and hw_avail values, and clean up the code with them. The two separated forward and rewind functions are gathered to the unified functions. No functional change but only code reductions. Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/core/pcm_compat.c | 10 ++------ sound/core/pcm_lib.c | 15 +++--------- sound/core/pcm_local.h | 18 ++++++++++++++ sound/core/pcm_native.c | 65 ++++++++----------------------------------------- 4 files changed, 33 insertions(+), 75 deletions(-) (limited to 'sound') diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c index b719d0bd833e..0be248543f1e 100644 --- a/sound/core/pcm_compat.c +++ b/sound/core/pcm_compat.c @@ -44,10 +44,7 @@ static int snd_pcm_ioctl_rewind_compat(struct snd_pcm_substream *substream, if (get_user(frames, src)) return -EFAULT; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - err = snd_pcm_playback_rewind(substream, frames); - else - err = snd_pcm_capture_rewind(substream, frames); + err = snd_pcm_rewind(substream, frames); if (put_user(err, src)) return -EFAULT; return err < 0 ? err : 0; @@ -61,10 +58,7 @@ static int snd_pcm_ioctl_forward_compat(struct snd_pcm_substream *substream, if (get_user(frames, src)) return -EFAULT; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - err = snd_pcm_playback_forward(substream, frames); - else - err = snd_pcm_capture_forward(substream, frames); + err = snd_pcm_forward(substream, frames); if (put_user(err, src)) return -EFAULT; return err < 0 ? err : 0; diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index f4a19509cccf..44b5ae833082 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -191,10 +191,7 @@ int snd_pcm_update_state(struct snd_pcm_substream *substream, { snd_pcm_uframes_t avail; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - avail = snd_pcm_playback_avail(runtime); - else - avail = snd_pcm_capture_avail(runtime); + avail = snd_pcm_avail(substream); if (avail > runtime->avail_max) runtime->avail_max = avail; if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) { @@ -1856,10 +1853,7 @@ static int wait_for_avail(struct snd_pcm_substream *substream, * This check must happen after been added to the waitqueue * and having current state be INTERRUPTIBLE. */ - if (is_playback) - avail = snd_pcm_playback_avail(runtime); - else - avail = snd_pcm_capture_avail(runtime); + avail = snd_pcm_avail(substream); if (avail >= runtime->twake) break; snd_pcm_stream_unlock_irq(substream); @@ -2175,10 +2169,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, runtime->twake = runtime->control->avail_min ? : 1; if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) snd_pcm_update_hw_ptr(substream); - if (is_playback) - avail = snd_pcm_playback_avail(runtime); - else - avail = snd_pcm_capture_avail(runtime); + avail = snd_pcm_avail(substream); while (size > 0) { snd_pcm_uframes_t frames, appl_ptr, appl_ofs; snd_pcm_uframes_t cont; diff --git a/sound/core/pcm_local.h b/sound/core/pcm_local.h index 16f254732b2a..7a499d02df6c 100644 --- a/sound/core/pcm_local.h +++ b/sound/core/pcm_local.h @@ -36,6 +36,24 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream); void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_uframes_t new_hw_ptr); +static inline snd_pcm_uframes_t +snd_pcm_avail(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return snd_pcm_playback_avail(substream->runtime); + else + return snd_pcm_capture_avail(substream->runtime); +} + +static inline snd_pcm_uframes_t +snd_pcm_hw_avail(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return snd_pcm_playback_hw_avail(substream->runtime); + else + return snd_pcm_capture_hw_avail(substream->runtime); +} + #ifdef CONFIG_SND_PCM_TIMER void snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream); void snd_pcm_timer_init(struct snd_pcm_substream *substream); diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 35ffccea94c3..f69d89c907b9 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -908,8 +908,8 @@ int snd_pcm_status(struct snd_pcm_substream *substream, _tstamp_end: status->appl_ptr = runtime->control->appl_ptr; status->hw_ptr = runtime->status->hw_ptr; + status->avail = snd_pcm_avail(substream); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - status->avail = snd_pcm_playback_avail(runtime); if (runtime->status->state == SNDRV_PCM_STATE_RUNNING || runtime->status->state == SNDRV_PCM_STATE_DRAINING) { status->delay = runtime->buffer_size - status->avail; @@ -917,7 +917,6 @@ int snd_pcm_status(struct snd_pcm_substream *substream, } else status->delay = 0; } else { - status->avail = snd_pcm_capture_avail(runtime); if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) status->delay = status->avail + runtime->delay; else @@ -2610,28 +2609,9 @@ static snd_pcm_sframes_t rewind_appl_ptr(struct snd_pcm_substream *substream, return ret < 0 ? 0 : frames; } -static snd_pcm_sframes_t snd_pcm_playback_rewind(struct snd_pcm_substream *substream, - snd_pcm_uframes_t frames) +static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream, + snd_pcm_uframes_t frames) { - struct snd_pcm_runtime *runtime = substream->runtime; - snd_pcm_sframes_t ret; - - if (frames == 0) - return 0; - - snd_pcm_stream_lock_irq(substream); - ret = do_pcm_hwsync(substream); - if (!ret) - ret = rewind_appl_ptr(substream, frames, - snd_pcm_playback_hw_avail(runtime)); - snd_pcm_stream_unlock_irq(substream); - return ret; -} - -static snd_pcm_sframes_t snd_pcm_capture_rewind(struct snd_pcm_substream *substream, - snd_pcm_uframes_t frames) -{ - struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_sframes_t ret; if (frames == 0) @@ -2641,33 +2621,14 @@ static snd_pcm_sframes_t snd_pcm_capture_rewind(struct snd_pcm_substream *substr ret = do_pcm_hwsync(substream); if (!ret) ret = rewind_appl_ptr(substream, frames, - snd_pcm_capture_hw_avail(runtime)); - snd_pcm_stream_unlock_irq(substream); - return ret; -} - -static snd_pcm_sframes_t snd_pcm_playback_forward(struct snd_pcm_substream *substream, - snd_pcm_uframes_t frames) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - snd_pcm_sframes_t ret; - - if (frames == 0) - return 0; - - snd_pcm_stream_lock_irq(substream); - ret = do_pcm_hwsync(substream); - if (!ret) - ret = forward_appl_ptr(substream, frames, - snd_pcm_playback_avail(runtime)); + snd_pcm_hw_avail(substream)); snd_pcm_stream_unlock_irq(substream); return ret; } -static snd_pcm_sframes_t snd_pcm_capture_forward(struct snd_pcm_substream *substream, - snd_pcm_uframes_t frames) +static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream, + snd_pcm_uframes_t frames) { - struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_sframes_t ret; if (frames == 0) @@ -2677,7 +2638,7 @@ static snd_pcm_sframes_t snd_pcm_capture_forward(struct snd_pcm_substream *subst ret = do_pcm_hwsync(substream); if (!ret) ret = forward_appl_ptr(substream, frames, - snd_pcm_capture_avail(runtime)); + snd_pcm_avail(substream)); snd_pcm_stream_unlock_irq(substream); return ret; } @@ -2830,10 +2791,7 @@ static int snd_pcm_rewind_ioctl(struct snd_pcm_substream *substream, return -EFAULT; if (put_user(0, _frames)) return -EFAULT; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - result = snd_pcm_playback_rewind(substream, frames); - else - result = snd_pcm_capture_rewind(substream, frames); + result = snd_pcm_rewind(substream, frames); __put_user(result, _frames); return result < 0 ? result : 0; } @@ -2848,10 +2806,7 @@ static int snd_pcm_forward_ioctl(struct snd_pcm_substream *substream, return -EFAULT; if (put_user(0, _frames)) return -EFAULT; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - result = snd_pcm_playback_forward(substream, frames); - else - result = snd_pcm_capture_forward(substream, frames); + result = snd_pcm_forward(substream, frames); __put_user(result, _frames); return result < 0 ? result : 0; } @@ -2992,7 +2947,7 @@ int snd_pcm_kernel_ioctl(struct snd_pcm_substream *substream, /* provided only for OSS; capture-only and no value returned */ if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) return -EINVAL; - result = snd_pcm_capture_forward(substream, *frames); + result = snd_pcm_forward(substream, *frames); return result < 0 ? result : 0; } case SNDRV_PCM_IOCTL_HW_PARAMS: -- cgit v1.2.3 From 6448fcba2a7fe6856ba74bef623559a00267f54e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 11 Apr 2018 18:05:00 +0200 Subject: ALSA: pcm: Unify playback and capture poll callbacks The poll callbacks for playback and capture directions are doing fairly similar but with a slight difference. This patch unifies the two functions into a single callback. The advantage of this refactoring is that the direction-specific procedures become clearer. There should be no functional change but only the code cleanup. Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 74 +++++++++++++------------------------------------ 1 file changed, 19 insertions(+), 55 deletions(-) (limited to 'sound') diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index f69d89c907b9..eddb0cd6d1eb 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -3095,82 +3095,46 @@ static ssize_t snd_pcm_writev(struct kiocb *iocb, struct iov_iter *from) return result; } -static __poll_t snd_pcm_playback_poll(struct file *file, poll_table * wait) +static __poll_t snd_pcm_poll(struct file *file, poll_table *wait) { struct snd_pcm_file *pcm_file; struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; - __poll_t mask; + __poll_t mask, ok; snd_pcm_uframes_t avail; pcm_file = file->private_data; substream = pcm_file->substream; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ok = EPOLLOUT | EPOLLWRNORM; + else + ok = EPOLLIN | EPOLLRDNORM; if (PCM_RUNTIME_CHECK(substream)) - return EPOLLOUT | EPOLLWRNORM | EPOLLERR; - runtime = substream->runtime; - - poll_wait(file, &runtime->sleep, wait); - - snd_pcm_stream_lock_irq(substream); - avail = snd_pcm_playback_avail(runtime); - switch (runtime->status->state) { - case SNDRV_PCM_STATE_RUNNING: - case SNDRV_PCM_STATE_PREPARED: - case SNDRV_PCM_STATE_PAUSED: - if (avail >= runtime->control->avail_min) { - mask = EPOLLOUT | EPOLLWRNORM; - break; - } - /* Fall through */ - case SNDRV_PCM_STATE_DRAINING: - mask = 0; - break; - default: - mask = EPOLLOUT | EPOLLWRNORM | EPOLLERR; - break; - } - snd_pcm_stream_unlock_irq(substream); - return mask; -} - -static __poll_t snd_pcm_capture_poll(struct file *file, poll_table * wait) -{ - struct snd_pcm_file *pcm_file; - struct snd_pcm_substream *substream; - struct snd_pcm_runtime *runtime; - __poll_t mask; - snd_pcm_uframes_t avail; + return ok | EPOLLERR; - pcm_file = file->private_data; - - substream = pcm_file->substream; - if (PCM_RUNTIME_CHECK(substream)) - return EPOLLIN | EPOLLRDNORM | EPOLLERR; runtime = substream->runtime; - poll_wait(file, &runtime->sleep, wait); + mask = 0; snd_pcm_stream_lock_irq(substream); - avail = snd_pcm_capture_avail(runtime); + avail = snd_pcm_avail(substream); switch (runtime->status->state) { case SNDRV_PCM_STATE_RUNNING: case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_PAUSED: - if (avail >= runtime->control->avail_min) { - mask = EPOLLIN | EPOLLRDNORM; - break; - } - mask = 0; + if (avail >= runtime->control->avail_min) + mask = ok; break; case SNDRV_PCM_STATE_DRAINING: - if (avail > 0) { - mask = EPOLLIN | EPOLLRDNORM; - break; + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + mask = ok; + if (!avail) + mask |= EPOLLERR; } - /* Fall through */ + break; default: - mask = EPOLLIN | EPOLLRDNORM | EPOLLERR; + mask = ok | EPOLLERR; break; } snd_pcm_stream_unlock_irq(substream); @@ -3662,7 +3626,7 @@ const struct file_operations snd_pcm_f_ops[2] = { .open = snd_pcm_playback_open, .release = snd_pcm_release, .llseek = no_llseek, - .poll = snd_pcm_playback_poll, + .poll = snd_pcm_poll, .unlocked_ioctl = snd_pcm_ioctl, .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, @@ -3676,7 +3640,7 @@ const struct file_operations snd_pcm_f_ops[2] = { .open = snd_pcm_capture_open, .release = snd_pcm_release, .llseek = no_llseek, - .poll = snd_pcm_capture_poll, + .poll = snd_pcm_poll, .unlocked_ioctl = snd_pcm_ioctl, .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, -- cgit v1.2.3 From c99c5a3bb575f67700f9d1b216652750ea4a31a5 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 11 Apr 2018 18:07:27 +0200 Subject: ALSA: pcm: Unify delay calculation in snd_pcm_status() and snd_pcm_delay() Yet another slight code cleanup: there are two places where calculating the PCM delay, and they can be unified in a single helper. It reduces the multiple open codes. Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) (limited to 'sound') diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index eddb0cd6d1eb..7585444352df 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -857,6 +857,18 @@ static int snd_pcm_sw_params_user(struct snd_pcm_substream *substream, return err; } +static inline snd_pcm_uframes_t +snd_pcm_calc_delay(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t delay; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + delay = snd_pcm_playback_hw_avail(substream->runtime); + else + delay = snd_pcm_capture_avail(substream->runtime); + return delay + substream->runtime->delay; +} + int snd_pcm_status(struct snd_pcm_substream *substream, struct snd_pcm_status *status) { @@ -909,19 +921,8 @@ int snd_pcm_status(struct snd_pcm_substream *substream, status->appl_ptr = runtime->control->appl_ptr; status->hw_ptr = runtime->status->hw_ptr; status->avail = snd_pcm_avail(substream); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - if (runtime->status->state == SNDRV_PCM_STATE_RUNNING || - runtime->status->state == SNDRV_PCM_STATE_DRAINING) { - status->delay = runtime->buffer_size - status->avail; - status->delay += runtime->delay; - } else - status->delay = 0; - } else { - if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) - status->delay = status->avail + runtime->delay; - else - status->delay = 0; - } + status->delay = snd_pcm_running(substream) ? + snd_pcm_calc_delay(substream) : 0; status->avail_max = runtime->avail_max; status->overrange = runtime->overrange; runtime->avail_max = 0; @@ -2655,19 +2656,13 @@ static int snd_pcm_hwsync(struct snd_pcm_substream *substream) static snd_pcm_sframes_t snd_pcm_delay(struct snd_pcm_substream *substream) { - struct snd_pcm_runtime *runtime = substream->runtime; int err; snd_pcm_sframes_t n = 0; snd_pcm_stream_lock_irq(substream); err = do_pcm_hwsync(substream); - if (!err) { - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - n = snd_pcm_playback_hw_avail(runtime); - else - n = snd_pcm_capture_avail(runtime); - n += runtime->delay; - } + if (!err) + n = snd_pcm_calc_delay(substream); snd_pcm_stream_unlock_irq(substream); return err < 0 ? err : n; } -- cgit v1.2.3 From 103e9625647ad74d201e26fb74afcd8479142a37 Mon Sep 17 00:00:00 2001 From: Alberto Aguirre Date: Wed, 18 Apr 2018 09:35:34 -0500 Subject: ALSA: usb-audio: simplify set_sync_ep_implicit_fb_quirk Signed-off-by: Alberto Aguirre Signed-off-by: Takashi Iwai --- sound/usb/pcm.c | 52 ++++++++++++++++++++-------------------------------- 1 file changed, 20 insertions(+), 32 deletions(-) (limited to 'sound') diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 3cbfae6604f9..c0746cc20ac4 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -321,6 +321,7 @@ static int set_sync_ep_implicit_fb_quirk(struct snd_usb_substream *subs, struct usb_host_interface *alts; struct usb_interface *iface; unsigned int ep; + unsigned int ifnum; /* Implicit feedback sync EPs consumers are always playback EPs */ if (subs->direction != SNDRV_PCM_STREAM_PLAYBACK) @@ -330,44 +331,23 @@ static int set_sync_ep_implicit_fb_quirk(struct snd_usb_substream *subs, case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */ case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C600 */ ep = 0x81; - iface = usb_ifnum_to_if(dev, 3); - - if (!iface || iface->num_altsetting == 0) - return -EINVAL; - - alts = &iface->altsetting[1]; - goto add_sync_ep; - break; + ifnum = 3; + goto add_sync_ep_from_ifnum; case USB_ID(0x0763, 0x2080): /* M-Audio FastTrack Ultra */ case USB_ID(0x0763, 0x2081): ep = 0x81; - iface = usb_ifnum_to_if(dev, 2); - - if (!iface || iface->num_altsetting == 0) - return -EINVAL; - - alts = &iface->altsetting[1]; - goto add_sync_ep; - case USB_ID(0x2466, 0x8003): + ifnum = 2; + goto add_sync_ep_from_ifnum; + case USB_ID(0x2466, 0x8003): /* Fractal Audio Axe-Fx II */ ep = 0x86; - iface = usb_ifnum_to_if(dev, 2); - - if (!iface || iface->num_altsetting == 0) - return -EINVAL; - - alts = &iface->altsetting[1]; - goto add_sync_ep; - case USB_ID(0x1397, 0x0002): + ifnum = 2; + goto add_sync_ep_from_ifnum; + case USB_ID(0x1397, 0x0002): /* Behringer UFX1204 */ ep = 0x81; - iface = usb_ifnum_to_if(dev, 1); - - if (!iface || iface->num_altsetting == 0) - return -EINVAL; - - alts = &iface->altsetting[1]; - goto add_sync_ep; - + ifnum = 1; + goto add_sync_ep_from_ifnum; } + if (attr == USB_ENDPOINT_SYNC_ASYNC && altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC && altsd->bInterfaceProtocol == 2 && @@ -382,6 +362,14 @@ static int set_sync_ep_implicit_fb_quirk(struct snd_usb_substream *subs, /* No quirk */ return 0; +add_sync_ep_from_ifnum: + iface = usb_ifnum_to_if(dev, ifnum); + + if (!iface || iface->num_altsetting == 0) + return -EINVAL; + + alts = &iface->altsetting[1]; + add_sync_ep: subs->sync_endpoint = snd_usb_add_endpoint(subs->stream->chip, alts, ep, !subs->direction, -- cgit v1.2.3 From 91a8561d0eed710330956a06487f5c888f5ae743 Mon Sep 17 00:00:00 2001 From: Alberto Aguirre Date: Wed, 18 Apr 2018 09:35:35 -0500 Subject: ALSA: usb-audio: add implicit fb quirk for Axe-Fx III The Axe-Fx III implicit feedback end point and the data sink endpoint are in different interface descriptors. Add quirk to ensure a sync endpoint is properly configured. Signed-off-by: Alberto Aguirre Signed-off-by: Takashi Iwai --- sound/usb/pcm.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'sound') diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index c0746cc20ac4..ad39b3cca247 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -342,6 +342,10 @@ static int set_sync_ep_implicit_fb_quirk(struct snd_usb_substream *subs, ep = 0x86; ifnum = 2; goto add_sync_ep_from_ifnum; + case USB_ID(0x2466, 0x8010): /* Fractal Audio Axe-Fx III */ + ep = 0x81; + ifnum = 2; + goto add_sync_ep_from_ifnum; case USB_ID(0x1397, 0x0002): /* Behringer UFX1204 */ ep = 0x81; ifnum = 1; -- cgit v1.2.3 From c4e4a8fb236a6893e2cf0b16d3c1c6c07ee017c8 Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Mon, 23 Apr 2018 02:05:03 +0800 Subject: ALSA: cmi8328: array_find() can be static Signed-off-by: Fengguang Wu Signed-off-by: Takashi Iwai --- sound/isa/cmi8328.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/isa/cmi8328.c b/sound/isa/cmi8328.c index d09e456107ad..de6ef1b1cf0e 100644 --- a/sound/isa/cmi8328.c +++ b/sound/isa/cmi8328.c @@ -192,7 +192,7 @@ static int snd_cmi8328_mixer(struct snd_wss *chip) } /* find index of an item in "-1"-ended array */ -int array_find(int array[], int item) +static int array_find(int array[], int item) { int i; @@ -203,7 +203,7 @@ int array_find(int array[], int item) return -1; } /* the same for long */ -int array_find_l(long array[], long item) +static int array_find_l(long array[], long item) { int i; -- cgit v1.2.3 From f656891c66193345ee90b212b280d726792dc16c Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Fri, 20 Apr 2018 22:29:41 +0200 Subject: ALSA: usb-audio: add more quirks for DSD interfaces Based on a downstream patch from Harry ten Berge. Signed-off-by: Daniel Mack Reported-and-tested-by: wenyi@tianyu-wool.com Original-by: Harry ten Berge Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'sound') diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index acbeb52f6fd6..5681767cc0d5 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -1327,20 +1327,47 @@ u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip, /* XMOS based USB DACs */ switch (chip->usb_id) { + case USB_ID(0x1511, 0x0037): /* AURALiC VEGA */ + case USB_ID(0x20b1, 0x0002): /* Wyred 4 Sound DAC-2 DSD */ + case USB_ID(0x20b1, 0x2004): /* Matrix Audio X-SPDIF 2 */ case USB_ID(0x20b1, 0x3008): /* iFi Audio micro/nano iDSD */ case USB_ID(0x20b1, 0x2008): /* Matrix Audio X-Sabre */ case USB_ID(0x20b1, 0x300a): /* Matrix Audio Mini-i Pro */ case USB_ID(0x22d9, 0x0416): /* OPPO HA-1 */ + case USB_ID(0x22d9, 0x0436): /* OPPO Sonica */ + case USB_ID(0x22d9, 0x0461): /* OPPO UDP-205 */ + case USB_ID(0x2522, 0x0012): /* LH Labs VI DAC Infinity */ + case USB_ID(0x25ce, 0x001f): /* Mytek Brooklyn DAC */ + case USB_ID(0x25ce, 0x0021): /* Mytek Manhattan DAC */ + case USB_ID(0x25ce, 0x8025): /* Mytek Brooklyn DAC+ */ case USB_ID(0x2772, 0x0230): /* Pro-Ject Pre Box S2 Digital */ if (fp->altsetting == 2) return SNDRV_PCM_FMTBIT_DSD_U32_BE; break; + case USB_ID(0x0d8c, 0x0316): /* Hegel HD12 DSD */ + case USB_ID(0x16b0, 0x06b2): /* NuPrime DAC-10 */ + case USB_ID(0x16d0, 0x0733): /* Furutech ADL Stratos */ + case USB_ID(0x16d0, 0x09db): /* NuPrime Audio DAC-9 */ + case USB_ID(0x1db5, 0x0003): /* Bryston BDA3 */ case USB_ID(0x20b1, 0x000a): /* Gustard DAC-X20U */ + case USB_ID(0x20b1, 0x2005): /* Denafrips Ares DAC */ case USB_ID(0x20b1, 0x2009): /* DIYINHK DSD DXD 384kHz USB to I2S/DSD */ case USB_ID(0x20b1, 0x2023): /* JLsounds I2SoverUSB */ + case USB_ID(0x20b1, 0x3021): /* Eastern El. MiniMax Tube DAC Supreme */ case USB_ID(0x20b1, 0x3023): /* Aune X1S 32BIT/384 DSD DAC */ + case USB_ID(0x20b1, 0x302d): /* Unison Research Unico CD Due */ + case USB_ID(0x20b1, 0x3036): /* Holo Springs Level 3 R2R DAC */ + case USB_ID(0x20b1, 0x307b): /* CH Precision C1 DAC */ + case USB_ID(0x20b1, 0x3086): /* Singxer F-1 converter board */ + case USB_ID(0x22d9, 0x0426): /* OPPO HA-2 */ + case USB_ID(0x22e1, 0xca01): /* HDTA Serenade DSD */ + case USB_ID(0x249c, 0x9326): /* M2Tech Young MkIII */ case USB_ID(0x2616, 0x0106): /* PS Audio NuWave DAC */ + case USB_ID(0x2622, 0x0041): /* Audiolab M-DAC+ */ + case USB_ID(0x27f7, 0x3002): /* W4S DAC-2v2SE */ + case USB_ID(0x29a2, 0x0086): /* Mutec MC3+ USB */ + case USB_ID(0x6b42, 0x0042): /* MSB Technology */ if (fp->altsetting == 3) return SNDRV_PCM_FMTBIT_DSD_U32_BE; break; -- cgit v1.2.3 From a9c2dfc8527318a27db045cd7ea51e8ecab8c884 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 23 Apr 2018 17:24:56 +0200 Subject: ALSA: hda - Use a macro for snd_array iteration loops Introduce a new helper macro, snd_array_for_each(), to iterate for each snd_array element. It slightly improves the readability than lengthy open codes at each place. Along with it, add const prefix to some obvious places. There should be no functional changes by this. Signed-off-by: Takashi Iwai --- include/sound/hdaudio.h | 5 +++++ sound/hda/hdac_regmap.c | 4 ++-- sound/pci/hda/hda_auto_parser.c | 10 +++++----- sound/pci/hda/hda_codec.c | 36 ++++++++++++++++++------------------ sound/pci/hda/hda_generic.c | 27 +++++++++++++-------------- sound/pci/hda/hda_sysfs.c | 20 ++++++++++---------- sound/pci/hda/patch_conexant.c | 5 ++--- sound/pci/hda/patch_realtek.c | 4 ++-- 8 files changed, 57 insertions(+), 54 deletions(-) (limited to 'sound') diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 06536e01ed94..c052afc27547 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -571,4 +571,9 @@ static inline unsigned int snd_array_index(struct snd_array *array, void *ptr) return (unsigned long)(ptr - array->list) / array->elem_size; } +/* a helper macro to iterate for each snd_array element */ +#define snd_array_for_each(array, idx, ptr) \ + for ((idx) = 0, (ptr) = (array)->list; (idx) < (array)->used; \ + (ptr) = snd_array_elem(array, ++(idx))) + #endif /* __SOUND_HDAUDIO_H */ diff --git a/sound/hda/hdac_regmap.c b/sound/hda/hdac_regmap.c index 47a358fab132..419e285e0226 100644 --- a/sound/hda/hdac_regmap.c +++ b/sound/hda/hdac_regmap.c @@ -65,10 +65,10 @@ static bool hda_writeable_reg(struct device *dev, unsigned int reg) { struct hdac_device *codec = dev_to_hdac_dev(dev); unsigned int verb = get_verb(reg); + const unsigned int *v; int i; - for (i = 0; i < codec->vendor_verbs.used; i++) { - unsigned int *v = snd_array_elem(&codec->vendor_verbs, i); + snd_array_for_each(&codec->vendor_verbs, i, v) { if (verb == *v) return true; } diff --git a/sound/pci/hda/hda_auto_parser.c b/sound/pci/hda/hda_auto_parser.c index d3ea73171a3d..b9a6b66aeb0e 100644 --- a/sound/pci/hda/hda_auto_parser.c +++ b/sound/pci/hda/hda_auto_parser.c @@ -793,11 +793,11 @@ EXPORT_SYMBOL_GPL(snd_hda_add_verbs); */ void snd_hda_apply_verbs(struct hda_codec *codec) { + const struct hda_verb **v; int i; - for (i = 0; i < codec->verbs.used; i++) { - struct hda_verb **v = snd_array_elem(&codec->verbs, i); + + snd_array_for_each(&codec->verbs, i, v) snd_hda_sequence_write(codec, *v); - } } EXPORT_SYMBOL_GPL(snd_hda_apply_verbs); @@ -890,10 +890,10 @@ EXPORT_SYMBOL_GPL(snd_hda_apply_fixup); static bool pin_config_match(struct hda_codec *codec, const struct hda_pintbl *pins) { + const struct hda_pincfg *pin; int i; - for (i = 0; i < codec->init_pins.used; i++) { - struct hda_pincfg *pin = snd_array_elem(&codec->init_pins, i); + snd_array_for_each(&codec->init_pins, i, pin) { hda_nid_t nid = pin->nid; u32 cfg = pin->cfg; const struct hda_pintbl *t_pins; diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 5bc3a7468e17..0aa923d129f5 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -481,9 +481,10 @@ static struct hda_pincfg *look_up_pincfg(struct hda_codec *codec, struct snd_array *array, hda_nid_t nid) { + struct hda_pincfg *pin; int i; - for (i = 0; i < array->used; i++) { - struct hda_pincfg *pin = snd_array_elem(array, i); + + snd_array_for_each(array, i, pin) { if (pin->nid == nid) return pin; } @@ -618,14 +619,15 @@ EXPORT_SYMBOL_GPL(snd_hda_codec_get_pin_target); */ void snd_hda_shutup_pins(struct hda_codec *codec) { + const struct hda_pincfg *pin; int i; + /* don't shut up pins when unloading the driver; otherwise it breaks * the default pin setup at the next load of the driver */ if (codec->bus->shutdown) return; - for (i = 0; i < codec->init_pins.used; i++) { - struct hda_pincfg *pin = snd_array_elem(&codec->init_pins, i); + snd_array_for_each(&codec->init_pins, i, pin) { /* use read here for syncing after issuing each verb */ snd_hda_codec_read(codec, pin->nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0); @@ -638,13 +640,14 @@ EXPORT_SYMBOL_GPL(snd_hda_shutup_pins); /* Restore the pin controls cleared previously via snd_hda_shutup_pins() */ static void restore_shutup_pins(struct hda_codec *codec) { + const struct hda_pincfg *pin; int i; + if (!codec->pins_shutup) return; if (codec->bus->shutdown) return; - for (i = 0; i < codec->init_pins.used; i++) { - struct hda_pincfg *pin = snd_array_elem(&codec->init_pins, i); + snd_array_for_each(&codec->init_pins, i, pin) { snd_hda_codec_write(codec, pin->nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, pin->ctrl); @@ -697,8 +700,7 @@ get_hda_cvt_setup(struct hda_codec *codec, hda_nid_t nid) struct hda_cvt_setup *p; int i; - for (i = 0; i < codec->cvt_setups.used; i++) { - p = snd_array_elem(&codec->cvt_setups, i); + snd_array_for_each(&codec->cvt_setups, i, p) { if (p->nid == nid) return p; } @@ -1076,8 +1078,7 @@ void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, /* make other inactive cvts with the same stream-tag dirty */ type = get_wcaps_type(get_wcaps(codec, nid)); list_for_each_codec(c, codec->bus) { - for (i = 0; i < c->cvt_setups.used; i++) { - p = snd_array_elem(&c->cvt_setups, i); + snd_array_for_each(&c->cvt_setups, i, p) { if (!p->active && p->stream_tag == stream_tag && get_wcaps_type(get_wcaps(c, p->nid)) == type) p->dirty = 1; @@ -1140,12 +1141,11 @@ static void really_cleanup_stream(struct hda_codec *codec, static void purify_inactive_streams(struct hda_codec *codec) { struct hda_codec *c; + struct hda_cvt_setup *p; int i; list_for_each_codec(c, codec->bus) { - for (i = 0; i < c->cvt_setups.used; i++) { - struct hda_cvt_setup *p; - p = snd_array_elem(&c->cvt_setups, i); + snd_array_for_each(&c->cvt_setups, i, p) { if (p->dirty) really_cleanup_stream(c, p); } @@ -1156,10 +1156,10 @@ static void purify_inactive_streams(struct hda_codec *codec) /* clean up all streams; called from suspend */ static void hda_cleanup_all_streams(struct hda_codec *codec) { + struct hda_cvt_setup *p; int i; - for (i = 0; i < codec->cvt_setups.used; i++) { - struct hda_cvt_setup *p = snd_array_elem(&codec->cvt_setups, i); + snd_array_for_each(&codec->cvt_setups, i, p) { if (p->stream_tag) really_cleanup_stream(codec, p); } @@ -2461,10 +2461,10 @@ EXPORT_SYMBOL_GPL(snd_hda_create_dig_out_ctls); struct hda_spdif_out *snd_hda_spdif_out_of_nid(struct hda_codec *codec, hda_nid_t nid) { + struct hda_spdif_out *spdif; int i; - for (i = 0; i < codec->spdif_out.used; i++) { - struct hda_spdif_out *spdif = - snd_array_elem(&codec->spdif_out, i); + + snd_array_for_each(&codec->spdif_out, i, spdif) { if (spdif->nid == nid) return spdif; } diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index 5cc65093d941..51030f040745 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -264,10 +264,10 @@ static struct nid_path *get_nid_path(struct hda_codec *codec, int anchor_nid) { struct hda_gen_spec *spec = codec->spec; + struct nid_path *path; int i; - for (i = 0; i < spec->paths.used; i++) { - struct nid_path *path = snd_array_elem(&spec->paths, i); + snd_array_for_each(&spec->paths, i, path) { if (path->depth <= 0) continue; if ((!from_nid || path->path[0] == from_nid) && @@ -325,10 +325,10 @@ EXPORT_SYMBOL_GPL(snd_hda_get_path_from_idx); static bool is_dac_already_used(struct hda_codec *codec, hda_nid_t nid) { struct hda_gen_spec *spec = codec->spec; + const struct nid_path *path; int i; - for (i = 0; i < spec->paths.used; i++) { - struct nid_path *path = snd_array_elem(&spec->paths, i); + snd_array_for_each(&spec->paths, i, path) { if (path->path[0] == nid) return true; } @@ -351,11 +351,11 @@ static bool is_reachable_path(struct hda_codec *codec, static bool is_ctl_used(struct hda_codec *codec, unsigned int val, int type) { struct hda_gen_spec *spec = codec->spec; + const struct nid_path *path; int i; val &= AMP_VAL_COMPARE_MASK; - for (i = 0; i < spec->paths.used; i++) { - struct nid_path *path = snd_array_elem(&spec->paths, i); + snd_array_for_each(&spec->paths, i, path) { if ((path->ctls[type] & AMP_VAL_COMPARE_MASK) == val) return true; } @@ -638,13 +638,13 @@ static bool is_active_nid(struct hda_codec *codec, hda_nid_t nid, { struct hda_gen_spec *spec = codec->spec; int type = get_wcaps_type(get_wcaps(codec, nid)); + const struct nid_path *path; int i, n; if (nid == codec->core.afg) return true; - for (n = 0; n < spec->paths.used; n++) { - struct nid_path *path = snd_array_elem(&spec->paths, n); + snd_array_for_each(&spec->paths, n, path) { if (!path->active) continue; if (codec->power_save_node) { @@ -2696,10 +2696,10 @@ static const struct snd_kcontrol_new out_jack_mode_enum = { static bool find_kctl_name(struct hda_codec *codec, const char *name, int idx) { struct hda_gen_spec *spec = codec->spec; + const struct snd_kcontrol_new *kctl; int i; - for (i = 0; i < spec->kctls.used; i++) { - struct snd_kcontrol_new *kctl = snd_array_elem(&spec->kctls, i); + snd_array_for_each(&spec->kctls, i, kctl) { if (!strcmp(kctl->name, name) && kctl->index == idx) return true; } @@ -4021,8 +4021,7 @@ static hda_nid_t set_path_power(struct hda_codec *codec, hda_nid_t nid, struct nid_path *path; int n; - for (n = 0; n < spec->paths.used; n++) { - path = snd_array_elem(&spec->paths, n); + snd_array_for_each(&spec->paths, n, path) { if (!path->depth) continue; if (path->path[0] == nid || @@ -5831,10 +5830,10 @@ static void init_digital(struct hda_codec *codec) */ static void clear_unsol_on_unused_pins(struct hda_codec *codec) { + const struct hda_pincfg *pin; int i; - for (i = 0; i < codec->init_pins.used; i++) { - struct hda_pincfg *pin = snd_array_elem(&codec->init_pins, i); + snd_array_for_each(&codec->init_pins, i, pin) { hda_nid_t nid = pin->nid; if (is_jack_detectable(codec, nid) && !snd_hda_jack_tbl_get(codec, nid)) diff --git a/sound/pci/hda/hda_sysfs.c b/sound/pci/hda/hda_sysfs.c index 9b7efece4484..6ec79c58d48d 100644 --- a/sound/pci/hda/hda_sysfs.c +++ b/sound/pci/hda/hda_sysfs.c @@ -80,10 +80,10 @@ static ssize_t pin_configs_show(struct hda_codec *codec, struct snd_array *list, char *buf) { + const struct hda_pincfg *pin; int i, len = 0; mutex_lock(&codec->user_mutex); - for (i = 0; i < list->used; i++) { - struct hda_pincfg *pin = snd_array_elem(list, i); + snd_array_for_each(list, i, pin) { len += sprintf(buf + len, "0x%02x 0x%08x\n", pin->nid, pin->cfg); } @@ -217,10 +217,10 @@ static ssize_t init_verbs_show(struct device *dev, char *buf) { struct hda_codec *codec = dev_get_drvdata(dev); + const struct hda_verb *v; int i, len = 0; mutex_lock(&codec->user_mutex); - for (i = 0; i < codec->init_verbs.used; i++) { - struct hda_verb *v = snd_array_elem(&codec->init_verbs, i); + snd_array_for_each(&codec->init_verbs, i, v) { len += snprintf(buf + len, PAGE_SIZE - len, "0x%02x 0x%03x 0x%04x\n", v->nid, v->verb, v->param); @@ -267,10 +267,10 @@ static ssize_t hints_show(struct device *dev, char *buf) { struct hda_codec *codec = dev_get_drvdata(dev); + const struct hda_hint *hint; int i, len = 0; mutex_lock(&codec->user_mutex); - for (i = 0; i < codec->hints.used; i++) { - struct hda_hint *hint = snd_array_elem(&codec->hints, i); + snd_array_for_each(&codec->hints, i, hint) { len += snprintf(buf + len, PAGE_SIZE - len, "%s = %s\n", hint->key, hint->val); } @@ -280,10 +280,10 @@ static ssize_t hints_show(struct device *dev, static struct hda_hint *get_hint(struct hda_codec *codec, const char *key) { + struct hda_hint *hint; int i; - for (i = 0; i < codec->hints.used; i++) { - struct hda_hint *hint = snd_array_elem(&codec->hints, i); + snd_array_for_each(&codec->hints, i, hint) { if (!strcmp(hint->key, key)) return hint; } @@ -783,13 +783,13 @@ void snd_hda_sysfs_init(struct hda_codec *codec) void snd_hda_sysfs_clear(struct hda_codec *codec) { #ifdef CONFIG_SND_HDA_RECONFIG + struct hda_hint *hint; int i; /* clear init verbs */ snd_array_free(&codec->init_verbs); /* clear hints */ - for (i = 0; i < codec->hints.used; i++) { - struct hda_hint *hint = snd_array_elem(&codec->hints, i); + snd_array_for_each(&codec->hints, i, hint) { kfree(hint->key); /* we don't need to free hint->val */ } snd_array_free(&codec->hints); diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 5b4dbcec6de8..093d2a9ece85 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -588,6 +588,7 @@ static void cxt_fixup_olpc_xo(struct hda_codec *codec, const struct hda_fixup *fix, int action) { struct conexant_spec *spec = codec->spec; + struct snd_kcontrol_new *kctl; int i; if (action != HDA_FIXUP_ACT_PROBE) @@ -606,9 +607,7 @@ static void cxt_fixup_olpc_xo(struct hda_codec *codec, snd_hda_codec_set_pin_target(codec, 0x1a, PIN_VREF50); /* override mic boost control */ - for (i = 0; i < spec->gen.kctls.used; i++) { - struct snd_kcontrol_new *kctl = - snd_array_elem(&spec->gen.kctls, i); + snd_array_for_each(&spec->gen.kctls, i, kctl) { if (!strcmp(kctl->name, "Mic Boost Volume")) { kctl->put = olpc_xo_mic_boost_put; break; diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index aef1f52db7d9..7f2d5b157b75 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -2828,6 +2828,7 @@ static int find_ext_mic_pin(struct hda_codec *codec); static void alc286_shutup(struct hda_codec *codec) { + const struct hda_pincfg *pin; int i; int mic_pin = find_ext_mic_pin(codec); /* don't shut up pins when unloading the driver; otherwise it breaks @@ -2835,8 +2836,7 @@ static void alc286_shutup(struct hda_codec *codec) */ if (codec->bus->shutdown) return; - for (i = 0; i < codec->init_pins.used; i++) { - struct hda_pincfg *pin = snd_array_elem(&codec->init_pins, i); + snd_array_for_each(&codec->init_pins, i, pin) { /* use read here for syncing after issuing each verb */ if (pin->nid != mic_pin) snd_hda_codec_read(codec, pin->nid, 0, -- cgit v1.2.3 From c1a36101040a71dbc42afca5e329048042e4afef Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Tue, 24 Apr 2018 22:24:32 +0900 Subject: ALSA: dice: improve support for ancient firmware for DICE In early stage of firmware SDK, DICE seems to lose its backward compatibility due to some registers on global address section. I found this with Alesis Multimix 12 FireWire with ancient firmware (approx. shipped version). According to retrieved log from the unit, global section has 96 byte space. On the other hand, current version of ALSA dice driver assumes that all of supported unit has at least 100 byte space. $ ./firewire-request /dev/fw1 read 0xffffe0000000 28 result: 000: 00 00 00 0a 00 00 00 18 00 00 00 22 00 00 00 8a result: 010: 00 00 00 ac 00 00 01 12 00 00 00 00 00 00 00 00 result: 020: 00 00 00 00 00 00 00 00 This commit adds support for the ancient firmware. Check of global section is loosened to accept the smaller space. The lack of information is already compensated by hard-coded parameters. I experienced that the latest version of Windows driver for this model can't handle this unit, too. This means that TCAT releases firmware SDK without backward compatibility for the ancient firmware. Below list is a early history of driver/firmware package released by Alesis. I investigated on wayback machine on Internet Archive: * Unknown: PAL v1.0.41.2, firmware v1.0.3 * Mar 2006: PAL v1.54.0, firmware v1.0.4 * Dec 2006: PAL v2.0.0.2, firmware v2.0 * Jun 2007: PAL v3.0.41.5, firmware v2.0 * Jul 2007: PAL v3.0.56.2. firmware v2.0 * Jan 2008: PAL v3.0.81.1080, firmware v2.0 If I can assume that firmware version is the same as DICE version, DICE version for the issued firmware may be v1.0.3. According to code base of userspace driver project (FFADO), I can read DICE v1.0.4 supports global space larger than 100 byte. I guess the smaller space of global section is a feature of DICE v1.0.3. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-interface.h | 9 +++++-- sound/firewire/dice/dice-proc.c | 10 +++---- sound/firewire/dice/dice-transaction.c | 49 +++++++++++++++++++--------------- 3 files changed, 40 insertions(+), 28 deletions(-) (limited to 'sound') diff --git a/sound/firewire/dice/dice-interface.h b/sound/firewire/dice/dice-interface.h index 15a484b05298..9cad3d608229 100644 --- a/sound/firewire/dice/dice-interface.h +++ b/sound/firewire/dice/dice-interface.h @@ -174,14 +174,19 @@ */ #define GLOBAL_SAMPLE_RATE 0x05c +/* + * Some old firmware versions do not have the following global registers. + * Windows drivers produced by TCAT lost backward compatibility in its + * early release because they can handle firmware only which supports the + * following registers. + */ + /* * The version of the DICE driver specification that this device conforms to; * read-only. */ #define GLOBAL_VERSION 0x060 -/* Some old firmware versions do not have the following global registers: */ - /* * Supported sample rates and clock sources; read-only. */ diff --git a/sound/firewire/dice/dice-proc.c b/sound/firewire/dice/dice-proc.c index f5c1d1bced59..cc079323ed30 100644 --- a/sound/firewire/dice/dice-proc.c +++ b/sound/firewire/dice/dice-proc.c @@ -148,12 +148,12 @@ static void dice_proc_read(struct snd_info_entry *entry, >> CLOCK_RATE_SHIFT)); snd_iprintf(buffer, " ext status: %08x\n", buf.global.extended_status); snd_iprintf(buffer, " sample rate: %u\n", buf.global.sample_rate); - snd_iprintf(buffer, " version: %u.%u.%u.%u\n", - (buf.global.version >> 24) & 0xff, - (buf.global.version >> 16) & 0xff, - (buf.global.version >> 8) & 0xff, - (buf.global.version >> 0) & 0xff); if (quadlets >= 90) { + snd_iprintf(buffer, " version: %u.%u.%u.%u\n", + (buf.global.version >> 24) & 0xff, + (buf.global.version >> 16) & 0xff, + (buf.global.version >> 8) & 0xff, + (buf.global.version >> 0) & 0xff); snd_iprintf(buffer, " clock caps:"); for (i = 0; i <= 6; ++i) if (buf.global.clock_caps & (1 << i)) diff --git a/sound/firewire/dice/dice-transaction.c b/sound/firewire/dice/dice-transaction.c index 0f0350320ae8..b7e138b5abcf 100644 --- a/sound/firewire/dice/dice-transaction.c +++ b/sound/firewire/dice/dice-transaction.c @@ -265,7 +265,7 @@ int snd_dice_transaction_reinit(struct snd_dice *dice) static int get_subaddrs(struct snd_dice *dice) { static const int min_values[10] = { - 10, 0x64 / 4, + 10, 0x60 / 4, 10, 0x18 / 4, 10, 0x18 / 4, 0, 0, @@ -301,33 +301,40 @@ static int get_subaddrs(struct snd_dice *dice) } } - /* - * Check that the implemented DICE driver specification major version - * number matches. - */ - err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, - DICE_PRIVATE_SPACE + - be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION, - &version, sizeof(version), 0); - if (err < 0) - goto end; + if (be32_to_cpu(pointers[1]) > 0x18) { + /* + * Check that the implemented DICE driver specification major + * version number matches. + */ + err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, + DICE_PRIVATE_SPACE + + be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION, + &version, sizeof(version), 0); + if (err < 0) + goto end; - if ((version & cpu_to_be32(0xff000000)) != cpu_to_be32(0x01000000)) { - dev_err(&dice->unit->device, - "unknown DICE version: 0x%08x\n", be32_to_cpu(version)); - err = -ENODEV; - goto end; + if ((version & cpu_to_be32(0xff000000)) != + cpu_to_be32(0x01000000)) { + dev_err(&dice->unit->device, + "unknown DICE version: 0x%08x\n", + be32_to_cpu(version)); + err = -ENODEV; + goto end; + } + + /* Set up later. */ + dice->clock_caps = 1; } dice->global_offset = be32_to_cpu(pointers[0]) * 4; dice->tx_offset = be32_to_cpu(pointers[2]) * 4; dice->rx_offset = be32_to_cpu(pointers[4]) * 4; - dice->sync_offset = be32_to_cpu(pointers[6]) * 4; - dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4; - /* Set up later. */ - if (be32_to_cpu(pointers[1]) * 4 >= GLOBAL_CLOCK_CAPABILITIES + 4) - dice->clock_caps = 1; + /* Old firmware doesn't support these fields. */ + if (pointers[7]) + dice->sync_offset = be32_to_cpu(pointers[6]) * 4; + if (pointers[9]) + dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4; end: kfree(pointers); return err; -- cgit v1.2.3 From 08605068df8bf52c0ec5a8897ddf2b4de753c9d0 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 24 Apr 2018 16:53:24 +0200 Subject: ALSA: hda - Sanity check of access to SPDIF controls array Put WARN_ON() and bail out if the given index is over the allocated array of the given SPDIF controls. It's merely a sanity check to catch any potential issues (if any). Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_codec.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 0aa923d129f5..63f177d975fd 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -2175,6 +2175,8 @@ static int snd_hda_spdif_default_get(struct snd_kcontrol *kcontrol, int idx = kcontrol->private_value; struct hda_spdif_out *spdif; + if (WARN_ON(codec->spdif_out.used <= idx)) + return -EINVAL; mutex_lock(&codec->spdif_mutex); spdif = snd_array_elem(&codec->spdif_out, idx); ucontrol->value.iec958.status[0] = spdif->status & 0xff; @@ -2282,6 +2284,8 @@ static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol, unsigned short val; int change; + if (WARN_ON(codec->spdif_out.used <= idx)) + return -EINVAL; mutex_lock(&codec->spdif_mutex); spdif = snd_array_elem(&codec->spdif_out, idx); nid = spdif->nid; @@ -2308,6 +2312,8 @@ static int snd_hda_spdif_out_switch_get(struct snd_kcontrol *kcontrol, int idx = kcontrol->private_value; struct hda_spdif_out *spdif; + if (WARN_ON(codec->spdif_out.used <= idx)) + return -EINVAL; mutex_lock(&codec->spdif_mutex); spdif = snd_array_elem(&codec->spdif_out, idx); ucontrol->value.integer.value[0] = spdif->ctls & AC_DIG1_ENABLE; @@ -2336,6 +2342,8 @@ static int snd_hda_spdif_out_switch_put(struct snd_kcontrol *kcontrol, unsigned short val; int change; + if (WARN_ON(codec->spdif_out.used <= idx)) + return -EINVAL; mutex_lock(&codec->spdif_mutex); spdif = snd_array_elem(&codec->spdif_out, idx); nid = spdif->nid; @@ -2483,6 +2491,8 @@ void snd_hda_spdif_ctls_unassign(struct hda_codec *codec, int idx) { struct hda_spdif_out *spdif; + if (WARN_ON(codec->spdif_out.used <= idx)) + return; mutex_lock(&codec->spdif_mutex); spdif = snd_array_elem(&codec->spdif_out, idx); spdif->nid = (u16)-1; @@ -2503,6 +2513,8 @@ void snd_hda_spdif_ctls_assign(struct hda_codec *codec, int idx, hda_nid_t nid) struct hda_spdif_out *spdif; unsigned short val; + if (WARN_ON(codec->spdif_out.used <= idx)) + return; mutex_lock(&codec->spdif_mutex); spdif = snd_array_elem(&codec->spdif_out, idx); if (spdif->nid != nid) { -- cgit v1.2.3 From dfd9944f7cf824c7249cfb8803af3a47532f9a1f Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 27 Apr 2018 20:02:45 +0100 Subject: ALSA: cs46xx: fix spelling mistake: "amplifer" -> "amplifier" Trivial fix to spelling mistake in module parameter description text Signed-off-by: Colin Ian King Signed-off-by: Takashi Iwai --- sound/pci/cs46xx/cs46xx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/cs46xx/cs46xx.c b/sound/pci/cs46xx/cs46xx.c index 655fbea1692c..4910d3f46d4b 100644 --- a/sound/pci/cs46xx/cs46xx.c +++ b/sound/pci/cs46xx/cs46xx.c @@ -58,7 +58,7 @@ MODULE_PARM_DESC(id, "ID string for the CS46xx soundcard."); module_param_array(enable, bool, NULL, 0444); MODULE_PARM_DESC(enable, "Enable CS46xx soundcard."); module_param_array(external_amp, bool, NULL, 0444); -MODULE_PARM_DESC(external_amp, "Force to enable external amplifer."); +MODULE_PARM_DESC(external_amp, "Force to enable external amplifier."); module_param_array(thinkpad, bool, NULL, 0444); MODULE_PARM_DESC(thinkpad, "Force to enable Thinkpad's CLKRUN control."); module_param_array(mmap_valid, bool, NULL, 0444); -- cgit v1.2.3 From f4a414aa6ef219ff4d13992762ecbbb625797d66 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 2 May 2018 09:03:26 +0200 Subject: ALSA: hda - Enforce CONFIG_SND_DYNAMIC_MINORS for HDMI/DP codec The DP-MST support requires more PCM streams than usual, hence CONFIG_SND_DYNAMIC_MINORS is almost mandatory. Currently the driver just warns and continues even if streams are missing, but it doesn't seem to enough convince users to switch to the modern setup. This patch adds the enforced selection of CONFIG_SND_DYNAMIC_MINORS from CONFIG_SND_HDA_CODEC_HDMI for covering that. Signed-off-by: Takashi Iwai --- sound/pci/hda/Kconfig | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index f7a492c382d9..4235907b7858 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -127,11 +127,15 @@ comment "Set to Y if you want auto-loading the codec driver" config SND_HDA_CODEC_HDMI tristate "Build HDMI/DisplayPort HD-audio codec support" + select SND_DYNAMIC_MINORS help Say Y or M here to include HDMI and DisplayPort HD-audio codec support in snd-hda-intel driver. This includes all AMD/ATI, Intel and Nvidia HDMI/DisplayPort codecs. + Note that this option mandatorily enables CONFIG_SND_DYNAMIC_MINORS + to assure the multiple streams for DP-MST support. + comment "Set to Y if you want auto-loading the codec driver" depends on SND_HDA=y && SND_HDA_CODEC_HDMI=m -- cgit v1.2.3 From 8e142e9e628975b0dddd05cf1b095331dff6e2de Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 22:48:16 +0900 Subject: ALSA: hda/ca0132: fix build failure when a local macro is defined DECLARE_TLV_DB_SCALE (alias of SNDRV_CTL_TLVD_DECLARE_DB_SCALE) is used but tlv.h is not included. This causes build failure when local macro is defined by comment-out. This commit fixes the bug. At the same time, the alias macro is replaced with a destination macro added at a commit 46e860f76804 ("ALSA: rename TLV-related macros so that they're friendly to user applications") Reported-by: Connor McAdams Fixes: 44f0c9782cc6 ('ALSA: hda/ca0132: Add tuning controls') Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 768ea8651993..84261ef02c93 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -39,6 +39,10 @@ /* Enable this to see controls for tuning purpose. */ /*#define ENABLE_TUNING_CONTROLS*/ +#ifdef ENABLE_TUNING_CONTROLS +#include +#endif + #define FLOAT_ZERO 0x00000000 #define FLOAT_ONE 0x3f800000 #define FLOAT_TWO 0x40000000 @@ -3068,8 +3072,8 @@ static int equalizer_ctl_put(struct snd_kcontrol *kcontrol, return 1; } -static const DECLARE_TLV_DB_SCALE(voice_focus_db_scale, 2000, 100, 0); -static const DECLARE_TLV_DB_SCALE(eq_db_scale, -2400, 100, 0); +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(voice_focus_db_scale, 2000, 100, 0); +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(eq_db_scale, -2400, 100, 0); static int add_tuning_control(struct hda_codec *codec, hda_nid_t pnid, hda_nid_t nid, -- cgit v1.2.3 From 6a300dc95e855d60a4c123a4a81becc938f6360a Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 2 May 2018 14:59:40 +0100 Subject: ALSA: sc6000: fix spelling mistake: "iomaped" -> "iomapped" Trivial fix to spelling mistake in KERN_ERR error messages Signed-off-by: Colin Ian King Signed-off-by: Takashi Iwai --- sound/isa/sc6000.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c index c09d9b914efe..a985e9183be9 100644 --- a/sound/isa/sc6000.c +++ b/sound/isa/sc6000.c @@ -592,7 +592,7 @@ static int snd_sc6000_probe(struct device *devptr, unsigned int dev) *vport = devm_ioport_map(devptr, port[dev], 0x10); if (*vport == NULL) { snd_printk(KERN_ERR PFX - "I/O port cannot be iomaped.\n"); + "I/O port cannot be iomapped.\n"); err = -EBUSY; goto err_unmap1; } @@ -607,7 +607,7 @@ static int snd_sc6000_probe(struct device *devptr, unsigned int dev) vmss_port = devm_ioport_map(devptr, mss_port[dev], 4); if (!vmss_port) { snd_printk(KERN_ERR PFX - "MSS port I/O cannot be iomaped.\n"); + "MSS port I/O cannot be iomapped.\n"); err = -EBUSY; goto err_unmap2; } -- cgit v1.2.3 From 8feda7ddb6a3047609060840a4631b70623b0131 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 19:16:39 +0900 Subject: ALSA: dice: add cache of stream formats A previous commit 6f688268b3f4 ('ALSA: dice: purge generating channel cache') purged cache of stream formats. DICE interface originally has no feature to assist drivers to retrieve available formats for all of supported sampling transmission frequencies, without changing the frequency actually. For later release of Dice ASICs such as TCD2210, Dice interface has extended protocol and can support the feature. This assists drivers to retrieve available stream formats. This commit is a first step to regain the cache to generate PCM rules for all of supported sampling transmission frequencies. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice.h | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'sound') diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index da00e75e09d4..8f68976930c5 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -63,6 +63,13 @@ */ #define MAX_STREAMS 2 +enum snd_dice_rate_mode { + SND_DICE_RATE_MODE_LOW = 0, + SND_DICE_RATE_MODE_MIDDLE, + SND_DICE_RATE_MODE_HIGH, + SND_DICE_RATE_MODE_COUNT, +}; + struct snd_dice { struct snd_card *card; struct fw_unit *unit; @@ -80,6 +87,10 @@ struct snd_dice { unsigned int rsrv_offset; unsigned int clock_caps; + unsigned int tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + unsigned int rx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + unsigned int tx_midi_ports[MAX_STREAMS]; + unsigned int rx_midi_ports[MAX_STREAMS]; struct fw_address_handler notification_handler; int owner_generation; -- cgit v1.2.3 From 37149d66879ba8babb916a039cc123f44d6e2ab9 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 19:16:40 +0900 Subject: ALSA: dice: add 'firewire' directory for proc nodes Unlike the other drivers in ALSA firewire stack, ALSA dice driver does't create 'firewire' directory for proc nodes because it has 'dice' node only. But this is inconvenient because I have a plan to add another proc node to output available stream formats from cache. This commit let the driver to create the directory and put 'dice' node into it. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-proc.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/firewire/dice/dice-proc.c b/sound/firewire/dice/dice-proc.c index cc079323ed30..43b130b7aa07 100644 --- a/sound/firewire/dice/dice-proc.c +++ b/sound/firewire/dice/dice-proc.c @@ -243,10 +243,39 @@ static void dice_proc_read(struct snd_info_entry *entry, } } -void snd_dice_create_proc(struct snd_dice *dice) +static void add_node(struct snd_dice *dice, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *entry, + struct snd_info_buffer *buffer)) { struct snd_info_entry *entry; - if (!snd_card_proc_new(dice->card, "dice", &entry)) - snd_info_set_text_ops(entry, dice, dice_proc_read); + entry = snd_info_create_card_entry(dice->card, name, root); + if (!entry) + return; + + snd_info_set_text_ops(entry, dice, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_dice_create_proc(struct snd_dice *dice) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(dice->card, "firewire", + dice->card->proc_root); + if (!root) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(dice, root, "dice", dice_proc_read); } -- cgit v1.2.3 From b7fd3d64e763c381de7ed57217fda3c720a98aad Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 19:16:41 +0900 Subject: ALSA: dice: add proc node for stream formation Products with DICE interface in market can support variable stream formats for three levels of sampling transmission frequencies. To record these formats, a proxy structure got several fields in former commit. This commit adds a proc node to output the stream formats for debugging purpose. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-proc.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'sound') diff --git a/sound/firewire/dice/dice-proc.c b/sound/firewire/dice/dice-proc.c index 43b130b7aa07..faf8c854d256 100644 --- a/sound/firewire/dice/dice-proc.c +++ b/sound/firewire/dice/dice-proc.c @@ -243,6 +243,40 @@ static void dice_proc_read(struct snd_info_entry *entry, } } +static void dice_proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + static const char *const rate_labels[] = { + [SND_DICE_RATE_MODE_LOW] = "low", + [SND_DICE_RATE_MODE_MIDDLE] = "middle", + [SND_DICE_RATE_MODE_HIGH] = "high", + }; + struct snd_dice *dice = entry->private_data; + int i, j; + + snd_iprintf(buffer, "Output stream from unit:\n"); + for (i = 0; i < SND_DICE_RATE_MODE_COUNT; ++i) + snd_iprintf(buffer, "\t%s", rate_labels[i]); + snd_iprintf(buffer, "\tMIDI\n"); + for (i = 0; i < MAX_STREAMS; ++i) { + snd_iprintf(buffer, "Tx %u:", i); + for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) + snd_iprintf(buffer, "\t%u", dice->tx_pcm_chs[i][j]); + snd_iprintf(buffer, "\t%u\n", dice->tx_midi_ports[i]); + } + + snd_iprintf(buffer, "Input stream to unit:\n"); + for (i = 0; i < SND_DICE_RATE_MODE_COUNT; ++i) + snd_iprintf(buffer, "\t%s", rate_labels[i]); + snd_iprintf(buffer, "\n"); + for (i = 0; i < MAX_STREAMS; ++i) { + snd_iprintf(buffer, "Rx %u:", i); + for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) + snd_iprintf(buffer, "\t%u", dice->rx_pcm_chs[i][j]); + snd_iprintf(buffer, "\t%u\n", dice->rx_midi_ports[i]); + } +} + static void add_node(struct snd_dice *dice, struct snd_info_entry *root, const char *name, void (*op)(struct snd_info_entry *entry, @@ -278,4 +312,5 @@ void snd_dice_create_proc(struct snd_dice *dice) } add_node(dice, root, "dice", dice_proc_read); + add_node(dice, root, "formation", dice_proc_read_formation); } -- cgit v1.2.3 From b60152f750ca22ddee20954228d1bcbf45c936f7 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 19:16:42 +0900 Subject: ALSA: dice: cache stream formats at current mode of sampling transmission frequency In former commits, proxy structure get members for cache of stream formats. This commit fills the cache with stream formats at current mode of sampling transmission frequency. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-stream.c | 86 +++++++++++++++++++++++++++++++++++++++ sound/firewire/dice/dice.c | 4 ++ sound/firewire/dice/dice.h | 3 ++ 3 files changed, 93 insertions(+) (limited to 'sound') diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c index 8573289c381e..6c859d2b9084 100644 --- a/sound/firewire/dice/dice-stream.c +++ b/sound/firewire/dice/dice-stream.c @@ -30,6 +30,34 @@ const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT] = { [6] = 192000, }; +int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate, + enum snd_dice_rate_mode *mode) +{ + /* Corresponding to each entry in snd_dice_rates. */ + static const enum snd_dice_rate_mode modes[] = { + [0] = SND_DICE_RATE_MODE_LOW, + [1] = SND_DICE_RATE_MODE_LOW, + [2] = SND_DICE_RATE_MODE_LOW, + [3] = SND_DICE_RATE_MODE_MIDDLE, + [4] = SND_DICE_RATE_MODE_MIDDLE, + [5] = SND_DICE_RATE_MODE_HIGH, + [6] = SND_DICE_RATE_MODE_HIGH, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); i++) { + if (!(dice->clock_caps & BIT(i))) + continue; + if (snd_dice_rates[i] != rate) + continue; + + *mode = modes[i]; + return 0; + } + + return -EINVAL; +} + /* * This operation has an effect to synchronize GLOBAL_STATUS/GLOBAL_SAMPLE_RATE * to GLOBAL_STATUS. Especially, just after powering on, these are different. @@ -484,6 +512,64 @@ void snd_dice_stream_update_duplex(struct snd_dice *dice) } } +int snd_dice_stream_detect_current_formats(struct snd_dice *dice) +{ + unsigned int rate; + enum snd_dice_rate_mode mode; + __be32 reg[2]; + struct reg_params tx_params, rx_params; + int i; + int err; + + /* + * Available stream format is restricted at current mode of sampling + * clock. + */ + err = snd_dice_transaction_get_rate(dice, &rate); + if (err < 0) + return err; + + err = snd_dice_stream_get_rate_mode(dice, rate, &mode); + if (err < 0) + return err; + + /* + * Just after owning the unit (GLOBAL_OWNER), the unit can return + * invalid stream formats. Selecting clock parameters have an effect + * for the unit to refine it. + */ + err = ensure_phase_lock(dice); + if (err < 0) + return err; + + err = get_register_params(dice, &tx_params, &rx_params); + if (err < 0) + return err; + + for (i = 0; i < tx_params.count; ++i) { + err = snd_dice_transaction_read_tx(dice, + tx_params.size * i + TX_NUMBER_AUDIO, + reg, sizeof(reg)); + if (err < 0) + return err; + dice->tx_pcm_chs[i][mode] = be32_to_cpu(reg[0]); + dice->tx_midi_ports[i] = max_t(unsigned int, + be32_to_cpu(reg[1]), dice->tx_midi_ports[i]); + } + for (i = 0; i < rx_params.count; ++i) { + err = snd_dice_transaction_read_rx(dice, + rx_params.size * i + RX_NUMBER_AUDIO, + reg, sizeof(reg)); + if (err < 0) + return err; + dice->rx_pcm_chs[i][mode] = be32_to_cpu(reg[0]); + dice->rx_midi_ports[i] = max_t(unsigned int, + be32_to_cpu(reg[1]), dice->rx_midi_ports[i]); + } + + return 0; +} + static void dice_lock_changed(struct snd_dice *dice) { dice->dev_lock_changed = true; diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c index 96bb01b6b751..002f3f3cbc6a 100644 --- a/sound/firewire/dice/dice.c +++ b/sound/firewire/dice/dice.c @@ -199,6 +199,10 @@ static void do_registration(struct work_struct *work) dice_card_strings(dice); + err = snd_dice_stream_detect_current_formats(dice); + if (err < 0) + goto error; + err = snd_dice_stream_init_duplex(dice); if (err < 0) goto error; diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index 8f68976930c5..0c044f28b9e7 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -201,11 +201,14 @@ void snd_dice_transaction_destroy(struct snd_dice *dice); #define SND_DICE_RATES_COUNT 7 extern const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT]; +int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate, + enum snd_dice_rate_mode *mode); int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate); void snd_dice_stream_stop_duplex(struct snd_dice *dice); int snd_dice_stream_init_duplex(struct snd_dice *dice); void snd_dice_stream_destroy_duplex(struct snd_dice *dice); void snd_dice_stream_update_duplex(struct snd_dice *dice); +int snd_dice_stream_detect_current_formats(struct snd_dice *dice); int snd_dice_stream_lock_try(struct snd_dice *dice); void snd_dice_stream_lock_release(struct snd_dice *dice); -- cgit v1.2.3 From f1f0f330b1d0ac1bcc38d7c84d439f4fde341a9c Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 19:16:43 +0900 Subject: ALSA: dice: add parameters of stream formats for models produced by TC Electronic TC Electronic shipped some models with DICE ASICs. All of them just support DICE original protocol and drivers can't retrieve all of available stream formats without changing status of sampling transmission frequency actually. This commit puts some hard-coded parameters for the models. When detecting the models, the corresponding parameters are copied as cache of stream formats. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/Makefile | 2 +- sound/firewire/dice/dice-tcelectronic.c | 100 ++++++++++++++++++++++++++++++++ sound/firewire/dice/dice.c | 76 +++++++++++++++++++++--- sound/firewire/dice/dice.h | 6 ++ 4 files changed, 174 insertions(+), 10 deletions(-) create mode 100644 sound/firewire/dice/dice-tcelectronic.c (limited to 'sound') diff --git a/sound/firewire/dice/Makefile b/sound/firewire/dice/Makefile index 55b4be9b0034..5ffaa366a88c 100644 --- a/sound/firewire/dice/Makefile +++ b/sound/firewire/dice/Makefile @@ -1,3 +1,3 @@ snd-dice-objs := dice-transaction.o dice-stream.o dice-proc.o dice-midi.o \ - dice-pcm.o dice-hwdep.o dice.o + dice-pcm.o dice-hwdep.o dice.o dice-tcelectronic.o obj-$(CONFIG_SND_DICE) += snd-dice.o diff --git a/sound/firewire/dice/dice-tcelectronic.c b/sound/firewire/dice/dice-tcelectronic.c new file mode 100644 index 000000000000..af8203b9d1a6 --- /dev/null +++ b/sound/firewire/dice/dice-tcelectronic.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dice-tc_electronic.c - a part of driver for DICE based devices + * + * Copyright (c) 2018 Takashi Sakamoto + */ + +#include "dice.h" + +struct dice_tc_spec { + unsigned int tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + unsigned int rx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + bool has_midi; +}; + +static const struct dice_tc_spec desktop_konnekt6 = { + .tx_pcm_chs = {{6, 6, 2}, {0, 0, 0} }, + .rx_pcm_chs = {{6, 6, 4}, {0, 0, 0} }, + .has_midi = false, +}; + +static const struct dice_tc_spec impact_twin = { + .tx_pcm_chs = {{14, 10, 6}, {0, 0, 0} }, + .rx_pcm_chs = {{14, 10, 6}, {0, 0, 0} }, + .has_midi = true, +}; + +static const struct dice_tc_spec konnekt_8 = { + .tx_pcm_chs = {{4, 4, 3}, {0, 0, 0} }, + .rx_pcm_chs = {{4, 4, 3}, {0, 0, 0} }, + .has_midi = true, +}; + +static const struct dice_tc_spec konnekt_24d = { + .tx_pcm_chs = {{16, 16, 6}, {0, 0, 0} }, + .rx_pcm_chs = {{16, 16, 6}, {0, 0, 0} }, + .has_midi = true, +}; + +static const struct dice_tc_spec konnekt_live = { + .tx_pcm_chs = {{16, 16, 16}, {0, 0, 0} }, + .rx_pcm_chs = {{16, 16, 16}, {0, 0, 0} }, + .has_midi = true, +}; + +static const struct dice_tc_spec studio_konnekt_48 = { + .tx_pcm_chs = {{16, 16, 16}, {16, 16, 0} }, + .rx_pcm_chs = {{16, 16, 16}, {16, 16, 0} }, + .has_midi = true, +}; + +int snd_dice_detect_tcelectronic_formats(struct snd_dice *dice) +{ + static const struct { + u32 model_id; + const struct dice_tc_spec *spec; + } *entry, entries[] = { + {0x00000020, &konnekt_24d}, + {0x00000021, &konnekt_8}, + {0x00000022, &studio_konnekt_48}, + {0x00000023, &konnekt_live}, + {0x00000024, &desktop_konnekt6}, + {0x00000027, &impact_twin}, + }; + struct fw_csr_iterator it; + int key, val, model_id; + int i; + + model_id = 0; + fw_csr_iterator_init(&it, dice->unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + if (key == CSR_MODEL) { + model_id = val; + break; + } + } + + entry = NULL; + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + entry = entries + i; + if (entry->model_id == model_id) + break; + } + if (!entry) + return -ENODEV; + + memcpy(dice->tx_pcm_chs, entry->spec->tx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + memcpy(dice->rx_pcm_chs, entry->spec->rx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + + for (i = 0; i < MAX_STREAMS; ++i) { + if (entry->spec->has_midi) { + dice->tx_midi_ports[i] = 1; + dice->rx_midi_ports[i] = 1; + } + } + + return 0; +} diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c index 002f3f3cbc6a..ea112506cc66 100644 --- a/sound/firewire/dice/dice.c +++ b/sound/firewire/dice/dice.c @@ -199,7 +199,7 @@ static void do_registration(struct work_struct *work) dice_card_strings(dice); - err = snd_dice_stream_detect_current_formats(dice); + err = dice->detect_formats(dice); if (err < 0) goto error; @@ -243,14 +243,17 @@ error: "Sound card registration failed: %d\n", err); } -static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) +static int dice_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) { struct snd_dice *dice; int err; - err = check_dice_category(unit); - if (err < 0) - return -ENODEV; + if (!entry->driver_data) { + err = check_dice_category(unit); + if (err < 0) + return -ENODEV; + } /* Allocate this independent of sound card instance. */ dice = kzalloc(sizeof(struct snd_dice), GFP_KERNEL); @@ -260,6 +263,13 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) dice->unit = fw_unit_get(unit); dev_set_drvdata(&unit->device, dice); + if (!entry->driver_data) { + dice->detect_formats = snd_dice_stream_detect_current_formats; + } else { + dice->detect_formats = + (snd_dice_detect_formats_t)entry->driver_data; + } + spin_lock_init(&dice->lock); mutex_init(&dice->mutex); init_completion(&dice->clock_accepted); @@ -317,10 +327,6 @@ static void dice_bus_reset(struct fw_unit *unit) #define DICE_INTERFACE 0x000001 static const struct ieee1394_device_id dice_id_table[] = { - { - .match_flags = IEEE1394_MATCH_VERSION, - .version = DICE_INTERFACE, - }, /* M-Audio Profire 610/2626 has a different value in version field. */ { .match_flags = IEEE1394_MATCH_VENDOR_ID | @@ -328,6 +334,58 @@ static const struct ieee1394_device_id dice_id_table[] = { .vendor_id = 0x000d6c, .specifier_id = 0x000d6c, }, + /* TC Electronic Konnekt 24D. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000020, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Konnekt 8. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000021, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Studio Konnekt 48. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000022, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Konnekt Live. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000023, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Desktop Konnekt 6. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000024, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Impact Twin. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000027, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + { + .match_flags = IEEE1394_MATCH_VERSION, + .version = DICE_INTERFACE, + }, { } }; MODULE_DEVICE_TABLE(ieee1394, dice_id_table); diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index 0c044f28b9e7..a4987dce9e0a 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -70,6 +70,9 @@ enum snd_dice_rate_mode { SND_DICE_RATE_MODE_COUNT, }; +struct snd_dice; +typedef int (*snd_dice_detect_formats_t)(struct snd_dice *dice); + struct snd_dice { struct snd_card *card; struct fw_unit *unit; @@ -91,6 +94,7 @@ struct snd_dice { unsigned int rx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; unsigned int tx_midi_ports[MAX_STREAMS]; unsigned int rx_midi_ports[MAX_STREAMS]; + snd_dice_detect_formats_t detect_formats; struct fw_address_handler notification_handler; int owner_generation; @@ -221,4 +225,6 @@ void snd_dice_create_proc(struct snd_dice *dice); int snd_dice_create_midi(struct snd_dice *dice); +int snd_dice_detect_tcelectronic_formats(struct snd_dice *dice); + #endif -- cgit v1.2.3 From 28b208f600a36f99365b7fcda2d425a2851c0c15 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 19:16:44 +0900 Subject: ALSA: dice: add parameters of stream formats for models produced by Alesis Alesis shipped some models with DICE ASICs. All of them just support DICE original protocol and drivers can't retrieve all of available stream formats without changing status of sampling transmission frequency actually. This commit puts some hard-coded parameters for the models. When detecting the models, the corresponding parameters are copied as cache of stream formats. I note that each of pair of iO14/iO26 and MultiMix 8/12/16 has the same model ID on their configuration ROM. The MultiMix 8/12/16 just support one mode for sampling transmission frequency and ALSA dice driver already handles them correctly. The iO14/iO26 support three modes and need hard-coded parameters. To distinguish these two models, this commit let the driver to retrieve current stream formats and compare it to known parameters, then decide it. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/Makefile | 3 ++- sound/firewire/dice/dice-alesis.c | 52 +++++++++++++++++++++++++++++++++++++++ sound/firewire/dice/dice.c | 11 +++++++++ sound/firewire/dice/dice.h | 1 + 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/dice/dice-alesis.c (limited to 'sound') diff --git a/sound/firewire/dice/Makefile b/sound/firewire/dice/Makefile index 5ffaa366a88c..5cfe618f45ef 100644 --- a/sound/firewire/dice/Makefile +++ b/sound/firewire/dice/Makefile @@ -1,3 +1,4 @@ snd-dice-objs := dice-transaction.o dice-stream.o dice-proc.o dice-midi.o \ - dice-pcm.o dice-hwdep.o dice.o dice-tcelectronic.o + dice-pcm.o dice-hwdep.o dice.o dice-tcelectronic.o \ + dice-alesis.o obj-$(CONFIG_SND_DICE) += snd-dice.o diff --git a/sound/firewire/dice/dice-alesis.c b/sound/firewire/dice/dice-alesis.c new file mode 100644 index 000000000000..b2efb1c71a98 --- /dev/null +++ b/sound/firewire/dice/dice-alesis.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dice-alesis.c - a part of driver for DICE based devices + * + * Copyright (c) 2018 Takashi Sakamoto + */ + +#include "dice.h" + +static const unsigned int +alesis_io14_tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT] = { + {6, 6, 4}, /* Tx0 = Analog + S/PDIF. */ + {8, 4, 0}, /* Tx1 = ADAT1. */ +}; + +static const unsigned int +alesis_io26_tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT] = { + {10, 10, 8}, /* Tx0 = Analog + S/PDIF. */ + {16, 8, 0}, /* Tx1 = ADAT1 + ADAT2. */ +}; + +int snd_dice_detect_alesis_formats(struct snd_dice *dice) +{ + __be32 reg; + u32 data; + int i; + int err; + + err = snd_dice_transaction_read_tx(dice, TX_NUMBER_AUDIO, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + if (data == 4 || data == 6) { + memcpy(dice->tx_pcm_chs, alesis_io14_tx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * + sizeof(unsigned int)); + } else { + memcpy(dice->rx_pcm_chs, alesis_io26_tx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * + sizeof(unsigned int)); + } + + for (i = 0; i < SND_DICE_RATE_MODE_COUNT; ++i) + dice->rx_pcm_chs[0][i] = 8; + + dice->tx_midi_ports[0] = 1; + dice->rx_midi_ports[0] = 1; + + return 0; +} diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c index ea112506cc66..cbd1a07e70b9 100644 --- a/sound/firewire/dice/dice.c +++ b/sound/firewire/dice/dice.c @@ -15,11 +15,14 @@ MODULE_LICENSE("GPL v2"); #define OUI_LOUD 0x000ff2 #define OUI_FOCUSRITE 0x00130e #define OUI_TCELECTRONIC 0x000166 +#define OUI_ALESIS 0x000595 #define DICE_CATEGORY_ID 0x04 #define WEISS_CATEGORY_ID 0x00 #define LOUD_CATEGORY_ID 0x10 +#define MODEL_ALESIS_IO_BOTH 0x000001 + /* * Some models support several isochronous channels, while these streams are not * always available. In this case, add the model name to this list. @@ -382,6 +385,14 @@ static const struct ieee1394_device_id dice_id_table[] = { .model_id = 0x000027, .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, }, + /* Alesis iO14/iO26. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_ALESIS, + .model_id = MODEL_ALESIS_IO_BOTH, + .driver_data = (kernel_ulong_t)snd_dice_detect_alesis_formats, + }, { .match_flags = IEEE1394_MATCH_VERSION, .version = DICE_INTERFACE, diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index a4987dce9e0a..6be1bcf00116 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -226,5 +226,6 @@ void snd_dice_create_proc(struct snd_dice *dice); int snd_dice_create_midi(struct snd_dice *dice); int snd_dice_detect_tcelectronic_formats(struct snd_dice *dice); +int snd_dice_detect_alesis_formats(struct snd_dice *dice); #endif -- cgit v1.2.3 From 58579c056c1c9510ae6695ed8e01ee05bbdcfb23 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 19:16:45 +0900 Subject: ALSA: dice: use extended protocol to detect available stream formats TC Applied Technologies (TCAT) have added extension to DICE protocol. This protocol extension is called as Extended Application Protocol, a.k.a. EAP. In this protocol extension, units get additional 9 address spaces. One of it is for current configuration. In this address space, a pair of router and stream formats are exposed per mode of three sampling transmission frequencies. This commit adds support the protocol extension for address space of the current configuration to generate cache of stream formats. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/Makefile | 2 +- sound/firewire/dice/dice-extension.c | 172 +++++++++++++++++++++++++++++++++++ sound/firewire/dice/dice-stream.c | 5 + sound/firewire/dice/dice.c | 18 +++- sound/firewire/dice/dice.h | 1 + 5 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 sound/firewire/dice/dice-extension.c (limited to 'sound') diff --git a/sound/firewire/dice/Makefile b/sound/firewire/dice/Makefile index 5cfe618f45ef..7b7997a5754c 100644 --- a/sound/firewire/dice/Makefile +++ b/sound/firewire/dice/Makefile @@ -1,4 +1,4 @@ snd-dice-objs := dice-transaction.o dice-stream.o dice-proc.o dice-midi.o \ dice-pcm.o dice-hwdep.o dice.o dice-tcelectronic.o \ - dice-alesis.o + dice-alesis.o dice-extension.o obj-$(CONFIG_SND_DICE) += snd-dice.o diff --git a/sound/firewire/dice/dice-extension.c b/sound/firewire/dice/dice-extension.c new file mode 100644 index 000000000000..a63fcbc875ad --- /dev/null +++ b/sound/firewire/dice/dice-extension.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dice-extension.c - a part of driver for DICE based devices + * + * Copyright (c) 2018 Takashi Sakamoto + */ + +#include "dice.h" + +/* For TCD2210/2220, TCAT defines extension of application protocol. */ + +#define DICE_EXT_APP_SPACE 0xffffe0200000uLL + +#define DICE_EXT_APP_CAPS_OFFSET 0x00 +#define DICE_EXT_APP_CAPS_SIZE 0x04 +#define DICE_EXT_APP_CMD_OFFSET 0x08 +#define DICE_EXT_APP_CMD_SIZE 0x0c +#define DICE_EXT_APP_MIXER_OFFSET 0x10 +#define DICE_EXT_APP_MIXER_SIZE 0x14 +#define DICE_EXT_APP_PEAK_OFFSET 0x18 +#define DICE_EXT_APP_PEAK_SIZE 0x1c +#define DICE_EXT_APP_ROUTER_OFFSET 0x20 +#define DICE_EXT_APP_ROUTER_SIZE 0x24 +#define DICE_EXT_APP_STREAM_OFFSET 0x28 +#define DICE_EXT_APP_STREAM_SIZE 0x2c +#define DICE_EXT_APP_CURRENT_OFFSET 0x30 +#define DICE_EXT_APP_CURRENT_SIZE 0x34 +#define DICE_EXT_APP_STANDALONE_OFFSET 0x38 +#define DICE_EXT_APP_STANDALONE_SIZE 0x3c +#define DICE_EXT_APP_APPLICATION_OFFSET 0x40 +#define DICE_EXT_APP_APPLICATION_SIZE 0x44 + +#define EXT_APP_STREAM_TX_NUMBER 0x0000 +#define EXT_APP_STREAM_RX_NUMBER 0x0004 +#define EXT_APP_STREAM_ENTRIES 0x0008 +#define EXT_APP_STREAM_ENTRY_SIZE 0x010c +#define EXT_APP_NUMBER_AUDIO 0x0000 +#define EXT_APP_NUMBER_MIDI 0x0004 +#define EXT_APP_NAMES 0x0008 +#define EXT_APP_NAMES_SIZE 256 +#define EXT_APP_AC3 0x0108 + +#define EXT_APP_CONFIG_LOW_ROUTER 0x0000 +#define EXT_APP_CONFIG_LOW_STREAM 0x1000 +#define EXT_APP_CONFIG_MIDDLE_ROUTER 0x2000 +#define EXT_APP_CONFIG_MIDDLE_STREAM 0x3000 +#define EXT_APP_CONFIG_HIGH_ROUTER 0x4000 +#define EXT_APP_CONFIG_HIGH_STREAM 0x5000 + +static inline int read_transaction(struct snd_dice *dice, u64 section_addr, + u32 offset, void *buf, size_t len) +{ + return snd_fw_transaction(dice->unit, + len == 4 ? TCODE_READ_QUADLET_REQUEST : + TCODE_READ_BLOCK_REQUEST, + section_addr + offset, buf, len, 0); +} + +static int read_stream_entries(struct snd_dice *dice, u64 section_addr, + u32 base_offset, unsigned int stream_count, + unsigned int mode, + unsigned int pcm_channels[MAX_STREAMS][3], + unsigned int midi_ports[MAX_STREAMS]) +{ + u32 entry_offset; + __be32 reg[2]; + int err; + int i; + + for (i = 0; i < stream_count; ++i) { + entry_offset = base_offset + i * EXT_APP_STREAM_ENTRY_SIZE; + err = read_transaction(dice, section_addr, + entry_offset + EXT_APP_NUMBER_AUDIO, + reg, sizeof(reg)); + if (err < 0) + return err; + pcm_channels[i][mode] = be32_to_cpu(reg[0]); + midi_ports[i] = max(midi_ports[i], be32_to_cpu(reg[1])); + } + + return 0; +} + +static int detect_stream_formats(struct snd_dice *dice, u64 section_addr) +{ + u32 base_offset; + __be32 reg[2]; + unsigned int stream_count; + int mode; + int err = 0; + + for (mode = 0; mode < SND_DICE_RATE_MODE_COUNT; ++mode) { + unsigned int cap; + + /* + * Some models report stream formats at highest mode, however + * they don't support the mode. Check clock capabilities. + */ + if (mode == 2) { + cap = CLOCK_CAP_RATE_176400 | CLOCK_CAP_RATE_192000; + } else if (mode == 1) { + cap = CLOCK_CAP_RATE_88200 | CLOCK_CAP_RATE_96000; + } else { + cap = CLOCK_CAP_RATE_32000 | CLOCK_CAP_RATE_44100 | + CLOCK_CAP_RATE_48000; + } + if (!(cap & dice->clock_caps)) + continue; + + base_offset = 0x2000 * mode + 0x1000; + + err = read_transaction(dice, section_addr, + base_offset + EXT_APP_STREAM_TX_NUMBER, + ®, sizeof(reg)); + if (err < 0) + break; + + base_offset += EXT_APP_STREAM_ENTRIES; + stream_count = be32_to_cpu(reg[0]); + err = read_stream_entries(dice, section_addr, base_offset, + stream_count, mode, + dice->tx_pcm_chs, + dice->tx_midi_ports); + if (err < 0) + break; + + base_offset += stream_count * EXT_APP_STREAM_ENTRY_SIZE; + stream_count = be32_to_cpu(reg[1]); + err = read_stream_entries(dice, section_addr, base_offset, + stream_count, + mode, dice->rx_pcm_chs, + dice->rx_midi_ports); + if (err < 0) + break; + } + + return err; +} + +int snd_dice_detect_extension_formats(struct snd_dice *dice) +{ + __be32 *pointers; + unsigned int i; + u64 section_addr; + int err; + + pointers = kmalloc_array(9, sizeof(__be32) * 2, GFP_KERNEL); + if (pointers == NULL) + return -ENOMEM; + + err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, + DICE_EXT_APP_SPACE, pointers, + 9 * sizeof(__be32) * 2, 0); + if (err < 0) + goto end; + + /* Check two of them for offset have the same value or not. */ + for (i = 0; i < 9; ++i) { + int j; + + for (j = i + 1; j < 9; ++j) { + if (pointers[i * 2] == pointers[j * 2]) + goto end; + } + } + + section_addr = DICE_EXT_APP_SPACE + be32_to_cpu(pointers[12]) * 4; + err = detect_stream_formats(dice, section_addr); +end: + kfree(pointers); + return err; +} diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c index 6c859d2b9084..d3fb460bb86c 100644 --- a/sound/firewire/dice/dice-stream.c +++ b/sound/firewire/dice/dice-stream.c @@ -521,6 +521,11 @@ int snd_dice_stream_detect_current_formats(struct snd_dice *dice) int i; int err; + /* If extended protocol is available, detect detail spec. */ + err = snd_dice_detect_extension_formats(dice); + if (err >= 0) + return err; + /* * Available stream format is restricted at current mode of sampling * clock. diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c index cbd1a07e70b9..6d55a62ec89e 100644 --- a/sound/firewire/dice/dice.c +++ b/sound/firewire/dice/dice.c @@ -16,6 +16,7 @@ MODULE_LICENSE("GPL v2"); #define OUI_FOCUSRITE 0x00130e #define OUI_TCELECTRONIC 0x000166 #define OUI_ALESIS 0x000595 +#define OUI_MAUDIO 0x000d6c #define DICE_CATEGORY_ID 0x04 #define WEISS_CATEGORY_ID 0x00 @@ -330,12 +331,21 @@ static void dice_bus_reset(struct fw_unit *unit) #define DICE_INTERFACE 0x000001 static const struct ieee1394_device_id dice_id_table[] = { - /* M-Audio Profire 610/2626 has a different value in version field. */ + /* M-Audio Profire 2626 has a different value in version field. */ { .match_flags = IEEE1394_MATCH_VENDOR_ID | - IEEE1394_MATCH_SPECIFIER_ID, - .vendor_id = 0x000d6c, - .specifier_id = 0x000d6c, + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_MAUDIO, + .model_id = 0x000010, + .driver_data = (kernel_ulong_t)snd_dice_detect_extension_formats, + }, + /* M-Audio Profire 610 has a different value in version field. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_MAUDIO, + .model_id = 0x000011, + .driver_data = (kernel_ulong_t)snd_dice_detect_extension_formats, }, /* TC Electronic Konnekt 24D. */ { diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index 6be1bcf00116..4465a5925641 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -227,5 +227,6 @@ int snd_dice_create_midi(struct snd_dice *dice); int snd_dice_detect_tcelectronic_formats(struct snd_dice *dice); int snd_dice_detect_alesis_formats(struct snd_dice *dice); +int snd_dice_detect_extension_formats(struct snd_dice *dice); #endif -- cgit v1.2.3 From ec592fd32e102ec91d7c61c901093ce17878ccb7 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 19:16:46 +0900 Subject: ALSA: dice: use cache of stream format to check running stream At present, to check running stream, available stream formats are used at current sampling transmission frequency (stf). But when changing stf, it's convenient to use cache of stream formats. This commit applies this idea. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-stream.c | 50 ++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 19 deletions(-) (limited to 'sound') diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c index d3fb460bb86c..b792e7bb6b14 100644 --- a/sound/firewire/dice/dice-stream.c +++ b/sound/firewire/dice/dice-stream.c @@ -294,16 +294,14 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) unsigned int curr_rate; unsigned int i; struct reg_params tx_params, rx_params; - bool need_to_start; + bool need_to_start = false; + enum snd_dice_rate_mode mode; int err; if (dice->substreams_counter == 0) return -EIO; - err = get_register_params(dice, &tx_params, &rx_params); - if (err < 0) - return err; - + /* Check sampling transmission frequency. */ err = snd_dice_transaction_get_rate(dice, &curr_rate); if (err < 0) { dev_err(&dice->unit->device, @@ -315,22 +313,36 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) if (rate != curr_rate) return -EINVAL; - /* Judge to need to restart streams. */ - for (i = 0; i < MAX_STREAMS; i++) { - if (i < tx_params.count) { - if (amdtp_streaming_error(&dice->tx_stream[i]) || - !amdtp_stream_running(&dice->tx_stream[i])) - break; - } - if (i < rx_params.count) { - if (amdtp_streaming_error(&dice->rx_stream[i]) || - !amdtp_stream_running(&dice->rx_stream[i])) - break; - } + /* Check error of packet streaming. */ + for (i = 0; i < MAX_STREAMS; ++i) { + if (amdtp_streaming_error(&dice->tx_stream[i])) + break; + if (amdtp_streaming_error(&dice->rx_stream[i])) + break; + } + if (i < MAX_STREAMS) + need_to_start = true; + + /* Check required streams are running or not. */ + err = snd_dice_stream_get_rate_mode(dice, rate, &mode); + if (err < 0) + return err; + for (i = 0; i < MAX_STREAMS; ++i) { + if (dice->tx_pcm_chs[i][mode] > 0 && + !amdtp_stream_running(&dice->tx_stream[i])) + break; + if (dice->rx_pcm_chs[i][mode] > 0 && + !amdtp_stream_running(&dice->rx_stream[i])) + break; } - need_to_start = (i < MAX_STREAMS); + if (i < MAX_STREAMS) + need_to_start = true; if (need_to_start) { + err = get_register_params(dice, &tx_params, &rx_params); + if (err < 0) + return err; + /* Stop transmission. */ snd_dice_transaction_clear_enable(dice); stop_streams(dice, AMDTP_IN_STREAM, &tx_params); @@ -341,7 +353,7 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) if (err < 0) { dev_err(&dice->unit->device, "fail to ensure phase lock\n"); - return err; + goto error; } /* Start both streams. */ -- cgit v1.2.3 From 20b94544792d2eb4e03b17c50300b8b7ae441826 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 19:16:47 +0900 Subject: ALSA: dice: add a helper function to restart all of available streams This commit is a small refactoring for better readability. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-stream.c | 119 ++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 57 deletions(-) (limited to 'sound') diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c index b792e7bb6b14..e93ceff8ce39 100644 --- a/sound/firewire/dice/dice-stream.c +++ b/sound/firewire/dice/dice-stream.c @@ -284,6 +284,63 @@ static int start_streams(struct snd_dice *dice, enum amdtp_stream_direction dir, return err; } +static int start_duplex_streams(struct snd_dice *dice, unsigned int rate) +{ + struct reg_params tx_params, rx_params; + int i; + int err; + + err = get_register_params(dice, &tx_params, &rx_params); + if (err < 0) + return err; + + /* Stop transmission. */ + stop_streams(dice, AMDTP_IN_STREAM, &tx_params); + stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); + snd_dice_transaction_clear_enable(dice); + release_resources(dice); + + err = ensure_phase_lock(dice); + if (err < 0) { + dev_err(&dice->unit->device, "fail to ensure phase lock\n"); + return err; + } + + /* Start both streams. */ + err = start_streams(dice, AMDTP_IN_STREAM, rate, &tx_params); + if (err < 0) + goto error; + err = start_streams(dice, AMDTP_OUT_STREAM, rate, &rx_params); + if (err < 0) + goto error; + + err = snd_dice_transaction_set_enable(dice); + if (err < 0) { + dev_err(&dice->unit->device, "fail to enable interface\n"); + goto error; + } + + for (i = 0; i < MAX_STREAMS; i++) { + if ((i < tx_params.count && + !amdtp_stream_wait_callback(&dice->tx_stream[i], + CALLBACK_TIMEOUT)) || + (i < rx_params.count && + !amdtp_stream_wait_callback(&dice->rx_stream[i], + CALLBACK_TIMEOUT))) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + stop_streams(dice, AMDTP_IN_STREAM, &tx_params); + stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); + snd_dice_transaction_clear_enable(dice); + release_resources(dice); + return err; +} + /* * MEMO: After this function, there're two states of streams: * - None streams are running. @@ -293,8 +350,6 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) { unsigned int curr_rate; unsigned int i; - struct reg_params tx_params, rx_params; - bool need_to_start = false; enum snd_dice_rate_mode mode; int err; @@ -321,7 +376,7 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) break; } if (i < MAX_STREAMS) - need_to_start = true; + goto restart; /* Check required streams are running or not. */ err = snd_dice_stream_get_rate_mode(dice, rate, &mode); @@ -336,61 +391,11 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) break; } if (i < MAX_STREAMS) - need_to_start = true; - - if (need_to_start) { - err = get_register_params(dice, &tx_params, &rx_params); - if (err < 0) - return err; - - /* Stop transmission. */ - snd_dice_transaction_clear_enable(dice); - stop_streams(dice, AMDTP_IN_STREAM, &tx_params); - stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); - release_resources(dice); + goto restart; - err = ensure_phase_lock(dice); - if (err < 0) { - dev_err(&dice->unit->device, - "fail to ensure phase lock\n"); - goto error; - } - - /* Start both streams. */ - err = start_streams(dice, AMDTP_IN_STREAM, rate, &tx_params); - if (err < 0) - goto error; - err = start_streams(dice, AMDTP_OUT_STREAM, rate, &rx_params); - if (err < 0) - goto error; - - err = snd_dice_transaction_set_enable(dice); - if (err < 0) { - dev_err(&dice->unit->device, - "fail to enable interface\n"); - goto error; - } - - for (i = 0; i < MAX_STREAMS; i++) { - if ((i < tx_params.count && - !amdtp_stream_wait_callback(&dice->tx_stream[i], - CALLBACK_TIMEOUT)) || - (i < rx_params.count && - !amdtp_stream_wait_callback(&dice->rx_stream[i], - CALLBACK_TIMEOUT))) { - err = -ETIMEDOUT; - goto error; - } - } - } - - return err; -error: - snd_dice_transaction_clear_enable(dice); - stop_streams(dice, AMDTP_IN_STREAM, &tx_params); - stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); - release_resources(dice); - return err; + return 0; +restart: + return start_duplex_streams(dice, rate); } /* -- cgit v1.2.3 From afa617f219761473881ace59f31bd6713fed1833 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 19:16:48 +0900 Subject: ALSA: dice: enable to change current sampling transmission frequency This is a preparation for userspace applications to change current sampling transmission frequency via ALSA PCM interface. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-stream.c | 47 +++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c index e93ceff8ce39..422a1ab1ddf5 100644 --- a/sound/firewire/dice/dice-stream.c +++ b/sound/firewire/dice/dice-stream.c @@ -62,9 +62,11 @@ int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate, * This operation has an effect to synchronize GLOBAL_STATUS/GLOBAL_SAMPLE_RATE * to GLOBAL_STATUS. Especially, just after powering on, these are different. */ -static int ensure_phase_lock(struct snd_dice *dice) +static int ensure_phase_lock(struct snd_dice *dice, unsigned int rate) { __be32 reg, nominal; + u32 data; + int i; int err; err = snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT, @@ -72,9 +74,21 @@ static int ensure_phase_lock(struct snd_dice *dice) if (err < 0) return err; + data = be32_to_cpu(reg); + + data &= ~CLOCK_RATE_MASK; + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { + if (snd_dice_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_dice_rates)) + return -EINVAL; + data |= i << CLOCK_RATE_SHIFT; + if (completion_done(&dice->clock_accepted)) reinit_completion(&dice->clock_accepted); + reg = cpu_to_be32(data); err = snd_dice_transaction_write_global(dice, GLOBAL_CLOCK_SELECT, ®, sizeof(reg)); if (err < 0) @@ -220,6 +234,7 @@ static int start_streams(struct snd_dice *dice, enum amdtp_stream_direction dir, unsigned int rate, struct reg_params *params) { __be32 reg[2]; + enum snd_dice_rate_mode mode; unsigned int i, pcm_chs, midi_ports; struct amdtp_stream *streams; struct fw_iso_resources *resources; @@ -234,12 +249,23 @@ static int start_streams(struct snd_dice *dice, enum amdtp_stream_direction dir, resources = dice->rx_resources; } + err = snd_dice_stream_get_rate_mode(dice, rate, &mode); + if (err < 0) + return err; + for (i = 0; i < params->count; i++) { + unsigned int pcm_cache; + unsigned int midi_cache; + if (dir == AMDTP_IN_STREAM) { + pcm_cache = dice->tx_pcm_chs[i][mode]; + midi_cache = dice->tx_midi_ports[i]; err = snd_dice_transaction_read_tx(dice, params->size * i + TX_NUMBER_AUDIO, reg, sizeof(reg)); } else { + pcm_cache = dice->rx_pcm_chs[i][mode]; + midi_cache = dice->rx_midi_ports[i]; err = snd_dice_transaction_read_rx(dice, params->size * i + RX_NUMBER_AUDIO, reg, sizeof(reg)); @@ -249,6 +275,14 @@ static int start_streams(struct snd_dice *dice, enum amdtp_stream_direction dir, pcm_chs = be32_to_cpu(reg[0]); midi_ports = be32_to_cpu(reg[1]); + /* These are important for developer of this driver. */ + if (pcm_chs != pcm_cache || midi_ports != midi_cache) { + dev_info(&dice->unit->device, + "cache mismatch: pcm: %u:%u, midi: %u:%u\n", + pcm_chs, pcm_cache, midi_ports, midi_cache); + return -EPROTO; + } + err = keep_resources(dice, dir, i, rate, pcm_chs, midi_ports); if (err < 0) return err; @@ -300,12 +334,17 @@ static int start_duplex_streams(struct snd_dice *dice, unsigned int rate) snd_dice_transaction_clear_enable(dice); release_resources(dice); - err = ensure_phase_lock(dice); + err = ensure_phase_lock(dice, rate); if (err < 0) { dev_err(&dice->unit->device, "fail to ensure phase lock\n"); return err; } + /* Likely to have changed stream formats. */ + err = get_register_params(dice, &tx_params, &rx_params); + if (err < 0) + return err; + /* Start both streams. */ err = start_streams(dice, AMDTP_IN_STREAM, rate, &tx_params); if (err < 0) @@ -366,7 +405,7 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) if (rate == 0) rate = curr_rate; if (rate != curr_rate) - return -EINVAL; + goto restart; /* Check error of packet streaming. */ for (i = 0; i < MAX_STREAMS; ++i) { @@ -560,7 +599,7 @@ int snd_dice_stream_detect_current_formats(struct snd_dice *dice) * invalid stream formats. Selecting clock parameters have an effect * for the unit to refine it. */ - err = ensure_phase_lock(dice); + err = ensure_phase_lock(dice, rate); if (err < 0) return err; -- cgit v1.2.3 From b8f78234aa6a180db9b29da5b48c18d26c06ecc2 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 19:16:49 +0900 Subject: ALSA: dice: use stream formats to add MIDI substreams In former commits, proxy structure gets members for cache of stream formats. The cache can be used to count the number of MIDI substreams to add. This commit uses the cache for this purpose. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-midi.c | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) (limited to 'sound') diff --git a/sound/firewire/dice/dice-midi.c b/sound/firewire/dice/dice-midi.c index 8ff6da3c51f7..84eca8a51a02 100644 --- a/sound/firewire/dice/dice-midi.c +++ b/sound/firewire/dice/dice-midi.c @@ -101,27 +101,18 @@ int snd_dice_create_midi(struct snd_dice *dice) .close = midi_close, .trigger = midi_playback_trigger, }; - __be32 reg; struct snd_rawmidi *rmidi; struct snd_rawmidi_str *str; unsigned int midi_in_ports, midi_out_ports; + int i; int err; - /* - * Use the number of MIDI conformant data channel at current sampling - * transfer frequency. - */ - err = snd_dice_transaction_read_tx(dice, TX_NUMBER_MIDI, - ®, sizeof(reg)); - if (err < 0) - return err; - midi_in_ports = be32_to_cpu(reg); - - err = snd_dice_transaction_read_rx(dice, RX_NUMBER_MIDI, - ®, sizeof(reg)); - if (err < 0) - return err; - midi_out_ports = be32_to_cpu(reg); + midi_in_ports = 0; + midi_out_ports = 0; + for (i = 0; i < MAX_STREAMS; ++i) { + midi_in_ports = max(midi_in_ports, dice->tx_midi_ports[i]); + midi_out_ports = max(midi_out_ports, dice->rx_midi_ports[i]); + } if (midi_in_ports + midi_out_ports == 0) return 0; -- cgit v1.2.3 From bd2b441c5744c93b0667fdad9df3271040abda51 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 19:16:50 +0900 Subject: ALSA: dice: use cache for PCM constraints and rules In former commits, proxy structure gets members for cache of stream formats. The cache allows to apply correct constraints and rules to runtime of PCM substream. They allows userspace applications to change current sampling transmission frequency. This commit uses the cacher for the PCM constraints and rules. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-pcm.c | 227 ++++++++++++++++++++++++++++++----------- 1 file changed, 170 insertions(+), 57 deletions(-) (limited to 'sound') diff --git a/sound/firewire/dice/dice-pcm.c b/sound/firewire/dice/dice-pcm.c index 7cb9e9713ac3..08a173170d52 100644 --- a/sound/firewire/dice/dice-pcm.c +++ b/sound/firewire/dice/dice-pcm.c @@ -9,43 +9,115 @@ #include "dice.h" +static int dice_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_substream *substream = rule->private; + struct snd_dice *dice = substream->private_data; + unsigned int index = substream->pcm->device; + + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval rates = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int *pcm_channels; + enum snd_dice_rate_mode mode; + unsigned int i, rate; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + pcm_channels = dice->tx_pcm_chs[index]; + else + pcm_channels = dice->rx_pcm_chs[index]; + + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { + rate = snd_dice_rates[i]; + if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0) + continue; + + if (!snd_interval_test(c, pcm_channels[mode])) + continue; + + rates.min = min(rates.min, rate); + rates.max = max(rates.max, rate); + } + + return snd_interval_refine(r, &rates); +} + +static int dice_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_substream *substream = rule->private; + struct snd_dice *dice = substream->private_data; + unsigned int index = substream->pcm->device; + + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval channels = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int *pcm_channels; + enum snd_dice_rate_mode mode; + unsigned int i, rate; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + pcm_channels = dice->tx_pcm_chs[index]; + else + pcm_channels = dice->rx_pcm_chs[index]; + + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { + rate = snd_dice_rates[i]; + if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0) + continue; + + if (!snd_interval_test(r, rate)) + continue; + + channels.min = min(channels.min, pcm_channels[mode]); + channels.max = max(channels.max, pcm_channels[mode]); + } + + return snd_interval_refine(c, &channels); +} + static int limit_channels_and_rates(struct snd_dice *dice, struct snd_pcm_runtime *runtime, enum amdtp_stream_direction dir, - unsigned int index, unsigned int size) + unsigned int index) { struct snd_pcm_hardware *hw = &runtime->hw; - struct amdtp_stream *stream; - unsigned int rate; - __be32 reg; - int err; - - /* - * Retrieve current Multi Bit Linear Audio data channel and limit to - * it. - */ - if (dir == AMDTP_IN_STREAM) { - stream = &dice->tx_stream[index]; - err = snd_dice_transaction_read_tx(dice, - size * index + TX_NUMBER_AUDIO, - ®, sizeof(reg)); - } else { - stream = &dice->rx_stream[index]; - err = snd_dice_transaction_read_rx(dice, - size * index + RX_NUMBER_AUDIO, - ®, sizeof(reg)); + unsigned int *pcm_channels; + unsigned int i; + + if (dir == AMDTP_IN_STREAM) + pcm_channels = dice->tx_pcm_chs[index]; + else + pcm_channels = dice->rx_pcm_chs[index]; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { + enum snd_dice_rate_mode mode; + unsigned int rate, channels; + + rate = snd_dice_rates[i]; + if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0) + continue; + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + + channels = pcm_channels[mode]; + if (channels == 0) + continue; + hw->channels_min = min(hw->channels_min, channels); + hw->channels_max = max(hw->channels_max, channels); } - if (err < 0) - return err; - - hw->channels_min = hw->channels_max = be32_to_cpu(reg); - - /* Retrieve current sampling transfer frequency and limit to it. */ - err = snd_dice_transaction_get_rate(dice, &rate); - if (err < 0) - return err; - hw->rates = snd_pcm_rate_to_rate_bit(rate); snd_pcm_limit_hw_rates(runtime); return 0; @@ -56,36 +128,34 @@ static int init_hw_info(struct snd_dice *dice, { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int index = substream->pcm->device; enum amdtp_stream_direction dir; struct amdtp_stream *stream; - __be32 reg[2]; - unsigned int count, size; int err; if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { hw->formats = AM824_IN_PCM_FORMAT_BITS; dir = AMDTP_IN_STREAM; - stream = &dice->tx_stream[substream->pcm->device]; - err = snd_dice_transaction_read_tx(dice, TX_NUMBER, reg, - sizeof(reg)); + stream = &dice->tx_stream[index]; } else { hw->formats = AM824_OUT_PCM_FORMAT_BITS; dir = AMDTP_OUT_STREAM; - stream = &dice->rx_stream[substream->pcm->device]; - err = snd_dice_transaction_read_rx(dice, RX_NUMBER, reg, - sizeof(reg)); + stream = &dice->rx_stream[index]; } + err = limit_channels_and_rates(dice, substream->runtime, dir, + index); if (err < 0) return err; - count = min_t(unsigned int, be32_to_cpu(reg[0]), MAX_STREAMS); - if (substream->pcm->device >= count) - return -ENXIO; - - size = be32_to_cpu(reg[1]) * 4; - err = limit_channels_and_rates(dice, substream->runtime, dir, - substream->pcm->device, size); + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + dice_rate_constraint, substream, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + dice_channels_constraint, substream, + SNDRV_PCM_HW_PARAM_RATE, -1); if (err < 0) return err; @@ -95,6 +165,8 @@ static int init_hw_info(struct snd_dice *dice, static int pcm_open(struct snd_pcm_substream *substream) { struct snd_dice *dice = substream->private_data; + unsigned int source; + bool internal; int err; err = snd_dice_stream_lock_try(dice); @@ -105,6 +177,43 @@ static int pcm_open(struct snd_pcm_substream *substream) if (err < 0) goto err_locked; + err = snd_dice_transaction_get_clock_source(dice, &source); + if (err < 0) + goto err_locked; + switch (source) { + case CLOCK_SOURCE_AES1: + case CLOCK_SOURCE_AES2: + case CLOCK_SOURCE_AES3: + case CLOCK_SOURCE_AES4: + case CLOCK_SOURCE_AES_ANY: + case CLOCK_SOURCE_ADAT: + case CLOCK_SOURCE_TDIF: + case CLOCK_SOURCE_WC: + internal = false; + break; + default: + internal = true; + break; + } + + /* + * When source of clock is not internal or any PCM streams are running, + * available sampling rate is limited at current sampling rate. + */ + if (!internal || + amdtp_stream_pcm_running(&dice->tx_stream[0]) || + amdtp_stream_pcm_running(&dice->tx_stream[1]) || + amdtp_stream_pcm_running(&dice->rx_stream[0]) || + amdtp_stream_pcm_running(&dice->rx_stream[1])) { + unsigned int rate; + + err = snd_dice_transaction_get_rate(dice, &rate); + if (err < 0) + goto err_locked; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + snd_pcm_set_sync(substream); end: return err; @@ -318,7 +427,6 @@ int snd_dice_create_pcm(struct snd_dice *dice) .page = snd_pcm_lib_get_vmalloc_page, .mmap = snd_pcm_lib_mmap_vmalloc, }; - __be32 reg; struct snd_pcm *pcm; unsigned int i, max_capture, max_playback, capture, playback; int err; @@ -327,18 +435,23 @@ int snd_dice_create_pcm(struct snd_dice *dice) if (dice->force_two_pcms) { max_capture = max_playback = 2; } else { + int j; max_capture = max_playback = 0; - err = snd_dice_transaction_read_tx(dice, TX_NUMBER, ®, - sizeof(reg)); - if (err < 0) - return err; - max_capture = min_t(unsigned int, be32_to_cpu(reg), MAX_STREAMS); - - err = snd_dice_transaction_read_rx(dice, RX_NUMBER, ®, - sizeof(reg)); - if (err < 0) - return err; - max_playback = min_t(unsigned int, be32_to_cpu(reg), MAX_STREAMS); + for (i = 0; i < MAX_STREAMS; ++i) { + for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) { + if (dice->tx_pcm_chs[i][j] > 0) { + ++max_capture; + break; + } + } + + for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) { + if (dice->rx_pcm_chs[i][j] > 0) { + ++max_playback; + break; + } + } + } } for (i = 0; i < MAX_STREAMS; i++) { -- cgit v1.2.3 From 9c367c01d3d5060a2bcb2ca76a447bdb42c83c91 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 2 May 2018 19:16:51 +0900 Subject: ALSA: dice: remove local frag of force_two_pcms At present, to add PCM substreams for each of available tx/rx streams, this driver uses a condition based on model-name. This is not enough to support unknown models. In former commits, this driver gains cache of stream formats. For models which support protocol extension, all of available steam formats are cached. For known models, hard-coded stream formats are used to generate the cache. For unknown models, stream formats at current mode of sampling transmission frequency is cached. Anyway, at least, the cached formats are used to expose constrains of PCM substreams for userspace applications. Thus, The cached data can be also used to add PCM substreams themselves, instead of the name-based conditions. This commit obsoletes local frag of force_two_pcms. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-pcm.c | 38 ++++++++------------------------------ sound/firewire/dice/dice.c | 38 -------------------------------------- sound/firewire/dice/dice.h | 2 -- 3 files changed, 8 insertions(+), 70 deletions(-) (limited to 'sound') diff --git a/sound/firewire/dice/dice-pcm.c b/sound/firewire/dice/dice-pcm.c index 08a173170d52..80351b29fe0d 100644 --- a/sound/firewire/dice/dice-pcm.c +++ b/sound/firewire/dice/dice-pcm.c @@ -428,40 +428,18 @@ int snd_dice_create_pcm(struct snd_dice *dice) .mmap = snd_pcm_lib_mmap_vmalloc, }; struct snd_pcm *pcm; - unsigned int i, max_capture, max_playback, capture, playback; + unsigned int capture, playback; + int i, j; int err; - /* Check whether PCM substreams are required. */ - if (dice->force_two_pcms) { - max_capture = max_playback = 2; - } else { - int j; - max_capture = max_playback = 0; - for (i = 0; i < MAX_STREAMS; ++i) { - for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) { - if (dice->tx_pcm_chs[i][j] > 0) { - ++max_capture; - break; - } - } - - for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) { - if (dice->rx_pcm_chs[i][j] > 0) { - ++max_playback; - break; - } - } - } - } - for (i = 0; i < MAX_STREAMS; i++) { capture = playback = 0; - if (i < max_capture) - capture = 1; - if (i < max_playback) - playback = 1; - if (capture == 0 && playback == 0) - break; + for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) { + if (dice->tx_pcm_chs[i][j] > 0) + capture = 1; + if (dice->rx_pcm_chs[i][j] > 0) + playback = 1; + } err = snd_pcm_new(dice->card, "DICE", i, playback, capture, &pcm); diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c index 6d55a62ec89e..40f7a32e4893 100644 --- a/sound/firewire/dice/dice.c +++ b/sound/firewire/dice/dice.c @@ -24,36 +24,6 @@ MODULE_LICENSE("GPL v2"); #define MODEL_ALESIS_IO_BOTH 0x000001 -/* - * Some models support several isochronous channels, while these streams are not - * always available. In this case, add the model name to this list. - */ -static bool force_two_pcm_support(struct fw_unit *unit) -{ - static const char *const models[] = { - /* TC Electronic models. */ - "StudioKonnekt48", - /* Focusrite models. */ - "SAFFIRE_PRO_40", - "LIQUID_SAFFIRE_56", - "SAFFIRE_PRO_40_1", - }; - char model[32]; - unsigned int i; - int err; - - err = fw_csr_string(unit->directory, CSR_MODEL, model, sizeof(model)); - if (err < 0) - return false; - - for (i = 0; i < ARRAY_SIZE(models); i++) { - if (strcmp(models[i], model) == 0) - break; - } - - return i < ARRAY_SIZE(models); -} - static int check_dice_category(struct fw_unit *unit) { struct fw_device *device = fw_parent_device(unit); @@ -79,11 +49,6 @@ static int check_dice_category(struct fw_unit *unit) } } - if (vendor == OUI_FOCUSRITE || vendor == OUI_TCELECTRONIC) { - if (force_two_pcm_support(unit)) - return 0; - } - if (vendor == OUI_WEISS) category = WEISS_CATEGORY_ID; else if (vendor == OUI_LOUD) @@ -190,9 +155,6 @@ static void do_registration(struct work_struct *work) if (err < 0) return; - if (force_two_pcm_support(dice->unit)) - dice->force_two_pcms = true; - err = snd_dice_transaction_init(dice); if (err < 0) goto error; diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index 4465a5925641..505b79fea6d9 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -113,8 +113,6 @@ struct snd_dice { bool global_enabled; struct completion clock_accepted; unsigned int substreams_counter; - - bool force_two_pcms; }; enum snd_dice_addr_type { -- cgit v1.2.3 From 964af639ad699404cbe36d0c1fc85970700e6107 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 27 Apr 2018 14:23:37 +0200 Subject: ALSA: usb-audio: Initialize Dell Dock playback volumes In the early commit adcdd0d5a1cb ("ALSA: usb-audio: Skip volume controls triggers hangup on Dell USB Dock"), we add the mixer quirks for Dell dock to skip two mixer FU's for playback. This supposed that the device has always the proper initial volume, but it doesn't seem always correct. This patch adds the explicit initialization of the volumes to the fixed 0dB at the device probe time. Also, such a fixup is needed after the resume, so a new function is hooked to the resume callback as well. Bugzilla: http://bugzilla.suse.com/show_bug.cgi?id=1089467 Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 2 ++ sound/usb/mixer_quirks.c | 34 ++++++++++++++++++++++++++++++++++ sound/usb/mixer_quirks.h | 4 ++++ 3 files changed, 40 insertions(+) (limited to 'sound') diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 344d7b069d59..76fabc4b72b5 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -2948,6 +2948,8 @@ int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume) } } + snd_usb_mixer_resume_quirk(mixer); + return snd_usb_mixer_activate(mixer); } #endif diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 56537a156580..4377374affd3 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -1799,6 +1799,26 @@ static int snd_soundblaster_e1_switch_create(struct usb_mixer_interface *mixer) NULL); } +static void dell_dock_init_vol(struct snd_usb_audio *chip, int ch, int id) +{ + u16 buf = 0; + + snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC_SET_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + ch, snd_usb_ctrl_intf(chip) | (id << 8), + &buf, 2); +} + +static int dell_dock_mixer_init(struct usb_mixer_interface *mixer) +{ + /* fix to 0dB playback volumes */ + dell_dock_init_vol(mixer->chip, 1, 16); + dell_dock_init_vol(mixer->chip, 2, 16); + dell_dock_init_vol(mixer->chip, 1, 19); + dell_dock_init_vol(mixer->chip, 2, 19); + return 0; +} + int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) { int err = 0; @@ -1884,11 +1904,25 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */ err = snd_soundblaster_e1_switch_create(mixer); break; + case USB_ID(0x0bda, 0x4014): /* Dell WD15 dock */ + err = dell_dock_mixer_init(mixer); + break; } return err; } +#ifdef CONFIG_PM +void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer) +{ + switch (mixer->chip->usb_id) { + case USB_ID(0x0bda, 0x4014): /* Dell WD15 dock */ + dell_dock_mixer_init(mixer); + break; + } +} +#endif + void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer, int unitid) { diff --git a/sound/usb/mixer_quirks.h b/sound/usb/mixer_quirks.h index b5abd328a361..52be26db558f 100644 --- a/sound/usb/mixer_quirks.h +++ b/sound/usb/mixer_quirks.h @@ -14,5 +14,9 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, int unitid, struct snd_kcontrol *kctl); +#ifdef CONFIG_PM +void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer); +#endif + #endif /* SND_USB_MIXER_QUIRKS_H */ -- cgit v1.2.3 From b099b9693d23d035c77c218508e083484ff63024 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 2 May 2018 09:36:28 +0200 Subject: ALSA: usb-audio: Avoid superfluous usb_set_interface() calls This is a preliminary change for the upcoming quirk implementation. Currently USB-audio driver tries to call usb_set_interface() whenever the format change with interface/altset modification happens. In this patch, the check is replaced with the comparison of cur_altsetting and the targeted altsetting pointer, so that the driver may skip the unnecessary function calls. Signed-off-by: Takashi Iwai --- sound/usb/pcm.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index ad39b3cca247..ae7d8a0a0a0a 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -499,7 +499,7 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) iface = usb_ifnum_to_if(dev, fmt->iface); if (WARN_ON(!iface)) return -EINVAL; - alts = &iface->altsetting[fmt->altset_idx]; + alts = usb_altnum_to_altsetting(iface, fmt->altsetting); altsd = get_iface_desc(alts); if (WARN_ON(altsd->bAlternateSetting != fmt->altsetting)) return -EINVAL; @@ -521,9 +521,7 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) } /* set interface */ - if (subs->interface != fmt->iface || - subs->altset_idx != fmt->altset_idx) { - + if (iface->cur_altsetting != alts) { err = snd_usb_select_mode_quirk(subs, fmt); if (err < 0) return -EIO; @@ -537,12 +535,11 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) } dev_dbg(&dev->dev, "setting usb interface %d:%d\n", fmt->iface, fmt->altsetting); - subs->interface = fmt->iface; - subs->altset_idx = fmt->altset_idx; - snd_usb_set_interface_quirk(dev); } + subs->interface = fmt->iface; + subs->altset_idx = fmt->altset_idx; subs->data_endpoint = snd_usb_add_endpoint(subs->stream->chip, alts, fmt->endpoint, subs->direction, SND_USB_ENDPOINT_TYPE_DATA); -- cgit v1.2.3 From 8a463225b11047455b374729d18c8a371fe6e591 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 2 May 2018 10:04:27 +0200 Subject: ALSA: usb-audio: Add keep_iface flag Introduce a new flag to struct snd_usb_audio for allowing the device to skip usb_set_interface() calls at changing or closing the stream. As of this patch, the flag is nowhere set, so it's just a place holder. The dynamic switching will be added in the following patch. A background information for this change: Dell WD15 dock with Realtek chip gives a very long pause at each time the driver changes the altset, which eventually happens at every PCM stream open/close and parameter change. As the long pause happens in each usb_set_interface() call, there is nothing we can do as long as it's called. The workaround is to reduce calling it as much as possible, and this flag indicates that behavior. Signed-off-by: Takashi Iwai --- sound/usb/pcm.c | 17 ++++++++++------- sound/usb/usbaudio.h | 3 +++ 2 files changed, 13 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index ae7d8a0a0a0a..dc2dfec9effd 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -509,12 +509,14 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) /* close the old interface */ if (subs->interface >= 0 && subs->interface != fmt->iface) { - err = usb_set_interface(subs->dev, subs->interface, 0); - if (err < 0) { - dev_err(&dev->dev, - "%d:%d: return to setting 0 failed (%d)\n", - fmt->iface, fmt->altsetting, err); - return -EIO; + if (!subs->stream->chip->keep_iface) { + err = usb_set_interface(subs->dev, subs->interface, 0); + if (err < 0) { + dev_err(&dev->dev, + "%d:%d: return to setting 0 failed (%d)\n", + fmt->iface, fmt->altsetting, err); + return -EIO; + } } subs->interface = -1; subs->altset_idx = 0; @@ -1253,7 +1255,8 @@ static int snd_usb_pcm_close(struct snd_pcm_substream *substream, int direction) stop_endpoints(subs, true); - if (subs->interface >= 0 && + if (!as->chip->keep_iface && + subs->interface >= 0 && !snd_usb_lock_shutdown(subs->stream->chip)) { usb_set_interface(subs->dev, subs->interface, 0); subs->interface = -1; diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 4d5c89a7ba2b..32f4a5425536 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -59,6 +59,9 @@ struct snd_usb_audio { int setup; /* from the 'device_setup' module param */ bool autoclock; /* from the 'autoclock' module param */ + bool keep_iface; /* keep interface/altset after closing + * or parameter change + */ struct usb_host_interface *ctrl_intf; /* the audio control interface */ }; -- cgit v1.2.3 From 4120fbedbb6c999686d03a9c74acc4f650ece8a2 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 2 May 2018 11:52:55 +0200 Subject: ALSA: usb-audio: Add "Keep Interface" control This patch adds "Keep Interface" control for each USB-audio device. The control element is with SND_CTL_IFACE_CARD, so that it won't appear on any sane mixer applications. For a device that is confirmed to work well with "keep-interface" mode, user can flip the control via amixer, e.g. % amixer -c1 cset iface=CARD,name='Keep Interface' on and save/restore the state via alsactl. The reason to provide this via control API is that the behavior must be pretty depending on the device (and the firmware in it), so it's not ideal to apply via module option. For a device that certainly works, we may set it statically via a quirk table entry. But a device like Dell WD15 dock behaves so differently depending on the firmware, and we can't set it statically. So leave this as a dynamic switch each user can adjust freely. Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'sound') diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 76fabc4b72b5..bb203b3684fc 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -2801,6 +2801,48 @@ static int snd_usb_mixer_status_create(struct usb_mixer_interface *mixer) return 0; } +static int keep_iface_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = mixer->chip->keep_iface; + return 0; +} + +static int keep_iface_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + bool keep_iface = !!ucontrol->value.integer.value[0]; + + if (mixer->chip->keep_iface == keep_iface) + return 0; + mixer->chip->keep_iface = keep_iface; + return 1; +} + +static const struct snd_kcontrol_new keep_iface_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Keep Interface", + .info = snd_ctl_boolean_mono_info, + .get = keep_iface_ctl_get, + .put = keep_iface_ctl_put, +}; + +static int create_keep_iface_ctl(struct usb_mixer_interface *mixer) +{ + struct snd_kcontrol *kctl = snd_ctl_new1(&keep_iface_ctl, mixer); + + /* need only one control per card */ + if (snd_ctl_find_id(mixer->chip->card, &kctl->id)) { + snd_ctl_free_one(kctl); + return 0; + } + + return snd_ctl_add(mixer->chip->card, kctl); +} + int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, int ignore_error) { @@ -2842,6 +2884,9 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, if ((err = snd_usb_mixer_controls(mixer)) < 0 || (err = snd_usb_mixer_status_create(mixer)) < 0) goto _error; + err = create_keep_iface_ctl(mixer); + if (err < 0) + goto _error; snd_usb_mixer_apply_create_quirk(mixer); -- cgit v1.2.3 From 07eca5fc3ebad1d33bc12a2f09670c0edd8e6eb6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 2 May 2018 14:45:37 +0200 Subject: ALSA: usb-audio: Allow to override the longname string Historically USB-audio driver sets the card's longname field with the details of the device and the bus information. It's good per se, but not preferable when it's referred as the identifier for UCM profile. This patch adds a quirk profile_name field to override the card's longname string to a pre-defined one, so that one can create a unique and consistent ID string for the specific USB device via a quirk table to be used as a UCM profile name. The patch does a slight code refactoring to split out the functions to set shortname and longname fields as well. Signed-off-by: Takashi Iwai --- sound/usb/card.c | 146 ++++++++++++++++++++++++++++++--------------------- sound/usb/usbaudio.h | 1 + 2 files changed, 88 insertions(+), 59 deletions(-) (limited to 'sound') diff --git a/sound/usb/card.c b/sound/usb/card.c index 4a1c6bb3dfa0..36c289bae169 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -349,6 +349,90 @@ static int snd_usb_audio_dev_free(struct snd_device *device) return snd_usb_audio_free(chip); } +static void usb_audio_make_shortname(struct usb_device *dev, + struct snd_usb_audio *chip, + const struct snd_usb_audio_quirk *quirk) +{ + struct snd_card *card = chip->card; + + if (quirk && quirk->product_name && *quirk->product_name) { + strlcpy(card->shortname, quirk->product_name, + sizeof(card->shortname)); + return; + } + + /* retrieve the device string as shortname */ + if (!dev->descriptor.iProduct || + usb_string(dev, dev->descriptor.iProduct, + card->shortname, sizeof(card->shortname)) <= 0) { + /* no name available from anywhere, so use ID */ + sprintf(card->shortname, "USB Device %#04x:%#04x", + USB_ID_VENDOR(chip->usb_id), + USB_ID_PRODUCT(chip->usb_id)); + } + + strim(card->shortname); +} + +static void usb_audio_make_longname(struct usb_device *dev, + struct snd_usb_audio *chip, + const struct snd_usb_audio_quirk *quirk) +{ + struct snd_card *card = chip->card; + int len; + + /* shortcut - if any pre-defined string is given, use it */ + if (quirk && quirk->profile_name && *quirk->profile_name) { + strlcpy(card->longname, quirk->profile_name, + sizeof(card->longname)); + return; + } + + if (quirk && quirk->vendor_name && *quirk->vendor_name) { + len = strlcpy(card->longname, quirk->vendor_name, sizeof(card->longname)); + } else { + /* retrieve the vendor and device strings as longname */ + if (dev->descriptor.iManufacturer) + len = usb_string(dev, dev->descriptor.iManufacturer, + card->longname, sizeof(card->longname)); + else + len = 0; + /* we don't really care if there isn't any vendor string */ + } + if (len > 0) { + strim(card->longname); + if (*card->longname) + strlcat(card->longname, " ", sizeof(card->longname)); + } + + strlcat(card->longname, card->shortname, sizeof(card->longname)); + + len = strlcat(card->longname, " at ", sizeof(card->longname)); + + if (len < sizeof(card->longname)) + usb_make_path(dev, card->longname + len, sizeof(card->longname) - len); + + switch (snd_usb_get_speed(dev)) { + case USB_SPEED_LOW: + strlcat(card->longname, ", low speed", sizeof(card->longname)); + break; + case USB_SPEED_FULL: + strlcat(card->longname, ", full speed", sizeof(card->longname)); + break; + case USB_SPEED_HIGH: + strlcat(card->longname, ", high speed", sizeof(card->longname)); + break; + case USB_SPEED_SUPER: + strlcat(card->longname, ", super speed", sizeof(card->longname)); + break; + case USB_SPEED_SUPER_PLUS: + strlcat(card->longname, ", super speed plus", sizeof(card->longname)); + break; + default: + break; + } +} + /* * create a chip instance and set its names. */ @@ -360,7 +444,7 @@ static int snd_usb_audio_create(struct usb_interface *intf, { struct snd_card *card; struct snd_usb_audio *chip; - int err, len; + int err; char component[14]; static struct snd_device_ops ops = { .dev_free = snd_usb_audio_dev_free, @@ -422,64 +506,8 @@ static int snd_usb_audio_create(struct usb_interface *intf, USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id)); snd_component_add(card, component); - /* retrieve the device string as shortname */ - if (quirk && quirk->product_name && *quirk->product_name) { - strlcpy(card->shortname, quirk->product_name, sizeof(card->shortname)); - } else { - if (!dev->descriptor.iProduct || - usb_string(dev, dev->descriptor.iProduct, - card->shortname, sizeof(card->shortname)) <= 0) { - /* no name available from anywhere, so use ID */ - sprintf(card->shortname, "USB Device %#04x:%#04x", - USB_ID_VENDOR(chip->usb_id), - USB_ID_PRODUCT(chip->usb_id)); - } - } - strim(card->shortname); - - /* retrieve the vendor and device strings as longname */ - if (quirk && quirk->vendor_name && *quirk->vendor_name) { - len = strlcpy(card->longname, quirk->vendor_name, sizeof(card->longname)); - } else { - if (dev->descriptor.iManufacturer) - len = usb_string(dev, dev->descriptor.iManufacturer, - card->longname, sizeof(card->longname)); - else - len = 0; - /* we don't really care if there isn't any vendor string */ - } - if (len > 0) { - strim(card->longname); - if (*card->longname) - strlcat(card->longname, " ", sizeof(card->longname)); - } - - strlcat(card->longname, card->shortname, sizeof(card->longname)); - - len = strlcat(card->longname, " at ", sizeof(card->longname)); - - if (len < sizeof(card->longname)) - usb_make_path(dev, card->longname + len, sizeof(card->longname) - len); - - switch (snd_usb_get_speed(dev)) { - case USB_SPEED_LOW: - strlcat(card->longname, ", low speed", sizeof(card->longname)); - break; - case USB_SPEED_FULL: - strlcat(card->longname, ", full speed", sizeof(card->longname)); - break; - case USB_SPEED_HIGH: - strlcat(card->longname, ", high speed", sizeof(card->longname)); - break; - case USB_SPEED_SUPER: - strlcat(card->longname, ", super speed", sizeof(card->longname)); - break; - case USB_SPEED_SUPER_PLUS: - strlcat(card->longname, ", super speed plus", sizeof(card->longname)); - break; - default: - break; - } + usb_audio_make_shortname(dev, chip, quirk); + usb_audio_make_longname(dev, chip, quirk); snd_usb_audio_create_proc(chip); diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 32f4a5425536..1cb6b3e9483c 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -112,6 +112,7 @@ enum quirk_type { struct snd_usb_audio_quirk { const char *vendor_name; const char *product_name; + const char *profile_name; /* override the card->longname */ int16_t ifnum; uint16_t type; const void *data; -- cgit v1.2.3 From 6455abb43374346f10b4842a9bc9b7f4d10fa038 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 2 May 2018 14:28:38 +0200 Subject: ALSA: usb-audio: Give proper vendor/product name for Dell WD15 Dock Dell WD15 Dock with 0bda:4014 doesn't give any useful strings for the vendor and the product names. Name them more specifically via quirk, as well as the UCM profile name. Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'sound') diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 754e632a27bd..0e37e358ca97 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -3371,5 +3371,15 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), } } }, +/* Dell WD15 Dock */ +{ + USB_DEVICE(0x0bda, 0x4014), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Dell", + .product_name = "WD15 Dock", + .profile_name = "Dell-WD15-Dock", + .ifnum = QUIRK_NO_INTERFACE + } +}, #undef USB_DEVICE_VENDOR_SPEC -- cgit v1.2.3 From 207459a2804a64d0f0f05c8aba04e0b0844661f2 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 3 May 2018 10:51:41 +0200 Subject: ALSA: sparc: Use GFP_KERNEL for non-atomic allocation dbri driver allocates a resource with GFP_ATOMIC unnecessarily in its probe function. Replace it with the standard GFP_KERNEL for avoiding the bogus allocation failures. Signed-off-by: Takashi Iwai --- sound/sparc/dbri.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c index abc7bd5055eb..f0e713527e91 100644 --- a/sound/sparc/dbri.c +++ b/sound/sparc/dbri.c @@ -2542,7 +2542,7 @@ static int snd_dbri_create(struct snd_card *card, dbri->irq = irq; dbri->dma = dma_zalloc_coherent(&op->dev, sizeof(struct dbri_dma), - &dbri->dma_dvma, GFP_ATOMIC); + &dbri->dma_dvma, GFP_KERNEL); if (!dbri->dma) return -ENOMEM; -- cgit v1.2.3 From 8c558076c740e8009a96c6fdc3d4245dde62be77 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 3 May 2018 12:33:32 +0200 Subject: ALSA: usb-audio: Clean up mixer element list traverse Introduce a new macro for iterating over mixer element list for avoiding the open codes in many places. Also the open-coded container_of() and the forced cast to struct usb_mixer_elem_info are replaced with another simple macro, too. No functional changes but just readability improvement. Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 20 +++++++++----------- sound/usb/mixer.h | 6 ++++++ sound/usb/mixer_quirks.c | 2 +- sound/usb/mixer_scarlett.c | 6 ++---- 4 files changed, 18 insertions(+), 16 deletions(-) (limited to 'sound') diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index bb203b3684fc..265258b0e74c 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -2598,9 +2598,9 @@ void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid) { struct usb_mixer_elem_list *list; - for (list = mixer->id_elems[unitid]; list; list = list->next_id_elem) { + for_each_mixer_elem(list, mixer, unitid) { struct usb_mixer_elem_info *info = - (struct usb_mixer_elem_info *)list; + mixer_elem_list_to_info(list); /* invalidate cache, so the value is read from the device */ info->cached = 0; snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, @@ -2611,7 +2611,7 @@ void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid) static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer, struct usb_mixer_elem_list *list) { - struct usb_mixer_elem_info *cval = (struct usb_mixer_elem_info *)list; + struct usb_mixer_elem_info *cval = mixer_elem_list_to_info(list); static char *val_types[] = {"BOOLEAN", "INV_BOOLEAN", "S8", "U8", "S16", "U16"}; snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%x, " @@ -2637,8 +2637,7 @@ static void snd_usb_mixer_proc_read(struct snd_info_entry *entry, mixer->ignore_ctl_error); snd_iprintf(buffer, "Card: %s\n", chip->card->longname); for (unitid = 0; unitid < MAX_ID_ELEMS; unitid++) { - for (list = mixer->id_elems[unitid]; list; - list = list->next_id_elem) { + for_each_mixer_elem(list, mixer, unitid) { snd_iprintf(buffer, " Unit: %i\n", list->id); if (list->kctl) snd_iprintf(buffer, @@ -2668,19 +2667,19 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, return; } - for (list = mixer->id_elems[unitid]; list; list = list->next_id_elem) + for_each_mixer_elem(list, mixer, unitid) count++; if (count == 0) return; - for (list = mixer->id_elems[unitid]; list; list = list->next_id_elem) { + for_each_mixer_elem(list, mixer, unitid) { struct usb_mixer_elem_info *info; if (!list->kctl) continue; - info = (struct usb_mixer_elem_info *)list; + info = mixer_elem_list_to_info(list); if (count > 1 && info->control != control) continue; @@ -2946,7 +2945,7 @@ int snd_usb_mixer_suspend(struct usb_mixer_interface *mixer) static int restore_mixer_value(struct usb_mixer_elem_list *list) { - struct usb_mixer_elem_info *cval = (struct usb_mixer_elem_info *)list; + struct usb_mixer_elem_info *cval = mixer_elem_list_to_info(list); int c, err, idx; if (cval->cmask) { @@ -2982,8 +2981,7 @@ int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume) if (reset_resume) { /* restore cached mixer values */ for (id = 0; id < MAX_ID_ELEMS; id++) { - for (list = mixer->id_elems[id]; list; - list = list->next_id_elem) { + for_each_mixer_elem(list, mixer, id) { if (list->resume) { err = list->resume(list); if (err < 0) diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index ba27f7ade670..e02653465e29 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -53,6 +53,12 @@ struct usb_mixer_elem_list { usb_mixer_elem_resume_func_t resume; }; +/* iterate over mixer element list of the given unit id */ +#define for_each_mixer_elem(list, mixer, id) \ + for ((list) = (mixer)->id_elems[id]; (list); (list) = (list)->next_id_elem) +#define mixer_elem_list_to_info(list) \ + container_of(list, struct usb_mixer_elem_info, head) + struct usb_mixer_elem_info { struct usb_mixer_elem_list head; unsigned int control; /* CS or ICN (high byte) */ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 4377374affd3..1b94387e18b6 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -1172,7 +1172,7 @@ void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, int unitid = 12; /* SamleRate ExtensionUnit ID */ list_for_each_entry(mixer, &chip->mixer_list, list) { - cval = (struct usb_mixer_elem_info *)mixer->id_elems[unitid]; + cval = mixer_elem_list_to_info(mixer->id_elems[unitid]); if (cval) { snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, cval->control << 8, diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c index c33e2378089d..4aeb9488a0c9 100644 --- a/sound/usb/mixer_scarlett.c +++ b/sound/usb/mixer_scarlett.c @@ -287,8 +287,7 @@ static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl, static int scarlett_ctl_resume(struct usb_mixer_elem_list *list) { - struct usb_mixer_elem_info *elem = - container_of(list, struct usb_mixer_elem_info, head); + struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); int i; for (i = 0; i < elem->channels; i++) @@ -447,8 +446,7 @@ static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl, static int scarlett_ctl_enum_resume(struct usb_mixer_elem_list *list) { - struct usb_mixer_elem_info *elem = - container_of(list, struct usb_mixer_elem_info, head); + struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); if (elem->cached) snd_usb_set_cur_mix_value(elem, 0, 0, *elem->cache_val); -- cgit v1.2.3 From 4d47fa8447691198c710daa62fab82fcc0f3f868 Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Fri, 4 May 2018 04:23:58 +0300 Subject: ALSA: usb: stream: move audioformat alloc/init into separate function Offload snd_usb_parse_audio_interface() function which became quite long after adding UAC3 spec support. Move audioformat allocation and initialization into separate function, this will make easier future refactoring. Attributes left in the original func because it'll be used for UAC3 BADD profiles suport in the future There is no functional change. Signed-off-by: Ruslan Bilovol Signed-off-by: Takashi Iwai --- sound/usb/stream.c | 52 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 16 deletions(-) (limited to 'sound') diff --git a/sound/usb/stream.c b/sound/usb/stream.c index 956be9f7c72a..8ec0a5206ebe 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -626,6 +626,37 @@ snd_usb_find_output_terminal_descriptor(struct usb_host_interface *ctrl_iface, return NULL; } +static struct audioformat * +audio_format_alloc_init(struct snd_usb_audio *chip, + struct usb_host_interface *alts, + int protocol, int iface_no, int altset_idx, + int altno, int num_channels, int clock) +{ + struct audioformat *fp; + + fp = kzalloc(sizeof(*fp), GFP_KERNEL); + if (!fp) + return NULL; + + fp->iface = iface_no; + fp->altsetting = altno; + fp->altset_idx = altset_idx; + fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress; + fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; + fp->datainterval = snd_usb_parse_datainterval(chip, alts); + fp->protocol = protocol; + fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + fp->channels = num_channels; + if (snd_usb_get_speed(chip->dev) == USB_SPEED_HIGH) + fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1) + * (fp->maxpacksize & 0x7ff); + fp->clock = clock; + INIT_LIST_HEAD(&fp->list); + + return fp; +} + + int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) { struct usb_device *dev; @@ -928,25 +959,14 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) continue; } - fp = kzalloc(sizeof(*fp), GFP_KERNEL); + fp = audio_format_alloc_init(chip, alts, protocol, iface_no, i, + altno, num_channels, clock); if (!fp) return -ENOMEM; - fp->iface = iface_no; - fp->altsetting = altno; - fp->altset_idx = i; - fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress; - fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; - fp->datainterval = snd_usb_parse_datainterval(chip, alts); - fp->protocol = protocol; - fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); - fp->channels = num_channels; - if (snd_usb_get_speed(dev) == USB_SPEED_HIGH) - fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1) - * (fp->maxpacksize & 0x7ff); - fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol, iface_no); - fp->clock = clock; - INIT_LIST_HEAD(&fp->list); + fp->attributes = parse_uac_endpoint_attributes(chip, alts, + protocol, + iface_no); /* some quirks for attributes here */ snd_usb_audioformat_attributes_quirk(chip, fp, stream); -- cgit v1.2.3 From 68faa863644c770cd60290421353b5d6aedfcc9c Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Fri, 4 May 2018 04:23:59 +0300 Subject: ALSA: usb: stream: refactor uac1/2 audio interface parsing Offload snd_usb_parse_audio_interface() function which became quite long after adding UAC3 spec support. Move class-specific parts of uac1/2 parsing to separate function which now produce audioformat structure that is ready to be fed to snd_usb_add_audio_stream(). This also broke Blue Microphones workaround (which relies on audioformat decoded from previous altsetting) into two parts: prepare quirk flag analyzing previous altsetting then use it with current altsetting. Signed-off-by: Ruslan Bilovol Signed-off-by: Takashi Iwai --- sound/usb/stream.c | 333 +++++++++++++++++++++++++++++------------------------ 1 file changed, 185 insertions(+), 148 deletions(-) (limited to 'sound') diff --git a/sound/usb/stream.c b/sound/usb/stream.c index 8ec0a5206ebe..336922696612 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -656,6 +656,156 @@ audio_format_alloc_init(struct snd_usb_audio *chip, return fp; } +static struct audioformat * +snd_usb_get_audioformat_uac12(struct snd_usb_audio *chip, + struct usb_host_interface *alts, + int protocol, int iface_no, int altset_idx, + int altno, int stream, int bm_quirk) +{ + struct usb_device *dev = chip->dev; + struct uac_format_type_i_continuous_descriptor *fmt; + unsigned int num_channels = 0, chconfig = 0; + struct audioformat *fp; + int clock = 0; + u64 format; + + /* get audio formats */ + if (protocol == UAC_VERSION_1) { + struct uac1_as_header_descriptor *as = + snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_AS_GENERAL); + struct uac_input_terminal_descriptor *iterm; + + if (!as) { + dev_err(&dev->dev, + "%u:%d : UAC_AS_GENERAL descriptor not found\n", + iface_no, altno); + return NULL; + } + + if (as->bLength < sizeof(*as)) { + dev_err(&dev->dev, + "%u:%d : invalid UAC_AS_GENERAL desc\n", + iface_no, altno); + return NULL; + } + + format = le16_to_cpu(as->wFormatTag); /* remember the format value */ + + iterm = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink); + if (iterm) { + num_channels = iterm->bNrChannels; + chconfig = le16_to_cpu(iterm->wChannelConfig); + } + } else { /* UAC_VERSION_2 */ + struct uac2_input_terminal_descriptor *input_term; + struct uac2_output_terminal_descriptor *output_term; + struct uac2_as_header_descriptor *as = + snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_AS_GENERAL); + + if (!as) { + dev_err(&dev->dev, + "%u:%d : UAC_AS_GENERAL descriptor not found\n", + iface_no, altno); + return NULL; + } + + if (as->bLength < sizeof(*as)) { + dev_err(&dev->dev, + "%u:%d : invalid UAC_AS_GENERAL desc\n", + iface_no, altno); + return NULL; + } + + num_channels = as->bNrChannels; + format = le32_to_cpu(as->bmFormats); + chconfig = le32_to_cpu(as->bmChannelConfig); + + /* + * lookup the terminal associated to this interface + * to extract the clock + */ + input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink); + if (input_term) { + clock = input_term->bCSourceID; + if (!chconfig && (num_channels == input_term->bNrChannels)) + chconfig = le32_to_cpu(input_term->bmChannelConfig); + goto found_clock; + } + + output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink); + if (output_term) { + clock = output_term->bCSourceID; + goto found_clock; + } + + dev_err(&dev->dev, + "%u:%d : bogus bTerminalLink %d\n", + iface_no, altno, as->bTerminalLink); + return NULL; + } + +found_clock: + /* get format type */ + fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_FORMAT_TYPE); + if (!fmt) { + dev_err(&dev->dev, + "%u:%d : no UAC_FORMAT_TYPE desc\n", + iface_no, altno); + return NULL; + } + if (((protocol == UAC_VERSION_1) && (fmt->bLength < 8)) + || ((protocol == UAC_VERSION_2) && + (fmt->bLength < 6))) { + dev_err(&dev->dev, + "%u:%d : invalid UAC_FORMAT_TYPE desc\n", + iface_no, altno); + return NULL; + } + + /* + * Blue Microphones workaround: The last altsetting is + * identical with the previous one, except for a larger + * packet size, but is actually a mislabeled two-channel + * setting; ignore it. + * + * Part 2: analyze quirk flag and format + */ + if (bm_quirk && fmt->bNrChannels == 1 && fmt->bSubframeSize == 2) + return NULL; + + fp = audio_format_alloc_init(chip, alts, protocol, iface_no, + altset_idx, altno, num_channels, clock); + if (!fp) + return ERR_PTR(-ENOMEM); + + fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol, + iface_no); + + /* some quirks for attributes here */ + snd_usb_audioformat_attributes_quirk(chip, fp, stream); + + /* ok, let's parse further... */ + if (snd_usb_parse_audio_format(chip, fp, format, + fmt, stream) < 0) { + kfree(fp->rate_table); + kfree(fp); + return NULL; + } + + /* Create chmap */ + if (fp->channels != num_channels) + chconfig = 0; + + fp->chmap = convert_chmap(fp->channels, chconfig, protocol); + + return fp; +} int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) { @@ -663,14 +813,13 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) struct usb_interface *iface; struct usb_host_interface *alts; struct usb_interface_descriptor *altsd; + struct uac3_as_header_descriptor *as = NULL; int i, altno, err, stream; u64 format = 0; unsigned int num_channels = 0; struct audioformat *fp = NULL; int num, protocol, clock = 0; - struct uac_format_type_i_continuous_descriptor *fmt = NULL; struct snd_pcm_chmap_elem *chmap_v3 = NULL; - unsigned int chconfig; dev = chip->dev; @@ -719,98 +868,41 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) protocol <= 2) protocol = UAC_VERSION_1; - chconfig = 0; - /* get audio formats */ switch (protocol) { default: dev_dbg(&dev->dev, "%u:%d: unknown interface protocol %#02x, assuming v1\n", iface_no, altno, protocol); protocol = UAC_VERSION_1; /* fall through */ - - case UAC_VERSION_1: { - struct uac1_as_header_descriptor *as = - snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); - struct uac_input_terminal_descriptor *iterm; - - if (!as) { - dev_err(&dev->dev, - "%u:%d : UAC_AS_GENERAL descriptor not found\n", - iface_no, altno); - continue; - } - - if (as->bLength < sizeof(*as)) { - dev_err(&dev->dev, - "%u:%d : invalid UAC_AS_GENERAL desc\n", - iface_no, altno); - continue; - } - - format = le16_to_cpu(as->wFormatTag); /* remember the format value */ - - iterm = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, - as->bTerminalLink); - if (iterm) { - num_channels = iterm->bNrChannels; - chconfig = le16_to_cpu(iterm->wChannelConfig); - } - - break; - } - + case UAC_VERSION_1: + /* fall through */ case UAC_VERSION_2: { - struct uac2_input_terminal_descriptor *input_term; - struct uac2_output_terminal_descriptor *output_term; - struct uac2_as_header_descriptor *as = - snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); - - if (!as) { - dev_err(&dev->dev, - "%u:%d : UAC_AS_GENERAL descriptor not found\n", - iface_no, altno); - continue; - } - - if (as->bLength < sizeof(*as)) { - dev_err(&dev->dev, - "%u:%d : invalid UAC_AS_GENERAL desc\n", - iface_no, altno); - continue; - } + int bm_quirk = 0; - num_channels = as->bNrChannels; - format = le32_to_cpu(as->bmFormats); - chconfig = le32_to_cpu(as->bmChannelConfig); - - /* lookup the terminal associated to this interface - * to extract the clock */ - input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, - as->bTerminalLink); - if (input_term) { - clock = input_term->bCSourceID; - if (!chconfig && (num_channels == input_term->bNrChannels)) - chconfig = le32_to_cpu(input_term->bmChannelConfig); - break; - } - - output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, - as->bTerminalLink); - if (output_term) { - clock = output_term->bCSourceID; - break; - } + /* + * Blue Microphones workaround: The last altsetting is + * identical with the previous one, except for a larger + * packet size, but is actually a mislabeled two-channel + * setting; ignore it. + * + * Part 1: prepare quirk flag + */ + if (altno == 2 && num == 3 && + fp && fp->altsetting == 1 && fp->channels == 1 && + fp->formats == SNDRV_PCM_FMTBIT_S16_LE && + protocol == UAC_VERSION_1 && + le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == + fp->maxpacksize * 2) + bm_quirk = 1; - dev_err(&dev->dev, - "%u:%d : bogus bTerminalLink %d\n", - iface_no, altno, as->bTerminalLink); - continue; + fp = snd_usb_get_audioformat_uac12(chip, alts, protocol, + iface_no, i, altno, + stream, bm_quirk); + break; } - case UAC_VERSION_3: { struct uac3_input_terminal_descriptor *input_term; struct uac3_output_terminal_descriptor *output_term; - struct uac3_as_header_descriptor *as; struct uac3_cluster_header_descriptor *cluster; struct uac3_hc_descriptor_header hc_header; u16 cluster_id, wLength; @@ -923,40 +1015,12 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) } if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) { - /* get format type */ - fmt = snd_usb_find_csint_desc(alts->extra, - alts->extralen, - NULL, UAC_FORMAT_TYPE); - if (!fmt) { - dev_err(&dev->dev, - "%u:%d : no UAC_FORMAT_TYPE desc\n", - iface_no, altno); - continue; - } - if (((protocol == UAC_VERSION_1) && (fmt->bLength < 8)) - || ((protocol == UAC_VERSION_2) && - (fmt->bLength < 6))) { - dev_err(&dev->dev, - "%u:%d : invalid UAC_FORMAT_TYPE desc\n", - iface_no, altno); + if (!fp) continue; - } + else if (IS_ERR(fp)) + return PTR_ERR(fp); - /* - * Blue Microphones workaround: The last altsetting is - * identical with the previous one, except for a larger - * packet size, but is actually a mislabeled two-channel - * setting; ignore it. - */ - if (fmt->bNrChannels == 1 && - fmt->bSubframeSize == 2 && - altno == 2 && num == 3 && - fp && fp->altsetting == 1 && fp->channels == 1 && - fp->formats == SNDRV_PCM_FMTBIT_S16_LE && - protocol == UAC_VERSION_1 && - le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == - fp->maxpacksize * 2) - continue; + goto skip_uac3; } fp = audio_format_alloc_init(chip, alts, protocol, iface_no, i, @@ -967,45 +1031,18 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol, iface_no); - - /* some quirks for attributes here */ - snd_usb_audioformat_attributes_quirk(chip, fp, stream); + fp->chmap = chmap_v3; /* ok, let's parse further... */ - if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) { - if (snd_usb_parse_audio_format(chip, fp, format, - fmt, stream) < 0) { - kfree(fp->rate_table); - kfree(fp); - fp = NULL; - continue; - } - } else { - struct uac3_as_header_descriptor *as; - - as = snd_usb_find_csint_desc(alts->extra, - alts->extralen, - NULL, UAC_AS_GENERAL); - - if (snd_usb_parse_audio_format_v3(chip, fp, as, - stream) < 0) { - kfree(fp->rate_table); - kfree(fp); - fp = NULL; - continue; - } + if (snd_usb_parse_audio_format_v3(chip, fp, as, + stream) < 0) { + kfree(fp->rate_table); + kfree(fp); + fp = NULL; + continue; } - /* Create chmap */ - if (fp->channels != num_channels) - chconfig = 0; - - if (protocol == UAC_VERSION_3) - fp->chmap = chmap_v3; - else - fp->chmap = convert_chmap(fp->channels, chconfig, - protocol); - +skip_uac3: dev_dbg(&dev->dev, "%u:%d: add audio endpoint %#x\n", iface_no, altno, fp->endpoint); err = snd_usb_add_audio_stream(chip, stream, fp); if (err < 0) { -- cgit v1.2.3 From eda553f43217ac873a771fd2bd538dfe5faae5e6 Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Fri, 4 May 2018 04:24:00 +0300 Subject: ALSA: usb: stream: refactor uac3 audio interface parsing Offload snd_usb_parse_audio_interface() function which became quite long after adding UAC3 spec support. Move class-specific parts of uac3 parsing to separate function which now produce audioformat structure that is ready to be fed to snd_usb_add_audio_stream(). Signed-off-by: Ruslan Bilovol Signed-off-by: Takashi Iwai --- sound/usb/stream.c | 289 +++++++++++++++++++++++++++-------------------------- 1 file changed, 146 insertions(+), 143 deletions(-) (limited to 'sound') diff --git a/sound/usb/stream.c b/sound/usb/stream.c index 336922696612..764be07474a8 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -807,19 +807,154 @@ found_clock: return fp; } +static struct audioformat * +snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip, + struct usb_host_interface *alts, + int iface_no, int altset_idx, + int altno, int stream) +{ + struct usb_device *dev = chip->dev; + struct uac3_input_terminal_descriptor *input_term; + struct uac3_output_terminal_descriptor *output_term; + struct uac3_cluster_header_descriptor *cluster; + struct uac3_as_header_descriptor *as; + struct uac3_hc_descriptor_header hc_header; + struct snd_pcm_chmap_elem *chmap; + unsigned int num_channels; + struct audioformat *fp; + u16 cluster_id, wLength; + int clock = 0; + int err; + + as = snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_AS_GENERAL); + if (!as) { + dev_err(&dev->dev, + "%u:%d : UAC_AS_GENERAL descriptor not found\n", + iface_no, altno); + return NULL; + } + + if (as->bLength < sizeof(*as)) { + dev_err(&dev->dev, + "%u:%d : invalid UAC_AS_GENERAL desc\n", + iface_no, altno); + return NULL; + } + + cluster_id = le16_to_cpu(as->wClusterDescrID); + if (!cluster_id) { + dev_err(&dev->dev, + "%u:%d : no cluster descriptor\n", + iface_no, altno); + return NULL; + } + + /* + * Get number of channels and channel map through + * High Capability Cluster Descriptor + * + * First step: get High Capability header and + * read size of Cluster Descriptor + */ + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + cluster_id, + snd_usb_ctrl_intf(chip), + &hc_header, sizeof(hc_header)); + if (err < 0) + return ERR_PTR(err); + else if (err != sizeof(hc_header)) { + dev_err(&dev->dev, + "%u:%d : can't get High Capability descriptor\n", + iface_no, altno); + return ERR_PTR(-EIO); + } + + /* + * Second step: allocate needed amount of memory + * and request Cluster Descriptor + */ + wLength = le16_to_cpu(hc_header.wLength); + cluster = kzalloc(wLength, GFP_KERNEL); + if (!cluster) + return ERR_PTR(-ENOMEM); + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + cluster_id, + snd_usb_ctrl_intf(chip), + cluster, wLength); + if (err < 0) { + kfree(cluster); + return ERR_PTR(err); + } else if (err != wLength) { + dev_err(&dev->dev, + "%u:%d : can't get Cluster Descriptor\n", + iface_no, altno); + kfree(cluster); + return ERR_PTR(-EIO); + } + + num_channels = cluster->bNrChannels; + chmap = convert_chmap_v3(cluster); + kfree(cluster); + + /* + * lookup the terminal associated to this interface + * to extract the clock + */ + input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink); + if (input_term) { + clock = input_term->bCSourceID; + goto found_clock; + } + + output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink); + if (output_term) { + clock = output_term->bCSourceID; + goto found_clock; + } + + dev_err(&dev->dev, "%u:%d : bogus bTerminalLink %d\n", + iface_no, altno, as->bTerminalLink); + return NULL; + +found_clock: + fp = audio_format_alloc_init(chip, alts, UAC_VERSION_3, iface_no, + altset_idx, altno, num_channels, clock); + if (!fp) + return ERR_PTR(-ENOMEM); + + fp->attributes = parse_uac_endpoint_attributes(chip, alts, + UAC_VERSION_3, + iface_no); + fp->chmap = chmap; + + /* ok, let's parse further... */ + if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) { + kfree(fp->rate_table); + kfree(fp); + return NULL; + } + + return fp; +} + int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) { struct usb_device *dev; struct usb_interface *iface; struct usb_host_interface *alts; struct usb_interface_descriptor *altsd; - struct uac3_as_header_descriptor *as = NULL; int i, altno, err, stream; - u64 format = 0; - unsigned int num_channels = 0; struct audioformat *fp = NULL; - int num, protocol, clock = 0; - struct snd_pcm_chmap_elem *chmap_v3 = NULL; + int num, protocol; dev = chip->dev; @@ -900,149 +1035,17 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) stream, bm_quirk); break; } - case UAC_VERSION_3: { - struct uac3_input_terminal_descriptor *input_term; - struct uac3_output_terminal_descriptor *output_term; - struct uac3_cluster_header_descriptor *cluster; - struct uac3_hc_descriptor_header hc_header; - u16 cluster_id, wLength; - - as = snd_usb_find_csint_desc(alts->extra, - alts->extralen, - NULL, UAC_AS_GENERAL); - - if (!as) { - dev_err(&dev->dev, - "%u:%d : UAC_AS_GENERAL descriptor not found\n", - iface_no, altno); - continue; - } - - if (as->bLength < sizeof(*as)) { - dev_err(&dev->dev, - "%u:%d : invalid UAC_AS_GENERAL desc\n", - iface_no, altno); - continue; - } - - cluster_id = le16_to_cpu(as->wClusterDescrID); - if (!cluster_id) { - dev_err(&dev->dev, - "%u:%d : no cluster descriptor\n", - iface_no, altno); - continue; - } - - /* - * Get number of channels and channel map through - * High Capability Cluster Descriptor - * - * First step: get High Capability header and - * read size of Cluster Descriptor - */ - err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), - UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, - cluster_id, - snd_usb_ctrl_intf(chip), - &hc_header, sizeof(hc_header)); - if (err < 0) - return err; - else if (err != sizeof(hc_header)) { - dev_err(&dev->dev, - "%u:%d : can't get High Capability descriptor\n", - iface_no, altno); - return -EIO; - } - - /* - * Second step: allocate needed amount of memory - * and request Cluster Descriptor - */ - wLength = le16_to_cpu(hc_header.wLength); - cluster = kzalloc(wLength, GFP_KERNEL); - if (!cluster) - return -ENOMEM; - err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), - UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, - cluster_id, - snd_usb_ctrl_intf(chip), - cluster, wLength); - if (err < 0) { - kfree(cluster); - return err; - } else if (err != wLength) { - dev_err(&dev->dev, - "%u:%d : can't get Cluster Descriptor\n", - iface_no, altno); - kfree(cluster); - return -EIO; - } - - num_channels = cluster->bNrChannels; - chmap_v3 = convert_chmap_v3(cluster); - - kfree(cluster); - - format = le64_to_cpu(as->bmFormats); - - /* lookup the terminal associated to this interface - * to extract the clock */ - input_term = snd_usb_find_input_terminal_descriptor( - chip->ctrl_intf, - as->bTerminalLink); - - if (input_term) { - clock = input_term->bCSourceID; - break; - } - - output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, - as->bTerminalLink); - if (output_term) { - clock = output_term->bCSourceID; - break; - } - - dev_err(&dev->dev, - "%u:%d : bogus bTerminalLink %d\n", - iface_no, altno, as->bTerminalLink); - continue; - } - } - - if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) { - if (!fp) - continue; - else if (IS_ERR(fp)) - return PTR_ERR(fp); - - goto skip_uac3; + case UAC_VERSION_3: + fp = snd_usb_get_audioformat_uac3(chip, alts, + iface_no, i, altno, stream); + break; } - fp = audio_format_alloc_init(chip, alts, protocol, iface_no, i, - altno, num_channels, clock); if (!fp) - return -ENOMEM; - - fp->attributes = parse_uac_endpoint_attributes(chip, alts, - protocol, - iface_no); - fp->chmap = chmap_v3; - - /* ok, let's parse further... */ - if (snd_usb_parse_audio_format_v3(chip, fp, as, - stream) < 0) { - kfree(fp->rate_table); - kfree(fp); - fp = NULL; continue; - } + else if (IS_ERR(fp)) + return PTR_ERR(fp); -skip_uac3: dev_dbg(&dev->dev, "%u:%d: add audio endpoint %#x\n", iface_no, altno, fp->endpoint); err = snd_usb_add_audio_stream(chip, stream, fp); if (err < 0) { -- cgit v1.2.3 From 3763f6186703d9939913996da1c71d8f8ddb035c Mon Sep 17 00:00:00 2001 From: Jorge Sanjuan Date: Fri, 4 May 2018 04:24:01 +0300 Subject: ALSA: usb: Only get AudioControl header for UAC1 class. The control header needs to be read from buffer at this point only in the case of UAC1 protocol. Move it inside the switch case as other protocols such as the Basic Audio Device spec will have an empty buffer that is latter filled as inferred. Signed-off-by: Jorge Sanjuan [Ruslan: updated with recently added sanity checks] Signed-off-by: Ruslan Bilovol Signed-off-by: Takashi Iwai --- sound/usb/card.c | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) (limited to 'sound') diff --git a/sound/usb/card.c b/sound/usb/card.c index 36c289bae169..0d7a5d70634e 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -221,32 +221,13 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) struct usb_device *dev = chip->dev; struct usb_host_interface *host_iface; struct usb_interface_descriptor *altsd; - void *control_header; int i, protocol; - int rest_bytes; /* find audiocontrol interface */ host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0]; - control_header = snd_usb_find_csint_desc(host_iface->extra, - host_iface->extralen, - NULL, UAC_HEADER); altsd = get_iface_desc(host_iface); protocol = altsd->bInterfaceProtocol; - if (!control_header) { - dev_err(&dev->dev, "cannot find UAC_HEADER\n"); - return -EINVAL; - } - - rest_bytes = (void *)(host_iface->extra + host_iface->extralen) - - control_header; - - /* just to be sure -- this shouldn't hit at all */ - if (rest_bytes <= 0) { - dev_err(&dev->dev, "invalid control header\n"); - return -EINVAL; - } - switch (protocol) { default: dev_warn(&dev->dev, @@ -255,7 +236,25 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) /* fall through */ case UAC_VERSION_1: { - struct uac1_ac_header_descriptor *h1 = control_header; + struct uac1_ac_header_descriptor *h1; + int rest_bytes; + + h1 = snd_usb_find_csint_desc(host_iface->extra, + host_iface->extralen, + NULL, UAC_HEADER); + if (!h1) { + dev_err(&dev->dev, "cannot find UAC_HEADER\n"); + return -EINVAL; + } + + rest_bytes = (void *)(host_iface->extra + + host_iface->extralen) - (void *)h1; + + /* just to be sure -- this shouldn't hit at all */ + if (rest_bytes <= 0) { + dev_err(&dev->dev, "invalid control header\n"); + return -EINVAL; + } if (rest_bytes < sizeof(*h1)) { dev_err(&dev->dev, "too short v1 buffer descriptor\n"); -- cgit v1.2.3 From eccfc1b868a9902bbfa2315a7c5385dbbf822dc4 Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Fri, 4 May 2018 04:24:02 +0300 Subject: ALSA: usb: mixer: make string parsing independent of mixer_build state Functions like snd_usb_copy_string_desc() or get_term_name() don't actually need mixer_build state but can use snd_usb_audio structure instead to get usb device. This patch has no functional change but prepares to future UAC3 BADD profiles support which don't have class-specific descriptors so won't have mixer_build state. Signed-off-by: Ruslan Bilovol Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) (limited to 'sound') diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 265258b0e74c..76417943ff85 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -201,10 +201,10 @@ static void *find_audio_control_unit(struct mixer_build *state, /* * copy a string with the given id */ -static int snd_usb_copy_string_desc(struct mixer_build *state, +static int snd_usb_copy_string_desc(struct snd_usb_audio *chip, int index, char *buf, int maxlen) { - int len = usb_string(state->chip->dev, index, buf, maxlen - 1); + int len = usb_string(chip->dev, index, buf, maxlen - 1); if (len < 0) return 0; @@ -658,14 +658,14 @@ static struct iterm_name_combo { { 0 }, }; -static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm, +static int get_term_name(struct snd_usb_audio *chip, struct usb_audio_term *iterm, unsigned char *name, int maxlen, int term_only) { struct iterm_name_combo *names; int len; if (iterm->name) { - len = snd_usb_copy_string_desc(state, iterm->name, + len = snd_usb_copy_string_desc(chip, iterm->name, name, maxlen); if (len) return len; @@ -1407,7 +1407,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); mapped_name = len != 0; if (!len && nameid) - len = snd_usb_copy_string_desc(state, nameid, + len = snd_usb_copy_string_desc(state->chip, nameid, kctl->id.name, sizeof(kctl->id.name)); switch (control) { @@ -1422,10 +1422,10 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, * - otherwise, anonymous name. */ if (!len) { - len = get_term_name(state, iterm, kctl->id.name, + len = get_term_name(state->chip, iterm, kctl->id.name, sizeof(kctl->id.name), 1); if (!len) - len = get_term_name(state, &state->oterm, + len = get_term_name(state->chip, &state->oterm, kctl->id.name, sizeof(kctl->id.name), 1); if (!len) @@ -1498,7 +1498,7 @@ static void get_connector_control_name(struct mixer_build *state, struct usb_audio_term *term, bool is_input, char *name, int name_size) { - int name_len = get_term_name(state, term, name, name_size, 0); + int name_len = get_term_name(state->chip, term, name, name_size, 0); if (name_len == 0) strlcpy(name, "Unknown", name_size); @@ -1597,7 +1597,7 @@ static int parse_clock_source_unit(struct mixer_build *state, int unitid, } kctl->private_free = snd_usb_mixer_elem_free; - ret = snd_usb_copy_string_desc(state, hdr->iClockSource, + ret = snd_usb_copy_string_desc(state->chip, hdr->iClockSource, name, sizeof(name)); if (ret > 0) snprintf(kctl->id.name, sizeof(kctl->id.name), @@ -1840,7 +1840,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); if (!len) - len = get_term_name(state, iterm, kctl->id.name, + len = get_term_name(state->chip, iterm, kctl->id.name, sizeof(kctl->id.name), 0); if (!len) len = sprintf(kctl->id.name, "Mixer Source %d", in_ch + 1); @@ -2154,7 +2154,8 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, nameid = uac_processing_unit_iProcessing(desc, state->mixer->protocol); len = 0; if (nameid) - len = snd_usb_copy_string_desc(state, nameid, + len = snd_usb_copy_string_desc(state->chip, + nameid, kctl->id.name, sizeof(kctl->id.name)); if (!len) @@ -2350,7 +2351,8 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, len = check_mapped_selector_name(state, unitid, i, namelist[i], MAX_ITEM_NAME_LEN); if (! len && check_input_term(state, desc->baSourceID[i], &iterm) >= 0) - len = get_term_name(state, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0); + len = get_term_name(state->chip, &iterm, namelist[i], + MAX_ITEM_NAME_LEN, 0); if (! len) sprintf(namelist[i], "Input %u", i); } @@ -2372,12 +2374,12 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, /* if iSelector is given, use it */ nameid = uac_selector_unit_iSelector(desc); if (nameid) - len = snd_usb_copy_string_desc(state, nameid, + len = snd_usb_copy_string_desc(state->chip, nameid, kctl->id.name, sizeof(kctl->id.name)); /* ... or pick up the terminal name at next */ if (!len) - len = get_term_name(state, &state->oterm, + len = get_term_name(state->chip, &state->oterm, kctl->id.name, sizeof(kctl->id.name), 0); /* ... or use the fixed string "USB" as the last resort */ if (!len) -- cgit v1.2.3 From 10aa7cad37d330dbff6a285af56dc4a7153a8f00 Mon Sep 17 00:00:00 2001 From: Anna-Maria Gleixner Date: Fri, 4 May 2018 17:28:10 +0200 Subject: ALSA: pcm: Hide local_irq_disable/enable() and local_irqsave/restore() The snd_pcm_stream_lock_irq*() functions decouple disabling interrupts from the actual locking process. This does not work as expected if the locking primitives are replaced like on preempt-rt. Provide one function for locking which uses correct locking primitives. Signed-off-by: Anna-Maria Gleixner Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 85 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 28 deletions(-) (limited to 'sound') diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 8ae42be160ae..04c6301394d0 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -99,6 +99,57 @@ static inline void down_write_nonblock(struct rw_semaphore *lock) cond_resched(); } +#define PCM_LOCK_DEFAULT 0 +#define PCM_LOCK_IRQ 1 +#define PCM_LOCK_IRQSAVE 2 + +static unsigned long __snd_pcm_stream_lock_mode(struct snd_pcm_substream *substream, + unsigned int mode) +{ + unsigned long flags = 0; + if (substream->pcm->nonatomic) { + down_read_nested(&snd_pcm_link_rwsem, SINGLE_DEPTH_NESTING); + mutex_lock(&substream->self_group.mutex); + } else { + switch (mode) { + case PCM_LOCK_DEFAULT: + read_lock(&snd_pcm_link_rwlock); + break; + case PCM_LOCK_IRQ: + read_lock_irq(&snd_pcm_link_rwlock); + break; + case PCM_LOCK_IRQSAVE: + read_lock_irqsave(&snd_pcm_link_rwlock, flags); + break; + } + spin_lock(&substream->self_group.lock); + } + return flags; +} + +static void __snd_pcm_stream_unlock_mode(struct snd_pcm_substream *substream, + unsigned int mode, unsigned long flags) +{ + if (substream->pcm->nonatomic) { + mutex_unlock(&substream->self_group.mutex); + up_read(&snd_pcm_link_rwsem); + } else { + spin_unlock(&substream->self_group.lock); + + switch (mode) { + case PCM_LOCK_DEFAULT: + read_unlock(&snd_pcm_link_rwlock); + break; + case PCM_LOCK_IRQ: + read_unlock_irq(&snd_pcm_link_rwlock); + break; + case PCM_LOCK_IRQSAVE: + read_unlock_irqrestore(&snd_pcm_link_rwlock, flags); + break; + } + } +} + /** * snd_pcm_stream_lock - Lock the PCM stream * @substream: PCM substream @@ -109,13 +160,7 @@ static inline void down_write_nonblock(struct rw_semaphore *lock) */ void snd_pcm_stream_lock(struct snd_pcm_substream *substream) { - if (substream->pcm->nonatomic) { - down_read_nested(&snd_pcm_link_rwsem, SINGLE_DEPTH_NESTING); - mutex_lock(&substream->self_group.mutex); - } else { - read_lock(&snd_pcm_link_rwlock); - spin_lock(&substream->self_group.lock); - } + __snd_pcm_stream_lock_mode(substream, PCM_LOCK_DEFAULT); } EXPORT_SYMBOL_GPL(snd_pcm_stream_lock); @@ -127,13 +172,7 @@ EXPORT_SYMBOL_GPL(snd_pcm_stream_lock); */ void snd_pcm_stream_unlock(struct snd_pcm_substream *substream) { - if (substream->pcm->nonatomic) { - mutex_unlock(&substream->self_group.mutex); - up_read(&snd_pcm_link_rwsem); - } else { - spin_unlock(&substream->self_group.lock); - read_unlock(&snd_pcm_link_rwlock); - } + __snd_pcm_stream_unlock_mode(substream, PCM_LOCK_DEFAULT, 0); } EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock); @@ -147,9 +186,7 @@ EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock); */ void snd_pcm_stream_lock_irq(struct snd_pcm_substream *substream) { - if (!substream->pcm->nonatomic) - local_irq_disable(); - snd_pcm_stream_lock(substream); + __snd_pcm_stream_lock_mode(substream, PCM_LOCK_IRQ); } EXPORT_SYMBOL_GPL(snd_pcm_stream_lock_irq); @@ -161,19 +198,13 @@ EXPORT_SYMBOL_GPL(snd_pcm_stream_lock_irq); */ void snd_pcm_stream_unlock_irq(struct snd_pcm_substream *substream) { - snd_pcm_stream_unlock(substream); - if (!substream->pcm->nonatomic) - local_irq_enable(); + __snd_pcm_stream_unlock_mode(substream, PCM_LOCK_IRQ, 0); } EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irq); unsigned long _snd_pcm_stream_lock_irqsave(struct snd_pcm_substream *substream) { - unsigned long flags = 0; - if (!substream->pcm->nonatomic) - local_irq_save(flags); - snd_pcm_stream_lock(substream); - return flags; + return __snd_pcm_stream_lock_mode(substream, PCM_LOCK_IRQSAVE); } EXPORT_SYMBOL_GPL(_snd_pcm_stream_lock_irqsave); @@ -187,9 +218,7 @@ EXPORT_SYMBOL_GPL(_snd_pcm_stream_lock_irqsave); void snd_pcm_stream_unlock_irqrestore(struct snd_pcm_substream *substream, unsigned long flags) { - snd_pcm_stream_unlock(substream); - if (!substream->pcm->nonatomic) - local_irq_restore(flags); + __snd_pcm_stream_unlock_mode(substream, PCM_LOCK_IRQSAVE, flags); } EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irqrestore); -- cgit v1.2.3 From 17156f23e93c0f59e06dd2aaffd06221341caaee Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Fri, 4 May 2018 04:24:04 +0300 Subject: ALSA: usb: add UAC3 BADD profiles support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recently released USB Audio Class 3.0 specification contains BADD (Basic Audio Device Definition) document which describes pre-defined UAC3 configurations. BADD support is mandatory for UAC3 devices, it should be implemented as a separate USB device configuration. As per BADD document, class-specific descriptors shall not be included in the Device’s Configuration descriptor ("inferred"), but host can guess them from BADD profile number, number of endpoints and their max packed sizes. This patch adds support of all BADD profiles from the spec Signed-off-by: Ruslan Bilovol Tested-by: Jorge Sanjuan Signed-off-by: Takashi Iwai --- sound/usb/card.c | 14 +++ sound/usb/clock.c | 9 +- sound/usb/mixer.c | 327 ++++++++++++++++++++++++++++++++++++++++++++----- sound/usb/mixer_maps.c | 65 ++++++++++ sound/usb/stream.c | 83 +++++++++++-- sound/usb/usbaudio.h | 2 + 6 files changed, 459 insertions(+), 41 deletions(-) (limited to 'sound') diff --git a/sound/usb/card.c b/sound/usb/card.c index 0d7a5d70634e..f6c3c1cd591e 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -307,6 +307,20 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) return -EINVAL; } + if (protocol == UAC_VERSION_3) { + int badd = assoc->bFunctionSubClass; + + if (badd != UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0 && + (badd < UAC3_FUNCTION_SUBCLASS_GENERIC_IO || + badd > UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE)) { + dev_err(&dev->dev, + "Unsupported UAC3 BADD profile\n"); + return -EINVAL; + } + + chip->badd_profile = badd; + } + for (i = 0; i < assoc->bInterfaceCount; i++) { int intf = assoc->bFirstInterface + i; diff --git a/sound/usb/clock.c b/sound/usb/clock.c index 0b030d8fe3fa..17673f37fcc8 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -587,8 +587,15 @@ int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, default: return set_sample_rate_v1(chip, iface, alts, fmt, rate); - case UAC_VERSION_2: case UAC_VERSION_3: + if (chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { + if (rate != UAC3_BADD_SAMPLING_RATE) + return -ENXIO; + else + return 0; + } + /* fall through */ + case UAC_VERSION_2: return set_sample_rate_v2v3(chip, iface, alts, fmt, rate); } } diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 76417943ff85..4987982250d5 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -112,14 +112,12 @@ enum { #include "mixer_maps.c" static const struct usbmix_name_map * -find_map(struct mixer_build *state, int unitid, int control) +find_map(const struct usbmix_name_map *p, int unitid, int control) { - const struct usbmix_name_map *p = state->map; - if (!p) return NULL; - for (p = state->map; p->id; p++) { + for (; p->id; p++) { if (p->id == unitid && (!control || !p->control || control == p->control)) return p; @@ -1333,16 +1331,16 @@ static struct usb_feature_control_info *get_feature_control_info(int control) return NULL; } -static void build_feature_ctl(struct mixer_build *state, void *raw_desc, - unsigned int ctl_mask, int control, - struct usb_audio_term *iterm, int unitid, - int readonly_mask) +static void __build_feature_ctl(struct usb_mixer_interface *mixer, + const struct usbmix_name_map *imap, + unsigned int ctl_mask, int control, + struct usb_audio_term *iterm, + struct usb_audio_term *oterm, + int unitid, int nameid, int readonly_mask) { - struct uac_feature_unit_descriptor *desc = raw_desc; struct usb_feature_control_info *ctl_info; unsigned int len = 0; int mapped_name = 0; - int nameid = uac_feature_unit_iFeature(desc); struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; const struct usbmix_name_map *map; @@ -1353,14 +1351,14 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, return; } - map = find_map(state, unitid, control); + map = find_map(imap, unitid, control); if (check_ignored_ctl(map)) return; cval = kzalloc(sizeof(*cval), GFP_KERNEL); if (!cval) return; - snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid); + snd_usb_mixer_elem_init_std(&cval->head, mixer, unitid); cval->control = control; cval->cmask = ctl_mask; @@ -1369,7 +1367,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kfree(cval); return; } - if (state->mixer->protocol == UAC_VERSION_1) + if (mixer->protocol == UAC_VERSION_1) cval->val_type = ctl_info->type; else /* UAC_VERSION_2 */ cval->val_type = ctl_info->type_uac2 >= 0 ? @@ -1398,7 +1396,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); if (!kctl) { - usb_audio_err(state->chip, "cannot malloc kcontrol\n"); + usb_audio_err(mixer->chip, "cannot malloc kcontrol\n"); kfree(cval); return; } @@ -1407,7 +1405,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); mapped_name = len != 0; if (!len && nameid) - len = snd_usb_copy_string_desc(state->chip, nameid, + len = snd_usb_copy_string_desc(mixer->chip, nameid, kctl->id.name, sizeof(kctl->id.name)); switch (control) { @@ -1422,10 +1420,12 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, * - otherwise, anonymous name. */ if (!len) { - len = get_term_name(state->chip, iterm, kctl->id.name, - sizeof(kctl->id.name), 1); - if (!len) - len = get_term_name(state->chip, &state->oterm, + if (iterm) + len = get_term_name(mixer->chip, iterm, + kctl->id.name, + sizeof(kctl->id.name), 1); + if (!len && oterm) + len = get_term_name(mixer->chip, oterm, kctl->id.name, sizeof(kctl->id.name), 1); if (!len) @@ -1434,15 +1434,15 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, } if (!mapped_name) - check_no_speaker_on_headset(kctl, state->mixer->chip->card); + check_no_speaker_on_headset(kctl, mixer->chip->card); /* * determine the stream direction: * if the connected output is USB stream, then it's likely a * capture stream. otherwise it should be playback (hopefully :) */ - if (!mapped_name && !(state->oterm.type >> 16)) { - if ((state->oterm.type & 0xff00) == 0x0100) + if (!mapped_name && oterm && !(oterm->type >> 16)) { + if ((oterm->type & 0xff00) == 0x0100) append_ctl_name(kctl, " Capture"); else append_ctl_name(kctl, " Playback"); @@ -1470,7 +1470,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, } } - snd_usb_mixer_fu_apply_quirk(state->mixer, cval, unitid, kctl); + snd_usb_mixer_fu_apply_quirk(mixer, cval, unitid, kctl); range = (cval->max - cval->min) / cval->res; /* @@ -1479,21 +1479,41 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, * devices. It will definitively catch all buggy Logitech devices. */ if (range > 384) { - usb_audio_warn(state->chip, + usb_audio_warn(mixer->chip, "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", range); - usb_audio_warn(state->chip, + usb_audio_warn(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d", cval->head.id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res); } - usb_audio_dbg(state->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", + usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", cval->head.id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res); snd_usb_mixer_add_control(&cval->head, kctl); } +static void build_feature_ctl(struct mixer_build *state, void *raw_desc, + unsigned int ctl_mask, int control, + struct usb_audio_term *iterm, int unitid, + int readonly_mask) +{ + struct uac_feature_unit_descriptor *desc = raw_desc; + int nameid = uac_feature_unit_iFeature(desc); + + __build_feature_ctl(state->mixer, state->map, ctl_mask, control, + iterm, &state->oterm, unitid, nameid, readonly_mask); +} + +static void build_feature_ctl_badd(struct usb_mixer_interface *mixer, + unsigned int ctl_mask, int control, int unitid, + const struct usbmix_name_map *badd_map) +{ + __build_feature_ctl(mixer, badd_map, ctl_mask, control, + NULL, NULL, unitid, 0, 0); +} + static void get_connector_control_name(struct mixer_build *state, struct usb_audio_term *term, bool is_input, char *name, int name_size) @@ -1807,7 +1827,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, struct snd_kcontrol *kctl; const struct usbmix_name_map *map; - map = find_map(state, unitid, 0); + map = find_map(state->map, unitid, 0); if (check_ignored_ctl(map)) return; @@ -2106,7 +2126,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, if (!(controls[valinfo->control / 8] & (1 << ((valinfo->control % 8) - 1)))) continue; - map = find_map(state, unitid, valinfo->control); + map = find_map(state->map, unitid, valinfo->control); if (check_ignored_ctl(map)) continue; cval = kzalloc(sizeof(*cval), GFP_KERNEL); @@ -2310,7 +2330,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, if (desc->bNrInPins == 1) /* only one ? nonsense! */ return 0; - map = find_map(state, unitid, 0); + map = find_map(state->map, unitid, 0); if (check_ignored_ctl(map)) return 0; @@ -2497,6 +2517,246 @@ static int snd_usb_mixer_dev_free(struct snd_device *device) return 0; } +/* UAC3 predefined channels configuration */ +struct uac3_badd_profile { + int subclass; + const char *name; + int c_chmask; /* capture channels mask */ + int p_chmask; /* playback channels mask */ + int st_chmask; /* side tone mixing channel mask */ +}; + +static struct uac3_badd_profile uac3_badd_profiles[] = { + { + /* + * BAIF, BAOF or combination of both + * IN: Mono or Stereo cfg, Mono alt possible + * OUT: Mono or Stereo cfg, Mono alt possible + */ + .subclass = UAC3_FUNCTION_SUBCLASS_GENERIC_IO, + .name = "GENERIC IO", + .c_chmask = -1, /* dynamic channels */ + .p_chmask = -1, /* dynamic channels */ + }, + { + /* BAOF; Stereo only cfg, Mono alt possible */ + .subclass = UAC3_FUNCTION_SUBCLASS_HEADPHONE, + .name = "HEADPHONE", + .p_chmask = 3, + }, + { + /* BAOF; Mono or Stereo cfg, Mono alt possible */ + .subclass = UAC3_FUNCTION_SUBCLASS_SPEAKER, + .name = "SPEAKER", + .p_chmask = -1, /* dynamic channels */ + }, + { + /* BAIF; Mono or Stereo cfg, Mono alt possible */ + .subclass = UAC3_FUNCTION_SUBCLASS_MICROPHONE, + .name = "MICROPHONE", + .c_chmask = -1, /* dynamic channels */ + }, + { + /* + * BAIOF topology + * IN: Mono only + * OUT: Mono or Stereo cfg, Mono alt possible + */ + .subclass = UAC3_FUNCTION_SUBCLASS_HEADSET, + .name = "HEADSET", + .c_chmask = 1, + .p_chmask = -1, /* dynamic channels */ + .st_chmask = 1, + }, + { + /* BAIOF; IN: Mono only; OUT: Stereo only, Mono alt possible */ + .subclass = UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER, + .name = "HEADSET ADAPTER", + .c_chmask = 1, + .p_chmask = 3, + .st_chmask = 1, + }, + { + /* BAIF + BAOF; IN: Mono only; OUT: Mono only */ + .subclass = UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE, + .name = "SPEAKERPHONE", + .c_chmask = 1, + .p_chmask = 1, + }, + { 0 } /* terminator */ +}; + +static bool uac3_badd_func_has_valid_channels(struct usb_mixer_interface *mixer, + struct uac3_badd_profile *f, + int c_chmask, int p_chmask) +{ + /* + * If both playback/capture channels are dynamic, make sure + * at least one channel is present + */ + if (f->c_chmask < 0 && f->p_chmask < 0) { + if (!c_chmask && !p_chmask) { + usb_audio_warn(mixer->chip, "BAAD %s: no channels?", + f->name); + return false; + } + return true; + } + + if ((f->c_chmask < 0 && !c_chmask) || + (f->c_chmask >= 0 && f->c_chmask != c_chmask)) { + usb_audio_warn(mixer->chip, "BAAD %s c_chmask mismatch", + f->name); + return false; + } + if ((f->p_chmask < 0 && !p_chmask) || + (f->p_chmask >= 0 && f->p_chmask != p_chmask)) { + usb_audio_warn(mixer->chip, "BAAD %s p_chmask mismatch", + f->name); + return false; + } + return true; +} + +/* + * create mixer controls for UAC3 BADD profiles + * + * UAC3 BADD device doesn't contain CS descriptors thus we will guess everything + * + * BADD device may contain Mixer Unit, which doesn't have any controls, skip it + */ +static int snd_usb_mixer_controls_badd(struct usb_mixer_interface *mixer, + int ctrlif) +{ + struct usb_device *dev = mixer->chip->dev; + struct usb_interface_assoc_descriptor *assoc; + int badd_profile = mixer->chip->badd_profile; + struct uac3_badd_profile *f; + const struct usbmix_ctl_map *map; + int p_chmask = 0, c_chmask = 0, st_chmask = 0; + int i; + + assoc = usb_ifnum_to_if(dev, ctrlif)->intf_assoc; + + /* Detect BADD capture/playback channels from AS EP descriptors */ + for (i = 0; i < assoc->bInterfaceCount; i++) { + int intf = assoc->bFirstInterface + i; + + struct usb_interface *iface; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + unsigned int maxpacksize; + char dir_in; + int chmask, num; + + if (intf == ctrlif) + continue; + + iface = usb_ifnum_to_if(dev, intf); + num = iface->num_altsetting; + + if (num < 2) + return -EINVAL; + + /* + * The number of Channels in an AudioStreaming interface + * and the audio sample bit resolution (16 bits or 24 + * bits) can be derived from the wMaxPacketSize field in + * the Standard AS Audio Data Endpoint descriptor in + * Alternate Setting 1 + */ + alts = &iface->altsetting[1]; + altsd = get_iface_desc(alts); + + if (altsd->bNumEndpoints < 1) + return -EINVAL; + + /* check direction */ + dir_in = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN); + maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + + switch (maxpacksize) { + default: + usb_audio_err(mixer->chip, + "incorrect wMaxPacketSize 0x%x for BADD profile\n", + maxpacksize); + return -EINVAL; + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16: + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24: + chmask = 1; + break; + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16: + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24: + chmask = 3; + break; + } + + if (dir_in) + c_chmask = chmask; + else + p_chmask = chmask; + } + + usb_audio_dbg(mixer->chip, + "UAC3 BADD profile 0x%x: detected c_chmask=%d p_chmask=%d\n", + badd_profile, c_chmask, p_chmask); + + /* check the mapping table */ + for (map = uac3_badd_usbmix_ctl_maps; map->id; map++) { + if (map->id == badd_profile) + break; + } + + if (!map->id) + return -EINVAL; + + for (f = uac3_badd_profiles; f->name; f++) { + if (badd_profile == f->subclass) + break; + } + if (!f->name) + return -EINVAL; + if (!uac3_badd_func_has_valid_channels(mixer, f, c_chmask, p_chmask)) + return -EINVAL; + st_chmask = f->st_chmask; + + /* Playback */ + if (p_chmask) { + /* Master channel, always writable */ + build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE, + UAC3_BADD_FU_ID2, map->map); + /* Mono/Stereo volume channels, always writable */ + build_feature_ctl_badd(mixer, p_chmask, UAC_FU_VOLUME, + UAC3_BADD_FU_ID2, map->map); + } + + /* Capture */ + if (c_chmask) { + /* Master channel, always writable */ + build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE, + UAC3_BADD_FU_ID5, map->map); + /* Mono/Stereo volume channels, always writable */ + build_feature_ctl_badd(mixer, c_chmask, UAC_FU_VOLUME, + UAC3_BADD_FU_ID5, map->map); + } + + /* Side tone-mixing */ + if (st_chmask) { + /* Master channel, always writable */ + build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE, + UAC3_BADD_FU_ID7, map->map); + /* Mono volume channel, always writable */ + build_feature_ctl_badd(mixer, 1, UAC_FU_VOLUME, + UAC3_BADD_FU_ID7, map->map); + } + + return 0; +} + /* * create mixer controls * @@ -2882,9 +3142,14 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, break; } - if ((err = snd_usb_mixer_controls(mixer)) < 0 || - (err = snd_usb_mixer_status_create(mixer)) < 0) + if (mixer->protocol == UAC_VERSION_3 && + chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { + if ((err = snd_usb_mixer_controls_badd(mixer, ctrlif)) < 0) + goto _error; + } else if ((err = snd_usb_mixer_controls(mixer)) < 0 || + (err = snd_usb_mixer_status_create(mixer)) < 0) { goto _error; + } err = create_keep_iface_ctl(mixer); if (err < 0) goto _error; diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index eaa03acd4686..71069e110897 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -485,3 +485,68 @@ static struct usbmix_ctl_map usbmix_ctl_maps[] = { { 0 } /* terminator */ }; +/* + * Control map entries for UAC3 BADD profiles + */ + +static struct usbmix_name_map uac3_badd_generic_io_map[] = { + { UAC3_BADD_FU_ID2, "Generic Out Playback" }, + { UAC3_BADD_FU_ID5, "Generic In Capture" }, + { 0 } /* terminator */ +}; +static struct usbmix_name_map uac3_badd_headphone_map[] = { + { UAC3_BADD_FU_ID2, "Headphone Playback" }, + { 0 } /* terminator */ +}; +static struct usbmix_name_map uac3_badd_speaker_map[] = { + { UAC3_BADD_FU_ID2, "Speaker Playback" }, + { 0 } /* terminator */ +}; +static struct usbmix_name_map uac3_badd_microphone_map[] = { + { UAC3_BADD_FU_ID5, "Mic Capture" }, + { 0 } /* terminator */ +}; +/* Covers also 'headset adapter' profile */ +static struct usbmix_name_map uac3_badd_headset_map[] = { + { UAC3_BADD_FU_ID2, "Headset Playback" }, + { UAC3_BADD_FU_ID5, "Headset Capture" }, + { UAC3_BADD_FU_ID7, "Sidetone Mixing" }, + { 0 } /* terminator */ +}; +static struct usbmix_name_map uac3_badd_speakerphone_map[] = { + { UAC3_BADD_FU_ID2, "Speaker Playback" }, + { UAC3_BADD_FU_ID5, "Mic Capture" }, + { 0 } /* terminator */ +}; + +static struct usbmix_ctl_map uac3_badd_usbmix_ctl_maps[] = { + { + .id = UAC3_FUNCTION_SUBCLASS_GENERIC_IO, + .map = uac3_badd_generic_io_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_HEADPHONE, + .map = uac3_badd_headphone_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_SPEAKER, + .map = uac3_badd_speaker_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_MICROPHONE, + .map = uac3_badd_microphone_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_HEADSET, + .map = uac3_badd_headset_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER, + .map = uac3_badd_headset_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE, + .map = uac3_badd_speakerphone_map, + }, + { 0 } /* terminator */ +}; diff --git a/sound/usb/stream.c b/sound/usb/stream.c index 764be07474a8..de8bbb304199 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -817,15 +817,67 @@ snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip, struct uac3_input_terminal_descriptor *input_term; struct uac3_output_terminal_descriptor *output_term; struct uac3_cluster_header_descriptor *cluster; - struct uac3_as_header_descriptor *as; + struct uac3_as_header_descriptor *as = NULL; struct uac3_hc_descriptor_header hc_header; struct snd_pcm_chmap_elem *chmap; + unsigned char badd_profile; + u64 badd_formats = 0; unsigned int num_channels; struct audioformat *fp; u16 cluster_id, wLength; int clock = 0; int err; + badd_profile = chip->badd_profile; + + if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { + unsigned int maxpacksize = + le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + + switch (maxpacksize) { + default: + dev_err(&dev->dev, + "%u:%d : incorrect wMaxPacketSize for BADD profile\n", + iface_no, altno); + return NULL; + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16: + badd_formats = SNDRV_PCM_FMTBIT_S16_LE; + num_channels = 1; + break; + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24: + badd_formats = SNDRV_PCM_FMTBIT_S24_3LE; + num_channels = 1; + break; + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16: + badd_formats = SNDRV_PCM_FMTBIT_S16_LE; + num_channels = 2; + break; + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24: + badd_formats = SNDRV_PCM_FMTBIT_S24_3LE; + num_channels = 2; + break; + } + + chmap = kzalloc(sizeof(*chmap), GFP_KERNEL); + if (!chmap) + return ERR_PTR(-ENOMEM); + + if (num_channels == 1) { + chmap->map[0] = SNDRV_CHMAP_MONO; + } else { + chmap->map[0] = SNDRV_CHMAP_FL; + chmap->map[1] = SNDRV_CHMAP_FR; + } + + chmap->channels = num_channels; + clock = UAC3_BADD_CS_ID9; + goto found_clock; + } + as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); if (!as) { @@ -931,16 +983,29 @@ found_clock: if (!fp) return ERR_PTR(-ENOMEM); - fp->attributes = parse_uac_endpoint_attributes(chip, alts, - UAC_VERSION_3, - iface_no); fp->chmap = chmap; - /* ok, let's parse further... */ - if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) { - kfree(fp->rate_table); - kfree(fp); - return NULL; + if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { + fp->attributes = 0; /* No attributes */ + + fp->fmt_type = UAC_FORMAT_TYPE_I; + fp->formats = badd_formats; + + fp->nr_rates = 0; /* SNDRV_PCM_RATE_CONTINUOUS */ + fp->rate_min = UAC3_BADD_SAMPLING_RATE; + fp->rate_max = UAC3_BADD_SAMPLING_RATE; + fp->rates = SNDRV_PCM_RATE_CONTINUOUS; + + } else { + fp->attributes = parse_uac_endpoint_attributes(chip, alts, + UAC_VERSION_3, + iface_no); + /* ok, let's parse further... */ + if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) { + kfree(fp->rate_table); + kfree(fp); + return NULL; + } } return fp; diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 1cb6b3e9483c..7b28cbde22c0 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -49,6 +49,8 @@ struct snd_usb_audio { int num_suspended_intf; int sample_rate_read_error; + int badd_profile; /* UAC3 BADD profile */ + struct list_head pcm_list; /* list of pcm streams */ struct list_head ep_list; /* list of audio-related endpoints */ int pcm_devs; -- cgit v1.2.3 From 8a19bceef68d8e88c9790868eece76b928a32a16 Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Tue, 8 May 2018 13:20:01 -0400 Subject: ALSA: hda/ca0132: R3Di and SBZ quirk entires + alt firmware loading This patch adds PCI quirk ID's for the Sound Blaster Z and Recon3Di. Only the currently tested ID's have been added. This patch also adds the ability to load alternative firmwares for each card, the firmwares can be obtained from within the Windows driver. The Recon3Di uses "ctefx-r3di.bin" and the Sound Blaster Z uses "ctefx-sbz.bin". If the alternative firmware for the given quirk is not found, the original ctefx.bin will be used. This has been confirmed to work for both the R3Di and the SBZ. This patch also makes the character array *dirstr a const. Signed-off-by: Connor McAdams Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 61 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 84261ef02c93..aa1cba3c18bd 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -76,12 +76,16 @@ #define SCP_GET 1 #define EFX_FILE "ctefx.bin" +#define SBZ_EFX_FILE "ctefx-sbz.bin" +#define R3DI_EFX_FILE "ctefx-r3di.bin" #ifdef CONFIG_SND_HDA_CODEC_CA0132_DSP MODULE_FIRMWARE(EFX_FILE); +MODULE_FIRMWARE(SBZ_EFX_FILE); +MODULE_FIRMWARE(R3DI_EFX_FILE); #endif -static char *dirstr[2] = { "Playback", "Capture" }; +static const char *dirstr[2] = { "Playback", "Capture" }; enum { SPEAKER_OUT, @@ -738,6 +742,7 @@ struct ca0132_spec { unsigned int scp_resp_header; unsigned int scp_resp_data[4]; unsigned int scp_resp_count; + bool alt_firmware_present; /* mixer and effects related */ unsigned char dmic_ctl; @@ -766,6 +771,8 @@ struct ca0132_spec { enum { QUIRK_NONE, QUIRK_ALIENWARE, + QUIRK_SBZ, + QUIRK_R3DI, }; static const struct hda_pintbl alienware_pincfgs[] = { @@ -786,6 +793,10 @@ static const struct snd_pci_quirk ca0132_quirks[] = { SND_PCI_QUIRK(0x1028, 0x0685, "Alienware 15 2015", QUIRK_ALIENWARE), SND_PCI_QUIRK(0x1028, 0x0688, "Alienware 17 2015", QUIRK_ALIENWARE), SND_PCI_QUIRK(0x1028, 0x0708, "Alienware 15 R2 2016", QUIRK_ALIENWARE), + SND_PCI_QUIRK(0x1102, 0x0010, "Sound Blaster Z", QUIRK_SBZ), + SND_PCI_QUIRK(0x1102, 0x0023, "Sound Blaster Z", QUIRK_SBZ), + SND_PCI_QUIRK(0x1458, 0xA016, "Recon3Di", QUIRK_R3DI), + SND_PCI_QUIRK(0x1458, 0xA036, "Recon3Di", QUIRK_R3DI), {} }; @@ -3211,7 +3222,7 @@ static int ca0132_select_out(struct hda_codec *codec) pin_ctl & ~PIN_HP); /* enable speaker node */ pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0, - AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); snd_hda_set_pin_ctl(codec, spec->out_pins[0], pin_ctl | PIN_OUT); } else { @@ -4374,11 +4385,49 @@ static void ca0132_set_dsp_msr(struct hda_codec *codec, bool is96k) static bool ca0132_download_dsp_images(struct hda_codec *codec) { bool dsp_loaded = false; + struct ca0132_spec *spec = codec->spec; const struct dsp_image_seg *dsp_os_image; const struct firmware *fw_entry; - - if (request_firmware(&fw_entry, EFX_FILE, codec->card->dev) != 0) - return false; + /* + * Alternate firmwares for different variants. The Recon3Di apparently + * can use the default firmware, but I'll leave the option in case + * it needs it again. + */ + switch (spec->quirk) { + case QUIRK_SBZ: + if (request_firmware(&fw_entry, SBZ_EFX_FILE, + codec->card->dev) != 0) { + codec_dbg(codec, "SBZ alt firmware not detected. "); + spec->alt_firmware_present = false; + } else { + codec_dbg(codec, "Sound Blaster Z firmware selected."); + spec->alt_firmware_present = true; + } + break; + case QUIRK_R3DI: + if (request_firmware(&fw_entry, R3DI_EFX_FILE, + codec->card->dev) != 0) { + codec_dbg(codec, "Recon3Di alt firmware not detected."); + spec->alt_firmware_present = false; + } else { + codec_dbg(codec, "Recon3Di firmware selected."); + spec->alt_firmware_present = true; + } + break; + default: + spec->alt_firmware_present = false; + break; + } + /* + * Use default ctefx.bin if no alt firmware is detected, or if none + * exists for your particular codec. + */ + if (!spec->alt_firmware_present) { + codec_dbg(codec, "Default firmware selected."); + if (request_firmware(&fw_entry, EFX_FILE, + codec->card->dev) != 0) + return false; + } dsp_os_image = (struct dsp_image_seg *)(fw_entry->data); if (dspload_image(codec, dsp_os_image, 0, 0, true, 0)) { @@ -4480,7 +4529,7 @@ static struct hda_verb ca0132_base_exit_verbs[] = { {} }; -/* Other verbs tables. Sends after DSP download. */ +/* Other verbs tables. Sends after DSP download. */ static struct hda_verb ca0132_init_verbs0[] = { /* chip init verbs */ {0x15, 0x70D, 0xF0}, -- cgit v1.2.3 From 63177afc98a509a2406e82cb1239144a0123a414 Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Tue, 8 May 2018 13:20:02 -0400 Subject: ALSA: hda/ca0132: Add pincfg for SBZ + R3Di, add fp hp auto-detect This patch adds an unsolicited response tag for the front headphone panel which uses the same hp_callback as the rear headphone detection. This patch also adds pincfgs for the R3Di and SBZ which were taken from the Windows driver. The pins are also defined in the function ca0132_config. Both the R3Di and SBZ are also given a max out channel value of 6 to handle 5.1 surround sound in later patches. Signed-off-by: Connor McAdams Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 111 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index aa1cba3c18bd..6ca8bc4321d4 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -727,6 +727,7 @@ struct ca0132_spec { hda_nid_t shared_mic_nid; hda_nid_t shared_out_nid; hda_nid_t unsol_tag_hp; + hda_nid_t unsol_tag_front_hp; /* for desktop ca0132 codecs */ hda_nid_t unsol_tag_amic1; /* chip access */ @@ -789,6 +790,36 @@ static const struct hda_pintbl alienware_pincfgs[] = { {} }; +/* Sound Blaster Z pin configs taken from Windows Driver */ +static const struct hda_pintbl sbz_pincfgs[] = { + { 0x0b, 0x01017010 }, /* Port G -- Lineout FRONT L/R */ + { 0x0c, 0x014510f0 }, /* SPDIF Out 1 */ + { 0x0d, 0x014510f0 }, /* Digital Out */ + { 0x0e, 0x01c510f0 }, /* SPDIF In */ + { 0x0f, 0x0221701f }, /* Port A -- BackPanel HP */ + { 0x10, 0x01017012 }, /* Port D -- Center/LFE or FP Hp */ + { 0x11, 0x01017014 }, /* Port B -- LineMicIn2 / Rear L/R */ + { 0x12, 0x01a170f0 }, /* Port C -- LineIn1 */ + { 0x13, 0x908700f0 }, /* What U Hear In*/ + { 0x18, 0x50d000f0 }, /* N/A */ + {} +}; + +/* Recon3D integrated pin configs taken from Windows Driver */ +static const struct hda_pintbl r3di_pincfgs[] = { + { 0x0b, 0x01014110 }, /* Port G -- Lineout FRONT L/R */ + { 0x0c, 0x014510f0 }, /* SPDIF Out 1 */ + { 0x0d, 0x014510f0 }, /* Digital Out */ + { 0x0e, 0x41c520f0 }, /* SPDIF In */ + { 0x0f, 0x0221401f }, /* Port A -- BackPanel HP */ + { 0x10, 0x01016011 }, /* Port D -- Center/LFE or FP Hp */ + { 0x11, 0x01011014 }, /* Port B -- LineMicIn2 / Rear L/R */ + { 0x12, 0x02a090f0 }, /* Port C -- LineIn1 */ + { 0x13, 0x908700f0 }, /* What U Hear In*/ + { 0x18, 0x500000f0 }, /* N/A */ + {} +}; + static const struct snd_pci_quirk ca0132_quirks[] = { SND_PCI_QUIRK(0x1028, 0x0685, "Alienware 15 2015", QUIRK_ALIENWARE), SND_PCI_QUIRK(0x1028, 0x0688, "Alienware 17 2015", QUIRK_ALIENWARE), @@ -4507,6 +4538,10 @@ static void ca0132_init_unsol(struct hda_codec *codec) amic_callback); snd_hda_jack_detect_enable_callback(codec, UNSOL_TAG_DSP, ca0132_process_dsp_response); + /* Front headphone jack detection */ + if (spec->quirk == QUIRK_SBZ || spec->quirk == QUIRK_R3DI) + snd_hda_jack_detect_enable_callback(codec, + spec->unsol_tag_front_hp, hp_callback); } /* @@ -4688,9 +4723,14 @@ static void ca0132_config(struct hda_codec *codec) spec->multiout.dac_nids = spec->dacs; spec->multiout.num_dacs = 3; - spec->multiout.max_channels = 2; - if (spec->quirk == QUIRK_ALIENWARE) { + if (spec->quirk == QUIRK_NONE || spec->quirk == QUIRK_ALIENWARE) + spec->multiout.max_channels = 2; + else + spec->multiout.max_channels = 6; + + switch (spec->quirk) { + case QUIRK_ALIENWARE: codec_dbg(codec, "ca0132_config: QUIRK_ALIENWARE applied.\n"); snd_hda_apply_pincfgs(codec, alienware_pincfgs); @@ -4710,7 +4750,71 @@ static void ca0132_config(struct hda_codec *codec) spec->input_pins[2] = 0x13; spec->shared_mic_nid = 0x7; spec->unsol_tag_amic1 = 0x11; - } else { + break; + case QUIRK_SBZ: + codec_dbg(codec, "%s: QUIRK_SBZ applied.\n", __func__); + snd_hda_apply_pincfgs(codec, sbz_pincfgs); + + spec->num_outputs = 2; + spec->out_pins[0] = 0x0B; /* Line out */ + spec->out_pins[1] = 0x0F; /* Rear headphone out */ + spec->out_pins[2] = 0x10; /* Front Headphone / Center/LFE*/ + spec->out_pins[3] = 0x11; /* Rear surround */ + spec->shared_out_nid = 0x2; + spec->unsol_tag_hp = spec->out_pins[1]; + spec->unsol_tag_front_hp = spec->out_pins[2]; + + spec->adcs[0] = 0x7; /* Rear Mic / Line-in */ + spec->adcs[1] = 0x8; /* Front Mic, but only if no DSP */ + spec->adcs[2] = 0xa; /* what u hear */ + + spec->num_inputs = 2; + spec->input_pins[0] = 0x12; /* Rear Mic / Line-in */ + spec->input_pins[1] = 0x13; /* What U Hear */ + spec->shared_mic_nid = 0x7; + spec->unsol_tag_amic1 = spec->input_pins[0]; + + /* SPDIF I/O */ + spec->dig_out = 0x05; + spec->multiout.dig_out_nid = spec->dig_out; + cfg->dig_out_pins[0] = 0x0c; + cfg->dig_outs = 1; + cfg->dig_out_type[0] = HDA_PCM_TYPE_SPDIF; + spec->dig_in = 0x09; + cfg->dig_in_pin = 0x0e; + cfg->dig_in_type = HDA_PCM_TYPE_SPDIF; + break; + case QUIRK_R3DI: + codec_dbg(codec, "%s: QUIRK_R3DI applied.\n", __func__); + snd_hda_apply_pincfgs(codec, r3di_pincfgs); + + spec->num_outputs = 2; + spec->out_pins[0] = 0x0B; /* Line out */ + spec->out_pins[1] = 0x0F; /* Rear headphone out */ + spec->out_pins[2] = 0x10; /* Front Headphone / Center/LFE*/ + spec->out_pins[3] = 0x11; /* Rear surround */ + spec->shared_out_nid = 0x2; + spec->unsol_tag_hp = spec->out_pins[1]; + spec->unsol_tag_front_hp = spec->out_pins[2]; + + spec->adcs[0] = 0x07; /* Rear Mic / Line-in */ + spec->adcs[1] = 0x08; /* Front Mic, but only if no DSP */ + spec->adcs[2] = 0x0a; /* what u hear */ + + spec->num_inputs = 2; + spec->input_pins[0] = 0x12; /* Rear Mic / Line-in */ + spec->input_pins[1] = 0x13; /* What U Hear */ + spec->shared_mic_nid = 0x7; + spec->unsol_tag_amic1 = spec->input_pins[0]; + + /* SPDIF I/O */ + spec->dig_out = 0x05; + spec->multiout.dig_out_nid = spec->dig_out; + cfg->dig_out_pins[0] = 0x0c; + cfg->dig_outs = 1; + cfg->dig_out_type[0] = HDA_PCM_TYPE_SPDIF; + break; + default: spec->num_outputs = 2; spec->out_pins[0] = 0x0b; /* speaker out */ spec->out_pins[1] = 0x10; /* headphone out */ @@ -4737,6 +4841,7 @@ static void ca0132_config(struct hda_codec *codec) spec->dig_in = 0x09; cfg->dig_in_pin = 0x0e; cfg->dig_in_type = HDA_PCM_TYPE_SPDIF; + break; } } -- cgit v1.2.3 From aa31704fd81c84f6a9e0b87f3455246936654ae6 Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Tue, 8 May 2018 13:20:03 -0400 Subject: ALSA: hda/ca0132: Add PCI region2 iomap for SBZ This patch adds iomapping for the region2 section of memory on the SBZ. This memory region is used in later patches for setting inputs and outputs. If the mapping fails, the quirk is changed back to QUIRK_NONE to avoid attempts to write to uninitialized memory. It also adds a new exit sequence to unmap the iomem for the SBZ. [ Reordered linux/*.h inclusion in the patch by tiwai ] Signed-off-by: Connor McAdams Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 6ca8bc4321d4..29772831e412 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -28,6 +28,9 @@ #include #include #include +#include +#include +#include #include #include "hda_codec.h" #include "hda_local.h" @@ -764,6 +767,11 @@ struct ca0132_spec { #ifdef ENABLE_TUNING_CONTROLS long cur_ctl_vals[TUNING_CTLS_COUNT]; #endif + /* + * Sound Blaster Z PCI region 2 iomem, used for input and output + * switching, and other unknown commands. + */ + void __iomem *mem_base; }; /* @@ -4700,6 +4708,8 @@ static void ca0132_free(struct hda_codec *codec) snd_hda_sequence_write(codec, spec->base_exit_verbs); ca0132_exit_chip(codec); snd_hda_power_down(codec); + if (spec->mem_base) + iounmap(spec->mem_base); kfree(spec->spec_init_verbs); kfree(codec->spec); } @@ -4915,6 +4925,15 @@ static int patch_ca0132(struct hda_codec *codec) else spec->quirk = QUIRK_NONE; + /* Setup BAR Region 2 for Sound Blaster Z */ + if (spec->quirk == QUIRK_SBZ) { + spec->mem_base = pci_iomap(codec->bus->pci, 2, 0xC20); + if (spec->mem_base == NULL) { + codec_warn(codec, "pci_iomap failed!"); + codec_info(codec, "perhaps this is not an SBZ?"); + spec->quirk = QUIRK_NONE; + } + } spec->dsp_state = DSP_DOWNLOAD_INIT; spec->num_mixers = 1; spec->mixers[0] = ca0132_mixer; -- cgit v1.2.3 From 2e48b2b7a29e391a61137d00ea1aa2a1623ba728 Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Tue, 8 May 2018 13:20:04 -0400 Subject: ALSA: hda/ca0132: Add extra exit functions for R3Di and SBZ This patch adds extra functions for shutdown on the Sound Blaster Z and Recon3Di. The Recon3Di only has one specific functions, which sets the GPIO data pins to 0 to prevent a popping noise. The Sound Blaster Z exit sequence was taken from Windows. Without this exit function, the card will not reload properly unless the PC has been shutdown to clear the onboard memory. There are commented out functions currently in the sbz_exit_chip function that are added in a later patch. Also, a reboot notify function has been added, to make sure these functions are ran before a reboot. This helps when using the card through VFIO in a virtual machine, to make sure the card reloads the DSP properly. Signed-off-by: Connor McAdams Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 131 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 29772831e412..fdce46d37df7 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -4645,6 +4645,115 @@ static void ca0132_init_chip(struct hda_codec *codec) #endif } +/* + * Recon3Di exit specific commands. + */ +/* prevents popping noise on shutdown */ +static void r3di_gpio_shutdown(struct hda_codec *codec) +{ + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 0x00); +} + +/* + * Sound Blaster Z exit specific commands. + */ +static void sbz_region2_exit(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int i; + + for (i = 0; i < 4; i++) + writeb(0x0, spec->mem_base + 0x100); + for (i = 0; i < 8; i++) + writeb(0xb3, spec->mem_base + 0x304); + /* + * I believe these are GPIO, with the right most hex digit being the + * gpio pin, and the second digit being on or off. We see this more in + * the input/output select functions. + */ + writew(0x0000, spec->mem_base + 0x320); + writew(0x0001, spec->mem_base + 0x320); + writew(0x0104, spec->mem_base + 0x320); + writew(0x0005, spec->mem_base + 0x320); + writew(0x0007, spec->mem_base + 0x320); +} + +static void sbz_set_pin_ctl_default(struct hda_codec *codec) +{ + hda_nid_t pins[5] = {0x0B, 0x0C, 0x0E, 0x12, 0x13}; + unsigned int i; + + snd_hda_codec_write(codec, 0x11, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40); + + for (i = 0; i < 5; i++) + snd_hda_codec_write(codec, pins[i], 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00); +} + +static void sbz_clear_unsolicited(struct hda_codec *codec) +{ + hda_nid_t pins[7] = {0x0B, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13}; + unsigned int i; + + for (i = 0; i < 7; i++) { + snd_hda_codec_write(codec, pins[i], 0, + AC_VERB_SET_UNSOLICITED_ENABLE, 0x00); + } +} + +/* On shutdown, sends commands in sets of three */ +static void sbz_gpio_shutdown_commands(struct hda_codec *codec, int dir, + int mask, int data) +{ + if (dir >= 0) + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DIRECTION, dir); + if (mask >= 0) + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_MASK, mask); + + if (data >= 0) + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, data); +} + +static void sbz_exit_chip(struct hda_codec *codec) +{ + + /* Mess with GPIO */ + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, -1); + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x05); + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x01); + + + chipio_set_conn_rate(codec, 0x41, SR_192_000); + chipio_set_conn_rate(codec, 0x91, SR_192_000); + + chipio_write(codec, 0x18a020, 0x00000083); + + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x03); + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x07); + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x06); + + + chipio_set_control_param(codec, 0x0D, 0x24); + + sbz_clear_unsolicited(codec); + sbz_set_pin_ctl_default(codec); + + snd_hda_codec_write(codec, 0x0B, 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x00); + + if (dspload_is_loaded(codec)) + dsp_reset(codec); + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_CT_EXTENSIONS_ENABLE, 0x00); + + sbz_region2_exit(codec); +} + static void ca0132_exit_chip(struct hda_codec *codec) { /* put any chip cleanup stuffs here. */ @@ -4705,8 +4814,20 @@ static void ca0132_free(struct hda_codec *codec) cancel_delayed_work_sync(&spec->unsol_hp_work); snd_hda_power_up(codec); - snd_hda_sequence_write(codec, spec->base_exit_verbs); - ca0132_exit_chip(codec); + switch (spec->quirk) { + case QUIRK_SBZ: + sbz_exit_chip(codec); + break; + case QUIRK_R3DI: + r3di_gpio_shutdown(codec); + snd_hda_sequence_write(codec, spec->base_exit_verbs); + ca0132_exit_chip(codec); + break; + default: + snd_hda_sequence_write(codec, spec->base_exit_verbs); + ca0132_exit_chip(codec); + break; + } snd_hda_power_down(codec); if (spec->mem_base) iounmap(spec->mem_base); @@ -4714,12 +4835,18 @@ static void ca0132_free(struct hda_codec *codec) kfree(codec->spec); } +static void ca0132_reboot_notify(struct hda_codec *codec) +{ + codec->patch_ops.free(codec); +} + static const struct hda_codec_ops ca0132_patch_ops = { .build_controls = ca0132_build_controls, .build_pcms = ca0132_build_pcms, .init = ca0132_init, .free = ca0132_free, .unsol_event = snd_hda_jack_unsol_event, + .reboot_notify = ca0132_reboot_notify, }; static void ca0132_config(struct hda_codec *codec) -- cgit v1.2.3 From e93ac30a32a6ba7ac3b4b2a4379af1dadb91e505 Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Tue, 8 May 2018 13:20:05 -0400 Subject: ALSA: hda/ca0132: add extra init functions for r3di + sbz This patch adds extra init functions for the Sound Blaster Z and Recon3Di. It also adds more checks to make sure that the DSP isn't downloaded twice on startup, by checking if the dsp_state is already set to DSP_DOWNLOADED. It also adds the ability to re-download the DSP on a resume. It also changes the init verbs table to apply to all codecs, and takes the two specific end verbs and puts them into a separate function in ca0132_init instead. GPIO functions are also added. Signed-off-by: Connor McAdams Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 273 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 265 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index fdce46d37df7..508ba35eadbb 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -714,6 +714,7 @@ struct ca0132_spec { const struct hda_verb *base_init_verbs; const struct hda_verb *base_exit_verbs; const struct hda_verb *chip_init_verbs; + const struct hda_verb *sbz_init_verbs; struct hda_verb *spec_init_verbs; struct auto_pin_cfg autocfg; @@ -747,6 +748,7 @@ struct ca0132_spec { unsigned int scp_resp_data[4]; unsigned int scp_resp_count; bool alt_firmware_present; + bool dsp_reload; /* mixer and effects related */ unsigned char dmic_ctl; @@ -2743,6 +2745,59 @@ static bool dspload_wait_loaded(struct hda_codec *codec) return false; } +/* + * Setup GPIO for the other variants of Core3D. + */ + +/* + * Sets up the GPIO pins so that they are discoverable. If this isn't done, + * the card shows as having no GPIO pins. + */ +static void ca0132_gpio_init(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + switch (spec->quirk) { + case QUIRK_SBZ: + snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00); + snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x53); + snd_hda_codec_write(codec, 0x01, 0, 0x790, 0x23); + break; + case QUIRK_R3DI: + snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00); + snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x5B); + break; + } + +} + +/* Sets the GPIO for audio output. */ +static void ca0132_gpio_setup(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + switch (spec->quirk) { + case QUIRK_SBZ: + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DIRECTION, 0x07); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_MASK, 0x07); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, 0x04); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, 0x06); + break; + case QUIRK_R3DI: + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DIRECTION, 0x1E); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_MASK, 0x1F); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, 0x0C); + break; + } +} + /* * PCM callbacks */ @@ -4494,11 +4549,14 @@ static void ca0132_download_dsp(struct hda_codec *codec) return; /* don't retry failures */ chipio_enable_clocks(codec); - spec->dsp_state = DSP_DOWNLOADING; - if (!ca0132_download_dsp_images(codec)) - spec->dsp_state = DSP_DOWNLOAD_FAILED; - else - spec->dsp_state = DSP_DOWNLOADED; + if (spec->dsp_state != DSP_DOWNLOADED) { + spec->dsp_state = DSP_DOWNLOADING; + + if (!ca0132_download_dsp_images(codec)) + spec->dsp_state = DSP_DOWNLOAD_FAILED; + else + spec->dsp_state = DSP_DOWNLOADED; + } if (spec->dsp_state == DSP_DOWNLOADED) ca0132_set_dsp_msr(codec, true); @@ -4573,6 +4631,7 @@ static struct hda_verb ca0132_base_exit_verbs[] = { }; /* Other verbs tables. Sends after DSP download. */ + static struct hda_verb ca0132_init_verbs0[] = { /* chip init verbs */ {0x15, 0x70D, 0xF0}, @@ -4602,8 +4661,27 @@ static struct hda_verb ca0132_init_verbs0[] = { {0x15, 0x546, 0xC9}, {0x15, 0x53B, 0xCE}, {0x15, 0x5E8, 0xC9}, - {0x15, 0x717, 0x0D}, - {0x15, 0x718, 0x20}, + {} +}; + +/* Extra init verbs for SBZ */ +static struct hda_verb sbz_init_verbs[] = { + {0x15, 0x70D, 0x20}, + {0x15, 0x70E, 0x19}, + {0x15, 0x707, 0x00}, + {0x15, 0x539, 0xCE}, + {0x15, 0x546, 0xC9}, + {0x15, 0x70D, 0xB7}, + {0x15, 0x70E, 0x09}, + {0x15, 0x707, 0x10}, + {0x15, 0x70D, 0xAF}, + {0x15, 0x70E, 0x09}, + {0x15, 0x707, 0x01}, + {0x15, 0x707, 0x05}, + {0x15, 0x70D, 0x73}, + {0x15, 0x70E, 0x09}, + {0x15, 0x707, 0x14}, + {0x15, 0x6FF, 0xC4}, {} }; @@ -4762,16 +4840,166 @@ static void ca0132_exit_chip(struct hda_codec *codec) dsp_reset(codec); } +/* + * This is for the extra volume verbs 0x797 (left) and 0x798 (right). These add + * extra precision for decibel values. If you had the dB value in floating point + * you would take the value after the decimal point, multiply by 64, and divide + * by 2. So for 8.59, it's (59 * 64) / 100. Useful if someone wanted to + * implement fixed point or floating point dB volumes. For now, I'll set them + * to 0 just incase a value has lingered from a boot into Windows. + */ +static void ca0132_alt_vol_setup(struct hda_codec *codec) +{ + snd_hda_codec_write(codec, 0x02, 0, 0x797, 0x00); + snd_hda_codec_write(codec, 0x02, 0, 0x798, 0x00); + snd_hda_codec_write(codec, 0x03, 0, 0x797, 0x00); + snd_hda_codec_write(codec, 0x03, 0, 0x798, 0x00); + snd_hda_codec_write(codec, 0x04, 0, 0x797, 0x00); + snd_hda_codec_write(codec, 0x04, 0, 0x798, 0x00); + snd_hda_codec_write(codec, 0x07, 0, 0x797, 0x00); + snd_hda_codec_write(codec, 0x07, 0, 0x798, 0x00); +} + +/* + * Extra commands that don't really fit anywhere else. + */ +static void sbz_pre_dsp_setup(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + writel(0x00820680, spec->mem_base + 0x01C); + writel(0x00820680, spec->mem_base + 0x01C); + + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xfc); + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xfd); + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xfe); + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xff); + + chipio_write(codec, 0x18b0a4, 0x000000c2); + + snd_hda_codec_write(codec, 0x11, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x44); +} + +/* + * Extra commands that don't really fit anywhere else. + */ +static void r3di_pre_dsp_setup(struct hda_codec *codec) +{ + chipio_write(codec, 0x18b0a4, 0x000000c2); + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x1E); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_ADDRESS_HIGH, 0x1C); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_DATA_WRITE, 0x5B); + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x20); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_ADDRESS_HIGH, 0x19); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_DATA_WRITE, 0x00); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_DATA_WRITE, 0x40); + + snd_hda_codec_write(codec, 0x11, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x04); +} + + +/* + * These are sent before the DSP is downloaded. Not sure + * what they do, or if they're necessary. Could possibly + * be removed. Figure they're better to leave in. + */ +static void sbz_region2_startup(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + writel(0x00000000, spec->mem_base + 0x400); + writel(0x00000000, spec->mem_base + 0x408); + writel(0x00000000, spec->mem_base + 0x40C); + writel(0x00880680, spec->mem_base + 0x01C); + writel(0x00000083, spec->mem_base + 0xC0C); + writel(0x00000030, spec->mem_base + 0xC00); + writel(0x00000000, spec->mem_base + 0xC04); + writel(0x00000003, spec->mem_base + 0xC0C); + writel(0x00000003, spec->mem_base + 0xC0C); + writel(0x00000003, spec->mem_base + 0xC0C); + writel(0x00000003, spec->mem_base + 0xC0C); + writel(0x000000C1, spec->mem_base + 0xC08); + writel(0x000000F1, spec->mem_base + 0xC08); + writel(0x00000001, spec->mem_base + 0xC08); + writel(0x000000C7, spec->mem_base + 0xC08); + writel(0x000000C1, spec->mem_base + 0xC08); + writel(0x00000080, spec->mem_base + 0xC04); +} + +/* + * Extra init functions for alternative ca0132 codecs. Done + * here so they don't clutter up the main ca0132_init function + * anymore than they have to. + */ +static void ca0132_alt_init(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + ca0132_alt_vol_setup(codec); + + switch (spec->quirk) { + case QUIRK_SBZ: + codec_dbg(codec, "SBZ alt_init"); + ca0132_gpio_init(codec); + sbz_pre_dsp_setup(codec); + snd_hda_sequence_write(codec, spec->chip_init_verbs); + snd_hda_sequence_write(codec, spec->sbz_init_verbs); + break; + case QUIRK_R3DI: + codec_dbg(codec, "R3DI alt_init"); + ca0132_gpio_init(codec); + ca0132_gpio_setup(codec); + r3di_pre_dsp_setup(codec); + snd_hda_sequence_write(codec, spec->chip_init_verbs); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x6FF, 0xC4); + break; + } +} + static int ca0132_init(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; struct auto_pin_cfg *cfg = &spec->autocfg; int i; + bool dsp_loaded; + + /* + * If the DSP is already downloaded, and init has been entered again, + * there's only two reasons for it. One, the codec has awaken from a + * suspended state, and in that case dspload_is_loaded will return + * false, and the init will be ran again. The other reason it gets + * re entered is on startup for some reason it triggers a suspend and + * resume state. In this case, it will check if the DSP is downloaded, + * and not run the init function again. For codecs using alt_functions, + * it will check if the DSP is loaded properly. + */ + if (spec->dsp_state == DSP_DOWNLOADED) { + dsp_loaded = dspload_is_loaded(codec); + if (!dsp_loaded) { + spec->dsp_reload = true; + spec->dsp_state = DSP_DOWNLOAD_INIT; + } else + return 0; + } if (spec->dsp_state != DSP_DOWNLOAD_FAILED) spec->dsp_state = DSP_DOWNLOAD_INIT; spec->curr_chip_addx = INVALID_CHIP_ADDRESS; + if (spec->quirk == QUIRK_SBZ) + sbz_region2_startup(codec); + snd_hda_power_up_pm(codec); ca0132_init_unsol(codec); @@ -4779,8 +5007,16 @@ static int ca0132_init(struct hda_codec *codec) ca0132_init_params(codec); ca0132_init_flags(codec); snd_hda_sequence_write(codec, spec->base_init_verbs); + + if (spec->quirk != QUIRK_NONE) + ca0132_alt_init(codec); + ca0132_download_dsp(codec); ca0132_refresh_widget_caps(codec); + + if (spec->quirk == QUIRK_SBZ) + writew(0x0107, spec->mem_base + 0x320); + ca0132_setup_defaults(codec); ca0132_init_analog_mic2(codec); ca0132_init_dmic(codec); @@ -4795,7 +5031,17 @@ static int ca0132_init(struct hda_codec *codec) init_input(codec, cfg->dig_in_pin, spec->dig_in); - snd_hda_sequence_write(codec, spec->chip_init_verbs); + if (spec->quirk == QUIRK_ALIENWARE || spec->quirk == QUIRK_NONE) { + snd_hda_sequence_write(codec, spec->chip_init_verbs); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_ID_SET, 0x0D); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_VALUE_SET, 0x20); + } + + if (spec->quirk == QUIRK_SBZ) + ca0132_gpio_setup(codec); + snd_hda_sequence_write(codec, spec->spec_init_verbs); ca0132_select_out(codec); @@ -4803,6 +5049,15 @@ static int ca0132_init(struct hda_codec *codec) snd_hda_jack_report_sync(codec); + /* + * Re set the PlayEnhancement switch on a resume event, because the + * controls will not be reloaded. + */ + if (spec->dsp_reload) { + spec->dsp_reload = false; + ca0132_pe_switch_set(codec); + } + snd_hda_power_down_pm(codec); return 0; @@ -4989,6 +5244,8 @@ static int ca0132_prepare_verbs(struct hda_codec *codec) struct ca0132_spec *spec = codec->spec; spec->chip_init_verbs = ca0132_init_verbs0; + if (spec->quirk == QUIRK_SBZ) + spec->sbz_init_verbs = sbz_init_verbs; spec->spec_init_verbs = kzalloc(sizeof(struct hda_verb) * NUM_SPEC_VERBS, GFP_KERNEL); if (!spec->spec_init_verbs) return -ENOMEM; -- cgit v1.2.3 From 009b8f979bf8cb5f7ec6d3dd7683585122ed10f8 Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Tue, 8 May 2018 13:20:06 -0400 Subject: ALSA: hda/ca0132: update core functions for sbz + r3di This patch updates core functions to accommodate the Sound Blaster Z and Recon3Di by changing which functions they use. It also adds the ability to enable/disable streams. Signed-off-by: Connor McAdams Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 176 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 153 insertions(+), 23 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 508ba35eadbb..096eb3bbeb48 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -774,6 +774,13 @@ struct ca0132_spec { * switching, and other unknown commands. */ void __iomem *mem_base; + + /* + * Whether or not to use the alt functions like alt_select_out, + * alt_select_in, etc. Only used on desktop codecs for now, because of + * surround sound support. + */ + bool use_alt_functions; }; /* @@ -1113,6 +1120,42 @@ static void chipio_set_control_param(struct hda_codec *codec, } } +/* + * Set chip parameters through the chip I/O widget. NO MUTEX. + */ +static void chipio_set_control_param_no_mutex(struct hda_codec *codec, + enum control_param_id param_id, int param_val) +{ + int val; + + if ((param_id < 32) && (param_val < 8)) { + val = (param_val << 5) | (param_id); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_SET, val); + } else { + if (chipio_send(codec, VENDOR_CHIPIO_STATUS, 0) == 0) { + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_ID_SET, + param_id); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_VALUE_SET, + param_val); + } + } +} + +/* + * Enable/Disable audio stream. + */ +static void chipio_set_stream_control(struct hda_codec *codec, + int streamid, int enable) +{ + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_ID, streamid); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_CONTROL, enable); +} + /* * Set sampling rate of the connection point. */ @@ -2631,14 +2674,16 @@ exit: */ static void dspload_post_setup(struct hda_codec *codec) { + struct ca0132_spec *spec = codec->spec; codec_dbg(codec, "---- dspload_post_setup ------\n"); + if (!spec->use_alt_functions) { + /*set DSP speaker to 2.0 configuration*/ + chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x18), 0x08080080); + chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x19), 0x3f800000); - /*set DSP speaker to 2.0 configuration*/ - chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x18), 0x08080080); - chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x19), 0x3f800000); - - /*update write pointer*/ - chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x29), 0x00000002); + /*update write pointer*/ + chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x29), 0x00000002); + } } /** @@ -3527,7 +3572,7 @@ static int ca0132_voicefx_set(struct hda_codec *codec, int enable) static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val) { struct ca0132_spec *spec = codec->spec; - unsigned int on; + unsigned int on, tmp; int num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT; int err = 0; int idx = nid - EFFECT_START_NID; @@ -3551,6 +3596,39 @@ static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val) /* Voice Focus applies to 2-ch Mic, Digital Mic */ if ((nid == VOICE_FOCUS) && (spec->cur_mic_type != DIGITAL_MIC)) val = 0; + + /* If Voice Focus on SBZ, set to two channel. */ + if ((nid == VOICE_FOCUS) && (spec->quirk == QUIRK_SBZ)) { + if (spec->effects_switch[CRYSTAL_VOICE - + EFFECT_START_NID]) { + + if (spec->effects_switch[VOICE_FOCUS - + EFFECT_START_NID]) { + tmp = FLOAT_TWO; + val = 1; + } else + tmp = FLOAT_ONE; + + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + } + } + /* + * For SBZ noise reduction, there's an extra command + * to module ID 0x47. No clue why. + */ + if ((nid == NOISE_REDUCTION) && (spec->quirk == QUIRK_SBZ)) { + if (spec->effects_switch[CRYSTAL_VOICE - + EFFECT_START_NID]) { + if (spec->effects_switch[NOISE_REDUCTION - + EFFECT_START_NID]) + tmp = FLOAT_ONE; + else + tmp = FLOAT_ZERO; + } else + tmp = FLOAT_ZERO; + + dspio_set_uint_param(codec, 0x47, 0x00, tmp); + } } codec_dbg(codec, "ca0132_effect_set: nid=0x%x, val=%ld\n", @@ -4185,12 +4263,16 @@ static int ca0132_build_pcms(struct hda_codec *codec) info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1; info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[0]; - info = snd_hda_codec_pcm_new(codec, "CA0132 Analog Mic-In2"); - if (!info) - return -ENOMEM; - info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0132_pcm_analog_capture; - info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1; - info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[1]; + /* With the DSP enabled, desktops don't use this ADC. */ + if (spec->use_alt_functions) { + info = snd_hda_codec_pcm_new(codec, "CA0132 Analog Mic-In2"); + if (!info) + return -ENOMEM; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = + ca0132_pcm_analog_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[1]; + } info = snd_hda_codec_pcm_new(codec, "CA0132 What U Hear"); if (!info) @@ -4445,12 +4527,32 @@ static void ca0132_setup_defaults(struct hda_codec *codec) */ static void ca0132_init_flags(struct hda_codec *codec) { - chipio_set_control_flag(codec, CONTROL_FLAG_IDLE_ENABLE, 0); - chipio_set_control_flag(codec, CONTROL_FLAG_PORT_A_COMMON_MODE, 0); - chipio_set_control_flag(codec, CONTROL_FLAG_PORT_D_COMMON_MODE, 0); - chipio_set_control_flag(codec, CONTROL_FLAG_PORT_A_10KOHM_LOAD, 0); - chipio_set_control_flag(codec, CONTROL_FLAG_PORT_D_10KOHM_LOAD, 0); - chipio_set_control_flag(codec, CONTROL_FLAG_ADC_C_HIGH_PASS, 1); + struct ca0132_spec *spec = codec->spec; + + if (spec->use_alt_functions) { + chipio_set_control_flag(codec, CONTROL_FLAG_DSP_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_DAC_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_ADC_B_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_ADC_C_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_SRC_RATE_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_IDLE_ENABLE, 0); + chipio_set_control_flag(codec, CONTROL_FLAG_SPDIF2OUT, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_D_10KOHM_LOAD, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_A_10KOHM_LOAD, 1); + } else { + chipio_set_control_flag(codec, CONTROL_FLAG_IDLE_ENABLE, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_A_COMMON_MODE, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_D_COMMON_MODE, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_A_10KOHM_LOAD, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_D_10KOHM_LOAD, 0); + chipio_set_control_flag(codec, CONTROL_FLAG_ADC_C_HIGH_PASS, 1); + } } /* @@ -4458,6 +4560,16 @@ static void ca0132_init_flags(struct hda_codec *codec) */ static void ca0132_init_params(struct hda_codec *codec) { + struct ca0132_spec *spec = codec->spec; + + if (spec->use_alt_functions) { + chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000); + chipio_set_conn_rate(codec, 0x0B, SR_48_000); + chipio_set_control_param(codec, CONTROL_PARAM_SPDIF1_SOURCE, 0); + chipio_set_control_param(codec, 0, 0); + chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, 0); + } + chipio_set_control_param(codec, CONTROL_PARAM_PORTA_160OHM_GAIN, 6); chipio_set_control_param(codec, CONTROL_PARAM_PORTD_160OHM_GAIN, 6); } @@ -4558,7 +4670,8 @@ static void ca0132_download_dsp(struct hda_codec *codec) spec->dsp_state = DSP_DOWNLOADED; } - if (spec->dsp_state == DSP_DOWNLOADED) + /* For codecs using alt functions, this is already done earlier */ + if (spec->dsp_state == DSP_DOWNLOADED && (!spec->use_alt_functions)) ca0132_set_dsp_msr(codec, true); } @@ -4605,7 +4718,7 @@ static void ca0132_init_unsol(struct hda_codec *codec) snd_hda_jack_detect_enable_callback(codec, UNSOL_TAG_DSP, ca0132_process_dsp_response); /* Front headphone jack detection */ - if (spec->quirk == QUIRK_SBZ || spec->quirk == QUIRK_R3DI) + if (spec->use_alt_functions) snd_hda_jack_detect_enable_callback(codec, spec->unsol_tag_front_hp, hp_callback); } @@ -4798,12 +4911,16 @@ static void sbz_gpio_shutdown_commands(struct hda_codec *codec, int dir, static void sbz_exit_chip(struct hda_codec *codec) { + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0); /* Mess with GPIO */ sbz_gpio_shutdown_commands(codec, 0x07, 0x07, -1); sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x05); sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x01); + chipio_set_stream_control(codec, 0x14, 0); + chipio_set_stream_control(codec, 0x0C, 0); chipio_set_conn_rate(codec, 0x41, SR_192_000); chipio_set_conn_rate(codec, 0x91, SR_192_000); @@ -4814,6 +4931,7 @@ static void sbz_exit_chip(struct hda_codec *codec) sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x07); sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x06); + chipio_set_stream_control(codec, 0x0C, 0); chipio_set_control_param(codec, 0x0D, 0x24); @@ -5031,7 +5149,7 @@ static int ca0132_init(struct hda_codec *codec) init_input(codec, cfg->dig_in_pin, spec->dig_in); - if (spec->quirk == QUIRK_ALIENWARE || spec->quirk == QUIRK_NONE) { + if (!spec->use_alt_functions) { snd_hda_sequence_write(codec, spec->chip_init_verbs); snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, VENDOR_CHIPIO_PARAM_EX_ID_SET, 0x0D); @@ -5116,7 +5234,7 @@ static void ca0132_config(struct hda_codec *codec) spec->multiout.dac_nids = spec->dacs; spec->multiout.num_dacs = 3; - if (spec->quirk == QUIRK_NONE || spec->quirk == QUIRK_ALIENWARE) + if (!spec->use_alt_functions) spec->multiout.max_channels = 2; else spec->multiout.max_channels = 6; @@ -5318,10 +5436,22 @@ static int patch_ca0132(struct hda_codec *codec) spec->quirk = QUIRK_NONE; } } + spec->dsp_state = DSP_DOWNLOAD_INIT; spec->num_mixers = 1; spec->mixers[0] = ca0132_mixer; + /* Setup whether or not to use alt functions */ + switch (spec->quirk) { + case QUIRK_SBZ: + case QUIRK_R3DI: + spec->use_alt_functions = true; + break; + default: + spec->use_alt_functions = false; + break; + } + spec->base_init_verbs = ca0132_base_init_verbs; spec->base_exit_verbs = ca0132_base_exit_verbs; -- cgit v1.2.3 From 38ba69ffcea397010a0887af28b495167dbf6f39 Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Tue, 8 May 2018 13:20:07 -0400 Subject: ALSA: hda/ca0132: add dsp setup related commands for the sbz Add dsp setup related functions for the Sound Blaster Z, along with other helper functions. Also, add sbz_dsp_startup_check, which fixes a bug where the card sometimes starts up and has no sound. Signed-off-by: Connor McAdams Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 341 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 335 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 096eb3bbeb48..bd7b30a43d4f 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -49,6 +49,7 @@ #define FLOAT_ZERO 0x00000000 #define FLOAT_ONE 0x3f800000 #define FLOAT_TWO 0x40000000 +#define FLOAT_THREE 0x40400000 #define FLOAT_MINUS_5 0xc0a00000 #define UNSOL_TAG_DSP 0x16 @@ -748,6 +749,7 @@ struct ca0132_spec { unsigned int scp_resp_data[4]; unsigned int scp_resp_count; bool alt_firmware_present; + bool startup_check_entered; bool dsp_reload; /* mixer and effects related */ @@ -1027,6 +1029,29 @@ exit: return err; } +/* + * Write given value to the given address through the chip I/O widget. + * not protected by the Mutex + */ +static int chipio_write_no_mutex(struct hda_codec *codec, + unsigned int chip_addx, const unsigned int data) +{ + int err; + + + /* write the address, and if successful proceed to write data */ + err = chipio_write_address(codec, chip_addx); + if (err < 0) + goto exit; + + err = chipio_write_data(codec, data); + if (err < 0) + goto exit; + +exit: + return err; +} + /* * Write multiple values to the given address through the chip I/O widget. * protected by the Mutex @@ -1143,6 +1168,32 @@ static void chipio_set_control_param_no_mutex(struct hda_codec *codec, } } } +/* + * Connect stream to a source point, and then connect + * that source point to a destination point. + */ +static void chipio_set_stream_source_dest(struct hda_codec *codec, + int streamid, int source_point, int dest_point) +{ + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_ID, streamid); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_SOURCE_CONN_POINT, source_point); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_DEST_CONN_POINT, dest_point); +} + +/* + * Set number of channels in the selected stream. + */ +static void chipio_set_stream_channels(struct hda_codec *codec, + int streamid, unsigned int channels) +{ + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_ID, streamid); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAMS_CHANNELS, channels); +} /* * Enable/Disable audio stream. @@ -1156,6 +1207,19 @@ static void chipio_set_stream_control(struct hda_codec *codec, CONTROL_PARAM_STREAM_CONTROL, enable); } + +/* + * Set sampling rate of the connection point. NO MUTEX. + */ +static void chipio_set_conn_rate_no_mutex(struct hda_codec *codec, + int connid, enum ca0132_sample_rate rate) +{ + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_CONN_POINT_ID, connid); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_CONN_POINT_SAMPLE_RATE, rate); +} + /* * Set sampling rate of the connection point. */ @@ -4478,6 +4542,123 @@ static void ca0132_refresh_widget_caps(struct hda_codec *codec) } } +/* + * Initialize Sound Blaster Z analog microphones. + */ +static void sbz_init_analog_mics(struct hda_codec *codec) +{ + unsigned int tmp; + + /* Mic 1 Setup */ + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + tmp = FLOAT_THREE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + /* Mic 2 Setup, even though it isn't connected on SBZ */ + chipio_set_conn_rate(codec, MEM_CONNID_MICIN2, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT2, SR_96_000); + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x01, tmp); + +} + +/* + * Sets the source of stream 0x14 to connpointID 0x48, and the destination + * connpointID to 0x91. If this isn't done, the destination is 0x71, and + * you get no sound. I'm guessing this has to do with the Sound Blaster Z + * having an updated DAC, which changes the destination to that DAC. + */ +static void sbz_connect_streams(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + + codec_dbg(codec, "Connect Streams entered, mutex locked and loaded.\n"); + + chipio_set_stream_channels(codec, 0x0C, 6); + chipio_set_stream_control(codec, 0x0C, 1); + + /* This value is 0x43 for 96khz, and 0x83 for 192khz. */ + chipio_write_no_mutex(codec, 0x18a020, 0x00000043); + + /* Setup stream 0x14 with it's source and destination points */ + chipio_set_stream_source_dest(codec, 0x14, 0x48, 0x91); + chipio_set_conn_rate_no_mutex(codec, 0x48, SR_96_000); + chipio_set_conn_rate_no_mutex(codec, 0x91, SR_96_000); + chipio_set_stream_channels(codec, 0x14, 2); + chipio_set_stream_control(codec, 0x14, 1); + + codec_dbg(codec, "Connect Streams exited, mutex released.\n"); + + mutex_unlock(&spec->chipio_mutex); + +} + +/* + * Write data through ChipIO to setup proper stream destinations. + * Not sure how it exactly works, but it seems to direct data + * to different destinations. Example is f8 to c0, e0 to c0. + * All I know is, if you don't set these, you get no sound. + */ +static void sbz_chipio_startup_data(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + codec_dbg(codec, "Startup Data entered, mutex locked and loaded.\n"); + + /* These control audio output */ + chipio_write_no_mutex(codec, 0x190060, 0x0001f8c0); + chipio_write_no_mutex(codec, 0x190064, 0x0001f9c1); + chipio_write_no_mutex(codec, 0x190068, 0x0001fac6); + chipio_write_no_mutex(codec, 0x19006c, 0x0001fbc7); + /* Signal to update I think */ + chipio_write_no_mutex(codec, 0x19042c, 0x00000001); + + chipio_set_stream_channels(codec, 0x0C, 6); + chipio_set_stream_control(codec, 0x0C, 1); + /* No clue what these control */ + chipio_write_no_mutex(codec, 0x190030, 0x0001e0c0); + chipio_write_no_mutex(codec, 0x190034, 0x0001e1c1); + chipio_write_no_mutex(codec, 0x190038, 0x0001e4c2); + chipio_write_no_mutex(codec, 0x19003c, 0x0001e5c3); + chipio_write_no_mutex(codec, 0x190040, 0x0001e2c4); + chipio_write_no_mutex(codec, 0x190044, 0x0001e3c5); + chipio_write_no_mutex(codec, 0x190048, 0x0001e8c6); + chipio_write_no_mutex(codec, 0x19004c, 0x0001e9c7); + chipio_write_no_mutex(codec, 0x190050, 0x0001ecc8); + chipio_write_no_mutex(codec, 0x190054, 0x0001edc9); + chipio_write_no_mutex(codec, 0x190058, 0x0001eaca); + chipio_write_no_mutex(codec, 0x19005c, 0x0001ebcb); + + chipio_write_no_mutex(codec, 0x19042c, 0x00000001); + + codec_dbg(codec, "Startup Data exited, mutex released.\n"); + mutex_unlock(&spec->chipio_mutex); +} + +static void sbz_dsp_initial_mic_setup(struct hda_codec *codec) +{ + unsigned int tmp; + + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0); + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + + tmp = FLOAT_THREE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + + chipio_write(codec, 0x18b098, 0x0000000c); + chipio_write(codec, 0x18b09C, 0x0000000c); +} + /* * Setup default parameters for DSP */ @@ -4522,6 +4703,83 @@ static void ca0132_setup_defaults(struct hda_codec *codec) dspio_set_uint_param(codec, 0x31, 0x00, tmp); } +/* + * Setup default parameters for the Sound Blaster Z DSP. A lot more going on + * than the Chromebook setup. + */ +static void sbz_setup_defaults(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp, stream_format; + int num_fx; + int idx, i; + + if (spec->dsp_state != DSP_DOWNLOADED) + return; + + + sbz_init_analog_mics(codec); + + sbz_connect_streams(codec); + + sbz_chipio_startup_data(codec); + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + + /* + * Sets internal input loopback to off, used to have a switch to + * enable input loopback, but turned out to be way too buggy. + */ + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x37, 0x08, tmp); + dspio_set_uint_param(codec, 0x37, 0x10, tmp); + + /*remove DSP headroom*/ + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x96, 0x3C, tmp); + + /* set WUH source */ + tmp = FLOAT_TWO; + dspio_set_uint_param(codec, 0x31, 0x00, tmp); + chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000); + + /* Set speaker source? */ + dspio_set_uint_param(codec, 0x32, 0x00, tmp); + + sbz_dsp_initial_mic_setup(codec); + + + /* out, in effects + voicefx */ + num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1; + for (idx = 0; idx < num_fx; idx++) { + for (i = 0; i <= ca0132_effects[idx].params; i++) { + dspio_set_uint_param(codec, + ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[i], + ca0132_effects[idx].def_vals[i]); + } + } + + /* + * Have to make a stream to bind the sound output to, otherwise + * you'll get dead audio. Before I did this, it would bind to an + * audio input, and would never work + */ + stream_format = snd_hdac_calc_stream_format(48000, 2, + SNDRV_PCM_FORMAT_S32_LE, 32, 0); + + snd_hda_codec_setup_stream(codec, spec->dacs[0], spec->dsp_stream_id, + 0, stream_format); + + snd_hda_codec_cleanup_stream(codec, spec->dacs[0]); + + snd_hda_codec_setup_stream(codec, spec->dacs[0], spec->dsp_stream_id, + 0, stream_format); + + snd_hda_codec_cleanup_stream(codec, spec->dacs[0]); +} + /* * Initialization of flags in chip */ @@ -4958,6 +5216,71 @@ static void ca0132_exit_chip(struct hda_codec *codec) dsp_reset(codec); } +/* + * This fixes a problem that was hard to reproduce. Very rarely, I would + * boot up, and there would be no sound, but the DSP indicated it had loaded + * properly. I did a few memory dumps to see if anything was different, and + * there were a few areas of memory uninitialized with a1a2a3a4. This function + * checks if those areas are uninitialized, and if they are, it'll attempt to + * reload the card 3 times. Usually it fixes by the second. + */ +static void sbz_dsp_startup_check(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int dsp_data_check[4]; + unsigned int cur_address = 0x390; + unsigned int i; + unsigned int failure = 0; + unsigned int reload = 3; + + if (spec->startup_check_entered) + return; + + spec->startup_check_entered = true; + + for (i = 0; i < 4; i++) { + chipio_read(codec, cur_address, &dsp_data_check[i]); + cur_address += 0x4; + } + for (i = 0; i < 4; i++) { + if (dsp_data_check[i] == 0xa1a2a3a4) + failure = 1; + } + + codec_dbg(codec, "Startup Check: %d ", failure); + if (failure) + codec_info(codec, "DSP not initialized properly. Attempting to fix."); + /* + * While the failure condition is true, and we haven't reached our + * three reload limit, continue trying to reload the driver and + * fix the issue. + */ + while (failure && (reload != 0)) { + codec_info(codec, "Reloading... Tries left: %d", reload); + sbz_exit_chip(codec); + spec->dsp_state = DSP_DOWNLOAD_INIT; + codec->patch_ops.init(codec); + failure = 0; + for (i = 0; i < 4; i++) { + chipio_read(codec, cur_address, &dsp_data_check[i]); + cur_address += 0x4; + } + for (i = 0; i < 4; i++) { + if (dsp_data_check[i] == 0xa1a2a3a4) + failure = 1; + } + reload--; + } + + if (!failure && reload < 3) + codec_info(codec, "DSP fixed."); + + if (!failure) + return; + + codec_info(codec, "DSP failed to initialize properly. Either try a full shutdown or a suspend to clear the internal memory."); +} + /* * This is for the extra volume verbs 0x797 (left) and 0x798 (right). These add * extra precision for decibel values. If you had the dB value in floating point @@ -5107,8 +5430,11 @@ static int ca0132_init(struct hda_codec *codec) if (!dsp_loaded) { spec->dsp_reload = true; spec->dsp_state = DSP_DOWNLOAD_INIT; - } else + } else { + if (spec->quirk == QUIRK_SBZ) + sbz_dsp_startup_check(codec); return 0; + } } if (spec->dsp_state != DSP_DOWNLOAD_FAILED) @@ -5121,7 +5447,6 @@ static int ca0132_init(struct hda_codec *codec) snd_hda_power_up_pm(codec); ca0132_init_unsol(codec); - ca0132_init_params(codec); ca0132_init_flags(codec); snd_hda_sequence_write(codec, spec->base_init_verbs); @@ -5135,9 +5460,11 @@ static int ca0132_init(struct hda_codec *codec) if (spec->quirk == QUIRK_SBZ) writew(0x0107, spec->mem_base + 0x320); - ca0132_setup_defaults(codec); - ca0132_init_analog_mic2(codec); - ca0132_init_dmic(codec); + if (spec->quirk != QUIRK_SBZ) { + ca0132_setup_defaults(codec); + ca0132_init_analog_mic2(codec); + ca0132_init_dmic(codec); + } for (i = 0; i < spec->num_outputs; i++) init_output(codec, spec->out_pins[i], spec->dacs[0]); @@ -5157,8 +5484,10 @@ static int ca0132_init(struct hda_codec *codec) VENDOR_CHIPIO_PARAM_EX_VALUE_SET, 0x20); } - if (spec->quirk == QUIRK_SBZ) + if (spec->quirk == QUIRK_SBZ) { ca0132_gpio_setup(codec); + sbz_setup_defaults(codec); + } snd_hda_sequence_write(codec, spec->spec_init_verbs); -- cgit v1.2.3 From 7e6ed62ebedb352be3a6f0907bcab25789db7914 Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Tue, 8 May 2018 13:20:08 -0400 Subject: ALSA: hda/ca0132: Add dsp setup + gpio functions for r3di Adds dsp setup functions for Recon3Di as well as the GPIO functions specific to it. Signed-off-by: Connor McAdams Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 149 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index bd7b30a43d4f..dd98b7731dc4 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -2907,6 +2907,76 @@ static void ca0132_gpio_setup(struct hda_codec *codec) } } +/* + * GPIO control functions for the Recon3D integrated. + */ + +enum r3di_gpio_bit { + /* Bit 1 - Switch between front/rear mic. 0 = rear, 1 = front */ + R3DI_MIC_SELECT_BIT = 1, + /* Bit 2 - Switch between headphone/line out. 0 = Headphone, 1 = Line */ + R3DI_OUT_SELECT_BIT = 2, + /* + * I dunno what this actually does, but it stays on until the dsp + * is downloaded. + */ + R3DI_GPIO_DSP_DOWNLOADING = 3, + /* + * Same as above, no clue what it does, but it comes on after the dsp + * is downloaded. + */ + R3DI_GPIO_DSP_DOWNLOADED = 4 +}; + +enum r3di_mic_select { + /* Set GPIO bit 1 to 0 for rear mic */ + R3DI_REAR_MIC = 0, + /* Set GPIO bit 1 to 1 for front microphone*/ + R3DI_FRONT_MIC = 1 +}; + +enum r3di_out_select { + /* Set GPIO bit 2 to 0 for headphone */ + R3DI_HEADPHONE_OUT = 0, + /* Set GPIO bit 2 to 1 for speaker */ + R3DI_LINE_OUT = 1 +}; +enum r3di_dsp_status { + /* Set GPIO bit 3 to 1 until DSP is downloaded */ + R3DI_DSP_DOWNLOADING = 0, + /* Set GPIO bit 4 to 1 once DSP is downloaded */ + R3DI_DSP_DOWNLOADED = 1 +}; + +static void r3di_gpio_dsp_status_set(struct hda_codec *codec, + enum r3di_dsp_status dsp_status) +{ + unsigned int cur_gpio; + + /* Get the current GPIO Data setup */ + cur_gpio = snd_hda_codec_read(codec, 0x01, 0, AC_VERB_GET_GPIO_DATA, 0); + + switch (dsp_status) { + case R3DI_DSP_DOWNLOADING: + cur_gpio |= (1 << R3DI_GPIO_DSP_DOWNLOADING); + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); + break; + case R3DI_DSP_DOWNLOADED: + /* Set DOWNLOADING bit to 0. */ + cur_gpio &= ~(1 << R3DI_GPIO_DSP_DOWNLOADING); + + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); + + cur_gpio |= (1 << R3DI_GPIO_DSP_DOWNLOADED); + break; + } + + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); +} + /* * PCM callbacks */ @@ -4542,6 +4612,30 @@ static void ca0132_refresh_widget_caps(struct hda_codec *codec) } } +/* + * Recon3Di r3di_setup_defaults sub functions. + */ + +static void r3di_dsp_initial_mic_setup(struct hda_codec *codec) +{ + unsigned int tmp; + + /* Mic 1 Setup */ + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + /* This ConnPointID is unique to Recon3Di. Haven't seen it elsewhere */ + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + /* Mic 2 Setup, even though it isn't connected on SBZ */ + chipio_set_conn_rate(codec, MEM_CONNID_MICIN2, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT2, SR_96_000); + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x01, tmp); +} + /* * Initialize Sound Blaster Z analog microphones. */ @@ -4703,6 +4797,50 @@ static void ca0132_setup_defaults(struct hda_codec *codec) dspio_set_uint_param(codec, 0x31, 0x00, tmp); } +/* + * Setup default parameters for Recon3Di DSP. + */ + +static void r3di_setup_defaults(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + int num_fx; + int idx, i; + + if (spec->dsp_state != DSP_DOWNLOADED) + return; + + + r3di_dsp_initial_mic_setup(codec); + + /*remove DSP headroom*/ + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x96, 0x3C, tmp); + + /* set WUH source */ + tmp = FLOAT_TWO; + dspio_set_uint_param(codec, 0x31, 0x00, tmp); + chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000); + + /* Set speaker source? */ + dspio_set_uint_param(codec, 0x32, 0x00, tmp); + + r3di_gpio_dsp_status_set(codec, R3DI_DSP_DOWNLOADED); + + /* Setup effect defaults */ + num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1; + for (idx = 0; idx < num_fx; idx++) { + for (i = 0; i <= ca0132_effects[idx].params; i++) { + dspio_set_uint_param(codec, + ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[i], + ca0132_effects[idx].def_vals[i]); + } + } + +} + /* * Setup default parameters for the Sound Blaster Z DSP. A lot more going on * than the Chromebook setup. @@ -5401,6 +5539,7 @@ static void ca0132_alt_init(struct hda_codec *codec) codec_dbg(codec, "R3DI alt_init"); ca0132_gpio_init(codec); ca0132_gpio_setup(codec); + r3di_gpio_dsp_status_set(codec, R3DI_DSP_DOWNLOADING); r3di_pre_dsp_setup(codec); snd_hda_sequence_write(codec, spec->chip_init_verbs); snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x6FF, 0xC4); @@ -5449,21 +5588,29 @@ static int ca0132_init(struct hda_codec *codec) ca0132_init_unsol(codec); ca0132_init_params(codec); ca0132_init_flags(codec); + snd_hda_sequence_write(codec, spec->base_init_verbs); if (spec->quirk != QUIRK_NONE) ca0132_alt_init(codec); ca0132_download_dsp(codec); + ca0132_refresh_widget_caps(codec); if (spec->quirk == QUIRK_SBZ) writew(0x0107, spec->mem_base + 0x320); - if (spec->quirk != QUIRK_SBZ) { + switch (spec->quirk) { + case QUIRK_R3DI: + r3di_setup_defaults(codec); + break; + case QUIRK_NONE: + case QUIRK_ALIENWARE: ca0132_setup_defaults(codec); ca0132_init_analog_mic2(codec); ca0132_init_dmic(codec); + break; } for (i = 0; i < spec->num_outputs; i++) -- cgit v1.2.3 From 447fd8e9a88e466326a07c85c1344e45d3a6cddf Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Tue, 8 May 2018 13:20:09 -0400 Subject: ALSA: hda/ca0132: add the ability to set src_id on scp commands This patch adds the ability to change the src_id on scp commands, which is used in the dsp setup of the Recon3Di and the Sound Blaster Z. It also makes sure to maintain backwards compatibility with the older dspio_set_uint_param function, and sets it's src to the default 0x20. Signed-off-by: Connor McAdams Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 86 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 12 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index dd98b7731dc4..3b83f07e8be7 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -1583,8 +1583,8 @@ static int dspio_send_scp_message(struct hda_codec *codec, * Returns zero or a negative error code. */ static int dspio_scp(struct hda_codec *codec, - int mod_id, int req, int dir, void *data, unsigned int len, - void *reply, unsigned int *reply_len) + int mod_id, int src_id, int req, int dir, const void *data, + unsigned int len, void *reply, unsigned int *reply_len) { int status = 0; struct scp_msg scp_send, scp_reply; @@ -1608,7 +1608,7 @@ static int dspio_scp(struct hda_codec *codec, return -EINVAL; } - scp_send.hdr = make_scp_header(mod_id, 0x20, (dir == SCP_GET), req, + scp_send.hdr = make_scp_header(mod_id, src_id, (dir == SCP_GET), req, 0, 0, 0, len/sizeof(unsigned int)); if (data != NULL && len > 0) { len = min((unsigned int)(sizeof(scp_send.data)), len); @@ -1665,15 +1665,24 @@ static int dspio_scp(struct hda_codec *codec, * Set DSP parameters */ static int dspio_set_param(struct hda_codec *codec, int mod_id, - int req, void *data, unsigned int len) + int src_id, int req, const void *data, unsigned int len) { - return dspio_scp(codec, mod_id, req, SCP_SET, data, len, NULL, NULL); + return dspio_scp(codec, mod_id, src_id, req, SCP_SET, data, len, NULL, + NULL); } static int dspio_set_uint_param(struct hda_codec *codec, int mod_id, - int req, unsigned int data) + int req, const unsigned int data) { - return dspio_set_param(codec, mod_id, req, &data, sizeof(unsigned int)); + return dspio_set_param(codec, mod_id, 0x20, req, &data, + sizeof(unsigned int)); +} + +static int dspio_set_uint_param_no_source(struct hda_codec *codec, int mod_id, + int req, const unsigned int data) +{ + return dspio_set_param(codec, mod_id, 0x00, req, &data, + sizeof(unsigned int)); } /* @@ -1685,8 +1694,9 @@ static int dspio_alloc_dma_chan(struct hda_codec *codec, unsigned int *dma_chan) unsigned int size = sizeof(dma_chan); codec_dbg(codec, " dspio_alloc_dma_chan() -- begin\n"); - status = dspio_scp(codec, MASTERCONTROL, MASTERCONTROL_ALLOC_DMA_CHAN, - SCP_GET, NULL, 0, dma_chan, &size); + status = dspio_scp(codec, MASTERCONTROL, 0x20, + MASTERCONTROL_ALLOC_DMA_CHAN, SCP_GET, NULL, 0, + dma_chan, &size); if (status < 0) { codec_dbg(codec, "dspio_alloc_dma_chan: SCP Failed\n"); @@ -1715,8 +1725,9 @@ static int dspio_free_dma_chan(struct hda_codec *codec, unsigned int dma_chan) codec_dbg(codec, " dspio_free_dma_chan() -- begin\n"); codec_dbg(codec, "dspio_free_dma_chan: chan=%d\n", dma_chan); - status = dspio_scp(codec, MASTERCONTROL, MASTERCONTROL_ALLOC_DMA_CHAN, - SCP_SET, &dma_chan, sizeof(dma_chan), NULL, &dummy); + status = dspio_scp(codec, MASTERCONTROL, 0x20, + MASTERCONTROL_ALLOC_DMA_CHAN, SCP_SET, &dma_chan, + sizeof(dma_chan), NULL, &dummy); if (status < 0) { codec_dbg(codec, "dspio_free_dma_chan: SCP Failed\n"); @@ -3230,7 +3241,7 @@ static int tuning_ctl_set(struct hda_codec *codec, hda_nid_t nid, break; snd_hda_power_up(codec); - dspio_set_param(codec, ca0132_tuning_ctls[i].mid, + dspio_set_param(codec, ca0132_tuning_ctls[i].mid, 0x20, ca0132_tuning_ctls[i].req, &(lookup[idx]), sizeof(unsigned int)); snd_hda_power_down(codec); @@ -4616,6 +4627,27 @@ static void ca0132_refresh_widget_caps(struct hda_codec *codec) * Recon3Di r3di_setup_defaults sub functions. */ +static void r3di_dsp_scp_startup(struct hda_codec *codec) +{ + unsigned int tmp; + + tmp = 0x00000000; + dspio_set_uint_param_no_source(codec, 0x80, 0x0A, tmp); + + tmp = 0x00000001; + dspio_set_uint_param_no_source(codec, 0x80, 0x0B, tmp); + + tmp = 0x00000004; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + + tmp = 0x00000005; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + + tmp = 0x00000000; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + +} + static void r3di_dsp_initial_mic_setup(struct hda_codec *codec) { unsigned int tmp; @@ -4733,6 +4765,34 @@ static void sbz_chipio_startup_data(struct hda_codec *codec) mutex_unlock(&spec->chipio_mutex); } +/* + * Sound Blaster Z uses these after DSP is loaded. Weird SCP commands + * without a 0x20 source like normal. + */ +static void sbz_dsp_scp_startup(struct hda_codec *codec) +{ + unsigned int tmp; + + tmp = 0x00000003; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + + tmp = 0x00000000; + dspio_set_uint_param_no_source(codec, 0x80, 0x0A, tmp); + + tmp = 0x00000001; + dspio_set_uint_param_no_source(codec, 0x80, 0x0B, tmp); + + tmp = 0x00000004; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + + tmp = 0x00000005; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + + tmp = 0x00000000; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + +} + static void sbz_dsp_initial_mic_setup(struct hda_codec *codec) { unsigned int tmp; @@ -4811,6 +4871,7 @@ static void r3di_setup_defaults(struct hda_codec *codec) if (spec->dsp_state != DSP_DOWNLOADED) return; + r3di_dsp_scp_startup(codec); r3di_dsp_initial_mic_setup(codec); @@ -4855,6 +4916,7 @@ static void sbz_setup_defaults(struct hda_codec *codec) if (spec->dsp_state != DSP_DOWNLOADED) return; + sbz_dsp_scp_startup(codec); sbz_init_analog_mics(codec); -- cgit v1.2.3 From 7cb9d94c05de9eef0af53bf7fcb0168b2e0e2267 Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Tue, 8 May 2018 13:20:10 -0400 Subject: ALSA: hda/ca0132: add alt_select_in/out for R3Di + SBZ Add functions ca0132_alt_select_out and ca0132_alt_select_in for switching outputs and inputs for r3di and sbz. Also, add enumerated controls for selecting output and input source. Signed-off-by: Connor McAdams Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 625 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 607 insertions(+), 18 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 3b83f07e8be7..60e8a0ce530e 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -50,6 +50,7 @@ #define FLOAT_ONE 0x3f800000 #define FLOAT_TWO 0x40000000 #define FLOAT_THREE 0x40400000 +#define FLOAT_EIGHT 0x41000000 #define FLOAT_MINUS_5 0xc0a00000 #define UNSOL_TAG_DSP 0x16 @@ -91,9 +92,11 @@ MODULE_FIRMWARE(R3DI_EFX_FILE); static const char *dirstr[2] = { "Playback", "Capture" }; +#define NUM_OF_OUTPUTS 3 enum { SPEAKER_OUT, - HEADPHONE_OUT + HEADPHONE_OUT, + SURROUND_OUT }; enum { @@ -101,6 +104,15 @@ enum { LINE_MIC_IN }; +/* Strings for Input Source Enum Control */ +static const char *in_src_str[3] = {"Rear Mic", "Line", "Front Mic" }; +#define IN_SRC_NUM_OF_INPUTS 3 +enum { + REAR_MIC, + REAR_LINE_IN, + FRONT_MIC, +}; + enum { #define VNODE_START_NID 0x80 VNID_SPK = VNODE_START_NID, /* Speaker vnid */ @@ -134,7 +146,9 @@ enum { VOICEFX = IN_EFFECT_END_NID, PLAY_ENHANCEMENT, CRYSTAL_VOICE, - EFFECT_END_NID + EFFECT_END_NID, + OUTPUT_SOURCE_ENUM, + INPUT_SOURCE_ENUM #define EFFECTS_COUNT (EFFECT_END_NID - EFFECT_START_NID) }; @@ -484,6 +498,49 @@ static struct ct_voicefx_preset ca0132_voicefx_presets[] = { } }; +/* DSP command sequences for ca0132_alt_select_out */ +#define ALT_OUT_SET_MAX_COMMANDS 9 /* Max number of commands in sequence */ +struct ca0132_alt_out_set { + char *name; /*preset name*/ + unsigned char commands; + unsigned int mids[ALT_OUT_SET_MAX_COMMANDS]; + unsigned int reqs[ALT_OUT_SET_MAX_COMMANDS]; + unsigned int vals[ALT_OUT_SET_MAX_COMMANDS]; +}; + +static const struct ca0132_alt_out_set alt_out_presets[] = { + { .name = "Line Out", + .commands = 7, + .mids = { 0x96, 0x96, 0x96, 0x8F, + 0x96, 0x96, 0x96 }, + .reqs = { 0x19, 0x17, 0x18, 0x01, + 0x1F, 0x15, 0x3A }, + .vals = { 0x3F000000, 0x42A00000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000 } + }, + { .name = "Headphone", + .commands = 7, + .mids = { 0x96, 0x96, 0x96, 0x8F, + 0x96, 0x96, 0x96 }, + .reqs = { 0x19, 0x17, 0x18, 0x01, + 0x1F, 0x15, 0x3A }, + .vals = { 0x3F000000, 0x42A00000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000 } + }, + { .name = "Surround", + .commands = 8, + .mids = { 0x96, 0x8F, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96 }, + .reqs = { 0x18, 0x01, 0x1F, 0x15, + 0x3A, 0x1A, 0x1B, 0x1C }, + .vals = { 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000 } + } +}; + enum hda_cmd_vendor_io { /* for DspIO node */ VENDOR_DSPIO_SCP_WRITE_DATA_LOW = 0x000, @@ -763,6 +820,9 @@ struct ca0132_spec { long effects_switch[EFFECTS_COUNT]; long voicefx_val; long cur_mic_boost; + /* ca0132_alt control related values */ + unsigned char in_enum_val; + unsigned char out_enum_val; struct hda_codec *codec; struct delayed_work unsol_hp_work; @@ -2959,6 +3019,47 @@ enum r3di_dsp_status { R3DI_DSP_DOWNLOADED = 1 }; + +static void r3di_gpio_mic_set(struct hda_codec *codec, + enum r3di_mic_select cur_mic) +{ + unsigned int cur_gpio; + + /* Get the current GPIO Data setup */ + cur_gpio = snd_hda_codec_read(codec, 0x01, 0, AC_VERB_GET_GPIO_DATA, 0); + + switch (cur_mic) { + case R3DI_REAR_MIC: + cur_gpio &= ~(1 << R3DI_MIC_SELECT_BIT); + break; + case R3DI_FRONT_MIC: + cur_gpio |= (1 << R3DI_MIC_SELECT_BIT); + break; + } + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); +} + +static void r3di_gpio_out_set(struct hda_codec *codec, + enum r3di_out_select cur_out) +{ + unsigned int cur_gpio; + + /* Get the current GPIO Data setup */ + cur_gpio = snd_hda_codec_read(codec, 0x01, 0, AC_VERB_GET_GPIO_DATA, 0); + + switch (cur_out) { + case R3DI_HEADPHONE_OUT: + cur_gpio &= ~(1 << R3DI_OUT_SELECT_BIT); + break; + case R3DI_LINE_OUT: + cur_gpio |= (1 << R3DI_OUT_SELECT_BIT); + break; + } + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); +} + static void r3di_gpio_dsp_status_set(struct hda_codec *codec, enum r3di_dsp_status dsp_status) { @@ -3550,13 +3651,209 @@ exit: return err < 0 ? err : 0; } +/* + * This function behaves similarly to the ca0132_select_out funciton above, + * except with a few differences. It adds the ability to select the current + * output with an enumerated control "output source" if the auto detect + * mute switch is set to off. If the auto detect mute switch is enabled, it + * will detect either headphone or lineout(SPEAKER_OUT) from jack detection. + * It also adds the ability to auto-detect the front headphone port. The only + * way to select surround is to disable auto detect, and set Surround with the + * enumerated control. + */ +static int ca0132_alt_select_out(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int pin_ctl; + int jack_present; + int auto_jack; + unsigned int i; + unsigned int tmp; + int err; + /* Default Headphone is rear headphone */ + hda_nid_t headphone_nid = spec->out_pins[1]; + + codec_dbg(codec, "%s\n", __func__); + + snd_hda_power_up_pm(codec); + + auto_jack = spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID]; + + /* + * If headphone rear or front is plugged in, set to headphone. + * If neither is plugged in, set to rear line out. Only if + * hp/speaker auto detect is enabled. + */ + if (auto_jack) { + jack_present = snd_hda_jack_detect(codec, spec->unsol_tag_hp) || + snd_hda_jack_detect(codec, spec->unsol_tag_front_hp); + + if (jack_present) + spec->cur_out_type = HEADPHONE_OUT; + else + spec->cur_out_type = SPEAKER_OUT; + } else + spec->cur_out_type = spec->out_enum_val; + + /* Begin DSP output switch */ + tmp = FLOAT_ONE; + err = dspio_set_uint_param(codec, 0x96, 0x3A, tmp); + if (err < 0) + goto exit; + + switch (spec->cur_out_type) { + case SPEAKER_OUT: + codec_dbg(codec, "%s speaker\n", __func__); + /*speaker out config*/ + switch (spec->quirk) { + case QUIRK_SBZ: + writew(0x0007, spec->mem_base + 0x320); + writew(0x0104, spec->mem_base + 0x320); + writew(0x0101, spec->mem_base + 0x320); + chipio_set_control_param(codec, 0x0D, 0x18); + break; + case QUIRK_R3DI: + chipio_set_control_param(codec, 0x0D, 0x24); + r3di_gpio_out_set(codec, R3DI_LINE_OUT); + break; + } + + /* disable headphone node */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[1], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[1], + pin_ctl & ~PIN_HP); + /* enable line-out node */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[0], + pin_ctl | PIN_OUT); + /* Enable EAPD */ + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x01); + + /* If PlayEnhancement is enabled, set different source */ + if (spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ONE); + else + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_EIGHT); + break; + case HEADPHONE_OUT: + codec_dbg(codec, "%s hp\n", __func__); + /* Headphone out config*/ + switch (spec->quirk) { + case QUIRK_SBZ: + writew(0x0107, spec->mem_base + 0x320); + writew(0x0104, spec->mem_base + 0x320); + writew(0x0001, spec->mem_base + 0x320); + chipio_set_control_param(codec, 0x0D, 0x12); + break; + case QUIRK_R3DI: + chipio_set_control_param(codec, 0x0D, 0x21); + r3di_gpio_out_set(codec, R3DI_HEADPHONE_OUT); + break; + } + + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x00); + + /* disable speaker*/ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[0], + pin_ctl & ~PIN_HP); + + /* enable headphone, either front or rear */ + + if (snd_hda_jack_detect(codec, spec->unsol_tag_front_hp)) + headphone_nid = spec->out_pins[2]; + else if (snd_hda_jack_detect(codec, spec->unsol_tag_hp)) + headphone_nid = spec->out_pins[1]; + + pin_ctl = snd_hda_codec_read(codec, headphone_nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, headphone_nid, + pin_ctl | PIN_HP); + + if (spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ONE); + else + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ZERO); + break; + case SURROUND_OUT: + codec_dbg(codec, "%s surround\n", __func__); + /* Surround out config*/ + switch (spec->quirk) { + case QUIRK_SBZ: + writew(0x0007, spec->mem_base + 0x320); + writew(0x0104, spec->mem_base + 0x320); + writew(0x0101, spec->mem_base + 0x320); + chipio_set_control_param(codec, 0x0D, 0x18); + break; + case QUIRK_R3DI: + chipio_set_control_param(codec, 0x0D, 0x24); + r3di_gpio_out_set(codec, R3DI_LINE_OUT); + break; + } + /* enable line out node */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[0], + pin_ctl | PIN_OUT); + /* Disable headphone out */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[1], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[1], + pin_ctl & ~PIN_HP); + /* Enable EAPD on line out */ + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x01); + /* enable center/lfe out node */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[2], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[2], + pin_ctl | PIN_OUT); + /* Now set rear surround node as out. */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[3], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[3], + pin_ctl | PIN_OUT); + + if (spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ONE); + else + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_EIGHT); + break; + } + + /* run through the output dsp commands for line-out */ + for (i = 0; i < alt_out_presets[spec->cur_out_type].commands; i++) { + err = dspio_set_uint_param(codec, + alt_out_presets[spec->cur_out_type].mids[i], + alt_out_presets[spec->cur_out_type].reqs[i], + alt_out_presets[spec->cur_out_type].vals[i]); + + if (err < 0) + goto exit; + } + +exit: + snd_hda_power_down_pm(codec); + + return err < 0 ? err : 0; +} + static void ca0132_unsol_hp_delayed(struct work_struct *work) { struct ca0132_spec *spec = container_of( to_delayed_work(work), struct ca0132_spec, unsol_hp_work); struct hda_jack_tbl *jack; - ca0132_select_out(spec->codec); + if (spec->use_alt_functions) + ca0132_alt_select_out(spec->codec); + else + ca0132_select_out(spec->codec); + jack = snd_hda_jack_tbl_get(spec->codec, spec->unsol_tag_hp); if (jack) { jack->block_report = 0; @@ -3661,6 +3958,122 @@ static int ca0132_select_mic(struct hda_codec *codec) return 0; } +/* + * Select the active input. + * Mic detection isn't used, because it's kind of pointless on the SBZ. + * The front mic has no jack-detection, so the only way to switch to it + * is to do it manually in alsamixer. + */ +static int ca0132_alt_select_in(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + + codec_dbg(codec, "%s\n", __func__); + + snd_hda_power_up_pm(codec); + + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0); + + spec->cur_mic_type = spec->in_enum_val; + + switch (spec->cur_mic_type) { + case REAR_MIC: + switch (spec->quirk) { + case QUIRK_SBZ: + writew(0x0000, spec->mem_base + 0x320); + tmp = FLOAT_THREE; + break; + case QUIRK_R3DI: + r3di_gpio_mic_set(codec, R3DI_REAR_MIC); + tmp = FLOAT_ONE; + break; + default: + tmp = FLOAT_ONE; + break; + } + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (spec->quirk == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + + if (spec->quirk == QUIRK_SBZ) { + chipio_write(codec, 0x18B098, 0x0000000C); + chipio_write(codec, 0x18B09C, 0x0000000C); + } + break; + case REAR_LINE_IN: + ca0132_mic_boost_set(codec, 0); + switch (spec->quirk) { + case QUIRK_SBZ: + writew(0x0000, spec->mem_base + 0x320); + break; + case QUIRK_R3DI: + r3di_gpio_mic_set(codec, R3DI_REAR_MIC); + break; + } + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (spec->quirk == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + if (spec->quirk == QUIRK_SBZ) { + chipio_write(codec, 0x18B098, 0x00000000); + chipio_write(codec, 0x18B09C, 0x00000000); + } + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + break; + case FRONT_MIC: + switch (spec->quirk) { + case QUIRK_SBZ: + writew(0x0100, spec->mem_base + 0x320); + writew(0x0005, spec->mem_base + 0x320); + tmp = FLOAT_THREE; + break; + case QUIRK_R3DI: + r3di_gpio_mic_set(codec, R3DI_FRONT_MIC); + tmp = FLOAT_ONE; + break; + default: + tmp = FLOAT_ONE; + break; + } + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (spec->quirk == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + + if (spec->quirk == QUIRK_SBZ) { + chipio_write(codec, 0x18B098, 0x0000000C); + chipio_write(codec, 0x18B09C, 0x000000CC); + } + break; + } + + snd_hda_power_down_pm(codec); + return 0; + +} + /* * Check if VNODE settings take effect immediately. */ @@ -3743,7 +4156,8 @@ static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val) val = 0; /* If Voice Focus on SBZ, set to two channel. */ - if ((nid == VOICE_FOCUS) && (spec->quirk == QUIRK_SBZ)) { + if ((nid == VOICE_FOCUS) && (spec->quirk == QUIRK_SBZ) + && (spec->cur_mic_type != REAR_LINE_IN)) { if (spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID]) { @@ -3761,7 +4175,8 @@ static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val) * For SBZ noise reduction, there's an extra command * to module ID 0x47. No clue why. */ - if ((nid == NOISE_REDUCTION) && (spec->quirk == QUIRK_SBZ)) { + if ((nid == NOISE_REDUCTION) && (spec->quirk == QUIRK_SBZ) + && (spec->cur_mic_type != REAR_LINE_IN)) { if (spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID]) { if (spec->effects_switch[NOISE_REDUCTION - @@ -3774,6 +4189,11 @@ static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val) dspio_set_uint_param(codec, 0x47, 0x00, tmp); } + + /* If rear line in disable effects. */ + if (spec->use_alt_functions && + spec->in_enum_val == REAR_LINE_IN) + val = 0; } codec_dbg(codec, "ca0132_effect_set: nid=0x%x, val=%ld\n", @@ -3801,6 +4221,9 @@ static int ca0132_pe_switch_set(struct hda_codec *codec) codec_dbg(codec, "ca0132_pe_switch_set: val=%ld\n", spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]); + if (spec->use_alt_functions) + ca0132_alt_select_out(codec); + i = OUT_EFFECT_START_NID - EFFECT_START_NID; nid = OUT_EFFECT_START_NID; /* PE affects all out effects */ @@ -3892,8 +4315,12 @@ static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol, if (nid == VNID_HP_SEL) { auto_jack = spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID]; - if (!auto_jack) - ca0132_select_out(codec); + if (!auto_jack) { + if (spec->use_alt_functions) + ca0132_alt_select_out(codec); + else + ca0132_select_out(codec); + } return 1; } @@ -3906,7 +4333,10 @@ static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol, } if (nid == VNID_HP_ASEL) { - ca0132_select_out(codec); + if (spec->use_alt_functions) + ca0132_alt_select_out(codec); + else + ca0132_select_out(codec); return 1; } @@ -3935,6 +4365,104 @@ static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol, } /* End of control change helpers. */ +/* + * Input Select Control for alternative ca0132 codecs. This exists because + * front microphone has no auto-detect, and we need a way to set the rear + * as line-in + */ +static int ca0132_alt_input_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = IN_SRC_NUM_OF_INPUTS; + if (uinfo->value.enumerated.item >= IN_SRC_NUM_OF_INPUTS) + uinfo->value.enumerated.item = IN_SRC_NUM_OF_INPUTS - 1; + strcpy(uinfo->value.enumerated.name, + in_src_str[uinfo->value.enumerated.item]); + return 0; +} + +static int ca0132_alt_input_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->in_enum_val; + return 0; +} + +static int ca0132_alt_input_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = IN_SRC_NUM_OF_INPUTS; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ca0132_alt_input_select: sel=%d, preset=%s\n", + sel, in_src_str[sel]); + + spec->in_enum_val = sel; + + ca0132_alt_select_in(codec); + + return 1; +} + +/* Sound Blaster Z Output Select Control */ +static int ca0132_alt_output_select_get_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_OF_OUTPUTS; + if (uinfo->value.enumerated.item >= NUM_OF_OUTPUTS) + uinfo->value.enumerated.item = NUM_OF_OUTPUTS - 1; + strcpy(uinfo->value.enumerated.name, + alt_out_presets[uinfo->value.enumerated.item].name); + return 0; +} + +static int ca0132_alt_output_select_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->out_enum_val; + return 0; +} + +static int ca0132_alt_output_select_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = NUM_OF_OUTPUTS; + unsigned int auto_jack; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ca0132_alt_output_select: sel=%d, preset=%s\n", + sel, alt_out_presets[sel].name); + + spec->out_enum_val = sel; + + auto_jack = spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID]; + + if (!auto_jack) + ca0132_alt_select_out(codec); + + return 1; +} + static int ca0132_voicefx_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -4085,10 +4613,15 @@ static int ca0132_switch_put(struct snd_kcontrol *kcontrol, /* mic boost */ if (nid == spec->input_pins[0]) { spec->cur_mic_boost = *valp; + if (spec->use_alt_functions) { + if (spec->in_enum_val != REAR_LINE_IN) + changed = ca0132_mic_boost_set(codec, *valp); + } else { + /* Mic boost does not apply to Digital Mic */ + if (spec->cur_mic_type != DIGITAL_MIC) + changed = ca0132_mic_boost_set(codec, *valp); + } - /* Mic boost does not apply to Digital Mic */ - if (spec->cur_mic_type != DIGITAL_MIC) - changed = ca0132_mic_boost_set(codec, *valp); goto exit; } @@ -4261,6 +4794,39 @@ static int add_voicefx(struct hda_codec *codec) return snd_hda_ctl_add(codec, VOICEFX, snd_ctl_new1(&knew, codec)); } +/* + * Create an Output Select enumerated control for codecs with surround + * out capabilities. + */ +static int ca0132_alt_add_output_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("Output Select", + OUTPUT_SOURCE_ENUM, 1, 0, HDA_OUTPUT); + knew.info = ca0132_alt_output_select_get_info; + knew.get = ca0132_alt_output_select_get; + knew.put = ca0132_alt_output_select_put; + return snd_hda_ctl_add(codec, OUTPUT_SOURCE_ENUM, + snd_ctl_new1(&knew, codec)); +} + +/* + * Create an Input Source enumerated control for the alternate ca0132 codecs + * because the front microphone has no auto-detect, and Line-in has to be set + * somehow. + */ +static int ca0132_alt_add_input_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("Input Source", + INPUT_SOURCE_ENUM, 1, 0, HDA_INPUT); + knew.info = ca0132_alt_input_source_info; + knew.get = ca0132_alt_input_source_get; + knew.put = ca0132_alt_input_source_put; + return snd_hda_ctl_add(codec, INPUT_SOURCE_ENUM, + snd_ctl_new1(&knew, codec)); +} + /* * When changing Node IDs for Mixer Controls below, make sure to update * Node IDs in ca0132_config() as well. @@ -4322,6 +4888,15 @@ static int ca0132_build_controls(struct hda_codec *codec) add_voicefx(codec); + /* + * If the codec uses alt_functions, you need the enumerated controls + * to select the new outputs and inputs, plus add the new mic boost + * setting control. + */ + if (spec->use_alt_functions) { + ca0132_alt_add_output_enum(codec); + ca0132_alt_add_input_enum(codec); + } #ifdef ENABLE_TUNING_CONTROLS add_tuning_ctls(codec); #endif @@ -5266,7 +5841,11 @@ static void ca0132_init_chip(struct hda_codec *codec) mutex_init(&spec->chipio_mutex); spec->cur_out_type = SPEAKER_OUT; - spec->cur_mic_type = DIGITAL_MIC; + if (!spec->use_alt_functions) + spec->cur_mic_type = DIGITAL_MIC; + else + spec->cur_mic_type = REAR_MIC; + spec->cur_mic_boost = 0; for (i = 0; i < VNODES_COUNT; i++) { @@ -5693,15 +6272,25 @@ static int ca0132_init(struct hda_codec *codec) VENDOR_CHIPIO_PARAM_EX_VALUE_SET, 0x20); } - if (spec->quirk == QUIRK_SBZ) { + if (spec->quirk == QUIRK_SBZ) ca0132_gpio_setup(codec); - sbz_setup_defaults(codec); - } snd_hda_sequence_write(codec, spec->spec_init_verbs); - - ca0132_select_out(codec); - ca0132_select_mic(codec); + switch (spec->quirk) { + case QUIRK_SBZ: + sbz_setup_defaults(codec); + ca0132_alt_select_out(codec); + ca0132_alt_select_in(codec); + break; + case QUIRK_R3DI: + ca0132_alt_select_out(codec); + ca0132_alt_select_in(codec); + break; + default: + ca0132_select_out(codec); + ca0132_select_mic(codec); + break; + } snd_hda_jack_report_sync(codec); -- cgit v1.2.3 From 017310fbe7670f522cdde4e68d4e1859f16d2757 Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Tue, 8 May 2018 13:20:11 -0400 Subject: ALSA: hda/ca0132: Add DSP Volume set and New mixers for SBZ + R3Di Adds lookup table for floating point decibel volume, and new functions to allow for setting the decibel level on the DSP. Signed-off-by: Connor McAdams Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 203 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 202 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 60e8a0ce530e..394e604c3787 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -541,6 +541,31 @@ static const struct ca0132_alt_out_set alt_out_presets[] = { } }; +/* + * DSP volume setting structs. Req 1 is left volume, req 2 is right volume, + * and I don't know what the third req is, but it's always zero. I assume it's + * some sort of update or set command to tell the DSP there's new volume info. + */ +#define DSP_VOL_OUT 0 +#define DSP_VOL_IN 1 + +struct ct_dsp_volume_ctl { + hda_nid_t vnid; + int mid; /* module ID*/ + unsigned int reqs[3]; /* scp req ID */ +}; + +static struct ct_dsp_volume_ctl ca0132_alt_vol_ctls[] = { + { .vnid = VNID_SPK, + .mid = 0x32, + .reqs = {3, 4, 2} + }, + { .vnid = VNID_MIC, + .mid = 0x37, + .reqs = {2, 3, 1} + } +}; + enum hda_cmd_vendor_io { /* for DspIO node */ VENDOR_DSPIO_SCP_WRITE_DATA_LOW = 0x000, @@ -3252,6 +3277,24 @@ static unsigned int ca0132_capture_pcm_delay(struct hda_pcm_stream *info, .tlv = { .c = ca0132_volume_tlv }, \ .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, 0, dir) } +/* + * Creates a mixer control that uses defaults of HDA_CODEC_VOL except for the + * volume put, which is used for setting the DSP volume. This was done because + * the ca0132 functions were taking too much time and causing lag. + */ +#define CA0132_ALT_CODEC_VOL_MONO(xname, nid, channel, dir) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .subdevice = HDA_SUBDEV_AMP_FLAG, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \ + .info = snd_hda_mixer_amp_volume_info, \ + .get = snd_hda_mixer_amp_volume_get, \ + .put = ca0132_alt_volume_put, \ + .tlv = { .c = snd_hda_mixer_amp_tlv }, \ + .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, 0, dir) } + #define CA0132_CODEC_MUTE_MONO(xname, nid, channel, dir) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ .name = xname, \ @@ -3264,9 +3307,40 @@ static unsigned int ca0132_capture_pcm_delay(struct hda_pcm_stream *info, /* stereo */ #define CA0132_CODEC_VOL(xname, nid, dir) \ CA0132_CODEC_VOL_MONO(xname, nid, 3, dir) +#define CA0132_ALT_CODEC_VOL(xname, nid, dir) \ + CA0132_ALT_CODEC_VOL_MONO(xname, nid, 3, dir) #define CA0132_CODEC_MUTE(xname, nid, dir) \ CA0132_CODEC_MUTE_MONO(xname, nid, 3, dir) +/* lookup tables */ +/* + * Lookup table with decibel values for the DSP. When volume is changed in + * Windows, the DSP is also sent the dB value in floating point. In Windows, + * these values have decimal points, probably because the Windows driver + * actually uses floating point. We can't here, so I made a lookup table of + * values -90 to 9. -90 is the lowest decibel value for both the ADC's and the + * DAC's, and 9 is the maximum. + */ +static const unsigned int float_vol_db_lookup[] = { +0xC2B40000, 0xC2B20000, 0xC2B00000, 0xC2AE0000, 0xC2AC0000, 0xC2AA0000, +0xC2A80000, 0xC2A60000, 0xC2A40000, 0xC2A20000, 0xC2A00000, 0xC29E0000, +0xC29C0000, 0xC29A0000, 0xC2980000, 0xC2960000, 0xC2940000, 0xC2920000, +0xC2900000, 0xC28E0000, 0xC28C0000, 0xC28A0000, 0xC2880000, 0xC2860000, +0xC2840000, 0xC2820000, 0xC2800000, 0xC27C0000, 0xC2780000, 0xC2740000, +0xC2700000, 0xC26C0000, 0xC2680000, 0xC2640000, 0xC2600000, 0xC25C0000, +0xC2580000, 0xC2540000, 0xC2500000, 0xC24C0000, 0xC2480000, 0xC2440000, +0xC2400000, 0xC23C0000, 0xC2380000, 0xC2340000, 0xC2300000, 0xC22C0000, +0xC2280000, 0xC2240000, 0xC2200000, 0xC21C0000, 0xC2180000, 0xC2140000, +0xC2100000, 0xC20C0000, 0xC2080000, 0xC2040000, 0xC2000000, 0xC1F80000, +0xC1F00000, 0xC1E80000, 0xC1E00000, 0xC1D80000, 0xC1D00000, 0xC1C80000, +0xC1C00000, 0xC1B80000, 0xC1B00000, 0xC1A80000, 0xC1A00000, 0xC1980000, +0xC1900000, 0xC1880000, 0xC1800000, 0xC1700000, 0xC1600000, 0xC1500000, +0xC1400000, 0xC1300000, 0xC1200000, 0xC1100000, 0xC1000000, 0xC0E00000, +0xC0C00000, 0xC0A00000, 0xC0800000, 0xC0400000, 0xC0000000, 0xBF800000, +0x00000000, 0x3F800000, 0x40000000, 0x40400000, 0x40800000, 0x40A00000, +0x40C00000, 0x40E00000, 0x41000000, 0x41100000 +}; + /* The following are for tuning of products */ #ifdef ENABLE_TUNING_CONTROLS @@ -4633,6 +4707,41 @@ exit: /* * Volume related */ +/* + * Sets the internal DSP decibel level to match the DAC for output, and the + * ADC for input. Currently only the SBZ sets dsp capture volume level, and + * all alternative codecs set DSP playback volume. + */ +static void ca0132_alt_dsp_volume_put(struct hda_codec *codec, hda_nid_t nid) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int dsp_dir; + unsigned int lookup_val; + + if (nid == VNID_SPK) + dsp_dir = DSP_VOL_OUT; + else + dsp_dir = DSP_VOL_IN; + + lookup_val = spec->vnode_lvol[nid - VNODE_START_NID]; + + dspio_set_uint_param(codec, + ca0132_alt_vol_ctls[dsp_dir].mid, + ca0132_alt_vol_ctls[dsp_dir].reqs[0], + float_vol_db_lookup[lookup_val]); + + lookup_val = spec->vnode_rvol[nid - VNODE_START_NID]; + + dspio_set_uint_param(codec, + ca0132_alt_vol_ctls[dsp_dir].mid, + ca0132_alt_vol_ctls[dsp_dir].reqs[1], + float_vol_db_lookup[lookup_val]); + + dspio_set_uint_param(codec, + ca0132_alt_vol_ctls[dsp_dir].mid, + ca0132_alt_vol_ctls[dsp_dir].reqs[2], FLOAT_ZERO); +} + static int ca0132_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -4734,6 +4843,51 @@ static int ca0132_volume_put(struct snd_kcontrol *kcontrol, return changed; } +/* + * This function is the same as the one above, because using an if statement + * inside of the above volume control for the DSP volume would cause too much + * lag. This is a lot more smooth. + */ +static int ca0132_alt_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + int ch = get_amp_channels(kcontrol); + long *valp = ucontrol->value.integer.value; + hda_nid_t vnid = 0; + int changed = 1; + + switch (nid) { + case 0x02: + vnid = VNID_SPK; + break; + case 0x07: + vnid = VNID_MIC; + break; + } + + /* store the left and right volume */ + if (ch & 1) { + spec->vnode_lvol[vnid - VNODE_START_NID] = *valp; + valp++; + } + if (ch & 2) { + spec->vnode_rvol[vnid - VNODE_START_NID] = *valp; + valp++; + } + + snd_hda_power_up(codec); + ca0132_alt_dsp_volume_put(codec, vnid); + mutex_lock(&codec->control_mutex); + changed = snd_hda_mixer_amp_volume_put(kcontrol, ucontrol); + mutex_unlock(&codec->control_mutex); + snd_hda_power_down(codec); + + return changed; +} + static int ca0132_volume_tlv(struct snd_kcontrol *kcontrol, int op_flag, unsigned int size, unsigned int __user *tlv) { @@ -4853,6 +5007,39 @@ static struct snd_kcontrol_new ca0132_mixer[] = { { } /* end */ }; +/* + * SBZ specific control mixer. Removes auto-detect for mic, and adds surround + * controls. Also sets both the Front Playback and Capture Volume controls to + * alt so they set the DSP's decibel level. + */ +static struct snd_kcontrol_new sbz_mixer[] = { + CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT), + CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT), + CA0132_ALT_CODEC_VOL("Capture Volume", 0x07, HDA_INPUT), + CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT), + HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT), + HDA_CODEC_MUTE("What U Hear Capture Switch", 0x0a, 0, HDA_INPUT), + CA0132_CODEC_MUTE_MONO("HP/Speaker Auto Detect Playback Switch", + VNID_HP_ASEL, 1, HDA_OUTPUT), + { } /* end */ +}; + +/* + * Same as the Sound Blaster Z, except doesn't use the alt volume for capture + * because it doesn't set decibel levels for the DSP for capture. + */ +static struct snd_kcontrol_new r3di_mixer[] = { + CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT), + CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT), + CA0132_CODEC_VOL("Capture Volume", VNID_MIC, HDA_INPUT), + CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT), + HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT), + HDA_CODEC_MUTE("What U Hear Capture Switch", 0x0a, 0, HDA_INPUT), + CA0132_CODEC_MUTE_MONO("HP/Speaker Auto Detect Playback Switch", + VNID_HP_ASEL, 1, HDA_OUTPUT), + { } /* end */ +}; + static int ca0132_build_controls(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; @@ -6566,7 +6753,21 @@ static int patch_ca0132(struct hda_codec *codec) spec->dsp_state = DSP_DOWNLOAD_INIT; spec->num_mixers = 1; - spec->mixers[0] = ca0132_mixer; + + /* Set which mixers each quirk uses. */ + switch (spec->quirk) { + case QUIRK_SBZ: + spec->mixers[0] = sbz_mixer; + snd_hda_codec_set_name(codec, "Sound Blaster Z"); + break; + case QUIRK_R3DI: + spec->mixers[0] = r3di_mixer; + snd_hda_codec_set_name(codec, "Recon3Di"); + break; + default: + spec->mixers[0] = ca0132_mixer; + break; + } /* Setup whether or not to use alt functions */ switch (spec->quirk) { -- cgit v1.2.3 From e0026d03942d38dd784baf4922badd980c692f89 Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Tue, 8 May 2018 13:20:12 -0400 Subject: ALSA: hda/ca0132: add ca0132_alt_set_vipsource Add function to set vipsource on cards that use_alt_controls. Different sequence. Also, add cvoice_switch_set at end of ca0132_select_in so that when switching between inputs cvoice state is maintained. Signed-off-by: Connor McAdams Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 74 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 394e604c3787..034fd12339f4 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -3938,6 +3938,9 @@ static void ca0132_unsol_hp_delayed(struct work_struct *work) static void ca0132_set_dmic(struct hda_codec *codec, int enable); static int ca0132_mic_boost_set(struct hda_codec *codec, long val); static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val); +static void resume_mic1(struct hda_codec *codec, unsigned int oldval); +static int stop_mic1(struct hda_codec *codec); +static int ca0132_cvoice_switch_set(struct hda_codec *codec); /* * Select the active VIP source @@ -3980,6 +3983,71 @@ static int ca0132_set_vipsource(struct hda_codec *codec, int val) return 1; } +static int ca0132_alt_set_vipsource(struct hda_codec *codec, int val) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + + if (spec->dsp_state != DSP_DOWNLOADED) + return 0; + + codec_dbg(codec, "%s\n", __func__); + + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0); + + /* if CrystalVoice is off, vipsource should be 0 */ + if (!spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] || + (val == 0) || spec->in_enum_val == REAR_LINE_IN) { + codec_dbg(codec, "%s: off.", __func__); + chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, 0); + + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x05, tmp); + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (spec->quirk == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + + if (spec->in_enum_val == REAR_LINE_IN) + tmp = FLOAT_ZERO; + else { + if (spec->quirk == QUIRK_SBZ) + tmp = FLOAT_THREE; + else + tmp = FLOAT_ONE; + } + + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + } else { + codec_dbg(codec, "%s: on.", __func__); + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_16_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_16_000); + if (spec->quirk == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_16_000); + + if (spec->effects_switch[VOICE_FOCUS - EFFECT_START_NID]) + tmp = FLOAT_TWO; + else + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x05, tmp); + + msleep(20); + chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, val); + } + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + + return 1; +} + /* * Select the active microphone. * If autodetect is enabled, mic will be selected based on jack detection. @@ -4142,6 +4210,7 @@ static int ca0132_alt_select_in(struct hda_codec *codec) } break; } + ca0132_cvoice_switch_set(codec); snd_hda_power_down_pm(codec); return 0; @@ -4355,7 +4424,10 @@ static int ca0132_cvoice_switch_set(struct hda_codec *codec) /* set correct vipsource */ oldval = stop_mic1(codec); - ret |= ca0132_set_vipsource(codec, 1); + if (spec->use_alt_functions) + ret |= ca0132_alt_set_vipsource(codec, 1); + else + ret |= ca0132_set_vipsource(codec, 1); resume_mic1(codec, oldval); return ret; } -- cgit v1.2.3 From 47cdf76e44e87d25b91c0f0d0539944c932941ba Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Tue, 8 May 2018 13:20:13 -0400 Subject: ALSA: hda/ca0132: Add new control changes for SBZ + R3Di This patch adds new controls to set the effect levels on the R3Di and SBZ. It also adds vmaster controls to control all surround sound channels. So that Surround effect switch doesn't conflict with Surround volume, FX: prefix added to all effect related switches. Tested-by: Mariusz Ceier Signed-off-by: Connor McAdams Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 758 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 748 insertions(+), 10 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 034fd12339f4..0fa67f45241d 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -148,13 +148,26 @@ enum { CRYSTAL_VOICE, EFFECT_END_NID, OUTPUT_SOURCE_ENUM, - INPUT_SOURCE_ENUM + INPUT_SOURCE_ENUM, + XBASS_XOVER, + EQ_PRESET_ENUM, + SMART_VOLUME_ENUM, + MIC_BOOST_ENUM #define EFFECTS_COUNT (EFFECT_END_NID - EFFECT_START_NID) }; /* Effects values size*/ #define EFFECT_VALS_MAX_COUNT 12 +/* + * Default values for the effect slider controls, they are in order of their + * effect NID's. Surround, Crystalizer, Dialog Plus, Smart Volume, and then + * X-bass. + */ +static const unsigned int effect_slider_defaults[] = {67, 65, 50, 74, 50}; +/* Amount of effect level sliders for ca0132_alt controls. */ +#define EFFECT_LEVEL_SLIDERS 5 + /* Latency introduced by DSP blocks in milliseconds. */ #define DSP_CAPTURE_INIT_LATENCY 0 #define DSP_CRYSTAL_VOICE_LATENCY 124 @@ -498,6 +511,93 @@ static struct ct_voicefx_preset ca0132_voicefx_presets[] = { } }; +/* ca0132 EQ presets, taken from Windows Sound Blaster Z Driver */ + +#define EQ_PRESET_MAX_PARAM_COUNT 11 + +struct ct_eq { + char *name; + hda_nid_t nid; + int mid; + int reqs[EQ_PRESET_MAX_PARAM_COUNT]; /*effect module request*/ +}; + +struct ct_eq_preset { + char *name; /*preset name*/ + unsigned int vals[EQ_PRESET_MAX_PARAM_COUNT]; +}; + +static struct ct_eq ca0132_alt_eq_enum = { + .name = "FX: Equalizer Preset Switch", + .nid = EQ_PRESET_ENUM, + .mid = 0x96, + .reqs = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} +}; + + +static struct ct_eq_preset ca0132_alt_eq_presets[] = { + { .name = "Flat", + .vals = { 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000 } + }, + { .name = "Acoustic", + .vals = { 0x00000000, 0x00000000, 0x3F8CCCCD, + 0x40000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x40000000, + 0x40000000, 0x40000000 } + }, + { .name = "Classical", + .vals = { 0x00000000, 0x00000000, 0x40C00000, + 0x40C00000, 0x40466666, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x40466666, 0x40466666 } + }, + { .name = "Country", + .vals = { 0x00000000, 0xBF99999A, 0x00000000, + 0x3FA66666, 0x3FA66666, 0x3F8CCCCD, + 0x00000000, 0x00000000, 0x40000000, + 0x40466666, 0x40800000 } + }, + { .name = "Dance", + .vals = { 0x00000000, 0xBF99999A, 0x40000000, + 0x40466666, 0x40866666, 0xBF99999A, + 0xBF99999A, 0x00000000, 0x00000000, + 0x40800000, 0x40800000 } + }, + { .name = "Jazz", + .vals = { 0x00000000, 0x00000000, 0x00000000, + 0x3F8CCCCD, 0x40800000, 0x40800000, + 0x40800000, 0x00000000, 0x3F8CCCCD, + 0x40466666, 0x40466666 } + }, + { .name = "New Age", + .vals = { 0x00000000, 0x00000000, 0x40000000, + 0x40000000, 0x00000000, 0x00000000, + 0x00000000, 0x3F8CCCCD, 0x40000000, + 0x40000000, 0x40000000 } + }, + { .name = "Pop", + .vals = { 0x00000000, 0xBFCCCCCD, 0x00000000, + 0x40000000, 0x40000000, 0x00000000, + 0xBF99999A, 0xBF99999A, 0x00000000, + 0x40466666, 0x40C00000 } + }, + { .name = "Rock", + .vals = { 0x00000000, 0xBF99999A, 0xBF99999A, + 0x3F8CCCCD, 0x40000000, 0xBF99999A, + 0xBF99999A, 0x00000000, 0x00000000, + 0x40800000, 0x40800000 } + }, + { .name = "Vocal", + .vals = { 0x00000000, 0xC0000000, 0xBF99999A, + 0xBF99999A, 0x00000000, 0x40466666, + 0x40800000, 0x40466666, 0x00000000, + 0x00000000, 0x3F8CCCCD } + } +}; + /* DSP command sequences for ca0132_alt_select_out */ #define ALT_OUT_SET_MAX_COMMANDS 9 /* Max number of commands in sequence */ struct ca0132_alt_out_set { @@ -848,6 +948,14 @@ struct ca0132_spec { /* ca0132_alt control related values */ unsigned char in_enum_val; unsigned char out_enum_val; + unsigned char mic_boost_enum_val; + unsigned char smart_volume_setting; + long fx_ctl_val[EFFECT_LEVEL_SLIDERS]; + long xbass_xover_freq; + long eq_preset_val; + unsigned int tlv[4]; + struct hda_vmaster_mute_hook vmaster_mute; + struct hda_codec *codec; struct delayed_work unsol_hp_work; @@ -868,6 +976,13 @@ struct ca0132_spec { * surround sound support. */ bool use_alt_functions; + + /* + * Whether or not to use alt controls: volume effect sliders, EQ + * presets, smart volume presets, and new control names with FX prefix. + * Renames PlayEnhancement and CrystalVoice too. + */ + bool use_alt_controls; }; /* @@ -3341,6 +3456,54 @@ static const unsigned int float_vol_db_lookup[] = { 0x40C00000, 0x40E00000, 0x41000000, 0x41100000 }; +/* + * This table counts from float 0 to 1 in increments of .01, which is + * useful for a few different sliders. + */ +static const unsigned int float_zero_to_one_lookup[] = { +0x00000000, 0x3C23D70A, 0x3CA3D70A, 0x3CF5C28F, 0x3D23D70A, 0x3D4CCCCD, +0x3D75C28F, 0x3D8F5C29, 0x3DA3D70A, 0x3DB851EC, 0x3DCCCCCD, 0x3DE147AE, +0x3DF5C28F, 0x3E051EB8, 0x3E0F5C29, 0x3E19999A, 0x3E23D70A, 0x3E2E147B, +0x3E3851EC, 0x3E428F5C, 0x3E4CCCCD, 0x3E570A3D, 0x3E6147AE, 0x3E6B851F, +0x3E75C28F, 0x3E800000, 0x3E851EB8, 0x3E8A3D71, 0x3E8F5C29, 0x3E947AE1, +0x3E99999A, 0x3E9EB852, 0x3EA3D70A, 0x3EA8F5C3, 0x3EAE147B, 0x3EB33333, +0x3EB851EC, 0x3EBD70A4, 0x3EC28F5C, 0x3EC7AE14, 0x3ECCCCCD, 0x3ED1EB85, +0x3ED70A3D, 0x3EDC28F6, 0x3EE147AE, 0x3EE66666, 0x3EEB851F, 0x3EF0A3D7, +0x3EF5C28F, 0x3EFAE148, 0x3F000000, 0x3F028F5C, 0x3F051EB8, 0x3F07AE14, +0x3F0A3D71, 0x3F0CCCCD, 0x3F0F5C29, 0x3F11EB85, 0x3F147AE1, 0x3F170A3D, +0x3F19999A, 0x3F1C28F6, 0x3F1EB852, 0x3F2147AE, 0x3F23D70A, 0x3F266666, +0x3F28F5C3, 0x3F2B851F, 0x3F2E147B, 0x3F30A3D7, 0x3F333333, 0x3F35C28F, +0x3F3851EC, 0x3F3AE148, 0x3F3D70A4, 0x3F400000, 0x3F428F5C, 0x3F451EB8, +0x3F47AE14, 0x3F4A3D71, 0x3F4CCCCD, 0x3F4F5C29, 0x3F51EB85, 0x3F547AE1, +0x3F570A3D, 0x3F59999A, 0x3F5C28F6, 0x3F5EB852, 0x3F6147AE, 0x3F63D70A, +0x3F666666, 0x3F68F5C3, 0x3F6B851F, 0x3F6E147B, 0x3F70A3D7, 0x3F733333, +0x3F75C28F, 0x3F7851EC, 0x3F7AE148, 0x3F7D70A4, 0x3F800000 +}; + +/* + * This table counts from float 10 to 1000, which is the range of the x-bass + * crossover slider in Windows. + */ +static const unsigned int float_xbass_xover_lookup[] = { +0x41200000, 0x41A00000, 0x41F00000, 0x42200000, 0x42480000, 0x42700000, +0x428C0000, 0x42A00000, 0x42B40000, 0x42C80000, 0x42DC0000, 0x42F00000, +0x43020000, 0x430C0000, 0x43160000, 0x43200000, 0x432A0000, 0x43340000, +0x433E0000, 0x43480000, 0x43520000, 0x435C0000, 0x43660000, 0x43700000, +0x437A0000, 0x43820000, 0x43870000, 0x438C0000, 0x43910000, 0x43960000, +0x439B0000, 0x43A00000, 0x43A50000, 0x43AA0000, 0x43AF0000, 0x43B40000, +0x43B90000, 0x43BE0000, 0x43C30000, 0x43C80000, 0x43CD0000, 0x43D20000, +0x43D70000, 0x43DC0000, 0x43E10000, 0x43E60000, 0x43EB0000, 0x43F00000, +0x43F50000, 0x43FA0000, 0x43FF0000, 0x44020000, 0x44048000, 0x44070000, +0x44098000, 0x440C0000, 0x440E8000, 0x44110000, 0x44138000, 0x44160000, +0x44188000, 0x441B0000, 0x441D8000, 0x44200000, 0x44228000, 0x44250000, +0x44278000, 0x442A0000, 0x442C8000, 0x442F0000, 0x44318000, 0x44340000, +0x44368000, 0x44390000, 0x443B8000, 0x443E0000, 0x44408000, 0x44430000, +0x44458000, 0x44480000, 0x444A8000, 0x444D0000, 0x444F8000, 0x44520000, +0x44548000, 0x44570000, 0x44598000, 0x445C0000, 0x445E8000, 0x44610000, +0x44638000, 0x44660000, 0x44688000, 0x446B0000, 0x446D8000, 0x44700000, +0x44728000, 0x44750000, 0x44778000, 0x447A0000 +}; + /* The following are for tuning of products */ #ifdef ENABLE_TUNING_CONTROLS @@ -3941,6 +4104,7 @@ static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val); static void resume_mic1(struct hda_codec *codec, unsigned int oldval); static int stop_mic1(struct hda_codec *codec); static int ca0132_cvoice_switch_set(struct hda_codec *codec); +static int ca0132_alt_mic_boost_set(struct hda_codec *codec, long val); /* * Select the active VIP source @@ -4150,6 +4314,7 @@ static int ca0132_alt_select_in(struct hda_codec *codec) chipio_write(codec, 0x18B098, 0x0000000C); chipio_write(codec, 0x18B09C, 0x0000000C); } + ca0132_alt_mic_boost_set(codec, spec->mic_boost_enum_val); break; case REAR_LINE_IN: ca0132_mic_boost_set(codec, 0); @@ -4208,6 +4373,7 @@ static int ca0132_alt_select_in(struct hda_codec *codec) chipio_write(codec, 0x18B098, 0x0000000C); chipio_write(codec, 0x18B09C, 0x000000CC); } + ca0132_alt_mic_boost_set(codec, spec->mic_boost_enum_val); break; } ca0132_cvoice_switch_set(codec); @@ -4447,6 +4613,16 @@ static int ca0132_mic_boost_set(struct hda_codec *codec, long val) return ret; } +static int ca0132_alt_mic_boost_set(struct hda_codec *codec, long val) +{ + struct ca0132_spec *spec = codec->spec; + int ret = 0; + + ret = snd_hda_codec_amp_update(codec, spec->input_pins[0], 0, + HDA_INPUT, 0, HDA_AMP_VOLMASK, val); + return ret; +} + static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -4510,6 +4686,207 @@ static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol, return ret; } /* End of control change helpers. */ +/* + * Below I've added controls to mess with the effect levels, I've only enabled + * them on the Sound Blaster Z, but they would probably also work on the + * Chromebook. I figured they were probably tuned specifically for it, and left + * out for a reason. + */ + +/* Sets DSP effect level from the sliders above the controls */ +static int ca0132_alt_slider_ctl_set(struct hda_codec *codec, hda_nid_t nid, + const unsigned int *lookup, int idx) +{ + int i = 0; + unsigned int y; + /* + * For X_BASS, req 2 is actually crossover freq instead of + * effect level + */ + if (nid == X_BASS) + y = 2; + else + y = 1; + + snd_hda_power_up(codec); + if (nid == XBASS_XOVER) { + for (i = 0; i < OUT_EFFECTS_COUNT; i++) + if (ca0132_effects[i].nid == X_BASS) + break; + + dspio_set_param(codec, ca0132_effects[i].mid, 0x20, + ca0132_effects[i].reqs[1], + &(lookup[idx - 1]), sizeof(unsigned int)); + } else { + /* Find the actual effect structure */ + for (i = 0; i < OUT_EFFECTS_COUNT; i++) + if (nid == ca0132_effects[i].nid) + break; + + dspio_set_param(codec, ca0132_effects[i].mid, 0x20, + ca0132_effects[i].reqs[y], + &(lookup[idx]), sizeof(unsigned int)); + } + + snd_hda_power_down(codec); + + return 0; +} + +static int ca0132_alt_xbass_xover_slider_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + long *valp = ucontrol->value.integer.value; + + *valp = spec->xbass_xover_freq; + return 0; +} + +static int ca0132_alt_slider_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + long *valp = ucontrol->value.integer.value; + int idx = nid - OUT_EFFECT_START_NID; + + *valp = spec->fx_ctl_val[idx]; + return 0; +} + +/* + * The X-bass crossover starts at 10hz, so the min is 1. The + * frequency is set in multiples of 10. + */ +static int ca0132_alt_xbass_xover_slider_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 100; + uinfo->value.integer.step = 1; + + return 0; +} + +static int ca0132_alt_effect_slider_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int chs = get_amp_channels(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = chs == 3 ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + uinfo->value.integer.step = 1; + + return 0; +} + +static int ca0132_alt_xbass_xover_slider_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + long *valp = ucontrol->value.integer.value; + int idx; + + /* any change? */ + if (spec->xbass_xover_freq == *valp) + return 0; + + spec->xbass_xover_freq = *valp; + + idx = *valp; + ca0132_alt_slider_ctl_set(codec, nid, float_xbass_xover_lookup, idx); + + return 0; +} + +static int ca0132_alt_effect_slider_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + long *valp = ucontrol->value.integer.value; + int idx; + + idx = nid - EFFECT_START_NID; + /* any change? */ + if (spec->fx_ctl_val[idx] == *valp) + return 0; + + spec->fx_ctl_val[idx] = *valp; + + idx = *valp; + ca0132_alt_slider_ctl_set(codec, nid, float_zero_to_one_lookup, idx); + + return 0; +} + + +/* + * Mic Boost Enum for alternative ca0132 codecs. I didn't like that the original + * only has off or full 30 dB, and didn't like making a volume slider that has + * traditional 0-100 in alsamixer that goes in big steps. I like enum better. + */ +#define MIC_BOOST_NUM_OF_STEPS 4 +#define MIC_BOOST_ENUM_MAX_STRLEN 10 + +static int ca0132_alt_mic_boost_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + char *sfx = "dB"; + char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = MIC_BOOST_NUM_OF_STEPS; + if (uinfo->value.enumerated.item >= MIC_BOOST_NUM_OF_STEPS) + uinfo->value.enumerated.item = MIC_BOOST_NUM_OF_STEPS - 1; + sprintf(namestr, "%d %s", (uinfo->value.enumerated.item * 10), sfx); + strcpy(uinfo->value.enumerated.name, namestr); + return 0; +} + +static int ca0132_alt_mic_boost_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->mic_boost_enum_val; + return 0; +} + +static int ca0132_alt_mic_boost_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = MIC_BOOST_NUM_OF_STEPS; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ca0132_alt_mic_boost: boost=%d\n", + sel); + + spec->mic_boost_enum_val = sel; + + if (spec->in_enum_val != REAR_LINE_IN) + ca0132_alt_mic_boost_set(codec, spec->mic_boost_enum_val); + + return 1; +} + /* * Input Select Control for alternative ca0132 codecs. This exists because @@ -4609,6 +4986,135 @@ static int ca0132_alt_output_select_put(struct snd_kcontrol *kcontrol, return 1; } +/* + * Smart Volume output setting control. Three different settings, Normal, + * which takes the value from the smart volume slider. The two others, loud + * and night, disregard the slider value and have uneditable values. + */ +#define NUM_OF_SVM_SETTINGS 3 +static const char *out_svm_set_enum_str[3] = {"Normal", "Loud", "Night" }; + +static int ca0132_alt_svm_setting_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_OF_SVM_SETTINGS; + if (uinfo->value.enumerated.item >= NUM_OF_SVM_SETTINGS) + uinfo->value.enumerated.item = NUM_OF_SVM_SETTINGS - 1; + strcpy(uinfo->value.enumerated.name, + out_svm_set_enum_str[uinfo->value.enumerated.item]); + return 0; +} + +static int ca0132_alt_svm_setting_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->smart_volume_setting; + return 0; +} + +static int ca0132_alt_svm_setting_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = NUM_OF_SVM_SETTINGS; + unsigned int idx = SMART_VOLUME - EFFECT_START_NID; + unsigned int tmp; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ca0132_alt_svm_setting: sel=%d, preset=%s\n", + sel, out_svm_set_enum_str[sel]); + + spec->smart_volume_setting = sel; + + switch (sel) { + case 0: + tmp = FLOAT_ZERO; + break; + case 1: + tmp = FLOAT_ONE; + break; + case 2: + tmp = FLOAT_TWO; + break; + default: + tmp = FLOAT_ZERO; + break; + } + /* Req 2 is the Smart Volume Setting req. */ + dspio_set_uint_param(codec, ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[2], tmp); + return 1; +} + +/* Sound Blaster Z EQ preset controls */ +static int ca0132_alt_eq_preset_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = sizeof(ca0132_alt_eq_presets) + / sizeof(struct ct_eq_preset); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + ca0132_alt_eq_presets[uinfo->value.enumerated.item].name); + return 0; +} + +static int ca0132_alt_eq_preset_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->eq_preset_val; + return 0; +} + +static int ca0132_alt_eq_preset_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int i, err = 0; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = sizeof(ca0132_alt_eq_presets) + / sizeof(struct ct_eq_preset); + + if (sel >= items) + return 0; + + codec_dbg(codec, "%s: sel=%d, preset=%s\n", __func__, sel, + ca0132_alt_eq_presets[sel].name); + /* + * Idx 0 is default. + * Default needs to qualify with CrystalVoice state. + */ + for (i = 0; i < EQ_PRESET_MAX_PARAM_COUNT; i++) { + err = dspio_set_uint_param(codec, ca0132_alt_eq_enum.mid, + ca0132_alt_eq_enum.reqs[i], + ca0132_alt_eq_presets[sel].vals[i]); + if (err < 0) + break; + } + + if (err >= 0) + spec->eq_preset_val = sel; + + return 1; +} + static int ca0132_voicefx_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -4998,14 +5504,61 @@ static int ca0132_volume_tlv(struct snd_kcontrol *kcontrol, int op_flag, return err; } +/* Add volume slider control for effect level */ +static int ca0132_alt_add_effect_slider(struct hda_codec *codec, hda_nid_t nid, + const char *pfx, int dir) +{ + char *fx = "FX:"; + char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int type = dir ? HDA_INPUT : HDA_OUTPUT; + struct snd_kcontrol_new knew = + HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type); + + sprintf(namestr, "%s %s %s Volume", fx, pfx, dirstr[dir]); + + knew.tlv.c = 0; + knew.tlv.p = 0; + + switch (nid) { + case XBASS_XOVER: + knew.info = ca0132_alt_xbass_xover_slider_info; + knew.get = ca0132_alt_xbass_xover_slider_ctl_get; + knew.put = ca0132_alt_xbass_xover_slider_put; + break; + default: + knew.info = ca0132_alt_effect_slider_info; + knew.get = ca0132_alt_slider_ctl_get; + knew.put = ca0132_alt_effect_slider_put; + knew.private_value = + HDA_COMPOSE_AMP_VAL(nid, 1, 0, type); + break; + } + + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); +} + +/* + * Added FX: prefix for the alternative codecs, because otherwise the surround + * effect would conflict with the Surround sound volume control. Also seems more + * clear as to what the switches do. Left alone for others. + */ static int add_fx_switch(struct hda_codec *codec, hda_nid_t nid, const char *pfx, int dir) { + struct ca0132_spec *spec = codec->spec; + char *fx = "FX:"; char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; int type = dir ? HDA_INPUT : HDA_OUTPUT; struct snd_kcontrol_new knew = CA0132_CODEC_MUTE_MONO(namestr, nid, 1, type); - sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); + /* If using alt_controls, add FX: prefix. But, don't add FX: + * prefix to OutFX or InFX enable controls. + */ + if ((spec->use_alt_controls) && (nid <= IN_EFFECT_END_NID)) + sprintf(namestr, "%s %s %s Switch", fx, pfx, dirstr[dir]); + else + sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); } @@ -5020,6 +5573,37 @@ static int add_voicefx(struct hda_codec *codec) return snd_hda_ctl_add(codec, VOICEFX, snd_ctl_new1(&knew, codec)); } +/* Create the EQ Preset control */ +static int add_ca0132_alt_eq_presets(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO(ca0132_alt_eq_enum.name, + EQ_PRESET_ENUM, 1, 0, HDA_OUTPUT); + knew.info = ca0132_alt_eq_preset_info; + knew.get = ca0132_alt_eq_preset_get; + knew.put = ca0132_alt_eq_preset_put; + return snd_hda_ctl_add(codec, EQ_PRESET_ENUM, + snd_ctl_new1(&knew, codec)); +} + +/* + * Add enumerated control for the three different settings of the smart volume + * output effect. Normal just uses the slider value, and loud and night are + * their own things that ignore that value. + */ +static int ca0132_alt_add_svm_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("FX: Smart Volume Setting", + SMART_VOLUME_ENUM, 1, 0, HDA_OUTPUT); + knew.info = ca0132_alt_svm_setting_info; + knew.get = ca0132_alt_svm_setting_get; + knew.put = ca0132_alt_svm_setting_put; + return snd_hda_ctl_add(codec, SMART_VOLUME_ENUM, + snd_ctl_new1(&knew, codec)); + +} + /* * Create an Output Select enumerated control for codecs with surround * out capabilities. @@ -5053,6 +5637,72 @@ static int ca0132_alt_add_input_enum(struct hda_codec *codec) snd_ctl_new1(&knew, codec)); } +/* + * Add mic boost enumerated control. Switches through 0dB to 30dB. This adds + * more control than the original mic boost, which is either full 30dB or off. + */ +static int ca0132_alt_add_mic_boost_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("Mic Boost Capture Switch", + MIC_BOOST_ENUM, 1, 0, HDA_INPUT); + knew.info = ca0132_alt_mic_boost_info; + knew.get = ca0132_alt_mic_boost_get; + knew.put = ca0132_alt_mic_boost_put; + return snd_hda_ctl_add(codec, MIC_BOOST_ENUM, + snd_ctl_new1(&knew, codec)); + +} + +/* + * Need to create slave controls for the alternate codecs that have surround + * capabilities. + */ +static const char * const ca0132_alt_slave_pfxs[] = { + "Front", "Surround", "Center", "LFE", NULL, +}; + +/* + * Also need special channel map, because the default one is incorrect. + * I think this has to do with the pin for rear surround being 0x11, + * and the center/lfe being 0x10. Usually the pin order is the opposite. + */ +const struct snd_pcm_chmap_elem ca0132_alt_chmaps[] = { + { .channels = 2, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 4, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 6, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { } +}; + +/* Add the correct chmap for streams with 6 channels. */ +static void ca0132_alt_add_chmap_ctls(struct hda_codec *codec) +{ + int err = 0; + struct hda_pcm *pcm; + + list_for_each_entry(pcm, &codec->pcm_list_head, list) { + struct hda_pcm_stream *hinfo = + &pcm->stream[SNDRV_PCM_STREAM_PLAYBACK]; + struct snd_pcm_chmap *chmap; + const struct snd_pcm_chmap_elem *elem; + + elem = ca0132_alt_chmaps; + if (hinfo->channels_max == 6) { + err = snd_pcm_add_chmap_ctls(pcm->pcm, + SNDRV_PCM_STREAM_PLAYBACK, + elem, hinfo->channels_max, 0, &chmap); + if (err < 0) + codec_dbg(codec, "snd_pcm_add_chmap_ctls failed!"); + } + } +} + /* * When changing Node IDs for Mixer Controls below, make sure to update * Node IDs in ca0132_config() as well. @@ -5087,6 +5737,12 @@ static struct snd_kcontrol_new ca0132_mixer[] = { static struct snd_kcontrol_new sbz_mixer[] = { CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT), CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x04, 0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x04, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x03, 1, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x03, 1, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x03, 2, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x03, 2, 0, HDA_OUTPUT), CA0132_ALT_CODEC_VOL("Capture Volume", 0x07, HDA_INPUT), CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT), HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT), @@ -5103,6 +5759,12 @@ static struct snd_kcontrol_new sbz_mixer[] = { static struct snd_kcontrol_new r3di_mixer[] = { CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT), CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x04, 0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x04, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x03, 1, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x03, 1, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x03, 2, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x03, 2, 0, HDA_OUTPUT), CA0132_CODEC_VOL("Capture Volume", VNID_MIC, HDA_INPUT), CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT), HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT), @@ -5115,7 +5777,7 @@ static struct snd_kcontrol_new r3di_mixer[] = { static int ca0132_build_controls(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; - int i, num_fx; + int i, num_fx, num_sliders; int err = 0; /* Add Mixer controls */ @@ -5124,27 +5786,82 @@ static int ca0132_build_controls(struct hda_codec *codec) if (err < 0) return err; } + /* Setup vmaster with surround slaves for desktop ca0132 devices */ + if (spec->use_alt_functions) { + snd_hda_set_vmaster_tlv(codec, spec->dacs[0], HDA_OUTPUT, + spec->tlv); + snd_hda_add_vmaster(codec, "Master Playback Volume", + spec->tlv, ca0132_alt_slave_pfxs, + "Playback Volume"); + err = __snd_hda_add_vmaster(codec, "Master Playback Switch", + NULL, ca0132_alt_slave_pfxs, + "Playback Switch", + true, &spec->vmaster_mute.sw_kctl); + + } /* Add in and out effects controls. * VoiceFX, PE and CrystalVoice are added separately. */ num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT; for (i = 0; i < num_fx; i++) { + /* SBZ breaks if Echo Cancellation is used */ + if (spec->quirk == QUIRK_SBZ) { + if (i == (ECHO_CANCELLATION - IN_EFFECT_START_NID + + OUT_EFFECTS_COUNT)) + continue; + } + err = add_fx_switch(codec, ca0132_effects[i].nid, ca0132_effects[i].name, ca0132_effects[i].direct); if (err < 0) return err; } + /* + * If codec has use_alt_controls set to true, add effect level sliders, + * EQ presets, and Smart Volume presets. Also, change names to add FX + * prefix, and change PlayEnhancement and CrystalVoice to match. + */ + if (spec->use_alt_controls) { + ca0132_alt_add_svm_enum(codec); + add_ca0132_alt_eq_presets(codec); + err = add_fx_switch(codec, PLAY_ENHANCEMENT, + "Enable OutFX", 0); + if (err < 0) + return err; - err = add_fx_switch(codec, PLAY_ENHANCEMENT, "PlayEnhancement", 0); - if (err < 0) - return err; + err = add_fx_switch(codec, CRYSTAL_VOICE, + "Enable InFX", 1); + if (err < 0) + return err; - err = add_fx_switch(codec, CRYSTAL_VOICE, "CrystalVoice", 1); - if (err < 0) - return err; + num_sliders = OUT_EFFECTS_COUNT - 1; + for (i = 0; i < num_sliders; i++) { + err = ca0132_alt_add_effect_slider(codec, + ca0132_effects[i].nid, + ca0132_effects[i].name, + ca0132_effects[i].direct); + if (err < 0) + return err; + } + + err = ca0132_alt_add_effect_slider(codec, XBASS_XOVER, + "X-Bass Crossover", EFX_DIR_OUT); + + if (err < 0) + return err; + } else { + err = add_fx_switch(codec, PLAY_ENHANCEMENT, + "PlayEnhancement", 0); + if (err < 0) + return err; + err = add_fx_switch(codec, CRYSTAL_VOICE, + "CrystalVoice", 1); + if (err < 0) + return err; + } add_voicefx(codec); /* @@ -5155,6 +5872,7 @@ static int ca0132_build_controls(struct hda_codec *codec) if (spec->use_alt_functions) { ca0132_alt_add_output_enum(codec); ca0132_alt_add_input_enum(codec); + ca0132_alt_add_mic_boost_enum(codec); } #ifdef ENABLE_TUNING_CONTROLS add_tuning_ctls(codec); @@ -5180,6 +5898,10 @@ static int ca0132_build_controls(struct hda_codec *codec) if (err < 0) return err; } + + if (spec->use_alt_functions) + ca0132_alt_add_chmap_ctls(codec); + return 0; } @@ -5234,6 +5956,11 @@ static int ca0132_build_pcms(struct hda_codec *codec) info = snd_hda_codec_pcm_new(codec, "CA0132 Analog"); if (!info) return -ENOMEM; + if (spec->use_alt_functions) { + info->own_chmap = true; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].chmap + = ca0132_alt_chmaps; + } info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ca0132_pcm_analog_playback; info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dacs[0]; info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = @@ -6122,6 +6849,15 @@ static void ca0132_init_chip(struct hda_codec *codec) on = (unsigned int)ca0132_effects[i].reqs[0]; spec->effects_switch[i] = on ? 1 : 0; } + /* + * Sets defaults for the effect slider controls, only for alternative + * ca0132 codecs. Also sets x-bass crossover frequency to 80hz. + */ + if (spec->use_alt_controls) { + spec->xbass_xover_freq = 8; + for (i = 0; i < EFFECT_LEVEL_SLIDERS; i++) + spec->fx_ctl_val[i] = effect_slider_defaults[i]; + } spec->voicefx_val = 0; spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID] = 1; @@ -6841,13 +7577,15 @@ static int patch_ca0132(struct hda_codec *codec) break; } - /* Setup whether or not to use alt functions */ + /* Setup whether or not to use alt functions/controls */ switch (spec->quirk) { case QUIRK_SBZ: case QUIRK_R3DI: + spec->use_alt_controls = true; spec->use_alt_functions = true; break; default: + spec->use_alt_controls = false; spec->use_alt_functions = false; break; } -- cgit v1.2.3 From 16bafa792c860e50768b2527ded364137c6ed21e Mon Sep 17 00:00:00 2001 From: Alberto Aguirre Date: Tue, 8 May 2018 17:14:13 -0500 Subject: ALSA: usb-audio: add boot quirk for Axe-Fx III Wait for Axe-Fx III to fully bootup before initializing card. Signed-off-by: Alberto Aguirre Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'sound') diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 5681767cc0d5..f4b69173682c 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -851,6 +851,36 @@ static int snd_usb_mbox2_boot_quirk(struct usb_device *dev) return 0; /* Successful boot */ } +static int snd_usb_axefx3_boot_quirk(struct usb_device *dev) +{ + int err; + + dev_dbg(&dev->dev, "Waiting for Axe-Fx III to boot up...\n"); + + /* If the Axe-Fx III has not fully booted, it will timeout when trying + * to enable the audio streaming interface. A more generous timeout is + * used here to detect when the Axe-Fx III has finished booting as the + * set interface message will be acked once it has + */ + err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_INTERFACE, USB_RECIP_INTERFACE, + 1, 1, NULL, 0, 120000); + if (err < 0) { + dev_err(&dev->dev, + "failed waiting for Axe-Fx III to boot: %d\n", err); + return err; + } + + dev_dbg(&dev->dev, "Axe-Fx III is now ready\n"); + + err = usb_set_interface(dev, 1, 0); + if (err < 0) + dev_dbg(&dev->dev, + "error stopping Axe-Fx III interface: %d\n", err); + + return 0; +} + /* * Setup quirks */ @@ -1026,6 +1056,8 @@ int snd_usb_apply_boot_quirk(struct usb_device *dev, return snd_usb_fasttrackpro_boot_quirk(dev); case USB_ID(0x047f, 0xc010): /* Plantronics Gamecom 780 */ return snd_usb_gamecon780_boot_quirk(dev); + case USB_ID(0x2466, 0x8010): /* Fractal Audio Axe-Fx 3 */ + return snd_usb_axefx3_boot_quirk(dev); } return 0; -- cgit v1.2.3 From 841bdb7c0bde0fb2068c64a09bb89a06be63c4d6 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Mon, 14 May 2018 07:09:51 +0900 Subject: ALSA: vmaster: use position offset macro of TLV data A series of SNDRV_CTL_TLVO_XXX macro was introduced for position offset of TLV data. This commit applies a code optimization. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/core/vmaster.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c index 9e96186742d0..58fa3f94722a 100644 --- a/sound/core/vmaster.c +++ b/sound/core/vmaster.c @@ -421,13 +421,15 @@ struct snd_kcontrol *snd_ctl_make_virtual_master(char *name, kctl->private_free = master_free; /* additional (constant) TLV read */ - if (tlv && - (tlv[0] == SNDRV_CTL_TLVT_DB_SCALE || - tlv[0] == SNDRV_CTL_TLVT_DB_MINMAX || - tlv[0] == SNDRV_CTL_TLVT_DB_MINMAX_MUTE)) { - kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; - memcpy(master->tlv, tlv, sizeof(master->tlv)); - kctl->tlv.p = master->tlv; + if (tlv) { + unsigned int type = tlv[SNDRV_CTL_TLVO_TYPE]; + if (type == SNDRV_CTL_TLVT_DB_SCALE || + type == SNDRV_CTL_TLVT_DB_MINMAX || + type == SNDRV_CTL_TLVT_DB_MINMAX_MUTE) { + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + memcpy(master->tlv, tlv, sizeof(master->tlv)); + kctl->tlv.p = master->tlv; + } } return kctl; -- cgit v1.2.3 From 51cdc8b6a659727ceb843674aae1b3fb69cbe6cb Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Mon, 14 May 2018 07:09:52 +0900 Subject: ALSA: hda: use position offset macro of TLV data A series of SNDRV_CTL_TLVO_XXX macro was introduced for position offset of TLV data. This commit applies a code optimization. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_codec.c | 22 +++++++++++----------- sound/pci/hda/hda_generic.c | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 63f177d975fd..08151f3c0b13 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1493,10 +1493,10 @@ static void get_ctl_amp_tlv(struct snd_kcontrol *kcontrol, unsigned int *tlv) val1 = ((int)val1) * ((int)val2); if (min_mute || (caps & AC_AMPCAP_MIN_MUTE)) val2 |= TLV_DB_SCALE_MUTE; - tlv[0] = SNDRV_CTL_TLVT_DB_SCALE; - tlv[1] = 2 * sizeof(unsigned int); - tlv[2] = val1; - tlv[3] = val2; + tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_SCALE; + tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int); + tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] = val1; + tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] = val2; } /** @@ -1544,10 +1544,10 @@ void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir, nums = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT; step = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT; step = (step + 1) * 25; - tlv[0] = SNDRV_CTL_TLVT_DB_SCALE; - tlv[1] = 2 * sizeof(unsigned int); - tlv[2] = -nums * step; - tlv[3] = step; + tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_SCALE; + tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int); + tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] = -nums * step; + tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] = step; } EXPORT_SYMBOL_GPL(snd_hda_set_vmaster_tlv); @@ -1845,10 +1845,10 @@ static int init_slave_0dB(struct snd_kcontrol *slave, } else if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) tlv = kctl->tlv.p; - if (!tlv || tlv[0] != SNDRV_CTL_TLVT_DB_SCALE) + if (!tlv || tlv[SNDRV_CTL_TLVO_TYPE] != SNDRV_CTL_TLVT_DB_SCALE) return 0; - step = tlv[3]; + step = tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP]; step &= ~TLV_DB_SCALE_MUTE; if (!step) return 0; @@ -1860,7 +1860,7 @@ static int init_slave_0dB(struct snd_kcontrol *slave, } arg->step = step; - val = -tlv[2] / step; + val = -tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] / step; if (val > 0) { put_kctl_with_value(slave, val); return val; diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index 51030f040745..db773e219aaa 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -2065,7 +2065,7 @@ static int parse_output_paths(struct hda_codec *codec) snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid, HDA_OUTPUT, spec->vmaster_tlv); if (spec->dac_min_mute) - spec->vmaster_tlv[3] |= TLV_DB_SCALE_MUTE; + spec->vmaster_tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] |= TLV_DB_SCALE_MUTE; } } -- cgit v1.2.3 From 4844f51282b60f8a96b05d422f452a785df308b9 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Mon, 14 May 2018 07:09:53 +0900 Subject: ALSA: isight: use position offset macro of TLV data A series of SNDRV_CTL_TLVO_XXX macro was introduced for position offset of TLV data. This commit applies a code optimization. Signed-off-by: Takashi Sakamoto Acked-by: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/firewire/isight.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c index 46092fa3ff9b..3919e186a30b 100644 --- a/sound/firewire/isight.c +++ b/sound/firewire/isight.c @@ -569,18 +569,20 @@ static int isight_create_mixer(struct isight *isight) return err; isight->gain_max = be32_to_cpu(value); - isight->gain_tlv[0] = SNDRV_CTL_TLVT_DB_MINMAX; - isight->gain_tlv[1] = 2 * sizeof(unsigned int); + isight->gain_tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_MINMAX; + isight->gain_tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int); err = reg_read(isight, REG_GAIN_DB_START, &value); if (err < 0) return err; - isight->gain_tlv[2] = (s32)be32_to_cpu(value) * 100; + isight->gain_tlv[SNDRV_CTL_TLVO_DB_MINMAX_MIN] = + (s32)be32_to_cpu(value) * 100; err = reg_read(isight, REG_GAIN_DB_END, &value); if (err < 0) return err; - isight->gain_tlv[3] = (s32)be32_to_cpu(value) * 100; + isight->gain_tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX] = + (s32)be32_to_cpu(value) * 100; ctl = snd_ctl_new1(&gain_control, isight); if (ctl) -- cgit v1.2.3 From c5f13d75fba09c499f8370e38f94624ff6510500 Mon Sep 17 00:00:00 2001 From: Fengguang Wu Date: Tue, 15 May 2018 03:02:14 +0800 Subject: ALSA: hda/ca0132: fix array_size.cocci warnings sound/pci/hda/patch_ca0132.c:5062:50-51: WARNING: Use ARRAY_SIZE sound/pci/hda/patch_ca0132.c:5092:50-51: WARNING: Use ARRAY_SIZE Use ARRAY_SIZE instead of dividing sizeof array with sizeof an element Semantic patch information: This makes an effort to find cases where ARRAY_SIZE can be used such as where there is a division of sizeof the array by the sizeof its first element or by any indexed element or the element type. It replaces the division of the two sizeofs by ARRAY_SIZE. Generated by: scripts/coccinelle/misc/array_size.cocci Fixes: 47cdf76e44e8 ("ALSA: hda/ca0132: Add new control changes for SBZ + R3Di") CC: Connor McAdams Signed-off-by: Fengguang Wu Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 0fa67f45241d..1cc4f5133645 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -5059,8 +5059,7 @@ static int ca0132_alt_svm_setting_put(struct snd_kcontrol *kcontrol, static int ca0132_alt_eq_preset_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - unsigned int items = sizeof(ca0132_alt_eq_presets) - / sizeof(struct ct_eq_preset); + unsigned int items = ARRAY_SIZE(ca0132_alt_eq_presets); uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = 1; @@ -5089,8 +5088,7 @@ static int ca0132_alt_eq_preset_put(struct snd_kcontrol *kcontrol, struct ca0132_spec *spec = codec->spec; int i, err = 0; int sel = ucontrol->value.enumerated.item[0]; - unsigned int items = sizeof(ca0132_alt_eq_presets) - / sizeof(struct ct_eq_preset); + unsigned int items = ARRAY_SIZE(ca0132_alt_eq_presets); if (sel >= items) return 0; -- cgit v1.2.3 From 6cfd839ae78ec3fac5ddbf7148155898727e90c3 Mon Sep 17 00:00:00 2001 From: Jorge Sanjuan Date: Fri, 11 May 2018 16:25:34 +0100 Subject: ALSA: usb-audio: UAC3. Add support for mixer unit. This adds support for the MIXER UNIT in UAC3. All the information is obtained from the (HIGH CAPABILITY) Cluster's header. We don't read the rest of the logical cluster to obtain the channel config as that wont make any difference in the current mixer behaviour. The name of the mixer unit is not yet requested as there is not support for the UAC3 Class Specific String requests. Tested in an UAC3 device working as a HEADSET with a basic mixer unit (same as the one in the BADD spec) with no controls. Signed-off-by: Jorge Sanjuan Reviewed-by: Ruslan Bilovol Tested-by: Ruslan Bilovol Signed-off-by: Takashi Iwai --- include/uapi/linux/usb/audio.h | 19 +++++++-- sound/usb/mixer.c | 88 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 97 insertions(+), 10 deletions(-) (limited to 'sound') diff --git a/include/uapi/linux/usb/audio.h b/include/uapi/linux/usb/audio.h index 3a78e7145689..13d98e6e0db1 100644 --- a/include/uapi/linux/usb/audio.h +++ b/include/uapi/linux/usb/audio.h @@ -285,9 +285,22 @@ static inline __u8 uac_mixer_unit_iChannelNames(struct uac_mixer_unit_descriptor static inline __u8 *uac_mixer_unit_bmControls(struct uac_mixer_unit_descriptor *desc, int protocol) { - return (protocol == UAC_VERSION_1) ? - &desc->baSourceID[desc->bNrInPins + 4] : - &desc->baSourceID[desc->bNrInPins + 6]; + switch (protocol) { + case UAC_VERSION_1: + return &desc->baSourceID[desc->bNrInPins + 4]; + case UAC_VERSION_2: + return &desc->baSourceID[desc->bNrInPins + 6]; + case UAC_VERSION_3: + return &desc->baSourceID[desc->bNrInPins + 2]; + default: + return NULL; + } +} + +static inline __u16 uac3_mixer_unit_wClusterDescrID(struct uac_mixer_unit_descriptor *desc) +{ + return (desc->baSourceID[desc->bNrInPins + 1] << 8) | + desc->baSourceID[desc->bNrInPins]; } static inline __u8 uac_mixer_unit_iMixer(struct uac_mixer_unit_descriptor *desc) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 173979e05e63..c4abb3bca2ad 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -716,6 +716,66 @@ static int get_term_name(struct snd_usb_audio *chip, struct usb_audio_term *iter return 0; } +/* + * Get logical cluster information for UAC3 devices. + */ +static int get_cluster_channels_v3(struct mixer_build *state, unsigned int cluster_id) +{ + struct uac3_cluster_header_descriptor c_header; + int err; + + err = snd_usb_ctl_msg(state->chip->dev, + usb_rcvctrlpipe(state->chip->dev, 0), + UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + cluster_id, + snd_usb_ctrl_intf(state->chip), + &c_header, sizeof(c_header)); + if (err < 0) + goto error; + if (err != sizeof(c_header)) { + err = -EIO; + goto error; + } + + return c_header.bNrChannels; + +error: + usb_audio_err(state->chip, "cannot request logical cluster ID: %d (err: %d)\n", cluster_id, err); + return err; +} + +/* + * Get number of channels for a Mixer Unit. + */ +static int uac_mixer_unit_get_channels(struct mixer_build *state, + struct uac_mixer_unit_descriptor *desc) +{ + int mu_channels; + + if (desc->bLength < 11) + return -EINVAL; + if (!desc->bNrInPins) + return -EINVAL; + + switch (state->mixer->protocol) { + case UAC_VERSION_1: + case UAC_VERSION_2: + default: + mu_channels = uac_mixer_unit_bNrChannels(desc); + break; + case UAC_VERSION_3: + mu_channels = get_cluster_channels_v3(state, + uac3_mixer_unit_wClusterDescrID(desc)); + break; + } + + if (!mu_channels) + return -EINVAL; + + return mu_channels; +} + /* * parse the source unit recursively until it reaches to a terminal * or a branched unit. @@ -863,6 +923,18 @@ static int check_input_term(struct mixer_build *state, int id, term->name = le16_to_cpu(d->wClockSourceStr); return 0; } + case UAC3_MIXER_UNIT: { + struct uac_mixer_unit_descriptor *d = p1; + + err = uac_mixer_unit_get_channels(state, d); + if (err < 0) + return err; + + term->channels = err; + term->type = d->bDescriptorSubtype << 16; /* virtual type */ + + return 0; + } default: return -ENODEV; } @@ -1826,11 +1898,10 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, */ static void build_mixer_unit_ctl(struct mixer_build *state, struct uac_mixer_unit_descriptor *desc, - int in_pin, int in_ch, int unitid, - struct usb_audio_term *iterm) + int in_pin, int in_ch, int num_outs, + int unitid, struct usb_audio_term *iterm) { struct usb_mixer_elem_info *cval; - unsigned int num_outs = uac_mixer_unit_bNrChannels(desc); unsigned int i, len; struct snd_kcontrol *kctl; const struct usbmix_name_map *map; @@ -1907,14 +1978,17 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, int input_pins, num_ins, num_outs; int pin, ich, err; - if (desc->bLength < 11 || !(input_pins = desc->bNrInPins) || - !(num_outs = uac_mixer_unit_bNrChannels(desc))) { + err = uac_mixer_unit_get_channels(state, desc); + if (err < 0) { usb_audio_err(state->chip, "invalid MIXER UNIT descriptor %d\n", unitid); - return -EINVAL; + return err; } + num_outs = err; + input_pins = desc->bNrInPins; + num_ins = 0; ich = 0; for (pin = 0; pin < input_pins; pin++) { @@ -1941,7 +2015,7 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, } } if (ich_has_controls) - build_mixer_unit_ctl(state, desc, pin, ich, + build_mixer_unit_ctl(state, desc, pin, ich, num_outs, unitid, &iterm); } } -- cgit v1.2.3 From 1d38f5d828b45546e53095a172c77556642a8a04 Mon Sep 17 00:00:00 2001 From: Jorge Sanjuan Date: Fri, 11 May 2018 16:25:36 +0100 Subject: ALSA: usb-audio: UAC3 Add support for connector insertion. This adds support for the UAC3 insertion controls. The status is reported as a boolean value in the same way it used to do for UAC2. Hence, the presence of any connector in the response will make the control saying the jack is connected. The UAC2 support for this control has been moved to a dedicated control for connectors as both UAC2 and UAC3 follow a specific Control Request Parameter Block for this control. This parameter block for UAC3 could not be read in the same simplistic manner as in UAC2. This implementation is not requesting additional information from the HIGH CAPABILITY Connectors descriptor. Tested with an UAC3 device with UAC2 as legacy configuration. The connector status can be read with `amixer` and the interrupt is also caught with `alsactl monitor`. Signed-off-by: Jorge Sanjuan Reviewed-by: Ruslan Bilovol Tested-by: Ruslan Bilovol Signed-off-by: Takashi Iwai --- include/linux/usb/audio-v2.h | 7 +++ include/linux/usb/audio-v3.h | 14 ++++++ sound/usb/mixer.c | 108 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 115 insertions(+), 14 deletions(-) (limited to 'sound') diff --git a/include/linux/usb/audio-v2.h b/include/linux/usb/audio-v2.h index aaafecf073ff..a96ed2ce3254 100644 --- a/include/linux/usb/audio-v2.h +++ b/include/linux/usb/audio-v2.h @@ -189,6 +189,13 @@ struct uac2_iso_endpoint_descriptor { #define UAC2_CONTROL_DATA_OVERRUN (3 << 2) #define UAC2_CONTROL_DATA_UNDERRUN (3 << 4) +/* 5.2.5.4.2 Connector Control Parameter Block */ +struct uac2_connectors_ctl_blk { + __u8 bNrChannels; + __le32 bmChannelConfig; + __u8 iChannelNames; +} __attribute__((packed)); + /* 6.1 Interrupt Data Message */ #define UAC2_INTERRUPT_DATA_MSG_VENDOR (1 << 0) diff --git a/include/linux/usb/audio-v3.h b/include/linux/usb/audio-v3.h index 38add1dedf2e..a710e28b5215 100644 --- a/include/linux/usb/audio-v3.h +++ b/include/linux/usb/audio-v3.h @@ -221,6 +221,12 @@ struct uac3_iso_endpoint_descriptor { __le16 wLockDelay; } __attribute__((packed)); +/* 5.2.1.6.1 INSERTION CONTROL PARAMETER BLOCK */ +struct uac3_insertion_ctl_blk { + __u8 bSize; + __u8 bmConInserted; +} __attribute__ ((packed)); + /* 6.1 INTERRUPT DATA MESSAGE */ struct uac3_interrupt_data_msg { __u8 bInfo; @@ -392,6 +398,14 @@ struct uac3_interrupt_data_msg { #define UAC3_AC_ACTIVE_INTERFACE_CONTROL 0x01 #define UAC3_AC_POWER_DOMAIN_CONTROL 0x02 +/* A.23.5 TERMINAL CONTROL SELECTORS */ +#define UAC3_TE_UNDEFINED 0x00 +#define UAC3_TE_INSERTION 0x01 +#define UAC3_TE_OVERLOAD 0x02 +#define UAC3_TE_UNDERFLOW 0x03 +#define UAC3_TE_OVERFLOW 0x04 +#define UAC3_TE_LATENCY 0x05 + /* BADD predefined Unit/Terminal values */ #define UAC3_BADD_IT_ID1 1 /* Input Terminal ID1: bTerminalID = 1 */ #define UAC3_BADD_FU_ID2 2 /* Feature Unit ID2: bUnitID = 2 */ diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index c4abb3bca2ad..fb77847cbffc 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1328,6 +1328,51 @@ static int mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol, return 0; } +/* get the connectors status and report it as boolean type */ +static int mixer_ctl_connector_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + struct snd_usb_audio *chip = cval->head.mixer->chip; + int idx = 0, validx, ret, val; + + validx = cval->control << 8 | 0; + + ret = snd_usb_lock_shutdown(chip) ? -EIO : 0; + if (ret) + goto error; + + idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8); + if (cval->head.mixer->protocol == UAC_VERSION_2) { + struct uac2_connectors_ctl_blk uac2_conn; + + ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx, idx, &uac2_conn, sizeof(uac2_conn)); + val = !!uac2_conn.bNrChannels; + } else { /* UAC_VERSION_3 */ + struct uac3_insertion_ctl_blk uac3_conn; + + ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx, idx, &uac3_conn, sizeof(uac3_conn)); + val = !!uac3_conn.bmConInserted; + } + + snd_usb_unlock_shutdown(chip); + + if (ret < 0) { +error: + usb_audio_err(chip, + "cannot get connectors status: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", + UAC_GET_CUR, validx, idx, cval->val_type); + return ret; + } + + ucontrol->value.integer.value[0] = val; + return 0; +} + static struct snd_kcontrol_new usb_feature_unit_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", /* will be filled later manually */ @@ -1358,6 +1403,15 @@ static struct snd_kcontrol_new usb_bool_master_control_ctl_ro = { .put = NULL, }; +static const struct snd_kcontrol_new usb_connector_ctl_ro = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "", /* will be filled later manually */ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ctl_boolean_mono_info, + .get = mixer_ctl_connector_get, + .put = NULL, +}; + /* * This symbol is exported in order to allow the mixer quirks to * hook up to the standard feature unit control mechanism @@ -1626,17 +1680,25 @@ static void build_connector_control(struct mixer_build *state, return; snd_usb_mixer_elem_init_std(&cval->head, state->mixer, term->id); /* - * The first byte from reading the UAC2_TE_CONNECTOR control returns the - * number of channels connected. This boolean ctl will simply report - * if any channels are connected or not. - * (Audio20_final.pdf Table 5-10: Connector Control CUR Parameter Block) + * UAC2: The first byte from reading the UAC2_TE_CONNECTOR control returns the + * number of channels connected. + * + * UAC3: The first byte specifies size of bitmap for the inserted controls. The + * following byte(s) specifies which connectors are inserted. + * + * This boolean ctl will simply report if any channels are connected + * or not. */ - cval->control = UAC2_TE_CONNECTOR; + if (state->mixer->protocol == UAC_VERSION_2) + cval->control = UAC2_TE_CONNECTOR; + else /* UAC_VERSION_3 */ + cval->control = UAC3_TE_INSERTION; + cval->val_type = USB_MIXER_BOOLEAN; cval->channels = 1; /* report true if any channel is connected */ cval->min = 0; cval->max = 1; - kctl = snd_ctl_new1(&usb_bool_master_control_ctl_ro, cval); + kctl = snd_ctl_new1(&usb_connector_ctl_ro, cval); if (!kctl) { usb_audio_err(state->chip, "cannot malloc kcontrol\n"); kfree(cval); @@ -1954,16 +2016,28 @@ static int parse_audio_input_terminal(struct mixer_build *state, int unitid, void *raw_desc) { struct usb_audio_term iterm; - struct uac2_input_terminal_descriptor *d = raw_desc; + unsigned int control, bmctls, term_id; - check_input_term(state, d->bTerminalID, &iterm); if (state->mixer->protocol == UAC_VERSION_2) { - /* Check for jack detection. */ - if (uac_v2v3_control_is_readable(le16_to_cpu(d->bmControls), - UAC2_TE_CONNECTOR)) { - build_connector_control(state, &iterm, true); - } + struct uac2_input_terminal_descriptor *d_v2 = raw_desc; + control = UAC2_TE_CONNECTOR; + term_id = d_v2->bTerminalID; + bmctls = le16_to_cpu(d_v2->bmControls); + } else if (state->mixer->protocol == UAC_VERSION_3) { + struct uac3_input_terminal_descriptor *d_v3 = raw_desc; + control = UAC3_TE_INSERTION; + term_id = d_v3->bTerminalID; + bmctls = le32_to_cpu(d_v3->bmControls); + } else { + return 0; /* UAC1. No Insertion control */ } + + check_input_term(state, term_id, &iterm); + + /* Check for jack detection. */ + if (uac_v2v3_control_is_readable(bmctls, control)) + build_connector_control(state, &iterm, true); + return 0; } @@ -2554,7 +2628,7 @@ static int parse_audio_unit(struct mixer_build *state, int unitid) } else { /* UAC_VERSION_3 */ switch (p1[2]) { case UAC_INPUT_TERMINAL: - return 0; /* NOP */ + return parse_audio_input_terminal(state, unitid, p1); case UAC3_MIXER_UNIT: return parse_audio_mixer_unit(state, unitid, p1); case UAC3_CLOCK_SOURCE: @@ -2932,6 +3006,12 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) err = parse_audio_unit(&state, desc->bCSourceID); if (err < 0 && err != -EINVAL) return err; + + if (uac_v2v3_control_is_readable(le32_to_cpu(desc->bmControls), + UAC3_TE_INSERTION)) { + build_connector_control(&state, &state.oterm, + false); + } } } -- cgit v1.2.3 From 710669455d9e6978336dcbcb220024fb64ec2d38 Mon Sep 17 00:00:00 2001 From: Jorge Sanjuan Date: Mon, 14 May 2018 12:03:42 +0100 Subject: ALSA: usb-audio: UAC3: Parse Input Terminal number of channels. Obtain the number of channels for the Input Terminal from the Logical Cluster Descriptor. This achieves a useful minimal parsing of this unit so it can be used in other units in the topology. Signed-off-by: Jorge Sanjuan Reviewed-by: Ruslan Bilovol Tested-by: Ruslan Bilovol Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index fb77847cbffc..bf74e7edc92b 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -902,8 +902,12 @@ static int check_input_term(struct mixer_build *state, int id, term->id = id; term->type = le16_to_cpu(d->wTerminalType); - /* REVISIT: UAC3 IT doesn't have channels/cfg */ - term->channels = 0; + err = get_cluster_channels_v3(state, le16_to_cpu(d->wClusterDescrID)); + if (err < 0) + return err; + term->channels = err; + + /* REVISIT: UAC3 IT doesn't have channels cfg */ term->chconfig = 0; term->name = le16_to_cpu(d->wTerminalDescrStr); -- cgit v1.2.3 From b0eaa0721df31d4a7c47989a1ac6faedd8495ba1 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Tue, 15 May 2018 22:12:57 +0900 Subject: ALSA: hda/ca0132: constify templates for control element set An array of templates for control element set is passed as an argument for snd_hda_add_new_ctls(). This argument has 'const' qualifier therefore the passed array can have the qualifier. This commit adds this optimization. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 1cc4f5133645..d18022d72e83 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -892,7 +892,7 @@ enum dsp_download_state { */ struct ca0132_spec { - struct snd_kcontrol_new *mixers[5]; + const struct snd_kcontrol_new *mixers[5]; unsigned int num_mixers; const struct hda_verb *base_init_verbs; const struct hda_verb *base_exit_verbs; @@ -5705,7 +5705,7 @@ static void ca0132_alt_add_chmap_ctls(struct hda_codec *codec) * When changing Node IDs for Mixer Controls below, make sure to update * Node IDs in ca0132_config() as well. */ -static struct snd_kcontrol_new ca0132_mixer[] = { +static const struct snd_kcontrol_new ca0132_mixer[] = { CA0132_CODEC_VOL("Master Playback Volume", VNID_SPK, HDA_OUTPUT), CA0132_CODEC_MUTE("Master Playback Switch", VNID_SPK, HDA_OUTPUT), CA0132_CODEC_VOL("Capture Volume", VNID_MIC, HDA_INPUT), @@ -5732,7 +5732,7 @@ static struct snd_kcontrol_new ca0132_mixer[] = { * controls. Also sets both the Front Playback and Capture Volume controls to * alt so they set the DSP's decibel level. */ -static struct snd_kcontrol_new sbz_mixer[] = { +static const struct snd_kcontrol_new sbz_mixer[] = { CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT), CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT), HDA_CODEC_VOLUME("Surround Playback Volume", 0x04, 0, HDA_OUTPUT), @@ -5754,7 +5754,7 @@ static struct snd_kcontrol_new sbz_mixer[] = { * Same as the Sound Blaster Z, except doesn't use the alt volume for capture * because it doesn't set decibel levels for the DSP for capture. */ -static struct snd_kcontrol_new r3di_mixer[] = { +static const struct snd_kcontrol_new r3di_mixer[] = { CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT), CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT), HDA_CODEC_VOLUME("Surround Playback Volume", 0x04, 0, HDA_OUTPUT), -- cgit v1.2.3 From 3a03f83b168b19f715cd043dc3a4600bd99f08ce Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Tue, 15 May 2018 22:12:58 +0900 Subject: ALSA: hda/ca0132: constify read-only members of string array This module has some strings just for printk therefore they can be read-only. This commit applies this optimization. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index d18022d72e83..8295bd06af66 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -90,7 +90,7 @@ MODULE_FIRMWARE(SBZ_EFX_FILE); MODULE_FIRMWARE(R3DI_EFX_FILE); #endif -static const char *dirstr[2] = { "Playback", "Capture" }; +static const char *const dirstr[2] = { "Playback", "Capture" }; #define NUM_OF_OUTPUTS 3 enum { @@ -105,7 +105,7 @@ enum { }; /* Strings for Input Source Enum Control */ -static const char *in_src_str[3] = {"Rear Mic", "Line", "Front Mic" }; +static const char *const in_src_str[3] = {"Rear Mic", "Line", "Front Mic" }; #define IN_SRC_NUM_OF_INPUTS 3 enum { REAR_MIC, @@ -4992,7 +4992,7 @@ static int ca0132_alt_output_select_put(struct snd_kcontrol *kcontrol, * and night, disregard the slider value and have uneditable values. */ #define NUM_OF_SVM_SETTINGS 3 -static const char *out_svm_set_enum_str[3] = {"Normal", "Loud", "Night" }; +static const char *const out_svm_set_enum_str[3] = {"Normal", "Loud", "Night" }; static int ca0132_alt_svm_setting_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) -- cgit v1.2.3 From 0cc1aa716226abf9c52e920fc04999fcafa17c67 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Tue, 15 May 2018 22:12:59 +0900 Subject: ALSA: hda/ca0132: merge strings just for printk This module has some function-local strings just for printk therefore it can be merged into format string. This commit applies this optimization. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 8295bd06af66..08a08dd16eab 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -5506,13 +5506,12 @@ static int ca0132_volume_tlv(struct snd_kcontrol *kcontrol, int op_flag, static int ca0132_alt_add_effect_slider(struct hda_codec *codec, hda_nid_t nid, const char *pfx, int dir) { - char *fx = "FX:"; char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; int type = dir ? HDA_INPUT : HDA_OUTPUT; struct snd_kcontrol_new knew = HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type); - sprintf(namestr, "%s %s %s Volume", fx, pfx, dirstr[dir]); + sprintf(namestr, "FX: %s %s Volume", pfx, dirstr[dir]); knew.tlv.c = 0; knew.tlv.p = 0; @@ -5544,7 +5543,6 @@ static int add_fx_switch(struct hda_codec *codec, hda_nid_t nid, const char *pfx, int dir) { struct ca0132_spec *spec = codec->spec; - char *fx = "FX:"; char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; int type = dir ? HDA_INPUT : HDA_OUTPUT; struct snd_kcontrol_new knew = @@ -5553,7 +5551,7 @@ static int add_fx_switch(struct hda_codec *codec, hda_nid_t nid, * prefix to OutFX or InFX enable controls. */ if ((spec->use_alt_controls) && (nid <= IN_EFFECT_END_NID)) - sprintf(namestr, "%s %s %s Switch", fx, pfx, dirstr[dir]); + sprintf(namestr, "FX: %s %s Switch", pfx, dirstr[dir]); else sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); -- cgit v1.2.3 From 862154bbd7d7f9c70eabd0e72b00a39673df71e5 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Tue, 15 May 2018 22:13:00 +0900 Subject: ALSA: hda/ca0132: constify parameter table for effects This module has a table for parameters of each effects. This table is read-only and can have 'const' qualifier. This commit adds this optimization. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 08a08dd16eab..292e2c592c17 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -189,7 +189,7 @@ struct ct_effect { #define EFX_DIR_OUT 0 #define EFX_DIR_IN 1 -static struct ct_effect ca0132_effects[EFFECTS_COUNT] = { +static const struct ct_effect ca0132_effects[EFFECTS_COUNT] = { { .name = "Surround", .nid = SURROUND, .mid = 0x96, @@ -316,7 +316,7 @@ struct ct_tuning_ctl { unsigned int def_val;/*effect default values*/ }; -static struct ct_tuning_ctl ca0132_tuning_ctls[] = { +static const struct ct_tuning_ctl ca0132_tuning_ctls[] = { { .name = "Wedge Angle", .parent_nid = VOICE_FOCUS, .nid = WEDGE_ANGLE, @@ -431,14 +431,14 @@ struct ct_voicefx_preset { unsigned int vals[VOICEFX_MAX_PARAM_COUNT]; }; -static struct ct_voicefx ca0132_voicefx = { +static const struct ct_voicefx ca0132_voicefx = { .name = "VoiceFX Capture Switch", .nid = VOICEFX, .mid = 0x95, .reqs = {10, 11, 12, 13, 14, 15, 16, 17, 18} }; -static struct ct_voicefx_preset ca0132_voicefx_presets[] = { +static const struct ct_voicefx_preset ca0132_voicefx_presets[] = { { .name = "Neutral", .vals = { 0x00000000, 0x43C80000, 0x44AF0000, 0x44FA0000, 0x3F800000, 0x3F800000, @@ -527,7 +527,7 @@ struct ct_eq_preset { unsigned int vals[EQ_PRESET_MAX_PARAM_COUNT]; }; -static struct ct_eq ca0132_alt_eq_enum = { +static const struct ct_eq ca0132_alt_eq_enum = { .name = "FX: Equalizer Preset Switch", .nid = EQ_PRESET_ENUM, .mid = 0x96, @@ -535,7 +535,7 @@ static struct ct_eq ca0132_alt_eq_enum = { }; -static struct ct_eq_preset ca0132_alt_eq_presets[] = { +static const struct ct_eq_preset ca0132_alt_eq_presets[] = { { .name = "Flat", .vals = { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, @@ -655,7 +655,7 @@ struct ct_dsp_volume_ctl { unsigned int reqs[3]; /* scp req ID */ }; -static struct ct_dsp_volume_ctl ca0132_alt_vol_ctls[] = { +static const struct ct_dsp_volume_ctl ca0132_alt_vol_ctls[] = { { .vnid = VNID_SPK, .mid = 0x32, .reqs = {3, 4, 2} -- cgit v1.2.3 From cc3196ae197c28cd6db0a2e9ddddc2e0aa1e694f Mon Sep 17 00:00:00 2001 From: Oleksandr Andrushchenko Date: Mon, 14 May 2018 09:27:37 +0300 Subject: ALSA: xen-front: Introduce Xen para-virtualized sound frontend driver Introduce skeleton of the para-virtualized Xen sound frontend driver. Initial handling for Xen bus states: implement Xen bus state machine for the frontend driver according to the state diagram and recovery flow from sound para-virtualized protocol: xen/interface/io/sndif.h. Signed-off-by: Oleksandr Andrushchenko Reviewed-by: Juergen Gross Signed-off-by: Takashi Iwai --- sound/Kconfig | 2 + sound/Makefile | 2 +- sound/xen/Kconfig | 10 +++ sound/xen/Makefile | 5 ++ sound/xen/xen_snd_front.c | 196 ++++++++++++++++++++++++++++++++++++++++++++++ sound/xen/xen_snd_front.h | 18 +++++ 6 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 sound/xen/Kconfig create mode 100644 sound/xen/Makefile create mode 100644 sound/xen/xen_snd_front.c create mode 100644 sound/xen/xen_snd_front.h (limited to 'sound') diff --git a/sound/Kconfig b/sound/Kconfig index 6833db9002ec..1140e9988fc5 100644 --- a/sound/Kconfig +++ b/sound/Kconfig @@ -96,6 +96,8 @@ source "sound/x86/Kconfig" source "sound/synth/Kconfig" +source "sound/xen/Kconfig" + endif # SND endif # !UML diff --git a/sound/Makefile b/sound/Makefile index 99d8c31262c8..797ecdcd35e2 100644 --- a/sound/Makefile +++ b/sound/Makefile @@ -5,7 +5,7 @@ obj-$(CONFIG_SOUND) += soundcore.o obj-$(CONFIG_DMASOUND) += oss/dmasound/ obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \ - firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/ + firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/ xen/ obj-$(CONFIG_SND_AOA) += aoa/ # This one must be compilable even if sound is configured out diff --git a/sound/xen/Kconfig b/sound/xen/Kconfig new file mode 100644 index 000000000000..4f1fceea82d2 --- /dev/null +++ b/sound/xen/Kconfig @@ -0,0 +1,10 @@ +# ALSA Xen drivers + +config SND_XEN_FRONTEND + tristate "Xen para-virtualized sound frontend driver" + depends on XEN + select SND_PCM + select XEN_XENBUS_FRONTEND + help + Choose this option if you want to enable a para-virtualized + frontend sound driver for Xen guest OSes. diff --git a/sound/xen/Makefile b/sound/xen/Makefile new file mode 100644 index 000000000000..4507ef3c27fd --- /dev/null +++ b/sound/xen/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 OR MIT + +snd_xen_front-objs := xen_snd_front.o + +obj-$(CONFIG_SND_XEN_FRONTEND) += snd_xen_front.o diff --git a/sound/xen/xen_snd_front.c b/sound/xen/xen_snd_front.c new file mode 100644 index 000000000000..bbbe2767b565 --- /dev/null +++ b/sound/xen/xen_snd_front.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#include +#include + +#include +#include +#include + +#include + +#include "xen_snd_front.h" + +static void xen_snd_drv_fini(struct xen_snd_front_info *front_info) +{ +} + +static int sndback_initwait(struct xen_snd_front_info *front_info) +{ + return 0; +} + +static int sndback_connect(struct xen_snd_front_info *front_info) +{ + return 0; +} + +static void sndback_disconnect(struct xen_snd_front_info *front_info) +{ + xen_snd_drv_fini(front_info); + xenbus_switch_state(front_info->xb_dev, XenbusStateInitialising); +} + +static void sndback_changed(struct xenbus_device *xb_dev, + enum xenbus_state backend_state) +{ + struct xen_snd_front_info *front_info = dev_get_drvdata(&xb_dev->dev); + int ret; + + dev_dbg(&xb_dev->dev, "Backend state is %s, front is %s\n", + xenbus_strstate(backend_state), + xenbus_strstate(xb_dev->state)); + + switch (backend_state) { + case XenbusStateReconfiguring: + /* fall through */ + case XenbusStateReconfigured: + /* fall through */ + case XenbusStateInitialised: + /* fall through */ + break; + + case XenbusStateInitialising: + /* Recovering after backend unexpected closure. */ + sndback_disconnect(front_info); + break; + + case XenbusStateInitWait: + /* Recovering after backend unexpected closure. */ + sndback_disconnect(front_info); + + ret = sndback_initwait(front_info); + if (ret < 0) + xenbus_dev_fatal(xb_dev, ret, "initializing frontend"); + else + xenbus_switch_state(xb_dev, XenbusStateInitialised); + break; + + case XenbusStateConnected: + if (xb_dev->state != XenbusStateInitialised) + break; + + ret = sndback_connect(front_info); + if (ret < 0) + xenbus_dev_fatal(xb_dev, ret, "initializing frontend"); + else + xenbus_switch_state(xb_dev, XenbusStateConnected); + break; + + case XenbusStateClosing: + /* + * In this state backend starts freeing resources, + * so let it go into closed state first, so we can also + * remove ours. + */ + break; + + case XenbusStateUnknown: + /* fall through */ + case XenbusStateClosed: + if (xb_dev->state == XenbusStateClosed) + break; + + sndback_disconnect(front_info); + break; + } +} + +static int xen_drv_probe(struct xenbus_device *xb_dev, + const struct xenbus_device_id *id) +{ + struct xen_snd_front_info *front_info; + + front_info = devm_kzalloc(&xb_dev->dev, + sizeof(*front_info), GFP_KERNEL); + if (!front_info) + return -ENOMEM; + + front_info->xb_dev = xb_dev; + dev_set_drvdata(&xb_dev->dev, front_info); + + return xenbus_switch_state(xb_dev, XenbusStateInitialising); +} + +static int xen_drv_remove(struct xenbus_device *dev) +{ + struct xen_snd_front_info *front_info = dev_get_drvdata(&dev->dev); + int to = 100; + + xenbus_switch_state(dev, XenbusStateClosing); + + /* + * On driver removal it is disconnected from XenBus, + * so no backend state change events come via .otherend_changed + * callback. This prevents us from exiting gracefully, e.g. + * signaling the backend to free event channels, waiting for its + * state to change to XenbusStateClosed and cleaning at our end. + * Normally when front driver removed backend will finally go into + * XenbusStateInitWait state. + * + * Workaround: read backend's state manually and wait with time-out. + */ + while ((xenbus_read_unsigned(front_info->xb_dev->otherend, "state", + XenbusStateUnknown) != XenbusStateInitWait) && + to--) + msleep(10); + + if (!to) { + unsigned int state; + + state = xenbus_read_unsigned(front_info->xb_dev->otherend, + "state", XenbusStateUnknown); + pr_err("Backend state is %s while removing driver\n", + xenbus_strstate(state)); + } + + xen_snd_drv_fini(front_info); + xenbus_frontend_closed(dev); + return 0; +} + +static const struct xenbus_device_id xen_drv_ids[] = { + { XENSND_DRIVER_NAME }, + { "" } +}; + +static struct xenbus_driver xen_driver = { + .ids = xen_drv_ids, + .probe = xen_drv_probe, + .remove = xen_drv_remove, + .otherend_changed = sndback_changed, +}; + +static int __init xen_drv_init(void) +{ + if (!xen_domain()) + return -ENODEV; + + if (!xen_has_pv_devices()) + return -ENODEV; + + pr_info("Initialising Xen " XENSND_DRIVER_NAME " frontend driver\n"); + return xenbus_register_frontend(&xen_driver); +} + +static void __exit xen_drv_fini(void) +{ + pr_info("Unregistering Xen " XENSND_DRIVER_NAME " frontend driver\n"); + xenbus_unregister_driver(&xen_driver); +} + +module_init(xen_drv_init); +module_exit(xen_drv_fini); + +MODULE_DESCRIPTION("Xen virtual sound device frontend"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("xen:" XENSND_DRIVER_NAME); +MODULE_SUPPORTED_DEVICE("{{ALSA,Virtual soundcard}}"); diff --git a/sound/xen/xen_snd_front.h b/sound/xen/xen_snd_front.h new file mode 100644 index 000000000000..4ae204b23d32 --- /dev/null +++ b/sound/xen/xen_snd_front.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#ifndef __XEN_SND_FRONT_H +#define __XEN_SND_FRONT_H + +struct xen_snd_front_info { + struct xenbus_device *xb_dev; +}; + +#endif /* __XEN_SND_FRONT_H */ -- cgit v1.2.3 From fd3b36045c2c612b5f44a47f388677af256d1d0a Mon Sep 17 00:00:00 2001 From: Oleksandr Andrushchenko Date: Mon, 14 May 2018 09:27:38 +0300 Subject: ALSA: xen-front: Read sound driver configuration from Xen store Read configuration values from Xen store according to xen/interface/io/sndif.h protocol: - introduce configuration structures for different components, e.g. sound card, device, stream - read PCM HW parameters, e.g rate, format etc. - detect stream type (capture/playback) - read device and card parameters Signed-off-by: Oleksandr Andrushchenko Signed-off-by: Takashi Iwai --- sound/xen/Makefile | 3 +- sound/xen/xen_snd_front.c | 7 + sound/xen/xen_snd_front.h | 4 + sound/xen/xen_snd_front_cfg.c | 517 ++++++++++++++++++++++++++++++++++++++++++ sound/xen/xen_snd_front_cfg.h | 46 ++++ 5 files changed, 576 insertions(+), 1 deletion(-) create mode 100644 sound/xen/xen_snd_front_cfg.c create mode 100644 sound/xen/xen_snd_front_cfg.h (limited to 'sound') diff --git a/sound/xen/Makefile b/sound/xen/Makefile index 4507ef3c27fd..06705bef61fa 100644 --- a/sound/xen/Makefile +++ b/sound/xen/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 OR MIT -snd_xen_front-objs := xen_snd_front.o +snd_xen_front-objs := xen_snd_front.o \ + xen_snd_front_cfg.o obj-$(CONFIG_SND_XEN_FRONTEND) += snd_xen_front.o diff --git a/sound/xen/xen_snd_front.c b/sound/xen/xen_snd_front.c index bbbe2767b565..70fa91683c71 100644 --- a/sound/xen/xen_snd_front.c +++ b/sound/xen/xen_snd_front.c @@ -25,6 +25,13 @@ static void xen_snd_drv_fini(struct xen_snd_front_info *front_info) static int sndback_initwait(struct xen_snd_front_info *front_info) { + int num_streams; + int ret; + + ret = xen_snd_front_cfg_card(front_info, &num_streams); + if (ret < 0) + return ret; + return 0; } diff --git a/sound/xen/xen_snd_front.h b/sound/xen/xen_snd_front.h index 4ae204b23d32..b52226cb30bc 100644 --- a/sound/xen/xen_snd_front.h +++ b/sound/xen/xen_snd_front.h @@ -11,8 +11,12 @@ #ifndef __XEN_SND_FRONT_H #define __XEN_SND_FRONT_H +#include "xen_snd_front_cfg.h" + struct xen_snd_front_info { struct xenbus_device *xb_dev; + + struct xen_front_cfg_card cfg; }; #endif /* __XEN_SND_FRONT_H */ diff --git a/sound/xen/xen_snd_front_cfg.c b/sound/xen/xen_snd_front_cfg.c new file mode 100644 index 000000000000..38c7e1eefbb9 --- /dev/null +++ b/sound/xen/xen_snd_front_cfg.c @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#include + +#include + +#include "xen_snd_front.h" +#include "xen_snd_front_cfg.h" + +/* Maximum number of supported streams. */ +#define VSND_MAX_STREAM 8 + +struct cfg_hw_sample_rate { + const char *name; + unsigned int mask; + unsigned int value; +}; + +static const struct cfg_hw_sample_rate CFG_HW_SUPPORTED_RATES[] = { + { .name = "5512", .mask = SNDRV_PCM_RATE_5512, .value = 5512 }, + { .name = "8000", .mask = SNDRV_PCM_RATE_8000, .value = 8000 }, + { .name = "11025", .mask = SNDRV_PCM_RATE_11025, .value = 11025 }, + { .name = "16000", .mask = SNDRV_PCM_RATE_16000, .value = 16000 }, + { .name = "22050", .mask = SNDRV_PCM_RATE_22050, .value = 22050 }, + { .name = "32000", .mask = SNDRV_PCM_RATE_32000, .value = 32000 }, + { .name = "44100", .mask = SNDRV_PCM_RATE_44100, .value = 44100 }, + { .name = "48000", .mask = SNDRV_PCM_RATE_48000, .value = 48000 }, + { .name = "64000", .mask = SNDRV_PCM_RATE_64000, .value = 64000 }, + { .name = "96000", .mask = SNDRV_PCM_RATE_96000, .value = 96000 }, + { .name = "176400", .mask = SNDRV_PCM_RATE_176400, .value = 176400 }, + { .name = "192000", .mask = SNDRV_PCM_RATE_192000, .value = 192000 }, +}; + +struct cfg_hw_sample_format { + const char *name; + u64 mask; +}; + +static const struct cfg_hw_sample_format CFG_HW_SUPPORTED_FORMATS[] = { + { + .name = XENSND_PCM_FORMAT_U8_STR, + .mask = SNDRV_PCM_FMTBIT_U8 + }, + { + .name = XENSND_PCM_FORMAT_S8_STR, + .mask = SNDRV_PCM_FMTBIT_S8 + }, + { + .name = XENSND_PCM_FORMAT_U16_LE_STR, + .mask = SNDRV_PCM_FMTBIT_U16_LE + }, + { + .name = XENSND_PCM_FORMAT_U16_BE_STR, + .mask = SNDRV_PCM_FMTBIT_U16_BE + }, + { + .name = XENSND_PCM_FORMAT_S16_LE_STR, + .mask = SNDRV_PCM_FMTBIT_S16_LE + }, + { + .name = XENSND_PCM_FORMAT_S16_BE_STR, + .mask = SNDRV_PCM_FMTBIT_S16_BE + }, + { + .name = XENSND_PCM_FORMAT_U24_LE_STR, + .mask = SNDRV_PCM_FMTBIT_U24_LE + }, + { + .name = XENSND_PCM_FORMAT_U24_BE_STR, + .mask = SNDRV_PCM_FMTBIT_U24_BE + }, + { + .name = XENSND_PCM_FORMAT_S24_LE_STR, + .mask = SNDRV_PCM_FMTBIT_S24_LE + }, + { + .name = XENSND_PCM_FORMAT_S24_BE_STR, + .mask = SNDRV_PCM_FMTBIT_S24_BE + }, + { + .name = XENSND_PCM_FORMAT_U32_LE_STR, + .mask = SNDRV_PCM_FMTBIT_U32_LE + }, + { + .name = XENSND_PCM_FORMAT_U32_BE_STR, + .mask = SNDRV_PCM_FMTBIT_U32_BE + }, + { + .name = XENSND_PCM_FORMAT_S32_LE_STR, + .mask = SNDRV_PCM_FMTBIT_S32_LE + }, + { + .name = XENSND_PCM_FORMAT_S32_BE_STR, + .mask = SNDRV_PCM_FMTBIT_S32_BE + }, + { + .name = XENSND_PCM_FORMAT_A_LAW_STR, + .mask = SNDRV_PCM_FMTBIT_A_LAW + }, + { + .name = XENSND_PCM_FORMAT_MU_LAW_STR, + .mask = SNDRV_PCM_FMTBIT_MU_LAW + }, + { + .name = XENSND_PCM_FORMAT_F32_LE_STR, + .mask = SNDRV_PCM_FMTBIT_FLOAT_LE + }, + { + .name = XENSND_PCM_FORMAT_F32_BE_STR, + .mask = SNDRV_PCM_FMTBIT_FLOAT_BE + }, + { + .name = XENSND_PCM_FORMAT_F64_LE_STR, + .mask = SNDRV_PCM_FMTBIT_FLOAT64_LE + }, + { + .name = XENSND_PCM_FORMAT_F64_BE_STR, + .mask = SNDRV_PCM_FMTBIT_FLOAT64_BE + }, + { + .name = XENSND_PCM_FORMAT_IEC958_SUBFRAME_LE_STR, + .mask = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE + }, + { + .name = XENSND_PCM_FORMAT_IEC958_SUBFRAME_BE_STR, + .mask = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE + }, + { + .name = XENSND_PCM_FORMAT_IMA_ADPCM_STR, + .mask = SNDRV_PCM_FMTBIT_IMA_ADPCM + }, + { + .name = XENSND_PCM_FORMAT_MPEG_STR, + .mask = SNDRV_PCM_FMTBIT_MPEG + }, + { + .name = XENSND_PCM_FORMAT_GSM_STR, + .mask = SNDRV_PCM_FMTBIT_GSM + }, +}; + +static void cfg_hw_rates(char *list, unsigned int len, + const char *path, struct snd_pcm_hardware *pcm_hw) +{ + char *cur_rate; + unsigned int cur_mask; + unsigned int cur_value; + unsigned int rates; + unsigned int rate_min; + unsigned int rate_max; + int i; + + rates = 0; + rate_min = -1; + rate_max = 0; + while ((cur_rate = strsep(&list, XENSND_LIST_SEPARATOR))) { + for (i = 0; i < ARRAY_SIZE(CFG_HW_SUPPORTED_RATES); i++) + if (!strncasecmp(cur_rate, + CFG_HW_SUPPORTED_RATES[i].name, + XENSND_SAMPLE_RATE_MAX_LEN)) { + cur_mask = CFG_HW_SUPPORTED_RATES[i].mask; + cur_value = CFG_HW_SUPPORTED_RATES[i].value; + rates |= cur_mask; + if (rate_min > cur_value) + rate_min = cur_value; + if (rate_max < cur_value) + rate_max = cur_value; + } + } + + if (rates) { + pcm_hw->rates = rates; + pcm_hw->rate_min = rate_min; + pcm_hw->rate_max = rate_max; + } +} + +static void cfg_formats(char *list, unsigned int len, + const char *path, struct snd_pcm_hardware *pcm_hw) +{ + u64 formats; + char *cur_format; + int i; + + formats = 0; + while ((cur_format = strsep(&list, XENSND_LIST_SEPARATOR))) { + for (i = 0; i < ARRAY_SIZE(CFG_HW_SUPPORTED_FORMATS); i++) + if (!strncasecmp(cur_format, + CFG_HW_SUPPORTED_FORMATS[i].name, + XENSND_SAMPLE_FORMAT_MAX_LEN)) + formats |= CFG_HW_SUPPORTED_FORMATS[i].mask; + } + + if (formats) + pcm_hw->formats = formats; +} + +#define MAX_BUFFER_SIZE (64 * 1024) +#define MIN_PERIOD_SIZE 64 +#define MAX_PERIOD_SIZE MAX_BUFFER_SIZE +#define USE_FORMATS (SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE) +#define USE_RATE (SNDRV_PCM_RATE_CONTINUOUS | \ + SNDRV_PCM_RATE_8000_48000) +#define USE_RATE_MIN 5512 +#define USE_RATE_MAX 48000 +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +#define USE_PERIODS_MIN 2 +#define USE_PERIODS_MAX (MAX_BUFFER_SIZE / MIN_PERIOD_SIZE) + +static const struct snd_pcm_hardware SND_DRV_PCM_HW_DEFAULT = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = MAX_PERIOD_SIZE, + .periods_min = USE_PERIODS_MIN, + .periods_max = USE_PERIODS_MAX, + .fifo_size = 0, +}; + +static void cfg_read_pcm_hw(const char *path, + struct snd_pcm_hardware *parent_pcm_hw, + struct snd_pcm_hardware *pcm_hw) +{ + char *list; + int val; + size_t buf_sz; + unsigned int len; + + /* Inherit parent's PCM HW and read overrides from XenStore. */ + if (parent_pcm_hw) + *pcm_hw = *parent_pcm_hw; + else + *pcm_hw = SND_DRV_PCM_HW_DEFAULT; + + val = xenbus_read_unsigned(path, XENSND_FIELD_CHANNELS_MIN, 0); + if (val) + pcm_hw->channels_min = val; + + val = xenbus_read_unsigned(path, XENSND_FIELD_CHANNELS_MAX, 0); + if (val) + pcm_hw->channels_max = val; + + list = xenbus_read(XBT_NIL, path, XENSND_FIELD_SAMPLE_RATES, &len); + if (!IS_ERR(list)) { + cfg_hw_rates(list, len, path, pcm_hw); + kfree(list); + } + + list = xenbus_read(XBT_NIL, path, XENSND_FIELD_SAMPLE_FORMATS, &len); + if (!IS_ERR(list)) { + cfg_formats(list, len, path, pcm_hw); + kfree(list); + } + + buf_sz = xenbus_read_unsigned(path, XENSND_FIELD_BUFFER_SIZE, 0); + if (buf_sz) + pcm_hw->buffer_bytes_max = buf_sz; + + /* Update configuration to match new values. */ + if (pcm_hw->channels_min > pcm_hw->channels_max) + pcm_hw->channels_min = pcm_hw->channels_max; + + if (pcm_hw->rate_min > pcm_hw->rate_max) + pcm_hw->rate_min = pcm_hw->rate_max; + + pcm_hw->period_bytes_max = pcm_hw->buffer_bytes_max; + + pcm_hw->periods_max = pcm_hw->period_bytes_max / + pcm_hw->period_bytes_min; +} + +static int cfg_get_stream_type(const char *path, int index, + int *num_pb, int *num_cap) +{ + char *str = NULL; + char *stream_path; + int ret; + + *num_pb = 0; + *num_cap = 0; + stream_path = kasprintf(GFP_KERNEL, "%s/%d", path, index); + if (!stream_path) { + ret = -ENOMEM; + goto fail; + } + + str = xenbus_read(XBT_NIL, stream_path, XENSND_FIELD_TYPE, NULL); + if (IS_ERR(str)) { + ret = PTR_ERR(str); + goto fail; + } + + if (!strncasecmp(str, XENSND_STREAM_TYPE_PLAYBACK, + sizeof(XENSND_STREAM_TYPE_PLAYBACK))) { + (*num_pb)++; + } else if (!strncasecmp(str, XENSND_STREAM_TYPE_CAPTURE, + sizeof(XENSND_STREAM_TYPE_CAPTURE))) { + (*num_cap)++; + } else { + ret = -EINVAL; + goto fail; + } + ret = 0; + +fail: + kfree(stream_path); + kfree(str); + return ret; +} + +static int cfg_stream(struct xen_snd_front_info *front_info, + struct xen_front_cfg_pcm_instance *pcm_instance, + const char *path, int index, int *cur_pb, int *cur_cap, + int *stream_cnt) +{ + char *str = NULL; + char *stream_path; + struct xen_front_cfg_stream *stream; + int ret; + + stream_path = devm_kasprintf(&front_info->xb_dev->dev, + GFP_KERNEL, "%s/%d", path, index); + if (!stream_path) { + ret = -ENOMEM; + goto fail; + } + + str = xenbus_read(XBT_NIL, stream_path, XENSND_FIELD_TYPE, NULL); + if (IS_ERR(str)) { + ret = PTR_ERR(str); + goto fail; + } + + if (!strncasecmp(str, XENSND_STREAM_TYPE_PLAYBACK, + sizeof(XENSND_STREAM_TYPE_PLAYBACK))) { + stream = &pcm_instance->streams_pb[(*cur_pb)++]; + } else if (!strncasecmp(str, XENSND_STREAM_TYPE_CAPTURE, + sizeof(XENSND_STREAM_TYPE_CAPTURE))) { + stream = &pcm_instance->streams_cap[(*cur_cap)++]; + } else { + ret = -EINVAL; + goto fail; + } + + /* Get next stream index. */ + stream->index = (*stream_cnt)++; + stream->xenstore_path = stream_path; + /* + * Check XenStore if PCM HW configuration exists for this stream + * and update if so, e.g. we inherit all values from device's PCM HW, + * but can still override some of the values for the stream. + */ + cfg_read_pcm_hw(stream->xenstore_path, + &pcm_instance->pcm_hw, &stream->pcm_hw); + ret = 0; + +fail: + kfree(str); + return ret; +} + +static int cfg_device(struct xen_snd_front_info *front_info, + struct xen_front_cfg_pcm_instance *pcm_instance, + struct snd_pcm_hardware *parent_pcm_hw, + const char *path, int node_index, int *stream_cnt) +{ + char *str; + char *device_path; + int ret, i, num_streams; + int num_pb, num_cap; + int cur_pb, cur_cap; + char node[3]; + + device_path = kasprintf(GFP_KERNEL, "%s/%d", path, node_index); + if (!device_path) + return -ENOMEM; + + str = xenbus_read(XBT_NIL, device_path, XENSND_FIELD_DEVICE_NAME, NULL); + if (!IS_ERR(str)) { + strncpy(pcm_instance->name, str, sizeof(pcm_instance->name)); + kfree(str); + } + + pcm_instance->device_id = node_index; + + /* + * Check XenStore if PCM HW configuration exists for this device + * and update if so, e.g. we inherit all values from card's PCM HW, + * but can still override some of the values for the device. + */ + cfg_read_pcm_hw(device_path, parent_pcm_hw, &pcm_instance->pcm_hw); + + /* Find out how many streams were configured in Xen store. */ + num_streams = 0; + do { + snprintf(node, sizeof(node), "%d", num_streams); + if (!xenbus_exists(XBT_NIL, device_path, node)) + break; + + num_streams++; + } while (num_streams < VSND_MAX_STREAM); + + pcm_instance->num_streams_pb = 0; + pcm_instance->num_streams_cap = 0; + /* Get number of playback and capture streams. */ + for (i = 0; i < num_streams; i++) { + ret = cfg_get_stream_type(device_path, i, &num_pb, &num_cap); + if (ret < 0) + goto fail; + + pcm_instance->num_streams_pb += num_pb; + pcm_instance->num_streams_cap += num_cap; + } + + if (pcm_instance->num_streams_pb) { + pcm_instance->streams_pb = + devm_kcalloc(&front_info->xb_dev->dev, + pcm_instance->num_streams_pb, + sizeof(struct xen_front_cfg_stream), + GFP_KERNEL); + if (!pcm_instance->streams_pb) { + ret = -ENOMEM; + goto fail; + } + } + + if (pcm_instance->num_streams_cap) { + pcm_instance->streams_cap = + devm_kcalloc(&front_info->xb_dev->dev, + pcm_instance->num_streams_cap, + sizeof(struct xen_front_cfg_stream), + GFP_KERNEL); + if (!pcm_instance->streams_cap) { + ret = -ENOMEM; + goto fail; + } + } + + cur_pb = 0; + cur_cap = 0; + for (i = 0; i < num_streams; i++) { + ret = cfg_stream(front_info, pcm_instance, device_path, i, + &cur_pb, &cur_cap, stream_cnt); + if (ret < 0) + goto fail; + } + ret = 0; + +fail: + kfree(device_path); + return ret; +} + +int xen_snd_front_cfg_card(struct xen_snd_front_info *front_info, + int *stream_cnt) +{ + struct xenbus_device *xb_dev = front_info->xb_dev; + struct xen_front_cfg_card *cfg = &front_info->cfg; + int ret, num_devices, i; + char node[3]; + + *stream_cnt = 0; + num_devices = 0; + do { + snprintf(node, sizeof(node), "%d", num_devices); + if (!xenbus_exists(XBT_NIL, xb_dev->nodename, node)) + break; + + num_devices++; + } while (num_devices < SNDRV_PCM_DEVICES); + + if (!num_devices) { + dev_warn(&xb_dev->dev, + "No devices configured for sound card at %s\n", + xb_dev->nodename); + return -ENODEV; + } + + /* Start from default PCM HW configuration for the card. */ + cfg_read_pcm_hw(xb_dev->nodename, NULL, &cfg->pcm_hw); + + cfg->pcm_instances = + devm_kcalloc(&front_info->xb_dev->dev, num_devices, + sizeof(struct xen_front_cfg_pcm_instance), + GFP_KERNEL); + if (!cfg->pcm_instances) + return -ENOMEM; + + for (i = 0; i < num_devices; i++) { + ret = cfg_device(front_info, &cfg->pcm_instances[i], + &cfg->pcm_hw, xb_dev->nodename, i, stream_cnt); + if (ret < 0) + return ret; + } + cfg->num_pcm_instances = num_devices; + return 0; +} + diff --git a/sound/xen/xen_snd_front_cfg.h b/sound/xen/xen_snd_front_cfg.h new file mode 100644 index 000000000000..2353fcc74889 --- /dev/null +++ b/sound/xen/xen_snd_front_cfg.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#ifndef __XEN_SND_FRONT_CFG_H +#define __XEN_SND_FRONT_CFG_H + +#include +#include + +struct xen_snd_front_info; + +struct xen_front_cfg_stream { + int index; + char *xenstore_path; + struct snd_pcm_hardware pcm_hw; +}; + +struct xen_front_cfg_pcm_instance { + char name[80]; + int device_id; + struct snd_pcm_hardware pcm_hw; + int num_streams_pb; + struct xen_front_cfg_stream *streams_pb; + int num_streams_cap; + struct xen_front_cfg_stream *streams_cap; +}; + +struct xen_front_cfg_card { + char name_short[32]; + char name_long[80]; + struct snd_pcm_hardware pcm_hw; + int num_pcm_instances; + struct xen_front_cfg_pcm_instance *pcm_instances; +}; + +int xen_snd_front_cfg_card(struct xen_snd_front_info *front_info, + int *stream_cnt); + +#endif /* __XEN_SND_FRONT_CFG_H */ -- cgit v1.2.3 From 788ef64a2caee38cc4b8890abd3d7e54dfa3bcc9 Mon Sep 17 00:00:00 2001 From: Oleksandr Andrushchenko Date: Mon, 14 May 2018 09:27:39 +0300 Subject: ALSA: xen-front: Implement Xen event channel handling Handle Xen event channels: - create for all configured streams and publish corresponding ring references and event channels in Xen store, so backend can connect - implement event channels interrupt handlers - create and destroy event channels with respect to Xen bus state Signed-off-by: Oleksandr Andrushchenko Signed-off-by: Takashi Iwai --- sound/xen/Makefile | 3 +- sound/xen/xen_snd_front.c | 9 +- sound/xen/xen_snd_front.h | 5 + sound/xen/xen_snd_front_evtchnl.c | 494 ++++++++++++++++++++++++++++++++++++++ sound/xen/xen_snd_front_evtchnl.h | 95 ++++++++ 5 files changed, 604 insertions(+), 2 deletions(-) create mode 100644 sound/xen/xen_snd_front_evtchnl.c create mode 100644 sound/xen/xen_snd_front_evtchnl.h (limited to 'sound') diff --git a/sound/xen/Makefile b/sound/xen/Makefile index 06705bef61fa..03c669984000 100644 --- a/sound/xen/Makefile +++ b/sound/xen/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 OR MIT snd_xen_front-objs := xen_snd_front.o \ - xen_snd_front_cfg.o + xen_snd_front_cfg.o \ + xen_snd_front_evtchnl.o obj-$(CONFIG_SND_XEN_FRONTEND) += snd_xen_front.o diff --git a/sound/xen/xen_snd_front.c b/sound/xen/xen_snd_front.c index 70fa91683c71..277214d4fd0a 100644 --- a/sound/xen/xen_snd_front.c +++ b/sound/xen/xen_snd_front.c @@ -18,9 +18,11 @@ #include #include "xen_snd_front.h" +#include "xen_snd_front_evtchnl.h" static void xen_snd_drv_fini(struct xen_snd_front_info *front_info) { + xen_snd_front_evtchnl_free_all(front_info); } static int sndback_initwait(struct xen_snd_front_info *front_info) @@ -32,7 +34,12 @@ static int sndback_initwait(struct xen_snd_front_info *front_info) if (ret < 0) return ret; - return 0; + /* create event channels for all streams and publish */ + ret = xen_snd_front_evtchnl_create_all(front_info, num_streams); + if (ret < 0) + return ret; + + return xen_snd_front_evtchnl_publish_all(front_info); } static int sndback_connect(struct xen_snd_front_info *front_info) diff --git a/sound/xen/xen_snd_front.h b/sound/xen/xen_snd_front.h index b52226cb30bc..9d0c92100c7b 100644 --- a/sound/xen/xen_snd_front.h +++ b/sound/xen/xen_snd_front.h @@ -13,9 +13,14 @@ #include "xen_snd_front_cfg.h" +struct xen_snd_front_evtchnl_pair; + struct xen_snd_front_info { struct xenbus_device *xb_dev; + int num_evt_pairs; + struct xen_snd_front_evtchnl_pair *evt_pairs; + struct xen_front_cfg_card cfg; }; diff --git a/sound/xen/xen_snd_front_evtchnl.c b/sound/xen/xen_snd_front_evtchnl.c new file mode 100644 index 000000000000..1faafff08807 --- /dev/null +++ b/sound/xen/xen_snd_front_evtchnl.c @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#include +#include +#include +#include + +#include "xen_snd_front.h" +#include "xen_snd_front_cfg.h" +#include "xen_snd_front_evtchnl.h" + +static irqreturn_t evtchnl_interrupt_req(int irq, void *dev_id) +{ + struct xen_snd_front_evtchnl *channel = dev_id; + struct xen_snd_front_info *front_info = channel->front_info; + struct xensnd_resp *resp; + RING_IDX i, rp; + + if (unlikely(channel->state != EVTCHNL_STATE_CONNECTED)) + return IRQ_HANDLED; + + mutex_lock(&channel->ring_io_lock); + +again: + rp = channel->u.req.ring.sring->rsp_prod; + /* Ensure we see queued responses up to rp. */ + rmb(); + + /* + * Assume that the backend is trusted to always write sane values + * to the ring counters, so no overflow checks on frontend side + * are required. + */ + for (i = channel->u.req.ring.rsp_cons; i != rp; i++) { + resp = RING_GET_RESPONSE(&channel->u.req.ring, i); + if (resp->id != channel->evt_id) + continue; + switch (resp->operation) { + case XENSND_OP_OPEN: + /* fall through */ + case XENSND_OP_CLOSE: + /* fall through */ + case XENSND_OP_READ: + /* fall through */ + case XENSND_OP_WRITE: + /* fall through */ + case XENSND_OP_TRIGGER: + channel->u.req.resp_status = resp->status; + complete(&channel->u.req.completion); + break; + case XENSND_OP_HW_PARAM_QUERY: + channel->u.req.resp_status = resp->status; + channel->u.req.resp.hw_param = + resp->resp.hw_param; + complete(&channel->u.req.completion); + break; + + default: + dev_err(&front_info->xb_dev->dev, + "Operation %d is not supported\n", + resp->operation); + break; + } + } + + channel->u.req.ring.rsp_cons = i; + if (i != channel->u.req.ring.req_prod_pvt) { + int more_to_do; + + RING_FINAL_CHECK_FOR_RESPONSES(&channel->u.req.ring, + more_to_do); + if (more_to_do) + goto again; + } else { + channel->u.req.ring.sring->rsp_event = i + 1; + } + + mutex_unlock(&channel->ring_io_lock); + return IRQ_HANDLED; +} + +static irqreturn_t evtchnl_interrupt_evt(int irq, void *dev_id) +{ + struct xen_snd_front_evtchnl *channel = dev_id; + struct xensnd_event_page *page = channel->u.evt.page; + u32 cons, prod; + + if (unlikely(channel->state != EVTCHNL_STATE_CONNECTED)) + return IRQ_HANDLED; + + mutex_lock(&channel->ring_io_lock); + + prod = page->in_prod; + /* Ensure we see ring contents up to prod. */ + virt_rmb(); + if (prod == page->in_cons) + goto out; + + /* + * Assume that the backend is trusted to always write sane values + * to the ring counters, so no overflow checks on frontend side + * are required. + */ + for (cons = page->in_cons; cons != prod; cons++) { + struct xensnd_evt *event; + + event = &XENSND_IN_RING_REF(page, cons); + if (unlikely(event->id != channel->evt_id++)) + continue; + + switch (event->type) { + case XENSND_EVT_CUR_POS: + /* Do nothing at the moment. */ + break; + } + } + + page->in_cons = cons; + /* Ensure ring contents. */ + virt_wmb(); + +out: + mutex_unlock(&channel->ring_io_lock); + return IRQ_HANDLED; +} + +void xen_snd_front_evtchnl_flush(struct xen_snd_front_evtchnl *channel) +{ + int notify; + + channel->u.req.ring.req_prod_pvt++; + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&channel->u.req.ring, notify); + if (notify) + notify_remote_via_irq(channel->irq); +} + +static void evtchnl_free(struct xen_snd_front_info *front_info, + struct xen_snd_front_evtchnl *channel) +{ + unsigned long page = 0; + + if (channel->type == EVTCHNL_TYPE_REQ) + page = (unsigned long)channel->u.req.ring.sring; + else if (channel->type == EVTCHNL_TYPE_EVT) + page = (unsigned long)channel->u.evt.page; + + if (!page) + return; + + channel->state = EVTCHNL_STATE_DISCONNECTED; + if (channel->type == EVTCHNL_TYPE_REQ) { + /* Release all who still waits for response if any. */ + channel->u.req.resp_status = -EIO; + complete_all(&channel->u.req.completion); + } + + if (channel->irq) + unbind_from_irqhandler(channel->irq, channel); + + if (channel->port) + xenbus_free_evtchn(front_info->xb_dev, channel->port); + + /* End access and free the page. */ + if (channel->gref != GRANT_INVALID_REF) + gnttab_end_foreign_access(channel->gref, 0, page); + else + free_page(page); + + memset(channel, 0, sizeof(*channel)); +} + +void xen_snd_front_evtchnl_free_all(struct xen_snd_front_info *front_info) +{ + int i; + + if (!front_info->evt_pairs) + return; + + for (i = 0; i < front_info->num_evt_pairs; i++) { + evtchnl_free(front_info, &front_info->evt_pairs[i].req); + evtchnl_free(front_info, &front_info->evt_pairs[i].evt); + } + + kfree(front_info->evt_pairs); + front_info->evt_pairs = NULL; +} + +static int evtchnl_alloc(struct xen_snd_front_info *front_info, int index, + struct xen_snd_front_evtchnl *channel, + enum xen_snd_front_evtchnl_type type) +{ + struct xenbus_device *xb_dev = front_info->xb_dev; + unsigned long page; + grant_ref_t gref; + irq_handler_t handler; + char *handler_name = NULL; + int ret; + + memset(channel, 0, sizeof(*channel)); + channel->type = type; + channel->index = index; + channel->front_info = front_info; + channel->state = EVTCHNL_STATE_DISCONNECTED; + channel->gref = GRANT_INVALID_REF; + page = get_zeroed_page(GFP_KERNEL); + if (!page) { + ret = -ENOMEM; + goto fail; + } + + handler_name = kasprintf(GFP_KERNEL, "%s-%s", XENSND_DRIVER_NAME, + type == EVTCHNL_TYPE_REQ ? + XENSND_FIELD_RING_REF : + XENSND_FIELD_EVT_RING_REF); + if (!handler_name) { + ret = -ENOMEM; + goto fail; + } + + mutex_init(&channel->ring_io_lock); + + if (type == EVTCHNL_TYPE_REQ) { + struct xen_sndif_sring *sring = (struct xen_sndif_sring *)page; + + init_completion(&channel->u.req.completion); + mutex_init(&channel->u.req.req_io_lock); + SHARED_RING_INIT(sring); + FRONT_RING_INIT(&channel->u.req.ring, sring, XEN_PAGE_SIZE); + + ret = xenbus_grant_ring(xb_dev, sring, 1, &gref); + if (ret < 0) { + channel->u.req.ring.sring = NULL; + goto fail; + } + + handler = evtchnl_interrupt_req; + } else { + ret = gnttab_grant_foreign_access(xb_dev->otherend_id, + virt_to_gfn((void *)page), 0); + if (ret < 0) + goto fail; + + channel->u.evt.page = (struct xensnd_event_page *)page; + gref = ret; + handler = evtchnl_interrupt_evt; + } + + channel->gref = gref; + + ret = xenbus_alloc_evtchn(xb_dev, &channel->port); + if (ret < 0) + goto fail; + + ret = bind_evtchn_to_irq(channel->port); + if (ret < 0) { + dev_err(&xb_dev->dev, + "Failed to bind IRQ for domid %d port %d: %d\n", + front_info->xb_dev->otherend_id, channel->port, ret); + goto fail; + } + + channel->irq = ret; + + ret = request_threaded_irq(channel->irq, NULL, handler, + IRQF_ONESHOT, handler_name, channel); + if (ret < 0) { + dev_err(&xb_dev->dev, "Failed to request IRQ %d: %d\n", + channel->irq, ret); + goto fail; + } + + kfree(handler_name); + return 0; + +fail: + if (page) + free_page(page); + kfree(handler_name); + dev_err(&xb_dev->dev, "Failed to allocate ring: %d\n", ret); + return ret; +} + +int xen_snd_front_evtchnl_create_all(struct xen_snd_front_info *front_info, + int num_streams) +{ + struct xen_front_cfg_card *cfg = &front_info->cfg; + struct device *dev = &front_info->xb_dev->dev; + int d, ret = 0; + + front_info->evt_pairs = + kcalloc(num_streams, + sizeof(struct xen_snd_front_evtchnl_pair), + GFP_KERNEL); + if (!front_info->evt_pairs) + return -ENOMEM; + + /* Iterate over devices and their streams and create event channels. */ + for (d = 0; d < cfg->num_pcm_instances; d++) { + struct xen_front_cfg_pcm_instance *pcm_instance; + int s, index; + + pcm_instance = &cfg->pcm_instances[d]; + + for (s = 0; s < pcm_instance->num_streams_pb; s++) { + index = pcm_instance->streams_pb[s].index; + + ret = evtchnl_alloc(front_info, index, + &front_info->evt_pairs[index].req, + EVTCHNL_TYPE_REQ); + if (ret < 0) { + dev_err(dev, "Error allocating control channel\n"); + goto fail; + } + + ret = evtchnl_alloc(front_info, index, + &front_info->evt_pairs[index].evt, + EVTCHNL_TYPE_EVT); + if (ret < 0) { + dev_err(dev, "Error allocating in-event channel\n"); + goto fail; + } + } + + for (s = 0; s < pcm_instance->num_streams_cap; s++) { + index = pcm_instance->streams_cap[s].index; + + ret = evtchnl_alloc(front_info, index, + &front_info->evt_pairs[index].req, + EVTCHNL_TYPE_REQ); + if (ret < 0) { + dev_err(dev, "Error allocating control channel\n"); + goto fail; + } + + ret = evtchnl_alloc(front_info, index, + &front_info->evt_pairs[index].evt, + EVTCHNL_TYPE_EVT); + if (ret < 0) { + dev_err(dev, "Error allocating in-event channel\n"); + goto fail; + } + } + } + if (ret < 0) + goto fail; + + front_info->num_evt_pairs = num_streams; + return 0; + +fail: + xen_snd_front_evtchnl_free_all(front_info); + return ret; +} + +static int evtchnl_publish(struct xenbus_transaction xbt, + struct xen_snd_front_evtchnl *channel, + const char *path, const char *node_ring, + const char *node_chnl) +{ + struct xenbus_device *xb_dev = channel->front_info->xb_dev; + int ret; + + /* Write control channel ring reference. */ + ret = xenbus_printf(xbt, path, node_ring, "%u", channel->gref); + if (ret < 0) { + dev_err(&xb_dev->dev, "Error writing ring-ref: %d\n", ret); + return ret; + } + + /* Write event channel ring reference. */ + ret = xenbus_printf(xbt, path, node_chnl, "%u", channel->port); + if (ret < 0) { + dev_err(&xb_dev->dev, "Error writing event channel: %d\n", ret); + return ret; + } + + return 0; +} + +int xen_snd_front_evtchnl_publish_all(struct xen_snd_front_info *front_info) +{ + struct xen_front_cfg_card *cfg = &front_info->cfg; + struct xenbus_transaction xbt; + int ret, d; + +again: + ret = xenbus_transaction_start(&xbt); + if (ret < 0) { + xenbus_dev_fatal(front_info->xb_dev, ret, + "starting transaction"); + return ret; + } + + for (d = 0; d < cfg->num_pcm_instances; d++) { + struct xen_front_cfg_pcm_instance *pcm_instance; + int s, index; + + pcm_instance = &cfg->pcm_instances[d]; + + for (s = 0; s < pcm_instance->num_streams_pb; s++) { + index = pcm_instance->streams_pb[s].index; + + ret = evtchnl_publish(xbt, + &front_info->evt_pairs[index].req, + pcm_instance->streams_pb[s].xenstore_path, + XENSND_FIELD_RING_REF, + XENSND_FIELD_EVT_CHNL); + if (ret < 0) + goto fail; + + ret = evtchnl_publish(xbt, + &front_info->evt_pairs[index].evt, + pcm_instance->streams_pb[s].xenstore_path, + XENSND_FIELD_EVT_RING_REF, + XENSND_FIELD_EVT_EVT_CHNL); + if (ret < 0) + goto fail; + } + + for (s = 0; s < pcm_instance->num_streams_cap; s++) { + index = pcm_instance->streams_cap[s].index; + + ret = evtchnl_publish(xbt, + &front_info->evt_pairs[index].req, + pcm_instance->streams_cap[s].xenstore_path, + XENSND_FIELD_RING_REF, + XENSND_FIELD_EVT_CHNL); + if (ret < 0) + goto fail; + + ret = evtchnl_publish(xbt, + &front_info->evt_pairs[index].evt, + pcm_instance->streams_cap[s].xenstore_path, + XENSND_FIELD_EVT_RING_REF, + XENSND_FIELD_EVT_EVT_CHNL); + if (ret < 0) + goto fail; + } + } + ret = xenbus_transaction_end(xbt, 0); + if (ret < 0) { + if (ret == -EAGAIN) + goto again; + + xenbus_dev_fatal(front_info->xb_dev, ret, + "completing transaction"); + goto fail_to_end; + } + return 0; +fail: + xenbus_transaction_end(xbt, 1); +fail_to_end: + xenbus_dev_fatal(front_info->xb_dev, ret, "writing XenStore"); + return ret; +} + +void xen_snd_front_evtchnl_pair_set_connected(struct xen_snd_front_evtchnl_pair *evt_pair, + bool is_connected) +{ + enum xen_snd_front_evtchnl_state state; + + if (is_connected) + state = EVTCHNL_STATE_CONNECTED; + else + state = EVTCHNL_STATE_DISCONNECTED; + + mutex_lock(&evt_pair->req.ring_io_lock); + evt_pair->req.state = state; + mutex_unlock(&evt_pair->req.ring_io_lock); + + mutex_lock(&evt_pair->evt.ring_io_lock); + evt_pair->evt.state = state; + mutex_unlock(&evt_pair->evt.ring_io_lock); +} + +void xen_snd_front_evtchnl_pair_clear(struct xen_snd_front_evtchnl_pair *evt_pair) +{ + mutex_lock(&evt_pair->req.ring_io_lock); + evt_pair->req.evt_next_id = 0; + mutex_unlock(&evt_pair->req.ring_io_lock); + + mutex_lock(&evt_pair->evt.ring_io_lock); + evt_pair->evt.evt_next_id = 0; + mutex_unlock(&evt_pair->evt.ring_io_lock); +} + diff --git a/sound/xen/xen_snd_front_evtchnl.h b/sound/xen/xen_snd_front_evtchnl.h new file mode 100644 index 000000000000..cbe51fd1ec15 --- /dev/null +++ b/sound/xen/xen_snd_front_evtchnl.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#ifndef __XEN_SND_FRONT_EVTCHNL_H +#define __XEN_SND_FRONT_EVTCHNL_H + +#include + +struct xen_snd_front_info; + +#ifndef GRANT_INVALID_REF +/* + * FIXME: usage of grant reference 0 as invalid grant reference: + * grant reference 0 is valid, but never exposed to a PV driver, + * because of the fact it is already in use/reserved by the PV console. + */ +#define GRANT_INVALID_REF 0 +#endif + +/* Timeout in ms to wait for backend to respond. */ +#define VSND_WAIT_BACK_MS 3000 + +enum xen_snd_front_evtchnl_state { + EVTCHNL_STATE_DISCONNECTED, + EVTCHNL_STATE_CONNECTED, +}; + +enum xen_snd_front_evtchnl_type { + EVTCHNL_TYPE_REQ, + EVTCHNL_TYPE_EVT, +}; + +struct xen_snd_front_evtchnl { + struct xen_snd_front_info *front_info; + int gref; + int port; + int irq; + int index; + /* State of the event channel. */ + enum xen_snd_front_evtchnl_state state; + enum xen_snd_front_evtchnl_type type; + /* Either response id or incoming event id. */ + u16 evt_id; + /* Next request id or next expected event id. */ + u16 evt_next_id; + /* Shared ring access lock. */ + struct mutex ring_io_lock; + union { + struct { + struct xen_sndif_front_ring ring; + struct completion completion; + /* Serializer for backend IO: request/response. */ + struct mutex req_io_lock; + + /* Latest response status. */ + int resp_status; + union { + struct xensnd_query_hw_param hw_param; + } resp; + } req; + struct { + struct xensnd_event_page *page; + /* This is needed to handle XENSND_EVT_CUR_POS event. */ + struct snd_pcm_substream *substream; + } evt; + } u; +}; + +struct xen_snd_front_evtchnl_pair { + struct xen_snd_front_evtchnl req; + struct xen_snd_front_evtchnl evt; +}; + +int xen_snd_front_evtchnl_create_all(struct xen_snd_front_info *front_info, + int num_streams); + +void xen_snd_front_evtchnl_free_all(struct xen_snd_front_info *front_info); + +int xen_snd_front_evtchnl_publish_all(struct xen_snd_front_info *front_info); + +void xen_snd_front_evtchnl_flush(struct xen_snd_front_evtchnl *evtchnl); + +void xen_snd_front_evtchnl_pair_set_connected(struct xen_snd_front_evtchnl_pair *evt_pair, + bool is_connected); + +void xen_snd_front_evtchnl_pair_clear(struct xen_snd_front_evtchnl_pair *evt_pair); + +#endif /* __XEN_SND_FRONT_EVTCHNL_H */ -- cgit v1.2.3 From d6e0fbb82e73a01e4cb3631b8b3dd7aae09ab14c Mon Sep 17 00:00:00 2001 From: Oleksandr Andrushchenko Date: Mon, 14 May 2018 09:27:40 +0300 Subject: ALSA: xen-front: Implement handling of shared buffers Implement shared buffer handling according to the para-virtualized sound device protocol at xen/interface/io/sndif.h: - manage buffer memory - handle granted references - handle page directories [ Fixed missing linux/kernel.h inclusion -- tiwai ] Signed-off-by: Oleksandr Andrushchenko Signed-off-by: Takashi Iwai --- sound/xen/Makefile | 3 +- sound/xen/xen_snd_front.c | 8 ++ sound/xen/xen_snd_front_shbuf.c | 194 ++++++++++++++++++++++++++++++++++++++++ sound/xen/xen_snd_front_shbuf.h | 36 ++++++++ 4 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 sound/xen/xen_snd_front_shbuf.c create mode 100644 sound/xen/xen_snd_front_shbuf.h (limited to 'sound') diff --git a/sound/xen/Makefile b/sound/xen/Makefile index 03c669984000..f028bc30af5d 100644 --- a/sound/xen/Makefile +++ b/sound/xen/Makefile @@ -2,6 +2,7 @@ snd_xen_front-objs := xen_snd_front.o \ xen_snd_front_cfg.o \ - xen_snd_front_evtchnl.o + xen_snd_front_evtchnl.o \ + xen_snd_front_shbuf.o obj-$(CONFIG_SND_XEN_FRONTEND) += snd_xen_front.o diff --git a/sound/xen/xen_snd_front.c b/sound/xen/xen_snd_front.c index 277214d4fd0a..cdf66ea516c4 100644 --- a/sound/xen/xen_snd_front.c +++ b/sound/xen/xen_snd_front.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -191,6 +192,13 @@ static int __init xen_drv_init(void) if (!xen_has_pv_devices()) return -ENODEV; + /* At the moment we only support case with XEN_PAGE_SIZE == PAGE_SIZE */ + if (XEN_PAGE_SIZE != PAGE_SIZE) { + pr_err(XENSND_DRIVER_NAME ": different kernel and Xen page sizes are not supported: XEN_PAGE_SIZE (%lu) != PAGE_SIZE (%lu)\n", + XEN_PAGE_SIZE, PAGE_SIZE); + return -ENODEV; + } + pr_info("Initialising Xen " XENSND_DRIVER_NAME " frontend driver\n"); return xenbus_register_frontend(&xen_driver); } diff --git a/sound/xen/xen_snd_front_shbuf.c b/sound/xen/xen_snd_front_shbuf.c new file mode 100644 index 000000000000..07ac176a41ba --- /dev/null +++ b/sound/xen/xen_snd_front_shbuf.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#include +#include +#include + +#include "xen_snd_front_shbuf.h" + +grant_ref_t xen_snd_front_shbuf_get_dir_start(struct xen_snd_front_shbuf *buf) +{ + if (!buf->grefs) + return GRANT_INVALID_REF; + + return buf->grefs[0]; +} + +void xen_snd_front_shbuf_clear(struct xen_snd_front_shbuf *buf) +{ + memset(buf, 0, sizeof(*buf)); +} + +void xen_snd_front_shbuf_free(struct xen_snd_front_shbuf *buf) +{ + int i; + + if (buf->grefs) { + for (i = 0; i < buf->num_grefs; i++) + if (buf->grefs[i] != GRANT_INVALID_REF) + gnttab_end_foreign_access(buf->grefs[i], + 0, 0UL); + kfree(buf->grefs); + } + kfree(buf->directory); + free_pages_exact(buf->buffer, buf->buffer_sz); + xen_snd_front_shbuf_clear(buf); +} + +/* + * number of grant references a page can hold with respect to the + * xensnd_page_directory header + */ +#define XENSND_NUM_GREFS_PER_PAGE ((XEN_PAGE_SIZE - \ + offsetof(struct xensnd_page_directory, gref)) / \ + sizeof(grant_ref_t)) + +static void fill_page_dir(struct xen_snd_front_shbuf *buf, + int num_pages_dir) +{ + struct xensnd_page_directory *page_dir; + unsigned char *ptr; + int i, cur_gref, grefs_left, to_copy; + + ptr = buf->directory; + grefs_left = buf->num_grefs - num_pages_dir; + /* + * skip grant references at the beginning, they are for pages granted + * for the page directory itself + */ + cur_gref = num_pages_dir; + for (i = 0; i < num_pages_dir; i++) { + page_dir = (struct xensnd_page_directory *)ptr; + if (grefs_left <= XENSND_NUM_GREFS_PER_PAGE) { + to_copy = grefs_left; + page_dir->gref_dir_next_page = GRANT_INVALID_REF; + } else { + to_copy = XENSND_NUM_GREFS_PER_PAGE; + page_dir->gref_dir_next_page = buf->grefs[i + 1]; + } + + memcpy(&page_dir->gref, &buf->grefs[cur_gref], + to_copy * sizeof(grant_ref_t)); + + ptr += XEN_PAGE_SIZE; + grefs_left -= to_copy; + cur_gref += to_copy; + } +} + +static int grant_references(struct xenbus_device *xb_dev, + struct xen_snd_front_shbuf *buf, + int num_pages_dir, int num_pages_buffer, + int num_grefs) +{ + grant_ref_t priv_gref_head; + unsigned long frame; + int ret, i, j, cur_ref; + int otherend_id; + + ret = gnttab_alloc_grant_references(num_grefs, &priv_gref_head); + if (ret) + return ret; + + buf->num_grefs = num_grefs; + otherend_id = xb_dev->otherend_id; + j = 0; + + for (i = 0; i < num_pages_dir; i++) { + cur_ref = gnttab_claim_grant_reference(&priv_gref_head); + if (cur_ref < 0) { + ret = cur_ref; + goto fail; + } + + frame = xen_page_to_gfn(virt_to_page(buf->directory + + XEN_PAGE_SIZE * i)); + gnttab_grant_foreign_access_ref(cur_ref, otherend_id, frame, 0); + buf->grefs[j++] = cur_ref; + } + + for (i = 0; i < num_pages_buffer; i++) { + cur_ref = gnttab_claim_grant_reference(&priv_gref_head); + if (cur_ref < 0) { + ret = cur_ref; + goto fail; + } + + frame = xen_page_to_gfn(virt_to_page(buf->buffer + + XEN_PAGE_SIZE * i)); + gnttab_grant_foreign_access_ref(cur_ref, otherend_id, frame, 0); + buf->grefs[j++] = cur_ref; + } + + gnttab_free_grant_references(priv_gref_head); + fill_page_dir(buf, num_pages_dir); + return 0; + +fail: + gnttab_free_grant_references(priv_gref_head); + return ret; +} + +static int alloc_int_buffers(struct xen_snd_front_shbuf *buf, + int num_pages_dir, int num_pages_buffer, + int num_grefs) +{ + buf->grefs = kcalloc(num_grefs, sizeof(*buf->grefs), GFP_KERNEL); + if (!buf->grefs) + return -ENOMEM; + + buf->directory = kcalloc(num_pages_dir, XEN_PAGE_SIZE, GFP_KERNEL); + if (!buf->directory) + goto fail; + + buf->buffer_sz = num_pages_buffer * XEN_PAGE_SIZE; + buf->buffer = alloc_pages_exact(buf->buffer_sz, GFP_KERNEL); + if (!buf->buffer) + goto fail; + + return 0; + +fail: + kfree(buf->grefs); + buf->grefs = NULL; + kfree(buf->directory); + buf->directory = NULL; + return -ENOMEM; +} + +int xen_snd_front_shbuf_alloc(struct xenbus_device *xb_dev, + struct xen_snd_front_shbuf *buf, + unsigned int buffer_sz) +{ + int num_pages_buffer, num_pages_dir, num_grefs; + int ret; + + xen_snd_front_shbuf_clear(buf); + + num_pages_buffer = DIV_ROUND_UP(buffer_sz, XEN_PAGE_SIZE); + /* number of pages the page directory consumes itself */ + num_pages_dir = DIV_ROUND_UP(num_pages_buffer, + XENSND_NUM_GREFS_PER_PAGE); + num_grefs = num_pages_buffer + num_pages_dir; + + ret = alloc_int_buffers(buf, num_pages_dir, + num_pages_buffer, num_grefs); + if (ret < 0) + return ret; + + ret = grant_references(xb_dev, buf, num_pages_dir, num_pages_buffer, + num_grefs); + if (ret < 0) + return ret; + + fill_page_dir(buf, num_pages_dir); + return 0; +} diff --git a/sound/xen/xen_snd_front_shbuf.h b/sound/xen/xen_snd_front_shbuf.h new file mode 100644 index 000000000000..d28e97c47b2c --- /dev/null +++ b/sound/xen/xen_snd_front_shbuf.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#ifndef __XEN_SND_FRONT_SHBUF_H +#define __XEN_SND_FRONT_SHBUF_H + +#include + +#include "xen_snd_front_evtchnl.h" + +struct xen_snd_front_shbuf { + int num_grefs; + grant_ref_t *grefs; + u8 *directory; + u8 *buffer; + size_t buffer_sz; +}; + +grant_ref_t xen_snd_front_shbuf_get_dir_start(struct xen_snd_front_shbuf *buf); + +int xen_snd_front_shbuf_alloc(struct xenbus_device *xb_dev, + struct xen_snd_front_shbuf *buf, + unsigned int buffer_sz); + +void xen_snd_front_shbuf_clear(struct xen_snd_front_shbuf *buf); + +void xen_snd_front_shbuf_free(struct xen_snd_front_shbuf *buf); + +#endif /* __XEN_SND_FRONT_SHBUF_H */ -- cgit v1.2.3 From 1cee559351a7cb57b405554bac10a6f33c28ed09 Mon Sep 17 00:00:00 2001 From: Oleksandr Andrushchenko Date: Mon, 14 May 2018 09:27:41 +0300 Subject: ALSA: xen-front: Implement ALSA virtual sound driver Implement essential initialization of the sound driver: - introduce required data structures - handle driver registration - handle sound card registration - register sound driver on backend connection - remove sound driver on backend disconnect Initialize virtual sound card with streams according to the Xen store configuration. Implement ALSA driver operations including: - manage frontend/backend shared buffers - manage Xen bus event channel states Implement requests from front to back for ALSA PCM operations. - report ALSA period elapsed event: handle XENSND_EVT_CUR_POS notifications from the backend when stream position advances during playback/capture. The event carries a value of how many octets were played/captured at the time of the event. - implement explicit stream parameter negotiation between backend and frontend: handle XENSND_OP_HW_PARAM_QUERY request to read/update configuration space for the parameter given: request passes desired parameter interval and the response to this request returns min/max interval for the parameter to be used. Signed-off-by: Oleksandr Andrushchenko Signed-off-by: Takashi Iwai --- sound/xen/Makefile | 3 +- sound/xen/xen_snd_front.c | 181 ++++++++- sound/xen/xen_snd_front.h | 27 ++ sound/xen/xen_snd_front_alsa.c | 821 ++++++++++++++++++++++++++++++++++++++ sound/xen/xen_snd_front_alsa.h | 23 ++ sound/xen/xen_snd_front_evtchnl.c | 4 +- 6 files changed, 1056 insertions(+), 3 deletions(-) create mode 100644 sound/xen/xen_snd_front_alsa.c create mode 100644 sound/xen/xen_snd_front_alsa.h (limited to 'sound') diff --git a/sound/xen/Makefile b/sound/xen/Makefile index f028bc30af5d..1e6470ecc2f2 100644 --- a/sound/xen/Makefile +++ b/sound/xen/Makefile @@ -3,6 +3,7 @@ snd_xen_front-objs := xen_snd_front.o \ xen_snd_front_cfg.o \ xen_snd_front_evtchnl.o \ - xen_snd_front_shbuf.o + xen_snd_front_shbuf.o \ + xen_snd_front_alsa.o obj-$(CONFIG_SND_XEN_FRONTEND) += snd_xen_front.o diff --git a/sound/xen/xen_snd_front.c b/sound/xen/xen_snd_front.c index cdf66ea516c4..c18973a9bc9b 100644 --- a/sound/xen/xen_snd_front.c +++ b/sound/xen/xen_snd_front.c @@ -19,10 +19,189 @@ #include #include "xen_snd_front.h" +#include "xen_snd_front_alsa.h" #include "xen_snd_front_evtchnl.h" +#include "xen_snd_front_shbuf.h" + +static struct xensnd_req * +be_stream_prepare_req(struct xen_snd_front_evtchnl *evtchnl, u8 operation) +{ + struct xensnd_req *req; + + req = RING_GET_REQUEST(&evtchnl->u.req.ring, + evtchnl->u.req.ring.req_prod_pvt); + req->operation = operation; + req->id = evtchnl->evt_next_id++; + evtchnl->evt_id = req->id; + return req; +} + +static int be_stream_do_io(struct xen_snd_front_evtchnl *evtchnl) +{ + if (unlikely(evtchnl->state != EVTCHNL_STATE_CONNECTED)) + return -EIO; + + reinit_completion(&evtchnl->u.req.completion); + xen_snd_front_evtchnl_flush(evtchnl); + return 0; +} + +static int be_stream_wait_io(struct xen_snd_front_evtchnl *evtchnl) +{ + if (wait_for_completion_timeout(&evtchnl->u.req.completion, + msecs_to_jiffies(VSND_WAIT_BACK_MS)) <= 0) + return -ETIMEDOUT; + + return evtchnl->u.req.resp_status; +} + +int xen_snd_front_stream_query_hw_param(struct xen_snd_front_evtchnl *evtchnl, + struct xensnd_query_hw_param *hw_param_req, + struct xensnd_query_hw_param *hw_param_resp) +{ + struct xensnd_req *req; + int ret; + + mutex_lock(&evtchnl->u.req.req_io_lock); + + mutex_lock(&evtchnl->ring_io_lock); + req = be_stream_prepare_req(evtchnl, XENSND_OP_HW_PARAM_QUERY); + req->op.hw_param = *hw_param_req; + mutex_unlock(&evtchnl->ring_io_lock); + + ret = be_stream_do_io(evtchnl); + + if (ret == 0) + ret = be_stream_wait_io(evtchnl); + + if (ret == 0) + *hw_param_resp = evtchnl->u.req.resp.hw_param; + + mutex_unlock(&evtchnl->u.req.req_io_lock); + return ret; +} + +int xen_snd_front_stream_prepare(struct xen_snd_front_evtchnl *evtchnl, + struct xen_snd_front_shbuf *sh_buf, + u8 format, unsigned int channels, + unsigned int rate, u32 buffer_sz, + u32 period_sz) +{ + struct xensnd_req *req; + int ret; + + mutex_lock(&evtchnl->u.req.req_io_lock); + + mutex_lock(&evtchnl->ring_io_lock); + req = be_stream_prepare_req(evtchnl, XENSND_OP_OPEN); + req->op.open.pcm_format = format; + req->op.open.pcm_channels = channels; + req->op.open.pcm_rate = rate; + req->op.open.buffer_sz = buffer_sz; + req->op.open.period_sz = period_sz; + req->op.open.gref_directory = xen_snd_front_shbuf_get_dir_start(sh_buf); + mutex_unlock(&evtchnl->ring_io_lock); + + ret = be_stream_do_io(evtchnl); + + if (ret == 0) + ret = be_stream_wait_io(evtchnl); + + mutex_unlock(&evtchnl->u.req.req_io_lock); + return ret; +} + +int xen_snd_front_stream_close(struct xen_snd_front_evtchnl *evtchnl) +{ + struct xensnd_req *req; + int ret; + + mutex_lock(&evtchnl->u.req.req_io_lock); + + mutex_lock(&evtchnl->ring_io_lock); + req = be_stream_prepare_req(evtchnl, XENSND_OP_CLOSE); + mutex_unlock(&evtchnl->ring_io_lock); + + ret = be_stream_do_io(evtchnl); + + if (ret == 0) + ret = be_stream_wait_io(evtchnl); + + mutex_unlock(&evtchnl->u.req.req_io_lock); + return ret; +} + +int xen_snd_front_stream_write(struct xen_snd_front_evtchnl *evtchnl, + unsigned long pos, unsigned long count) +{ + struct xensnd_req *req; + int ret; + + mutex_lock(&evtchnl->u.req.req_io_lock); + + mutex_lock(&evtchnl->ring_io_lock); + req = be_stream_prepare_req(evtchnl, XENSND_OP_WRITE); + req->op.rw.length = count; + req->op.rw.offset = pos; + mutex_unlock(&evtchnl->ring_io_lock); + + ret = be_stream_do_io(evtchnl); + + if (ret == 0) + ret = be_stream_wait_io(evtchnl); + + mutex_unlock(&evtchnl->u.req.req_io_lock); + return ret; +} + +int xen_snd_front_stream_read(struct xen_snd_front_evtchnl *evtchnl, + unsigned long pos, unsigned long count) +{ + struct xensnd_req *req; + int ret; + + mutex_lock(&evtchnl->u.req.req_io_lock); + + mutex_lock(&evtchnl->ring_io_lock); + req = be_stream_prepare_req(evtchnl, XENSND_OP_READ); + req->op.rw.length = count; + req->op.rw.offset = pos; + mutex_unlock(&evtchnl->ring_io_lock); + + ret = be_stream_do_io(evtchnl); + + if (ret == 0) + ret = be_stream_wait_io(evtchnl); + + mutex_unlock(&evtchnl->u.req.req_io_lock); + return ret; +} + +int xen_snd_front_stream_trigger(struct xen_snd_front_evtchnl *evtchnl, + int type) +{ + struct xensnd_req *req; + int ret; + + mutex_lock(&evtchnl->u.req.req_io_lock); + + mutex_lock(&evtchnl->ring_io_lock); + req = be_stream_prepare_req(evtchnl, XENSND_OP_TRIGGER); + req->op.trigger.type = type; + mutex_unlock(&evtchnl->ring_io_lock); + + ret = be_stream_do_io(evtchnl); + + if (ret == 0) + ret = be_stream_wait_io(evtchnl); + + mutex_unlock(&evtchnl->u.req.req_io_lock); + return ret; +} static void xen_snd_drv_fini(struct xen_snd_front_info *front_info) { + xen_snd_front_alsa_fini(front_info); xen_snd_front_evtchnl_free_all(front_info); } @@ -45,7 +224,7 @@ static int sndback_initwait(struct xen_snd_front_info *front_info) static int sndback_connect(struct xen_snd_front_info *front_info) { - return 0; + return xen_snd_front_alsa_init(front_info); } static void sndback_disconnect(struct xen_snd_front_info *front_info) diff --git a/sound/xen/xen_snd_front.h b/sound/xen/xen_snd_front.h index 9d0c92100c7b..a2ea2463bcc5 100644 --- a/sound/xen/xen_snd_front.h +++ b/sound/xen/xen_snd_front.h @@ -13,15 +13,42 @@ #include "xen_snd_front_cfg.h" +struct xen_snd_front_card_info; +struct xen_snd_front_evtchnl; struct xen_snd_front_evtchnl_pair; +struct xen_snd_front_shbuf; +struct xensnd_query_hw_param; struct xen_snd_front_info { struct xenbus_device *xb_dev; + struct xen_snd_front_card_info *card_info; + int num_evt_pairs; struct xen_snd_front_evtchnl_pair *evt_pairs; struct xen_front_cfg_card cfg; }; +int xen_snd_front_stream_query_hw_param(struct xen_snd_front_evtchnl *evtchnl, + struct xensnd_query_hw_param *hw_param_req, + struct xensnd_query_hw_param *hw_param_resp); + +int xen_snd_front_stream_prepare(struct xen_snd_front_evtchnl *evtchnl, + struct xen_snd_front_shbuf *sh_buf, + u8 format, unsigned int channels, + unsigned int rate, u32 buffer_sz, + u32 period_sz); + +int xen_snd_front_stream_close(struct xen_snd_front_evtchnl *evtchnl); + +int xen_snd_front_stream_write(struct xen_snd_front_evtchnl *evtchnl, + unsigned long pos, unsigned long count); + +int xen_snd_front_stream_read(struct xen_snd_front_evtchnl *evtchnl, + unsigned long pos, unsigned long count); + +int xen_snd_front_stream_trigger(struct xen_snd_front_evtchnl *evtchnl, + int type); + #endif /* __XEN_SND_FRONT_H */ diff --git a/sound/xen/xen_snd_front_alsa.c b/sound/xen/xen_snd_front_alsa.c new file mode 100644 index 000000000000..5041f83e98d2 --- /dev/null +++ b/sound/xen/xen_snd_front_alsa.c @@ -0,0 +1,821 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#include + +#include +#include +#include + +#include + +#include "xen_snd_front.h" +#include "xen_snd_front_alsa.h" +#include "xen_snd_front_cfg.h" +#include "xen_snd_front_evtchnl.h" +#include "xen_snd_front_shbuf.h" + +struct xen_snd_front_pcm_stream_info { + struct xen_snd_front_info *front_info; + struct xen_snd_front_evtchnl_pair *evt_pair; + struct xen_snd_front_shbuf sh_buf; + int index; + + bool is_open; + struct snd_pcm_hardware pcm_hw; + + /* Number of processed frames as reported by the backend. */ + snd_pcm_uframes_t be_cur_frame; + /* Current HW pointer to be reported via .period callback. */ + atomic_t hw_ptr; + /* Modulo of the number of processed frames - for period detection. */ + u32 out_frames; +}; + +struct xen_snd_front_pcm_instance_info { + struct xen_snd_front_card_info *card_info; + struct snd_pcm *pcm; + struct snd_pcm_hardware pcm_hw; + int num_pcm_streams_pb; + struct xen_snd_front_pcm_stream_info *streams_pb; + int num_pcm_streams_cap; + struct xen_snd_front_pcm_stream_info *streams_cap; +}; + +struct xen_snd_front_card_info { + struct xen_snd_front_info *front_info; + struct snd_card *card; + struct snd_pcm_hardware pcm_hw; + int num_pcm_instances; + struct xen_snd_front_pcm_instance_info *pcm_instances; +}; + +struct alsa_sndif_sample_format { + u8 sndif; + snd_pcm_format_t alsa; +}; + +struct alsa_sndif_hw_param { + u8 sndif; + snd_pcm_hw_param_t alsa; +}; + +static const struct alsa_sndif_sample_format ALSA_SNDIF_FORMATS[] = { + { + .sndif = XENSND_PCM_FORMAT_U8, + .alsa = SNDRV_PCM_FORMAT_U8 + }, + { + .sndif = XENSND_PCM_FORMAT_S8, + .alsa = SNDRV_PCM_FORMAT_S8 + }, + { + .sndif = XENSND_PCM_FORMAT_U16_LE, + .alsa = SNDRV_PCM_FORMAT_U16_LE + }, + { + .sndif = XENSND_PCM_FORMAT_U16_BE, + .alsa = SNDRV_PCM_FORMAT_U16_BE + }, + { + .sndif = XENSND_PCM_FORMAT_S16_LE, + .alsa = SNDRV_PCM_FORMAT_S16_LE + }, + { + .sndif = XENSND_PCM_FORMAT_S16_BE, + .alsa = SNDRV_PCM_FORMAT_S16_BE + }, + { + .sndif = XENSND_PCM_FORMAT_U24_LE, + .alsa = SNDRV_PCM_FORMAT_U24_LE + }, + { + .sndif = XENSND_PCM_FORMAT_U24_BE, + .alsa = SNDRV_PCM_FORMAT_U24_BE + }, + { + .sndif = XENSND_PCM_FORMAT_S24_LE, + .alsa = SNDRV_PCM_FORMAT_S24_LE + }, + { + .sndif = XENSND_PCM_FORMAT_S24_BE, + .alsa = SNDRV_PCM_FORMAT_S24_BE + }, + { + .sndif = XENSND_PCM_FORMAT_U32_LE, + .alsa = SNDRV_PCM_FORMAT_U32_LE + }, + { + .sndif = XENSND_PCM_FORMAT_U32_BE, + .alsa = SNDRV_PCM_FORMAT_U32_BE + }, + { + .sndif = XENSND_PCM_FORMAT_S32_LE, + .alsa = SNDRV_PCM_FORMAT_S32_LE + }, + { + .sndif = XENSND_PCM_FORMAT_S32_BE, + .alsa = SNDRV_PCM_FORMAT_S32_BE + }, + { + .sndif = XENSND_PCM_FORMAT_A_LAW, + .alsa = SNDRV_PCM_FORMAT_A_LAW + }, + { + .sndif = XENSND_PCM_FORMAT_MU_LAW, + .alsa = SNDRV_PCM_FORMAT_MU_LAW + }, + { + .sndif = XENSND_PCM_FORMAT_F32_LE, + .alsa = SNDRV_PCM_FORMAT_FLOAT_LE + }, + { + .sndif = XENSND_PCM_FORMAT_F32_BE, + .alsa = SNDRV_PCM_FORMAT_FLOAT_BE + }, + { + .sndif = XENSND_PCM_FORMAT_F64_LE, + .alsa = SNDRV_PCM_FORMAT_FLOAT64_LE + }, + { + .sndif = XENSND_PCM_FORMAT_F64_BE, + .alsa = SNDRV_PCM_FORMAT_FLOAT64_BE + }, + { + .sndif = XENSND_PCM_FORMAT_IEC958_SUBFRAME_LE, + .alsa = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE + }, + { + .sndif = XENSND_PCM_FORMAT_IEC958_SUBFRAME_BE, + .alsa = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE + }, + { + .sndif = XENSND_PCM_FORMAT_IMA_ADPCM, + .alsa = SNDRV_PCM_FORMAT_IMA_ADPCM + }, + { + .sndif = XENSND_PCM_FORMAT_MPEG, + .alsa = SNDRV_PCM_FORMAT_MPEG + }, + { + .sndif = XENSND_PCM_FORMAT_GSM, + .alsa = SNDRV_PCM_FORMAT_GSM + }, +}; + +static int to_sndif_format(snd_pcm_format_t format) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ALSA_SNDIF_FORMATS); i++) + if (ALSA_SNDIF_FORMATS[i].alsa == format) + return ALSA_SNDIF_FORMATS[i].sndif; + + return -EINVAL; +} + +static u64 to_sndif_formats_mask(u64 alsa_formats) +{ + u64 mask; + int i; + + mask = 0; + for (i = 0; i < ARRAY_SIZE(ALSA_SNDIF_FORMATS); i++) + if (1 << ALSA_SNDIF_FORMATS[i].alsa & alsa_formats) + mask |= 1 << ALSA_SNDIF_FORMATS[i].sndif; + + return mask; +} + +static u64 to_alsa_formats_mask(u64 sndif_formats) +{ + u64 mask; + int i; + + mask = 0; + for (i = 0; i < ARRAY_SIZE(ALSA_SNDIF_FORMATS); i++) + if (1 << ALSA_SNDIF_FORMATS[i].sndif & sndif_formats) + mask |= 1 << ALSA_SNDIF_FORMATS[i].alsa; + + return mask; +} + +static void stream_clear(struct xen_snd_front_pcm_stream_info *stream) +{ + stream->is_open = false; + stream->be_cur_frame = 0; + stream->out_frames = 0; + atomic_set(&stream->hw_ptr, 0); + xen_snd_front_evtchnl_pair_clear(stream->evt_pair); + xen_snd_front_shbuf_clear(&stream->sh_buf); +} + +static void stream_free(struct xen_snd_front_pcm_stream_info *stream) +{ + xen_snd_front_shbuf_free(&stream->sh_buf); + stream_clear(stream); +} + +static struct xen_snd_front_pcm_stream_info * +stream_get(struct snd_pcm_substream *substream) +{ + struct xen_snd_front_pcm_instance_info *pcm_instance = + snd_pcm_substream_chip(substream); + struct xen_snd_front_pcm_stream_info *stream; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + stream = &pcm_instance->streams_pb[substream->number]; + else + stream = &pcm_instance->streams_cap[substream->number]; + + return stream; +} + +static int alsa_hw_rule(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct xen_snd_front_pcm_stream_info *stream = rule->private; + struct device *dev = &stream->front_info->xb_dev->dev; + struct snd_mask *formats = + hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_interval *rates = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *period = + hw_param_interval(params, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + struct snd_interval *buffer = + hw_param_interval(params, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE); + struct xensnd_query_hw_param req; + struct xensnd_query_hw_param resp; + struct snd_interval interval; + struct snd_mask mask; + u64 sndif_formats; + int changed, ret; + + /* Collect all the values we need for the query. */ + + req.formats = to_sndif_formats_mask((u64)formats->bits[0] | + (u64)(formats->bits[1]) << 32); + + req.rates.min = rates->min; + req.rates.max = rates->max; + + req.channels.min = channels->min; + req.channels.max = channels->max; + + req.buffer.min = buffer->min; + req.buffer.max = buffer->max; + + req.period.min = period->min; + req.period.max = period->max; + + ret = xen_snd_front_stream_query_hw_param(&stream->evt_pair->req, + &req, &resp); + if (ret < 0) { + /* Check if this is due to backend communication error. */ + if (ret == -EIO || ret == -ETIMEDOUT) + dev_err(dev, "Failed to query ALSA HW parameters\n"); + return ret; + } + + /* Refine HW parameters after the query. */ + changed = 0; + + sndif_formats = to_alsa_formats_mask(resp.formats); + snd_mask_none(&mask); + mask.bits[0] = (u32)sndif_formats; + mask.bits[1] = (u32)(sndif_formats >> 32); + ret = snd_mask_refine(formats, &mask); + if (ret < 0) + return ret; + changed |= ret; + + interval.openmin = 0; + interval.openmax = 0; + interval.integer = 1; + + interval.min = resp.rates.min; + interval.max = resp.rates.max; + ret = snd_interval_refine(rates, &interval); + if (ret < 0) + return ret; + changed |= ret; + + interval.min = resp.channels.min; + interval.max = resp.channels.max; + ret = snd_interval_refine(channels, &interval); + if (ret < 0) + return ret; + changed |= ret; + + interval.min = resp.buffer.min; + interval.max = resp.buffer.max; + ret = snd_interval_refine(buffer, &interval); + if (ret < 0) + return ret; + changed |= ret; + + interval.min = resp.period.min; + interval.max = resp.period.max; + ret = snd_interval_refine(period, &interval); + if (ret < 0) + return ret; + changed |= ret; + + return changed; +} + +static int alsa_open(struct snd_pcm_substream *substream) +{ + struct xen_snd_front_pcm_instance_info *pcm_instance = + snd_pcm_substream_chip(substream); + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct xen_snd_front_info *front_info = + pcm_instance->card_info->front_info; + struct device *dev = &front_info->xb_dev->dev; + int ret; + + /* + * Return our HW properties: override defaults with those configured + * via XenStore. + */ + runtime->hw = stream->pcm_hw; + runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_DOUBLE | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_PAUSE); + runtime->hw.info |= SNDRV_PCM_INFO_INTERLEAVED; + + stream->evt_pair = &front_info->evt_pairs[stream->index]; + + stream->front_info = front_info; + + stream->evt_pair->evt.u.evt.substream = substream; + + stream_clear(stream); + + xen_snd_front_evtchnl_pair_set_connected(stream->evt_pair, true); + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + alsa_hw_rule, stream, + SNDRV_PCM_HW_PARAM_FORMAT, -1); + if (ret) { + dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_FORMAT\n"); + return ret; + } + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + alsa_hw_rule, stream, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (ret) { + dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_RATE\n"); + return ret; + } + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + alsa_hw_rule, stream, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret) { + dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_CHANNELS\n"); + return ret; + } + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + alsa_hw_rule, stream, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1); + if (ret) { + dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_PERIOD_SIZE\n"); + return ret; + } + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + alsa_hw_rule, stream, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1); + if (ret) { + dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_BUFFER_SIZE\n"); + return ret; + } + + return 0; +} + +static int alsa_close(struct snd_pcm_substream *substream) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + + xen_snd_front_evtchnl_pair_set_connected(stream->evt_pair, false); + return 0; +} + +static int alsa_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + int ret; + + /* + * This callback may be called multiple times, + * so free the previously allocated shared buffer if any. + */ + stream_free(stream); + + ret = xen_snd_front_shbuf_alloc(stream->front_info->xb_dev, + &stream->sh_buf, + params_buffer_bytes(params)); + if (ret < 0) { + stream_free(stream); + dev_err(&stream->front_info->xb_dev->dev, + "Failed to allocate buffers for stream with index %d\n", + stream->index); + return ret; + } + + return 0; +} + +static int alsa_hw_free(struct snd_pcm_substream *substream) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + int ret; + + ret = xen_snd_front_stream_close(&stream->evt_pair->req); + stream_free(stream); + return ret; +} + +static int alsa_prepare(struct snd_pcm_substream *substream) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + + if (!stream->is_open) { + struct snd_pcm_runtime *runtime = substream->runtime; + u8 sndif_format; + int ret; + + sndif_format = to_sndif_format(runtime->format); + if (sndif_format < 0) { + dev_err(&stream->front_info->xb_dev->dev, + "Unsupported sample format: %d\n", + runtime->format); + return sndif_format; + } + + ret = xen_snd_front_stream_prepare(&stream->evt_pair->req, + &stream->sh_buf, + sndif_format, + runtime->channels, + runtime->rate, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream)); + if (ret < 0) + return ret; + + stream->is_open = true; + } + + return 0; +} + +static int alsa_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + int type; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + type = XENSND_OP_TRIGGER_START; + break; + + case SNDRV_PCM_TRIGGER_RESUME: + type = XENSND_OP_TRIGGER_RESUME; + break; + + case SNDRV_PCM_TRIGGER_STOP: + type = XENSND_OP_TRIGGER_STOP; + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + type = XENSND_OP_TRIGGER_PAUSE; + break; + + default: + return -EINVAL; + } + + return xen_snd_front_stream_trigger(&stream->evt_pair->req, type); +} + +void xen_snd_front_alsa_handle_cur_pos(struct xen_snd_front_evtchnl *evtchnl, + u64 pos_bytes) +{ + struct snd_pcm_substream *substream = evtchnl->u.evt.substream; + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + snd_pcm_uframes_t delta, new_hw_ptr, cur_frame; + + cur_frame = bytes_to_frames(substream->runtime, pos_bytes); + + delta = cur_frame - stream->be_cur_frame; + stream->be_cur_frame = cur_frame; + + new_hw_ptr = (snd_pcm_uframes_t)atomic_read(&stream->hw_ptr); + new_hw_ptr = (new_hw_ptr + delta) % substream->runtime->buffer_size; + atomic_set(&stream->hw_ptr, (int)new_hw_ptr); + + stream->out_frames += delta; + if (stream->out_frames > substream->runtime->period_size) { + stream->out_frames %= substream->runtime->period_size; + snd_pcm_period_elapsed(substream); + } +} + +static snd_pcm_uframes_t alsa_pointer(struct snd_pcm_substream *substream) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + + return (snd_pcm_uframes_t)atomic_read(&stream->hw_ptr); +} + +static int alsa_pb_copy_user(struct snd_pcm_substream *substream, + int channel, unsigned long pos, void __user *src, + unsigned long count) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + + if (unlikely(pos + count > stream->sh_buf.buffer_sz)) + return -EINVAL; + + if (copy_from_user(stream->sh_buf.buffer + pos, src, count)) + return -EFAULT; + + return xen_snd_front_stream_write(&stream->evt_pair->req, pos, count); +} + +static int alsa_pb_copy_kernel(struct snd_pcm_substream *substream, + int channel, unsigned long pos, void *src, + unsigned long count) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + + if (unlikely(pos + count > stream->sh_buf.buffer_sz)) + return -EINVAL; + + memcpy(stream->sh_buf.buffer + pos, src, count); + + return xen_snd_front_stream_write(&stream->evt_pair->req, pos, count); +} + +static int alsa_cap_copy_user(struct snd_pcm_substream *substream, + int channel, unsigned long pos, void __user *dst, + unsigned long count) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + int ret; + + if (unlikely(pos + count > stream->sh_buf.buffer_sz)) + return -EINVAL; + + ret = xen_snd_front_stream_read(&stream->evt_pair->req, pos, count); + if (ret < 0) + return ret; + + return copy_to_user(dst, stream->sh_buf.buffer + pos, count) ? + -EFAULT : 0; +} + +static int alsa_cap_copy_kernel(struct snd_pcm_substream *substream, + int channel, unsigned long pos, void *dst, + unsigned long count) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + int ret; + + if (unlikely(pos + count > stream->sh_buf.buffer_sz)) + return -EINVAL; + + ret = xen_snd_front_stream_read(&stream->evt_pair->req, pos, count); + if (ret < 0) + return ret; + + memcpy(dst, stream->sh_buf.buffer + pos, count); + + return 0; +} + +static int alsa_pb_fill_silence(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + unsigned long count) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + + if (unlikely(pos + count > stream->sh_buf.buffer_sz)) + return -EINVAL; + + memset(stream->sh_buf.buffer + pos, 0, count); + + return xen_snd_front_stream_write(&stream->evt_pair->req, pos, count); +} + +/* + * FIXME: The mmaped data transfer is asynchronous and there is no + * ack signal from user-space when it is done. This is the + * reason it is not implemented in the PV driver as we do need + * to know when the buffer can be transferred to the backend. + */ + +static struct snd_pcm_ops snd_drv_alsa_playback_ops = { + .open = alsa_open, + .close = alsa_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = alsa_hw_params, + .hw_free = alsa_hw_free, + .prepare = alsa_prepare, + .trigger = alsa_trigger, + .pointer = alsa_pointer, + .copy_user = alsa_pb_copy_user, + .copy_kernel = alsa_pb_copy_kernel, + .fill_silence = alsa_pb_fill_silence, +}; + +static struct snd_pcm_ops snd_drv_alsa_capture_ops = { + .open = alsa_open, + .close = alsa_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = alsa_hw_params, + .hw_free = alsa_hw_free, + .prepare = alsa_prepare, + .trigger = alsa_trigger, + .pointer = alsa_pointer, + .copy_user = alsa_cap_copy_user, + .copy_kernel = alsa_cap_copy_kernel, +}; + +static int new_pcm_instance(struct xen_snd_front_card_info *card_info, + struct xen_front_cfg_pcm_instance *instance_cfg, + struct xen_snd_front_pcm_instance_info *pcm_instance_info) +{ + struct snd_pcm *pcm; + int ret, i; + + dev_dbg(&card_info->front_info->xb_dev->dev, + "New PCM device \"%s\" with id %d playback %d capture %d", + instance_cfg->name, + instance_cfg->device_id, + instance_cfg->num_streams_pb, + instance_cfg->num_streams_cap); + + pcm_instance_info->card_info = card_info; + + pcm_instance_info->pcm_hw = instance_cfg->pcm_hw; + + if (instance_cfg->num_streams_pb) { + pcm_instance_info->streams_pb = + devm_kcalloc(&card_info->card->card_dev, + instance_cfg->num_streams_pb, + sizeof(struct xen_snd_front_pcm_stream_info), + GFP_KERNEL); + if (!pcm_instance_info->streams_pb) + return -ENOMEM; + } + + if (instance_cfg->num_streams_cap) { + pcm_instance_info->streams_cap = + devm_kcalloc(&card_info->card->card_dev, + instance_cfg->num_streams_cap, + sizeof(struct xen_snd_front_pcm_stream_info), + GFP_KERNEL); + if (!pcm_instance_info->streams_cap) + return -ENOMEM; + } + + pcm_instance_info->num_pcm_streams_pb = + instance_cfg->num_streams_pb; + pcm_instance_info->num_pcm_streams_cap = + instance_cfg->num_streams_cap; + + for (i = 0; i < pcm_instance_info->num_pcm_streams_pb; i++) { + pcm_instance_info->streams_pb[i].pcm_hw = + instance_cfg->streams_pb[i].pcm_hw; + pcm_instance_info->streams_pb[i].index = + instance_cfg->streams_pb[i].index; + } + + for (i = 0; i < pcm_instance_info->num_pcm_streams_cap; i++) { + pcm_instance_info->streams_cap[i].pcm_hw = + instance_cfg->streams_cap[i].pcm_hw; + pcm_instance_info->streams_cap[i].index = + instance_cfg->streams_cap[i].index; + } + + ret = snd_pcm_new(card_info->card, instance_cfg->name, + instance_cfg->device_id, + instance_cfg->num_streams_pb, + instance_cfg->num_streams_cap, + &pcm); + if (ret < 0) + return ret; + + pcm->private_data = pcm_instance_info; + pcm->info_flags = 0; + /* we want to handle all PCM operations in non-atomic context */ + pcm->nonatomic = true; + strncpy(pcm->name, "Virtual card PCM", sizeof(pcm->name)); + + if (instance_cfg->num_streams_pb) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_drv_alsa_playback_ops); + + if (instance_cfg->num_streams_cap) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_drv_alsa_capture_ops); + + pcm_instance_info->pcm = pcm; + return 0; +} + +int xen_snd_front_alsa_init(struct xen_snd_front_info *front_info) +{ + struct device *dev = &front_info->xb_dev->dev; + struct xen_front_cfg_card *cfg = &front_info->cfg; + struct xen_snd_front_card_info *card_info; + struct snd_card *card; + int ret, i; + + dev_dbg(dev, "Creating virtual sound card\n"); + + ret = snd_card_new(dev, 0, XENSND_DRIVER_NAME, THIS_MODULE, + sizeof(struct xen_snd_front_card_info), &card); + if (ret < 0) + return ret; + + card_info = card->private_data; + card_info->front_info = front_info; + front_info->card_info = card_info; + card_info->card = card; + card_info->pcm_instances = + devm_kcalloc(dev, cfg->num_pcm_instances, + sizeof(struct xen_snd_front_pcm_instance_info), + GFP_KERNEL); + if (!card_info->pcm_instances) { + ret = -ENOMEM; + goto fail; + } + + card_info->num_pcm_instances = cfg->num_pcm_instances; + card_info->pcm_hw = cfg->pcm_hw; + + for (i = 0; i < cfg->num_pcm_instances; i++) { + ret = new_pcm_instance(card_info, &cfg->pcm_instances[i], + &card_info->pcm_instances[i]); + if (ret < 0) + goto fail; + } + + strncpy(card->driver, XENSND_DRIVER_NAME, sizeof(card->driver)); + strncpy(card->shortname, cfg->name_short, sizeof(card->shortname)); + strncpy(card->longname, cfg->name_long, sizeof(card->longname)); + + ret = snd_card_register(card); + if (ret < 0) + goto fail; + + return 0; + +fail: + snd_card_free(card); + return ret; +} + +void xen_snd_front_alsa_fini(struct xen_snd_front_info *front_info) +{ + struct xen_snd_front_card_info *card_info; + struct snd_card *card; + + card_info = front_info->card_info; + if (!card_info) + return; + + card = card_info->card; + if (!card) + return; + + dev_dbg(&front_info->xb_dev->dev, "Removing virtual sound card %d\n", + card->number); + snd_card_free(card); + + /* Card_info will be freed when destroying front_info->xb_dev->dev. */ + card_info->card = NULL; +} diff --git a/sound/xen/xen_snd_front_alsa.h b/sound/xen/xen_snd_front_alsa.h new file mode 100644 index 000000000000..18abd9eec967 --- /dev/null +++ b/sound/xen/xen_snd_front_alsa.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#ifndef __XEN_SND_FRONT_ALSA_H +#define __XEN_SND_FRONT_ALSA_H + +struct xen_snd_front_info; + +int xen_snd_front_alsa_init(struct xen_snd_front_info *front_info); + +void xen_snd_front_alsa_fini(struct xen_snd_front_info *front_info); + +void xen_snd_front_alsa_handle_cur_pos(struct xen_snd_front_evtchnl *evtchnl, + u64 pos_bytes); + +#endif /* __XEN_SND_FRONT_ALSA_H */ diff --git a/sound/xen/xen_snd_front_evtchnl.c b/sound/xen/xen_snd_front_evtchnl.c index 1faafff08807..d70a62e7f910 100644 --- a/sound/xen/xen_snd_front_evtchnl.c +++ b/sound/xen/xen_snd_front_evtchnl.c @@ -14,6 +14,7 @@ #include #include "xen_snd_front.h" +#include "xen_snd_front_alsa.h" #include "xen_snd_front_cfg.h" #include "xen_snd_front_evtchnl.h" @@ -118,7 +119,8 @@ static irqreturn_t evtchnl_interrupt_evt(int irq, void *dev_id) switch (event->type) { case XENSND_EVT_CUR_POS: - /* Do nothing at the moment. */ + xen_snd_front_alsa_handle_cur_pos(channel, + event->op.cur_pos.position); break; } } -- cgit v1.2.3 From 377a879d9832f4ba69bd6a1fc996bb4181b1e504 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 16 May 2018 20:07:18 +0200 Subject: ALSA: usb-audio: Apply rate limit to warning messages in URB complete callback retire_capture_urb() may print warning messages when the given URB doesn't align, and this may flood the system log easily. Put the rate limit to the message for avoiding it. Bugzilla: https://bugzilla.suse.com/show_bug.cgi?id=1093485 Signed-off-by: Takashi Iwai --- sound/usb/pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index dc2dfec9effd..20bed1c7a312 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -1303,7 +1303,7 @@ static void retire_capture_urb(struct snd_usb_substream *subs, if (bytes % (runtime->sample_bits >> 3) != 0) { int oldbytes = bytes; bytes = frames * stride; - dev_warn(&subs->dev->dev, + dev_warn_ratelimited(&subs->dev->dev, "Corrected urb data len. %d->%d\n", oldbytes, bytes); } -- cgit v1.2.3 From dc82e52492f684dcd5ed9e4773e72dbf2203d75e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 15 May 2018 20:25:29 +0200 Subject: ALSA: core: Assure control device to be registered at last The commit 289ca025ee1d ("ALSA: Use priority list for managing device list") changed the way to register/disconnect/free devices via a single priority list. This helped to make behavior consistent, but it also changed a slight behavior change: namely, the control device is registered earlier than others, while it was supposed to be the very last one. I've put SNDRV_DEV_CONTROL in the current position as the release of ctl elements often conflict with the private ctl elements some PCM or other components may create, which often leads to a double-free. But, the order of register and disconnect should be indeed fixed as expected in the early days: the control device gets registered at last, and disconnected at first. This patch changes the priority list order to move SNDRV_DEV_CONTROL as the last guy to assure the register / disconnect order. Meanwhile, for keeping the messy resource release order, manually treat the control and lowlevel devices as last freed one. Additional note: The lowlevel device is the device where a card driver creates at probe. And, we still keep the release order control -> lowlevel, as there might be link from a control element back to a lowlevel object. Fixes: 289ca025ee1d ("ALSA: Use priority list for managing device list") Reported-by: Tzung-Bi Shih Tested-by: Tzung-Bi Shih Signed-off-by: Takashi Iwai --- include/sound/core.h | 2 +- sound/core/device.c | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/include/sound/core.h b/include/sound/core.h index 5f181b875c2f..36a5934cf4b1 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -51,7 +51,6 @@ struct completion; */ enum snd_device_type { SNDRV_DEV_LOWLEVEL, - SNDRV_DEV_CONTROL, SNDRV_DEV_INFO, SNDRV_DEV_BUS, SNDRV_DEV_CODEC, @@ -62,6 +61,7 @@ enum snd_device_type { SNDRV_DEV_SEQUENCER, SNDRV_DEV_HWDEP, SNDRV_DEV_JACK, + SNDRV_DEV_CONTROL, /* NOTE: this must be the last one */ }; enum snd_device_state { diff --git a/sound/core/device.c b/sound/core/device.c index cb0e46f66cc9..535102d564e3 100644 --- a/sound/core/device.c +++ b/sound/core/device.c @@ -240,6 +240,15 @@ void snd_device_free_all(struct snd_card *card) if (snd_BUG_ON(!card)) return; + list_for_each_entry_safe_reverse(dev, next, &card->devices, list) { + /* exception: free ctl and lowlevel stuff later */ + if (dev->type == SNDRV_DEV_CONTROL || + dev->type == SNDRV_DEV_LOWLEVEL) + continue; + __snd_device_free(dev); + } + + /* free all */ list_for_each_entry_safe_reverse(dev, next, &card->devices, list) __snd_device_free(dev); } -- cgit v1.2.3 From bf8b47fe2016e7c3a99bcdb918fd07838efa8135 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 17 May 2018 09:04:20 +0100 Subject: ALSA: emu10k1: fix spelling mistake: "Caputre" -> "Capture" Trivial fix to spelling mistakes in audigy_outs arrays. Signed-off-by: Colin Ian King Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 2 +- sound/pci/emu10k1/emuproc.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 608ff4857d70..b45a01bb73e5 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -170,7 +170,7 @@ static char *audigy_outs[32] = { /* 0x0f */ "Rear Right", /* 0x10 */ "AC97 Front Left", /* 0x11 */ "AC97 Front Right", - /* 0x12 */ "ADC Caputre Left", + /* 0x12 */ "ADC Capture Left", /* 0x13 */ "ADC Capture Right", /* 0x14 */ NULL, /* 0x15 */ NULL, diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index bde0d1954f56..055227caa7ca 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -135,7 +135,7 @@ static void snd_emu10k1_proc_read(struct snd_info_entry *entry, /* 15 */ "Rear Right", /* 16 */ "AC97 Front Left", /* 17 */ "AC97 Front Right", - /* 18 */ "ADC Caputre Left", + /* 18 */ "ADC Capture Left", /* 19 */ "ADC Capture Right", /* 20 */ "???", /* 21 */ "???", -- cgit v1.2.3 From 11d42c81036324697d367600bfc16f6dd37636fd Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 17 May 2018 20:02:23 +0200 Subject: ALSA: emu10k1: Rate-limit error messages about page errors The error messages at sanity checks of memory pages tend to repeat too many times once when it hits, and without the rate limit, it may flood and become unreadable. Replace such messages with the *_ratelimited() variant. Bugzilla: http://bugzilla.opensuse.org/show_bug.cgi?id=1093027 Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/memory.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/memory.c b/sound/pci/emu10k1/memory.c index 5865f3b90b34..dbc7d8d0e1c4 100644 --- a/sound/pci/emu10k1/memory.c +++ b/sound/pci/emu10k1/memory.c @@ -248,13 +248,13 @@ __found_pages: static int is_valid_page(struct snd_emu10k1 *emu, dma_addr_t addr) { if (addr & ~emu->dma_mask) { - dev_err(emu->card->dev, + dev_err_ratelimited(emu->card->dev, "max memory size is 0x%lx (addr = 0x%lx)!!\n", emu->dma_mask, (unsigned long)addr); return 0; } if (addr & (EMUPAGESIZE-1)) { - dev_err(emu->card->dev, "page is not aligned\n"); + dev_err_ratelimited(emu->card->dev, "page is not aligned\n"); return 0; } return 1; @@ -345,7 +345,7 @@ snd_emu10k1_alloc_pages(struct snd_emu10k1 *emu, struct snd_pcm_substream *subst else addr = snd_pcm_sgbuf_get_addr(substream, ofs); if (! is_valid_page(emu, addr)) { - dev_err(emu->card->dev, + dev_err_ratelimited(emu->card->dev, "emu: failure page = %d\n", idx); mutex_unlock(&hdr->block_mutex); return NULL; -- cgit v1.2.3 From 6cd17ea70b26f66651c7afc123245e379dd14450 Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Fri, 18 May 2018 01:08:59 +0300 Subject: ALSA: usb: stream: fix potential memory leak during uac3 interface parsing UAC3 channel map is created during interface parsing, and in some cases was not freed in failure paths. Reported-by: Dan Carpenter Signed-off-by: Ruslan Bilovol Signed-off-by: Takashi Iwai --- sound/usb/stream.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/usb/stream.c b/sound/usb/stream.c index bce315240955..d16e1c23f4e9 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -982,13 +982,16 @@ snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip, dev_err(&dev->dev, "%u:%d : bogus bTerminalLink %d\n", iface_no, altno, as->bTerminalLink); + kfree(chmap); return NULL; found_clock: fp = audio_format_alloc_init(chip, alts, UAC_VERSION_3, iface_no, altset_idx, altno, num_channels, clock); - if (!fp) + if (!fp) { + kfree(chmap); return ERR_PTR(-ENOMEM); + } fp->chmap = chmap; @@ -1009,6 +1012,7 @@ found_clock: iface_no); /* ok, let's parse further... */ if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) { + kfree(fp->chmap); kfree(fp->rate_table); kfree(fp); return NULL; -- cgit v1.2.3 From fdcb5761c1580b03fb1ab8625eaa7db03fb8b7d0 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 16 May 2018 23:45:33 +0200 Subject: ALSA: timer: Simplify timer hw resolution calls There multiple open-codes to get the hardware timer resolution. Make a local helper function snd_timer_hw_resolution() and call it from all relevant places. There is no functional change by this, just a preliminary work for the following timer resolution hardening patch. Signed-off-by: Takashi Iwai --- sound/core/timer.c | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) (limited to 'sound') diff --git a/sound/core/timer.c b/sound/core/timer.c index 0ddcae495838..22c72857f379 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -427,6 +427,14 @@ int snd_timer_close(struct snd_timer_instance *timeri) } EXPORT_SYMBOL(snd_timer_close); +static unsigned long snd_timer_hw_resolution(struct snd_timer *timer) +{ + if (timer->hw.c_resolution) + return timer->hw.c_resolution(timer); + else + return timer->hw.resolution; +} + unsigned long snd_timer_resolution(struct snd_timer_instance *timeri) { struct snd_timer * timer; @@ -434,11 +442,8 @@ unsigned long snd_timer_resolution(struct snd_timer_instance *timeri) if (timeri == NULL) return 0; timer = timeri->timer; - if (timer) { - if (timer->hw.c_resolution) - return timer->hw.c_resolution(timer); - return timer->hw.resolution; - } + if (timer) + return snd_timer_hw_resolution(timer); return 0; } EXPORT_SYMBOL(snd_timer_resolution); @@ -771,10 +776,7 @@ void snd_timer_interrupt(struct snd_timer * timer, unsigned long ticks_left) spin_lock_irqsave(&timer->lock, flags); /* remember the current resolution */ - if (timer->hw.c_resolution) - resolution = timer->hw.c_resolution(timer); - else - resolution = timer->hw.resolution; + resolution = snd_timer_hw_resolution(timer); /* loop for all active instances * Here we cannot use list_for_each_entry because the active_list of a @@ -1014,12 +1016,8 @@ void snd_timer_notify(struct snd_timer *timer, int event, struct timespec *tstam spin_lock_irqsave(&timer->lock, flags); if (event == SNDRV_TIMER_EVENT_MSTART || event == SNDRV_TIMER_EVENT_MCONTINUE || - event == SNDRV_TIMER_EVENT_MRESUME) { - if (timer->hw.c_resolution) - resolution = timer->hw.c_resolution(timer); - else - resolution = timer->hw.resolution; - } + event == SNDRV_TIMER_EVENT_MRESUME) + resolution = snd_timer_hw_resolution(timer); list_for_each_entry(ti, &timer->active_list_head, active_list) { if (ti->ccallback) ti->ccallback(ti, event, tstamp, resolution); @@ -1656,10 +1654,7 @@ static int snd_timer_user_gstatus(struct file *file, mutex_lock(®ister_mutex); t = snd_timer_find(&tid); if (t != NULL) { - if (t->hw.c_resolution) - gstatus.resolution = t->hw.c_resolution(t); - else - gstatus.resolution = t->hw.resolution; + gstatus.resolution = snd_timer_hw_resolution(t); if (t->hw.precise_resolution) { t->hw.precise_resolution(t, &gstatus.resolution_num, &gstatus.resolution_den); -- cgit v1.2.3 From 21244e3d6a9d36f32a2aa40f8948324c7b5f35b0 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 17 May 2018 10:43:16 +0200 Subject: ALSA: seq: Avoid open-code for getting timer resolution Instead of open-coding for getting the timer resolution, use the standard snd_timer_resolution() helper. The original code falls back to the callback function when the resolution is zero, but it must be always so when the callback function is defined. So this should be no functional change. Signed-off-by: Takashi Iwai --- sound/core/seq/seq_timer.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/core/seq/seq_timer.c b/sound/core/seq/seq_timer.c index 23167578231f..f587d0e27476 100644 --- a/sound/core/seq/seq_timer.c +++ b/sound/core/seq/seq_timer.c @@ -371,9 +371,7 @@ static int initialize_timer(struct snd_seq_timer *tmr) tmr->ticks = 1; if (!(t->hw.flags & SNDRV_TIMER_HW_SLAVE)) { - unsigned long r = t->hw.resolution; - if (! r && t->hw.c_resolution) - r = t->hw.c_resolution(t); + unsigned long r = snd_timer_resolution(tmr->timeri); if (r) { tmr->ticks = (unsigned int)(1000000000uL / (r * freq)); if (! tmr->ticks) -- cgit v1.2.3 From 9d4d207d1346329c5295420254f1dbef1a6ab6ba Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 16 May 2018 23:52:42 +0200 Subject: ALSA: timer: Assure timer resolution access always locked There are still many places calling the timer's hw.c_resolution callback without lock, and this may lead to some races, as we faced in the commit a820ccbe21e8 ("ALSA: pcm: Fix UAF at PCM release via PCM timer access"). This patch changes snd_timer_resolution() to take the timer->lock for avoiding the races. A place calling this function already inside the lock (from the notifier) is replaced with the snd_timer_hw_resolution() accordingly, as well as wrapping with the lock around another place calling snd_timer_hw_resolution(), too. Reported-by: Ben Hutchings Signed-off-by: Takashi Iwai --- sound/core/timer.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/core/timer.c b/sound/core/timer.c index 22c72857f379..665089c45560 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -438,19 +438,24 @@ static unsigned long snd_timer_hw_resolution(struct snd_timer *timer) unsigned long snd_timer_resolution(struct snd_timer_instance *timeri) { struct snd_timer * timer; + unsigned long ret = 0; + unsigned long flags; if (timeri == NULL) return 0; timer = timeri->timer; - if (timer) - return snd_timer_hw_resolution(timer); - return 0; + if (timer) { + spin_lock_irqsave(&timer->lock, flags); + ret = snd_timer_hw_resolution(timer); + spin_unlock_irqrestore(&timer->lock, flags); + } + return ret; } EXPORT_SYMBOL(snd_timer_resolution); static void snd_timer_notify1(struct snd_timer_instance *ti, int event) { - struct snd_timer *timer; + struct snd_timer *timer = ti->timer; unsigned long resolution = 0; struct snd_timer_instance *ts; struct timespec tstamp; @@ -462,14 +467,14 @@ static void snd_timer_notify1(struct snd_timer_instance *ti, int event) if (snd_BUG_ON(event < SNDRV_TIMER_EVENT_START || event > SNDRV_TIMER_EVENT_PAUSE)) return; - if (event == SNDRV_TIMER_EVENT_START || - event == SNDRV_TIMER_EVENT_CONTINUE) - resolution = snd_timer_resolution(ti); + if (timer && + (event == SNDRV_TIMER_EVENT_START || + event == SNDRV_TIMER_EVENT_CONTINUE)) + resolution = snd_timer_hw_resolution(timer); if (ti->ccallback) ti->ccallback(ti, event, &tstamp, resolution); if (ti->flags & SNDRV_TIMER_IFLG_SLAVE) return; - timer = ti->timer; if (timer == NULL) return; if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE) @@ -1654,6 +1659,7 @@ static int snd_timer_user_gstatus(struct file *file, mutex_lock(®ister_mutex); t = snd_timer_find(&tid); if (t != NULL) { + spin_lock_irq(&t->lock); gstatus.resolution = snd_timer_hw_resolution(t); if (t->hw.precise_resolution) { t->hw.precise_resolution(t, &gstatus.resolution_num, @@ -1662,6 +1668,7 @@ static int snd_timer_user_gstatus(struct file *file, gstatus.resolution_num = gstatus.resolution; gstatus.resolution_den = 1000000000uL; } + spin_unlock_irq(&t->lock); } else { err = -ENODEV; } -- cgit v1.2.3 From 7c1543f6b57fa9c0e202c4b5a3cb5ffbb63dc9d0 Mon Sep 17 00:00:00 2001 From: Melvin Vermeeren Date: Thu, 17 May 2018 21:00:00 +0200 Subject: ALSA: dice: add stream format parameters for Mytek devices --nextPart3916812.EicPReet6m Content-Transfer-Encoding: 7Bit Content-Type: text/plain; charset="us-ascii" Mytek manufactures some equipment with DICE-based firewire ports. These devices contain old versions of DICE firmware which lacks detailed stream format reporting for all sampling clock modes. Building upon the recent work by Takashi Sakamoto, hard-coded parameters are added for the Stereo 192 DSD-DAC. When the device vendor and model match the coded parameters are copied into the stream format cache. Signed-off-by: Melvin Vermeeren Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/Makefile | 2 +- sound/firewire/dice/dice-mytek.c | 46 ++++++++++++++++++++++++++++++++++++++++ sound/firewire/dice/dice.c | 9 ++++++++ sound/firewire/dice/dice.h | 1 + 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/dice/dice-mytek.c (limited to 'sound') diff --git a/sound/firewire/dice/Makefile b/sound/firewire/dice/Makefile index 7b7997a5754c..37062a233f6a 100644 --- a/sound/firewire/dice/Makefile +++ b/sound/firewire/dice/Makefile @@ -1,4 +1,4 @@ snd-dice-objs := dice-transaction.o dice-stream.o dice-proc.o dice-midi.o \ dice-pcm.o dice-hwdep.o dice.o dice-tcelectronic.o \ - dice-alesis.o dice-extension.o + dice-alesis.o dice-extension.o dice-mytek.o obj-$(CONFIG_SND_DICE) += snd-dice.o diff --git a/sound/firewire/dice/dice-mytek.c b/sound/firewire/dice/dice-mytek.c new file mode 100644 index 000000000000..eb7d5492d10b --- /dev/null +++ b/sound/firewire/dice/dice-mytek.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dice-mytek.c - a part of driver for DICE based devices + * + * Copyright (c) 2018 Melvin Vermeeren + */ + +#include "dice.h" + +struct dice_mytek_spec { + unsigned int tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + unsigned int rx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; +}; + +static const struct dice_mytek_spec stereo_192_dsd_dac = { + /* AES, TOSLINK, SPDIF, ADAT inputs on device */ + .tx_pcm_chs = {{8, 8, 8}, {0, 0, 0} }, + /* PCM 44.1-192, native DSD64/DSD128 to device */ + .rx_pcm_chs = {{4, 4, 4}, {0, 0, 0} } +}; + +/* + * Mytek has a few other firewire-capable devices, though newer models appear + * to lack the port more often than not. As I don't have access to any of them + * they are missing here. An example is the Mytek 8x192 ADDA, which is DICE. + */ + +int snd_dice_detect_mytek_formats(struct snd_dice *dice) +{ + int i; + const struct dice_mytek_spec *dev; + + dev = &stereo_192_dsd_dac; + + memcpy(dice->tx_pcm_chs, dev->tx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + memcpy(dice->rx_pcm_chs, dev->rx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + + for (i = 0; i < MAX_STREAMS; ++i) { + dice->tx_midi_ports[i] = 0; + dice->rx_midi_ports[i] = 0; + } + + return 0; +} diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c index 40f7a32e4893..beeef62581ba 100644 --- a/sound/firewire/dice/dice.c +++ b/sound/firewire/dice/dice.c @@ -17,6 +17,7 @@ MODULE_LICENSE("GPL v2"); #define OUI_TCELECTRONIC 0x000166 #define OUI_ALESIS 0x000595 #define OUI_MAUDIO 0x000d6c +#define OUI_MYTEK 0x001ee8 #define DICE_CATEGORY_ID 0x04 #define WEISS_CATEGORY_ID 0x00 @@ -365,6 +366,14 @@ static const struct ieee1394_device_id dice_id_table[] = { .model_id = MODEL_ALESIS_IO_BOTH, .driver_data = (kernel_ulong_t)snd_dice_detect_alesis_formats, }, + /* Mytek Stereo 192 DSD-DAC. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_MYTEK, + .model_id = 0x000002, + .driver_data = (kernel_ulong_t)snd_dice_detect_mytek_formats, + }, { .match_flags = IEEE1394_MATCH_VERSION, .version = DICE_INTERFACE, diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index 505b79fea6d9..83353a3559e8 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -226,5 +226,6 @@ int snd_dice_create_midi(struct snd_dice *dice); int snd_dice_detect_tcelectronic_formats(struct snd_dice *dice); int snd_dice_detect_alesis_formats(struct snd_dice *dice); int snd_dice_detect_extension_formats(struct snd_dice *dice); +int snd_dice_detect_mytek_formats(struct snd_dice *dice); #endif -- cgit v1.2.3 From ed1812c44cdc04fc56c5e7f7fdcd31941ffce58d Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 18 May 2018 12:16:07 +0300 Subject: ALSA: dice: fix a bounds check in snd_dice_detect_tcelectronic_formats() The "entry" pointer is always non-NULL so this test for out of bounds won't work. Fixes: f1f0f330b1d0 ("ALSA: dice: add parameters of stream formats for models produced by TC Electronic") Signed-off-by: Dan Carpenter Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-tcelectronic.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/firewire/dice/dice-tcelectronic.c b/sound/firewire/dice/dice-tcelectronic.c index af8203b9d1a6..e134a5110c6c 100644 --- a/sound/firewire/dice/dice-tcelectronic.c +++ b/sound/firewire/dice/dice-tcelectronic.c @@ -75,13 +75,12 @@ int snd_dice_detect_tcelectronic_formats(struct snd_dice *dice) } } - entry = NULL; for (i = 0; i < ARRAY_SIZE(entries); ++i) { entry = entries + i; if (entry->model_id == model_id) break; } - if (!entry) + if (i == ARRAY_SIZE(entries)) return -ENODEV; memcpy(dice->tx_pcm_chs, entry->spec->tx_pcm_chs, -- cgit v1.2.3 From d0aa5909625e2366f4b31762ad518a9690873c6f Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 20 May 2018 14:40:44 +0900 Subject: ALSA: dice: add stream format parameters for TC Electronic Digital Konnekt x32 TC Electronic Digital Konnekt x32 is an application of WaveFront DiceII STD and doesn't support TCAT extended application protocol. For such devices, ALSA dice driver needs to have hard-coded parameters for stream formats. This commit adds stream format parameters for this model. Unfortunately, at sampling transmission frequencies of 88.2/96.0kHz, I confirmed that current ALSA dice driver doesn't drive the device appropriately due to detecting packet discontinuities. $ journalctl kernel: snd_dice fw1.0: Detect discontinuity of CIP: 90 80 At the frequencies, the device transfers 16 data blocks per packet and 16 data channels per data block, as a result one packet includes 1032 bytes if it's not NODATA. However, as long as I checked, the device often postpone packet transmission and continue with truncated payload than metadata in isochronous packet header. Below is a sample of sequence I got. sec cycle bytes CIP1 CIP2 37 3314 1032 0x01100090 0x900449E2 37 3315 8 0x011000A0 0x9004FFFF 37 3316 1032 0x011000A0 0x900461E2 37 3317 1032 0x011000B0 0x900475E2 37 3318 1032 0x011000C0 0x900489E2 37 3319 8 0x011000D0 0x9004FFFF 37 3320 1032 0x011000D0 0x9004A1E2 37 3321 1032 0x011000E0 0x9004B5E2 37 3322 1032 0x011000F0 0x9004C9E2 37 3323 8 0x01100000 0x9004FFFF 37 3324 1032 0x01100000 0x9004E1E2 37 3325 1032 0x01100010 0x9004F5E2 37 3326 1032 0x01100020 0x900409E2 37 3327 8 0x01100030 0x9004FFFF 37 3328 1032 0x01100030 0x900421E2 37 3329 1032 0x01100040 0x900435E2 37 3330 (skip) 37 3331 (skip) 37 3332 (skip) 37 3333 (skip) 37 3334 (skip) 37 3335 (skip) 37 3336 (skip) 37 3337 (skip) 37 3338 (skip) 37 3339 (skip) 37 3340 (skip) 37 3341 (skip) 37 3342 (skip) 37 3343 (skip) 37 3344 (skip) 37 3345 (skip) 37 3346 (skip) 37 3347 (skip) 37 3348 (skip) 37 3349 (skip) 37 3350 (skip) 37 3351 (skip) 37 3352 (skip) 37 3353 (skip) 37 3354 (skip) 37 3355 (skip) 37 3356 (skip) 37 3357 (skip) 37 3358 (skip) 37 3359 (skip) 37 3360 (skip) 37 3361 (skip) 37 3362 (skip) 37 3363 (skip) 37 3364 (skip) 37 3365 (skip) 37 3366 (skip) 37 3367 1032 0x01100050 0x900461E1 37 3368 1032 0x01100060 0x900475E1 37 3369 1032 0x01100070 0x9004A1E1 37 3370 1032 0x01100080 0x9004A1E1 but content of payload is truncated. 37 3371 (skip) 37 3371 1032 0x01100080 0x9004B5E0 detect discontinuity 37 3372 1032 0x01100090 0x9004C9E0 37 3373 1032 0x011000A0 0x9004E1E0 37 3374 1032 0x011000B0 0x9004F5E0 37 3375 1032 0x011000C0 0x900409E0 37 3376 1032 0x011000D0 0x900421E0 37 3377 1032 0x011000E0 0x900435E0 37 3378 1032 0x011000F0 0x900449DF 37 3379 8 0x01100000 0x9004FFFF 37 3380 1032 0x01100000 0x900461DF 37 3381 1032 0x01100010 0x900475DF 37 3382 1032 0x01100020 0x900489DF 37 3383 8 0x01100030 0x9004FFFF 37 3384 1032 0x01100030 0x9004A1DF 37 3385 1032 0x01100040 0x9004B5DF 37 3386 1032 0x01100050 0x9004C9DF 37 3387 8 0x01100060 0x9004FFFF I cannot confirm this quirks with Windows driver. ALSA dice driver has a cause if assumed differences between these two drivers are ways of timestampling to RX packets from the drivers to the device. I've already reported timestamping quirk of Dice-based devices and this might bring this issue. [alsa-devel] Dice packet sequence quirk and ALSA firewire stack in Linux 4.6 http://mailman.alsa-project.org/pipermail/alsa-devel/2016-May/107715.html Well, nevertheless, I enable ALSA dice driver to work at the frequencies. This may brings inconvenience to users but I expect developers and users to fix it. $ cd linux-firewire-utils/src $ python2 crpp < /sys/bus/firewire/devices/fw1/config_rom ROM header and bus information block ----------------------------------------------------------------- 400 040423bb bus_info_length 4, crc_length 4, crc 9147 404 31333934 bus_name "1394" 408 e0ff8112 irmc 1, cmc 1, isc 1, bmc 0, pmc 0, cyc_clk_acc 255, max_rec 8 (512), max_rom 1, gen 1, spd 2 (S400) 40c 00016604 company_id 000166 | 410 0c232c28 device_id 040c232c28 | EUI-64 000166040c232c28 root directory ----------------------------------------------------------------- 414 0006b6cb directory_length 6, crc 46795 418 03000166 vendor 41c 8100000a --> descriptor leaf at 444 420 17000030 model 424 8100000f --> descriptor leaf at 460 428 0c0087c0 node capabilities per IEEE 1394 42c d1000001 --> unit directory at 430 unit directory at 430 ----------------------------------------------------------------- 430 000476c2 directory_length 4, crc 30402 434 12000166 specifier id 438 13000001 version 43c 17000030 model 440 81000010 --> descriptor leaf at 480 descriptor leaf at 444 ----------------------------------------------------------------- 444 0006c490 leaf_length 6, crc 50320 448 00000000 textual descriptor 44c 00000000 minimal ASCII 450 54432045 "TC E" 454 6c656374 "lect" 458 726f6e69 "roni" 45c 63000000 "c" descriptor leaf at 460 ----------------------------------------------------------------- 460 000772b4 leaf_length 7, crc 29364 464 00000000 textual descriptor 468 00000000 minimal ASCII 46c 44696769 "Digi" 470 74616c4b "talK" 474 6f6e6e65 "onne" 478 6b747833 "ktx3" 47c 32000000 "2" descriptor leaf at 480 ----------------------------------------------------------------- 480 000772b4 leaf_length 7, crc 29364 484 00000000 textual descriptor 488 00000000 minimal ASCII 48c 44696769 "Digi" 490 74616c4b "talK" 494 6f6e6e65 "onne" 498 6b747833 "ktx3" 49c 32000000 "2" Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-tcelectronic.c | 7 +++++++ sound/firewire/dice/dice.c | 8 ++++++++ 2 files changed, 15 insertions(+) (limited to 'sound') diff --git a/sound/firewire/dice/dice-tcelectronic.c b/sound/firewire/dice/dice-tcelectronic.c index e134a5110c6c..f9e0072deb81 100644 --- a/sound/firewire/dice/dice-tcelectronic.c +++ b/sound/firewire/dice/dice-tcelectronic.c @@ -49,6 +49,12 @@ static const struct dice_tc_spec studio_konnekt_48 = { .has_midi = true, }; +static const struct dice_tc_spec digital_konnekt_x32 = { + .tx_pcm_chs = {{16, 16, 4}, {0, 0, 0} }, + .rx_pcm_chs = {{16, 16, 4}, {0, 0, 0} }, + .has_midi = false, +}; + int snd_dice_detect_tcelectronic_formats(struct snd_dice *dice) { static const struct { @@ -61,6 +67,7 @@ int snd_dice_detect_tcelectronic_formats(struct snd_dice *dice) {0x00000023, &konnekt_live}, {0x00000024, &desktop_konnekt6}, {0x00000027, &impact_twin}, + {0x00000030, &digital_konnekt_x32}, }; struct fw_csr_iterator it; int key, val, model_id; diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c index beeef62581ba..774eb2205668 100644 --- a/sound/firewire/dice/dice.c +++ b/sound/firewire/dice/dice.c @@ -358,6 +358,14 @@ static const struct ieee1394_device_id dice_id_table[] = { .model_id = 0x000027, .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, }, + /* TC Electronic Digital Konnekt x32. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000030, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, /* Alesis iO14/iO26. */ { .match_flags = IEEE1394_MATCH_VENDOR_ID | -- cgit v1.2.3 From 2cc47d31ebf967cfca74045ac8fc9bb742f17ab3 Mon Sep 17 00:00:00 2001 From: Yisheng Xie Date: Mon, 21 May 2018 19:58:06 +0800 Subject: ALSA: oxfw: use match_string() helper match_string() returns the index of an array for a matching string, which can be used intead of open coded variant. Signed-off-by: Yisheng Xie Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/oxfw/oxfw.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index 413ab6313bb6..1e5b2c802635 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -49,7 +49,6 @@ static bool detect_loud_models(struct fw_unit *unit) "Tapco LINK.firewire 4x6", "U.420"}; char model[32]; - unsigned int i; int err; err = fw_csr_string(unit->directory, CSR_MODEL, @@ -57,12 +56,7 @@ static bool detect_loud_models(struct fw_unit *unit) if (err < 0) return false; - for (i = 0; i < ARRAY_SIZE(models); i++) { - if (strcmp(models[i], model) == 0) - break; - } - - return (i < ARRAY_SIZE(models)); + return match_string(models, ARRAY_SIZE(models), model) >= 0; } static int name_card(struct snd_oxfw *oxfw) -- cgit v1.2.3 From f16041df4c360eccacfe90f96673b37829e4c959 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 18 May 2018 12:14:32 +0200 Subject: ALSA: hda/conexant - Add fixup for HP Z2 G4 workstation HP Z2 G4 requires the same workaround as other HP machines that have no mic-pin detection. Cc: Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_conexant.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 093d2a9ece85..dad7e9d5e5a6 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -964,6 +964,7 @@ static const struct snd_pci_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x103c, 0x822e, "HP ProBook 440 G4", CXT_FIXUP_MUTE_LED_GPIO), SND_PCI_QUIRK(0x103c, 0x8299, "HP 800 G3 SFF", CXT_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x829a, "HP 800 G3 DM", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x8455, "HP Z2 G4", CXT_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x138d, "Asus", CXT_FIXUP_HEADPHONE_MIC_PIN), SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT_FIXUP_OLPC_XO), SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo T400", CXT_PINCFG_LENOVO_TP410), -- cgit v1.2.3 From 09b83d107d8ac70ff8b0c368182e36a6e06b8cf4 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 18 May 2018 12:18:59 +0200 Subject: ALSA: hda/conexant - Add hp-mic-fix model string Add "hp-mic-fix" model string for Conexant codecs so that user can test the quirk without recompiling. Signed-off-by: Takashi Iwai --- Documentation/sound/hd-audio/models.rst | 2 ++ sound/pci/hda/patch_conexant.c | 1 + 2 files changed, 3 insertions(+) (limited to 'sound') diff --git a/Documentation/sound/hd-audio/models.rst b/Documentation/sound/hd-audio/models.rst index 1fee5a4f6660..7c2d37571af0 100644 --- a/Documentation/sound/hd-audio/models.rst +++ b/Documentation/sound/hd-audio/models.rst @@ -263,6 +263,8 @@ hp-dock HP dock support mute-led-gpio Mute LED control via GPIO +hp-mic-fix + Fix for headset mic pin on HP boxes STAC9200 ======== diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index dad7e9d5e5a6..dbf9910c5269 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -998,6 +998,7 @@ static const struct hda_model_fixup cxt5066_fixup_models[] = { { .id = CXT_FIXUP_MUTE_LED_EAPD, .name = "mute-led-eapd" }, { .id = CXT_FIXUP_HP_DOCK, .name = "hp-dock" }, { .id = CXT_FIXUP_MUTE_LED_GPIO, .name = "mute-led-gpio" }, + { .id = CXT_FIXUP_HP_MIC_NO_PRESENCE, .name = "hp-mic-fix" }, {} }; -- cgit v1.2.3 From dd6dd5365404a31292715e6f54184f47f9b6aca5 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 23 May 2018 15:27:02 +0200 Subject: ALSA: hda: Add Intel NUC7i3BNB to the power_save blacklist Power-saving is causing a humming sound when active on the Intel NUC7i3BNB, add it to the blacklist. BugLink: https://bugzilla.redhat.com/show_bug.cgi?id=1520902 Signed-off-by: Hans de Goede Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index a0c93b9c9a28..aa21609901c4 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -2210,6 +2210,8 @@ static struct snd_pci_quirk power_save_blacklist[] = { SND_PCI_QUIRK(0x1849, 0x0c0c, "Asrock B85M-ITX", 0), /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ SND_PCI_QUIRK(0x1043, 0x8733, "Asus Prime X370-Pro", 0), + /* https://bugzilla.redhat.com/show_bug.cgi?id=1520902 */ + SND_PCI_QUIRK(0x8086, 0x2068, "Intel NUC7i3BNB", 0), /* https://bugzilla.redhat.com/show_bug.cgi?id=1572975 */ SND_PCI_QUIRK(0x17aa, 0x36a7, "Lenovo C50 All in one", 0), /* https://bugzilla.kernel.org/show_bug.cgi?id=198611 */ -- cgit v1.2.3 From b529ef2464ad3c9fcfa260c98d258b51d61418f0 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 23 May 2018 15:27:03 +0200 Subject: ALSA: hda: Add Clevo W35xSS_370SS to the power_save blacklist Power-saving is causing a plop and silences the first 2 seconds (give or take) of audio, silencing notifications sounds on Medion / Clevo W35xSS_370SS laptops. Add the Clevo W35xSS_370SS to the power_save blacklist. BugLink: https://bugzilla.redhat.com/show_bug.cgi?id=1581607 Signed-off-by: Hans de Goede Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index aa21609901c4..3fa550849345 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -2210,6 +2210,8 @@ static struct snd_pci_quirk power_save_blacklist[] = { SND_PCI_QUIRK(0x1849, 0x0c0c, "Asrock B85M-ITX", 0), /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ SND_PCI_QUIRK(0x1043, 0x8733, "Asus Prime X370-Pro", 0), + /* https://bugzilla.redhat.com/show_bug.cgi?id=1581607 */ + SND_PCI_QUIRK(0x1558, 0x3501, "Clevo W35xSS_370SS", 0), /* https://bugzilla.redhat.com/show_bug.cgi?id=1520902 */ SND_PCI_QUIRK(0x8086, 0x2068, "Intel NUC7i3BNB", 0), /* https://bugzilla.redhat.com/show_bug.cgi?id=1572975 */ -- cgit v1.2.3 From 38d9c12c0a6d41a82fb6d1278d430bbf35301ce5 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 23 May 2018 15:27:04 +0200 Subject: ALSA: hda: Add Gigabyte P55A-UD3 and Z87-D3HP to the power_save blacklist Power-saving is causing plops on audio start/stop on Gigabyte P55A-UD3 and Gigabyte Z87-D3HP machines, add these to the power_save blacklist. Note these 2 boards both use 1458:a002 as subsystem ids, so they share a single entry. BugLink: https://bugzilla.redhat.com/show_bug.cgi?id=1525104 Signed-off-by: Hans de Goede Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 3fa550849345..122f130fa9e0 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -2212,6 +2212,9 @@ static struct snd_pci_quirk power_save_blacklist[] = { SND_PCI_QUIRK(0x1043, 0x8733, "Asus Prime X370-Pro", 0), /* https://bugzilla.redhat.com/show_bug.cgi?id=1581607 */ SND_PCI_QUIRK(0x1558, 0x3501, "Clevo W35xSS_370SS", 0), + /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ + /* Note the P55A-UD3 and Z87-D3HP share the subsys id for the HDA dev */ + SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte P55A-UD3 / Z87-D3HP", 0), /* https://bugzilla.redhat.com/show_bug.cgi?id=1520902 */ SND_PCI_QUIRK(0x8086, 0x2068, "Intel NUC7i3BNB", 0), /* https://bugzilla.redhat.com/show_bug.cgi?id=1572975 */ -- cgit v1.2.3 From 45e5fbc27387eb7c6b2fa300cedf79106e6f84c3 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 23 May 2018 15:27:05 +0200 Subject: ALSA: hda: Add ASRock H81M-HDS to the power_save blacklist Power-saving is causing plops on audio start/stop on ASRock H81M-HDS machines, add these to the power_save blacklist. BugLink: https://bugzilla.redhat.com/show_bug.cgi?id=1525104 Signed-off-by: Hans de Goede Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 122f130fa9e0..40d50118f9f6 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -2209,6 +2209,8 @@ static struct snd_pci_quirk power_save_blacklist[] = { /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ SND_PCI_QUIRK(0x1849, 0x0c0c, "Asrock B85M-ITX", 0), /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ + SND_PCI_QUIRK(0x1849, 0x7662, "Asrock H81M-HDS", 0), + /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ SND_PCI_QUIRK(0x1043, 0x8733, "Asus Prime X370-Pro", 0), /* https://bugzilla.redhat.com/show_bug.cgi?id=1581607 */ SND_PCI_QUIRK(0x1558, 0x3501, "Clevo W35xSS_370SS", 0), -- cgit v1.2.3 From b6622f573ece9ddbf1d4c6808a6ad564f32b7b47 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 24 May 2018 11:15:45 +0200 Subject: ALSA: usb-audio: Drop superfluous ifndef Drop the superfluous #ifndef checks that had been put just for allowing building the alsa-driver kernel modules externally. Since the external build was discontinued years ago, let's clean up the old kludges. Signed-off-by: Takashi Iwai --- sound/usb/helper.h | 4 ---- 1 file changed, 4 deletions(-) (limited to 'sound') diff --git a/sound/usb/helper.h b/sound/usb/helper.h index 4463e6d6dcb3..d338bd0e0ca6 100644 --- a/sound/usb/helper.h +++ b/sound/usb/helper.h @@ -18,16 +18,12 @@ unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip, * retrieve usb_interface descriptor from the host interface * (conditional for compatibility with the older API) */ -#ifndef get_iface_desc #define get_iface_desc(iface) (&(iface)->desc) #define get_endpoint(alt,ep) (&(alt)->endpoint[ep].desc) #define get_ep_desc(ep) (&(ep)->desc) #define get_cfg_desc(cfg) (&(cfg)->desc) -#endif -#ifndef snd_usb_get_speed #define snd_usb_get_speed(dev) ((dev)->speed) -#endif static inline int snd_usb_ctrl_intf(struct snd_usb_audio *chip) { -- cgit v1.2.3 From afe5da3eba8796c259e5134872e844436e716f7e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 24 May 2018 11:20:06 +0200 Subject: ALSA: echoaudio: Drop superfluous macro Drop pci_device() macro that just leads to chip->pci->dev, and pass it directly to request_firmware(). It was introduced for allowing the external alsa-driver kernel module builds. Since it was discontinued years ago, we should clean it up now. Signed-off-by: Takashi Iwai --- sound/pci/echoaudio/echoaudio.c | 2 +- sound/pci/echoaudio/echoaudio.h | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c index 0935a5c8741f..358ef7dcf410 100644 --- a/sound/pci/echoaudio/echoaudio.c +++ b/sound/pci/echoaudio/echoaudio.c @@ -59,7 +59,7 @@ static int get_firmware(const struct firmware **fw_entry, dev_dbg(chip->card->dev, "firmware requested: %s\n", card_fw[fw_index].data); snprintf(name, sizeof(name), "ea/%s", card_fw[fw_index].data); - err = request_firmware(fw_entry, name, pci_device(chip)); + err = request_firmware(fw_entry, name, &chip->pci->dev); if (err < 0) dev_err(chip->card->dev, "get_firmware(): Firmware not available (%d)\n", err); diff --git a/sound/pci/echoaudio/echoaudio.h b/sound/pci/echoaudio/echoaudio.h index 152ce158583c..44b390a667d5 100644 --- a/sound/pci/echoaudio/echoaudio.h +++ b/sound/pci/echoaudio/echoaudio.h @@ -559,10 +559,4 @@ static inline int monitor_index(const struct echoaudio *chip, int out, int in) return out * num_busses_in(chip) + in; } - -#ifndef pci_device -#define pci_device(chip) (&chip->pci->dev) -#endif - - #endif /* _ECHOAUDIO_H_ */ -- cgit v1.2.3 From 6231a895f537959fecbc5d5f7558dabfa3b76d47 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sat, 26 May 2018 16:11:00 +0100 Subject: ALSA: seq: fix spelling mistake "Unamed" -> "Unnamed" Trivial fix to spelling mistake in string Signed-off-by: Colin Ian King Signed-off-by: Takashi Iwai --- sound/core/seq/seq_ports.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index d21ece9f8d73..24d90abfc64d 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -669,7 +669,7 @@ int snd_seq_event_port_attach(int client, /* Set up the port */ memset(&portinfo, 0, sizeof(portinfo)); portinfo.addr.client = client; - strlcpy(portinfo.name, portname ? portname : "Unamed port", + strlcpy(portinfo.name, portname ? portname : "Unnamed port", sizeof(portinfo.name)); portinfo.capability = cap; -- cgit v1.2.3 From 1ceb506d631f512f8e5b04821c21104b80c15dee Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 27 May 2018 10:13:29 +0900 Subject: ALSA: dice: fix stream format parameters for TC Electronic Studio Konnekt 48 TC Electronic Studio Konnekt 48 is an application of combination of WaveFront Dice II STD and TC Applied Technologies (TCAT) TCD2210 (Dice Mini). The latter is on a board with BNC and optical interfaces, thus used for signal processing for word clock, S/PDIF and ADAT. This model doesn't support TCAT extended application protocol. For such devices, ALSA dice driver needs to have hard-coded parameters for stream formats. This commit fixes stream format parameters for this model. Unfortunately, at sampling transmission frequencies over 48.0kHz, I confirmed that current ALSA dice driver doesn't drive the device appropriately to generate sounds (silence). I guess that this comes from timestamping quirk of Dice-based devices, which I reported. [alsa-devel] Dice packet sequence quirk and ALSA firewire stack in Linux 4.6 http://mailman.alsa-project.org/pipermail/alsa-devel/2016-May/107715.html $ cd linux-firewire-utils/src $ python2 crpp < /sys/bus/firewire/devices/fw1/config_rom ROM header and bus information block ----------------------------------------------------------------- 400 04044a26 bus_info_length 4, crc_length 4, crc 18982 404 31333934 bus_name "1394" 408 e0ff8112 irmc 1, cmc 1, isc 1, bmc 0, pmc 0, cyc_clk_acc 255, max_rec 8 (512), max_rom 1, gen 1, spd 2 (S400) 40c 00016604 company_id 000166 | 410 08a65810 device_id 0408a65810 | EUI-64 0001660408a65810 root directory ----------------------------------------------------------------- 414 00062ab9 directory_length 6, crc 10937 418 03000166 vendor 41c 8100000a --> descriptor leaf at 444 420 17000022 model 424 8100000f --> descriptor leaf at 460 428 0c0087c0 node capabilities per IEEE 1394 42c d1000001 --> unit directory at 430 unit directory at 430 ----------------------------------------------------------------- 430 0004d5c5 directory_length 4, crc 54725 434 12000166 specifier id 438 13000001 version 43c 17000022 model 440 8100000f --> descriptor leaf at 47c descriptor leaf at 444 ----------------------------------------------------------------- 444 0006c490 leaf_length 6, crc 50320 448 00000000 textual descriptor 44c 00000000 minimal ASCII 450 54432045 "TC E" 454 6c656374 "lect" 458 726f6e69 "roni" 45c 63000000 "c" descriptor leaf at 460 ----------------------------------------------------------------- 460 0006e08e leaf_length 6, crc 57486 464 00000000 textual descriptor 468 00000000 minimal ASCII 46c 53747564 "Stud" 470 696f4b6f "ioKo" 474 6e6e656b "nnek" 478 74343800 "t48" descriptor leaf at 47c ----------------------------------------------------------------- 47c 0006e08e leaf_length 6, crc 57486 480 00000000 textual descriptor 484 00000000 minimal ASCII 488 53747564 "Stud" 48c 696f4b6f "ioKo" 490 6e6e656b "nnek" 494 74343800 "t48" Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-tcelectronic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/firewire/dice/dice-tcelectronic.c b/sound/firewire/dice/dice-tcelectronic.c index f9e0072deb81..a4cbe2da8c15 100644 --- a/sound/firewire/dice/dice-tcelectronic.c +++ b/sound/firewire/dice/dice-tcelectronic.c @@ -44,8 +44,8 @@ static const struct dice_tc_spec konnekt_live = { }; static const struct dice_tc_spec studio_konnekt_48 = { - .tx_pcm_chs = {{16, 16, 16}, {16, 16, 0} }, - .rx_pcm_chs = {{16, 16, 16}, {16, 16, 0} }, + .tx_pcm_chs = {{16, 16, 8}, {16, 16, 7} }, + .rx_pcm_chs = {{16, 16, 8}, {14, 14, 7} }, .has_midi = true, }; -- cgit v1.2.3 From 84eaaef2ae196ccdac0154a9a9daac95b9b367fd Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 27 May 2018 10:13:30 +0900 Subject: ALSA: dice: unuse second stream for MIDI conformant data channel for TC Electronic models At present, all of models produced by TC Electronic except for Konnekt Live are supported with hard-coded their stream formats. Studio Konnekt 48 is sore model to support dual streams for both directions. The second stream has no MIDI conformant data channel in its data block. But current implementation transfers the second stream with MIDI conformant data channel. This commit fixes this issue. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice-tcelectronic.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/firewire/dice/dice-tcelectronic.c b/sound/firewire/dice/dice-tcelectronic.c index a4cbe2da8c15..a8875d24ba2a 100644 --- a/sound/firewire/dice/dice-tcelectronic.c +++ b/sound/firewire/dice/dice-tcelectronic.c @@ -95,11 +95,9 @@ int snd_dice_detect_tcelectronic_formats(struct snd_dice *dice) memcpy(dice->rx_pcm_chs, entry->spec->rx_pcm_chs, MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); - for (i = 0; i < MAX_STREAMS; ++i) { - if (entry->spec->has_midi) { - dice->tx_midi_ports[i] = 1; - dice->rx_midi_ports[i] = 1; - } + if (entry->spec->has_midi) { + dice->tx_midi_ports[0] = 1; + dice->rx_midi_ports[0] = 1; } return 0; -- cgit v1.2.3 From 6a73cf46ce9d1b382ea14d74ce4bc9aa0c52337a Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Wed, 23 May 2018 12:20:59 -0700 Subject: sound: Use octal not symbolic permissions Convert the S_ symbolic permissions to their octal equivalents as using octal and not symbolic permissions is preferred by many as more readable. see: https://lkml.org/lkml/2016/8/2/1945 Done with automated conversion via: $ ./scripts/checkpatch.pl -f --types=SYMBOLIC_PERMS --fix-inplace Miscellanea: o Wrapped one multi-line call to a single line Signed-off-by: Joe Perches Acked-by: Vinod Koul Signed-off-by: Takashi Iwai --- sound/core/compress_offload.c | 2 +- sound/core/info.c | 6 +++--- sound/core/init.c | 4 ++-- sound/core/oss/mixer_oss.c | 2 +- sound/core/oss/pcm_oss.c | 2 +- sound/core/pcm.c | 10 +++++----- sound/core/pcm_memory.c | 2 +- sound/drivers/dummy.c | 2 +- sound/drivers/mts64.c | 6 +++--- sound/drivers/opl4/opl4_proc.c | 2 +- sound/drivers/portman2x4.c | 6 +++--- sound/firewire/bebob/bebob_proc.c | 2 +- sound/firewire/dice/dice-proc.c | 2 +- sound/firewire/digi00x/digi00x-proc.c | 2 +- sound/firewire/fireface/ff-proc.c | 2 +- sound/firewire/fireworks/fireworks_proc.c | 2 +- sound/firewire/motu/motu-proc.c | 2 +- sound/firewire/oxfw/oxfw-proc.c | 2 +- sound/firewire/tascam/tascam-proc.c | 2 +- sound/isa/msnd/msnd_pinnacle.c | 32 +++++++++++++++---------------- sound/pci/ac97/ac97_proc.c | 4 ++-- sound/pci/asihpi/asihpi.c | 12 ++++++------ sound/pci/asihpi/hpioctl.c | 4 ++-- sound/pci/ca0106/ca0106_proc.c | 6 +++--- sound/pci/cs46xx/cs46xx_lib.c | 2 +- sound/pci/cs46xx/dsp_spos.c | 14 +++++++------- sound/pci/cs46xx/dsp_spos_scb_lib.c | 2 +- sound/pci/ctxfi/cttimer.c | 2 +- sound/pci/ctxfi/xfi.c | 4 ++-- sound/pci/emu10k1/emu10k1x.c | 2 +- sound/pci/emu10k1/emuproc.c | 22 ++++++++++----------- sound/pci/hda/patch_hdmi.c | 2 +- sound/pci/ice1712/pontis.c | 2 +- sound/pci/ice1712/prodigy_hifi.c | 2 +- sound/pci/lola/lola_proc.c | 2 +- sound/pci/pcxhr/pcxhr.c | 2 +- sound/soc/codecs/cs43130.c | 8 ++++---- sound/soc/codecs/wm_adsp.c | 11 +++++------ sound/soc/fsl/fsl_ssi_dbg.c | 2 +- sound/sound_core.c | 6 +++--- sound/sparc/dbri.c | 2 +- 41 files changed, 102 insertions(+), 103 deletions(-) (limited to 'sound') diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 4563432badba..4b01a37c836e 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -1001,7 +1001,7 @@ static int snd_compress_proc_init(struct snd_compr *compr) compr->card->proc_root); if (!entry) return -ENOMEM; - entry->mode = S_IFDIR | S_IRUGO | S_IXUGO; + entry->mode = S_IFDIR | 0555; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); return -ENOMEM; diff --git a/sound/core/info.c b/sound/core/info.c index 4b36767af9e1..fe502bc5e6d2 100644 --- a/sound/core/info.c +++ b/sound/core/info.c @@ -454,7 +454,7 @@ static struct snd_info_entry *create_subdir(struct module *mod, entry = snd_info_create_module_entry(mod, name, NULL); if (!entry) return NULL; - entry->mode = S_IFDIR | S_IRUGO | S_IXUGO; + entry->mode = S_IFDIR | 0555; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); return NULL; @@ -470,7 +470,7 @@ int __init snd_info_init(void) snd_proc_root = snd_info_create_entry("asound", NULL); if (!snd_proc_root) return -ENOMEM; - snd_proc_root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + snd_proc_root->mode = S_IFDIR | 0555; snd_proc_root->p = proc_mkdir("asound", NULL); if (!snd_proc_root->p) goto error; @@ -716,7 +716,7 @@ snd_info_create_entry(const char *name, struct snd_info_entry *parent) kfree(entry); return NULL; } - entry->mode = S_IFREG | S_IRUGO; + entry->mode = S_IFREG | 0444; entry->content = SNDRV_INFO_CONTENT_TEXT; mutex_init(&entry->access); INIT_LIST_HEAD(&entry->children); diff --git a/sound/core/init.c b/sound/core/init.c index 79b4df1c1dc7..4849c611c0fe 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -703,7 +703,7 @@ card_id_store_attr(struct device *dev, struct device_attribute *attr, return count; } -static DEVICE_ATTR(id, S_IRUGO | S_IWUSR, card_id_show_attr, card_id_store_attr); +static DEVICE_ATTR(id, 0644, card_id_show_attr, card_id_store_attr); static ssize_t card_number_show_attr(struct device *dev, @@ -713,7 +713,7 @@ card_number_show_attr(struct device *dev, return scnprintf(buf, PAGE_SIZE, "%i\n", card->number); } -static DEVICE_ATTR(number, S_IRUGO, card_number_show_attr, NULL); +static DEVICE_ATTR(number, 0444, card_number_show_attr, NULL); static struct attribute *card_dev_attrs[] = { &dev_attr_id.attr, diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c index 379bf486ccc7..64d904bee8bb 100644 --- a/sound/core/oss/mixer_oss.c +++ b/sound/core/oss/mixer_oss.c @@ -1247,7 +1247,7 @@ static void snd_mixer_oss_proc_init(struct snd_mixer_oss *mixer) if (! entry) return; entry->content = SNDRV_INFO_CONTENT_TEXT; - entry->mode = S_IFREG | S_IRUGO | S_IWUSR; + entry->mode = S_IFREG | 0644; entry->c.text.read = snd_mixer_oss_proc_read; entry->c.text.write = snd_mixer_oss_proc_write; entry->private_data = mixer; diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index 1980f68246cb..905a53c1cde5 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -3045,7 +3045,7 @@ static void snd_pcm_oss_proc_init(struct snd_pcm *pcm) continue; if ((entry = snd_info_create_card_entry(pcm->card, "oss", pstr->proc_root)) != NULL) { entry->content = SNDRV_INFO_CONTENT_TEXT; - entry->mode = S_IFREG | S_IRUGO | S_IWUSR; + entry->mode = S_IFREG | 0644; entry->c.text.read = snd_pcm_oss_proc_read; entry->c.text.write = snd_pcm_oss_proc_write; entry->private_data = pstr; diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 66ac89aad681..c352bfb973cc 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -530,7 +530,7 @@ static int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr) pcm->card->proc_root); if (!entry) return -ENOMEM; - entry->mode = S_IFDIR | S_IRUGO | S_IXUGO; + entry->mode = S_IFDIR | 0555; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); return -ENOMEM; @@ -552,7 +552,7 @@ static int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr) if (entry) { entry->c.text.read = snd_pcm_xrun_debug_read; entry->c.text.write = snd_pcm_xrun_debug_write; - entry->mode |= S_IWUSR; + entry->mode |= 0200; entry->private_data = pstr; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); @@ -590,7 +590,7 @@ static int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream) substream->pstr->proc_root); if (!entry) return -ENOMEM; - entry->mode = S_IFDIR | S_IRUGO | S_IXUGO; + entry->mode = S_IFDIR | 0555; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); return -ENOMEM; @@ -647,7 +647,7 @@ static int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream) entry->private_data = substream; entry->c.text.read = NULL; entry->c.text.write = snd_pcm_xrun_injection_write; - entry->mode = S_IFREG | S_IWUSR; + entry->mode = S_IFREG | 0200; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); entry = NULL; @@ -1087,7 +1087,7 @@ static ssize_t show_pcm_class(struct device *dev, return snprintf(buf, PAGE_SIZE, "%s\n", str); } -static DEVICE_ATTR(pcm_class, S_IRUGO, show_pcm_class, NULL); +static DEVICE_ATTR(pcm_class, 0444, show_pcm_class, NULL); static struct attribute *pcm_dev_attrs[] = { &dev_attr_pcm_class.attr, NULL diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c index ae33e456708c..4b5356a10315 100644 --- a/sound/core/pcm_memory.c +++ b/sound/core/pcm_memory.c @@ -201,7 +201,7 @@ static inline void preallocate_info_init(struct snd_pcm_substream *substream) if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", substream->proc_root)) != NULL) { entry->c.text.read = snd_pcm_lib_preallocate_proc_read; entry->c.text.write = snd_pcm_lib_preallocate_proc_write; - entry->mode |= S_IWUSR; + entry->mode |= 0200; entry->private_data = substream; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index 8fb9a54fe8ba..9af154db530a 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -1042,7 +1042,7 @@ static void dummy_proc_init(struct snd_dummy *chip) if (!snd_card_proc_new(chip->card, "dummy_pcm", &entry)) { snd_info_set_text_ops(entry, chip, dummy_proc_read); entry->c.text.write = dummy_proc_write; - entry->mode |= S_IWUSR; + entry->mode |= 0200; entry->private_data = chip; } } diff --git a/sound/drivers/mts64.c b/sound/drivers/mts64.c index f32e81342247..b68e71ca7abd 100644 --- a/sound/drivers/mts64.c +++ b/sound/drivers/mts64.c @@ -41,11 +41,11 @@ static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; static struct platform_device *platform_devices[SNDRV_CARDS]; static int device_count; -module_param_array(index, int, NULL, S_IRUGO); +module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard."); -module_param_array(id, charp, NULL, S_IRUGO); +module_param_array(id, charp, NULL, 0444); MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard."); -module_param_array(enable, bool, NULL, S_IRUGO); +module_param_array(enable, bool, NULL, 0444); MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); MODULE_AUTHOR("Matthias Koenig "); diff --git a/sound/drivers/opl4/opl4_proc.c b/sound/drivers/opl4/opl4_proc.c index cd2c07fa2ef4..16b24091d799 100644 --- a/sound/drivers/opl4/opl4_proc.c +++ b/sound/drivers/opl4/opl4_proc.c @@ -104,7 +104,7 @@ int snd_opl4_create_proc(struct snd_opl4 *opl4) if (entry) { if (opl4->hardware < OPL3_HW_OPL4_ML) { /* OPL4 can access 4 MB external ROM/SRAM */ - entry->mode |= S_IWUSR; + entry->mode |= 0200; entry->size = 4 * 1024 * 1024; } else { /* OPL4-ML has 1 MB internal ROM */ diff --git a/sound/drivers/portman2x4.c b/sound/drivers/portman2x4.c index ec8a94325ef6..3cdf0a88d71b 100644 --- a/sound/drivers/portman2x4.c +++ b/sound/drivers/portman2x4.c @@ -60,11 +60,11 @@ static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; static struct platform_device *platform_devices[SNDRV_CARDS]; static int device_count; -module_param_array(index, int, NULL, S_IRUGO); +module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard."); -module_param_array(id, charp, NULL, S_IRUGO); +module_param_array(id, charp, NULL, 0444); MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard."); -module_param_array(enable, bool, NULL, S_IRUGO); +module_param_array(enable, bool, NULL, 0444); MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); MODULE_AUTHOR("Levent Guendogdu, Tobias Gehrig, Matthias Koenig"); diff --git a/sound/firewire/bebob/bebob_proc.c b/sound/firewire/bebob/bebob_proc.c index ec24f96794f5..8096891af913 100644 --- a/sound/firewire/bebob/bebob_proc.c +++ b/sound/firewire/bebob/bebob_proc.c @@ -183,7 +183,7 @@ void snd_bebob_proc_init(struct snd_bebob *bebob) bebob->card->proc_root); if (root == NULL) return; - root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + root->mode = S_IFDIR | 0555; if (snd_info_register(root) < 0) { snd_info_free_entry(root); return; diff --git a/sound/firewire/dice/dice-proc.c b/sound/firewire/dice/dice-proc.c index faf8c854d256..bb870fc73f99 100644 --- a/sound/firewire/dice/dice-proc.c +++ b/sound/firewire/dice/dice-proc.c @@ -305,7 +305,7 @@ void snd_dice_create_proc(struct snd_dice *dice) dice->card->proc_root); if (!root) return; - root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + root->mode = S_IFDIR | 0555; if (snd_info_register(root) < 0) { snd_info_free_entry(root); return; diff --git a/sound/firewire/digi00x/digi00x-proc.c b/sound/firewire/digi00x/digi00x-proc.c index a1d601f31165..6996d5a6ff5f 100644 --- a/sound/firewire/digi00x/digi00x-proc.c +++ b/sound/firewire/digi00x/digi00x-proc.c @@ -79,7 +79,7 @@ void snd_dg00x_proc_init(struct snd_dg00x *dg00x) if (root == NULL) return; - root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + root->mode = S_IFDIR | 0555; if (snd_info_register(root) < 0) { snd_info_free_entry(root); return; diff --git a/sound/firewire/fireface/ff-proc.c b/sound/firewire/fireface/ff-proc.c index 69441d121f71..40ccbfd8ef89 100644 --- a/sound/firewire/fireface/ff-proc.c +++ b/sound/firewire/fireface/ff-proc.c @@ -52,7 +52,7 @@ void snd_ff_proc_init(struct snd_ff *ff) ff->card->proc_root); if (root == NULL) return; - root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + root->mode = S_IFDIR | 0555; if (snd_info_register(root) < 0) { snd_info_free_entry(root); return; diff --git a/sound/firewire/fireworks/fireworks_proc.c b/sound/firewire/fireworks/fireworks_proc.c index 9c21f31b8b21..779ecec5af62 100644 --- a/sound/firewire/fireworks/fireworks_proc.c +++ b/sound/firewire/fireworks/fireworks_proc.c @@ -219,7 +219,7 @@ void snd_efw_proc_init(struct snd_efw *efw) efw->card->proc_root); if (root == NULL) return; - root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + root->mode = S_IFDIR | 0555; if (snd_info_register(root) < 0) { snd_info_free_entry(root); return; diff --git a/sound/firewire/motu/motu-proc.c b/sound/firewire/motu/motu-proc.c index 4edc064999ed..ab6830a6d242 100644 --- a/sound/firewire/motu/motu-proc.c +++ b/sound/firewire/motu/motu-proc.c @@ -107,7 +107,7 @@ void snd_motu_proc_init(struct snd_motu *motu) motu->card->proc_root); if (root == NULL) return; - root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + root->mode = S_IFDIR | 0555; if (snd_info_register(root) < 0) { snd_info_free_entry(root); return; diff --git a/sound/firewire/oxfw/oxfw-proc.c b/sound/firewire/oxfw/oxfw-proc.c index 8ba4f9f262b8..27dac071bc73 100644 --- a/sound/firewire/oxfw/oxfw-proc.c +++ b/sound/firewire/oxfw/oxfw-proc.c @@ -103,7 +103,7 @@ void snd_oxfw_proc_init(struct snd_oxfw *oxfw) oxfw->card->proc_root); if (root == NULL) return; - root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + root->mode = S_IFDIR | 0555; if (snd_info_register(root) < 0) { snd_info_free_entry(root); return; diff --git a/sound/firewire/tascam/tascam-proc.c b/sound/firewire/tascam/tascam-proc.c index bfd4a4c06914..fee3bf32a0da 100644 --- a/sound/firewire/tascam/tascam-proc.c +++ b/sound/firewire/tascam/tascam-proc.c @@ -78,7 +78,7 @@ void snd_tscm_proc_init(struct snd_tscm *tscm) tscm->card->proc_root); if (root == NULL) return; - root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + root->mode = S_IFDIR | 0555; if (snd_info_register(root) < 0) { snd_info_free_entry(root); return; diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c index 45e561c425bf..6c584d9b6c42 100644 --- a/sound/isa/msnd/msnd_pinnacle.c +++ b/sound/isa/msnd/msnd_pinnacle.c @@ -757,9 +757,9 @@ static int snd_msnd_pinnacle_cfg_reset(int cfg) static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ -module_param_array(index, int, NULL, S_IRUGO); +module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for msnd_pinnacle soundcard."); -module_param_array(id, charp, NULL, S_IRUGO); +module_param_array(id, charp, NULL, 0444); MODULE_PARM_DESC(id, "ID string for msnd_pinnacle soundcard."); static long io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; @@ -801,22 +801,22 @@ MODULE_LICENSE("GPL"); MODULE_FIRMWARE(INITCODEFILE); MODULE_FIRMWARE(PERMCODEFILE); -module_param_hw_array(io, long, ioport, NULL, S_IRUGO); +module_param_hw_array(io, long, ioport, NULL, 0444); MODULE_PARM_DESC(io, "IO port #"); -module_param_hw_array(irq, int, irq, NULL, S_IRUGO); -module_param_hw_array(mem, long, iomem, NULL, S_IRUGO); -module_param_array(write_ndelay, int, NULL, S_IRUGO); -module_param(calibrate_signal, int, S_IRUGO); +module_param_hw_array(irq, int, irq, NULL, 0444); +module_param_hw_array(mem, long, iomem, NULL, 0444); +module_param_array(write_ndelay, int, NULL, 0444); +module_param(calibrate_signal, int, 0444); #ifndef MSND_CLASSIC -module_param_array(digital, int, NULL, S_IRUGO); -module_param_hw_array(cfg, long, ioport, NULL, S_IRUGO); -module_param_array(reset, int, 0, S_IRUGO); -module_param_hw_array(mpu_io, long, ioport, NULL, S_IRUGO); -module_param_hw_array(mpu_irq, int, irq, NULL, S_IRUGO); -module_param_hw_array(ide_io0, long, ioport, NULL, S_IRUGO); -module_param_hw_array(ide_io1, long, ioport, NULL, S_IRUGO); -module_param_hw_array(ide_irq, int, irq, NULL, S_IRUGO); -module_param_hw_array(joystick_io, long, ioport, NULL, S_IRUGO); +module_param_array(digital, int, NULL, 0444); +module_param_hw_array(cfg, long, ioport, NULL, 0444); +module_param_array(reset, int, 0, 0444); +module_param_hw_array(mpu_io, long, ioport, NULL, 0444); +module_param_hw_array(mpu_irq, int, irq, NULL, 0444); +module_param_hw_array(ide_io0, long, ioport, NULL, 0444); +module_param_hw_array(ide_io1, long, ioport, NULL, 0444); +module_param_hw_array(ide_irq, int, irq, NULL, 0444); +module_param_hw_array(joystick_io, long, ioport, NULL, 0444); #endif diff --git a/sound/pci/ac97/ac97_proc.c b/sound/pci/ac97/ac97_proc.c index 6320bf084e47..e120a11c69e8 100644 --- a/sound/pci/ac97/ac97_proc.c +++ b/sound/pci/ac97/ac97_proc.c @@ -448,7 +448,7 @@ void snd_ac97_proc_init(struct snd_ac97 * ac97) if ((entry = snd_info_create_card_entry(ac97->bus->card, name, ac97->bus->proc)) != NULL) { snd_info_set_text_ops(entry, ac97, snd_ac97_proc_regs_read); #ifdef CONFIG_SND_DEBUG - entry->mode |= S_IWUSR; + entry->mode |= 0200; entry->c.text.write = snd_ac97_proc_regs_write; #endif if (snd_info_register(entry) < 0) { @@ -474,7 +474,7 @@ void snd_ac97_bus_proc_init(struct snd_ac97_bus * bus) sprintf(name, "codec97#%d", bus->num); if ((entry = snd_info_create_card_entry(bus->card, name, bus->card->proc_root)) != NULL) { - entry->mode = S_IFDIR | S_IRUGO | S_IXUGO; + entry->mode = S_IFDIR | 0555; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); entry = NULL; diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c index 720361455c60..64e0961f93ba 100644 --- a/sound/pci/asihpi/asihpi.c +++ b/sound/pci/asihpi/asihpi.c @@ -69,27 +69,27 @@ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; static bool enable_hpi_hwdep = 1; -module_param_array(index, int, NULL, S_IRUGO); +module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "ALSA index value for AudioScience soundcard."); -module_param_array(id, charp, NULL, S_IRUGO); +module_param_array(id, charp, NULL, 0444); MODULE_PARM_DESC(id, "ALSA ID string for AudioScience soundcard."); -module_param_array(enable, bool, NULL, S_IRUGO); +module_param_array(enable, bool, NULL, 0444); MODULE_PARM_DESC(enable, "ALSA enable AudioScience soundcard."); -module_param(enable_hpi_hwdep, bool, S_IRUGO|S_IWUSR); +module_param(enable_hpi_hwdep, bool, 0644); MODULE_PARM_DESC(enable_hpi_hwdep, "ALSA enable HPI hwdep for AudioScience soundcard "); /* identify driver */ #ifdef KERNEL_ALSA_BUILD static char *build_info = "Built using headers from kernel source"; -module_param(build_info, charp, S_IRUGO); +module_param(build_info, charp, 0444); MODULE_PARM_DESC(build_info, "Built using headers from kernel source"); #else static char *build_info = "Built within ALSA source"; -module_param(build_info, charp, S_IRUGO); +module_param(build_info, charp, 0444); MODULE_PARM_DESC(build_info, "Built within ALSA source"); #endif diff --git a/sound/pci/asihpi/hpioctl.c b/sound/pci/asihpi/hpioctl.c index b1a2a7ea4172..7d049569012c 100644 --- a/sound/pci/asihpi/hpioctl.c +++ b/sound/pci/asihpi/hpioctl.c @@ -46,14 +46,14 @@ MODULE_FIRMWARE("asihpi/dsp8900.bin"); #endif static int prealloc_stream_buf; -module_param(prealloc_stream_buf, int, S_IRUGO); +module_param(prealloc_stream_buf, int, 0444); MODULE_PARM_DESC(prealloc_stream_buf, "Preallocate size for per-adapter stream buffer"); /* Allow the debug level to be changed after module load. E.g. echo 2 > /sys/module/asihpi/parameters/hpiDebugLevel */ -module_param(hpi_debug_level, int, S_IRUGO | S_IWUSR); +module_param(hpi_debug_level, int, 0644); MODULE_PARM_DESC(hpi_debug_level, "debug verbosity 0..5"); /* List of adapters found */ diff --git a/sound/pci/ca0106/ca0106_proc.c b/sound/pci/ca0106/ca0106_proc.c index 9b2b8b38122f..a2c85cc37972 100644 --- a/sound/pci/ca0106/ca0106_proc.c +++ b/sound/pci/ca0106/ca0106_proc.c @@ -431,7 +431,7 @@ int snd_ca0106_proc_init(struct snd_ca0106 *emu) if(! snd_card_proc_new(emu->card, "ca0106_reg32", &entry)) { snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read32); entry->c.text.write = snd_ca0106_proc_reg_write32; - entry->mode |= S_IWUSR; + entry->mode |= 0200; } if(! snd_card_proc_new(emu->card, "ca0106_reg16", &entry)) snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read16); @@ -440,12 +440,12 @@ int snd_ca0106_proc_init(struct snd_ca0106 *emu) if(! snd_card_proc_new(emu->card, "ca0106_regs1", &entry)) { snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read1); entry->c.text.write = snd_ca0106_proc_reg_write; - entry->mode |= S_IWUSR; + entry->mode |= 0200; } if(! snd_card_proc_new(emu->card, "ca0106_i2c", &entry)) { entry->c.text.write = snd_ca0106_proc_i2c_write; entry->private_data = emu; - entry->mode |= S_IWUSR; + entry->mode |= 0200; } if(! snd_card_proc_new(emu->card, "ca0106_regs2", &entry)) snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read2); diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c index 0020fd0efc46..ed1251c5f449 100644 --- a/sound/pci/cs46xx/cs46xx_lib.c +++ b/sound/pci/cs46xx/cs46xx_lib.c @@ -2849,7 +2849,7 @@ static int snd_cs46xx_proc_init(struct snd_card *card, struct snd_cs46xx *chip) entry->private_data = chip; entry->c.ops = &snd_cs46xx_proc_io_ops; entry->size = region->size; - entry->mode = S_IFREG | S_IRUSR; + entry->mode = S_IFREG | 0400; } } #ifdef CONFIG_SND_CS46XX_NEW_DSP diff --git a/sound/pci/cs46xx/dsp_spos.c b/sound/pci/cs46xx/dsp_spos.c index aa61615288ff..c44eadef64ae 100644 --- a/sound/pci/cs46xx/dsp_spos.c +++ b/sound/pci/cs46xx/dsp_spos.c @@ -798,7 +798,7 @@ int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip) if ((entry = snd_info_create_card_entry(card, "dsp", card->proc_root)) != NULL) { entry->content = SNDRV_INFO_CONTENT_TEXT; - entry->mode = S_IFDIR | S_IRUGO | S_IXUGO; + entry->mode = S_IFDIR | 0555; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); @@ -814,7 +814,7 @@ int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip) if ((entry = snd_info_create_card_entry(card, "spos_symbols", ins->proc_dsp_dir)) != NULL) { entry->content = SNDRV_INFO_CONTENT_TEXT; entry->private_data = chip; - entry->mode = S_IFREG | S_IRUGO | S_IWUSR; + entry->mode = S_IFREG | 0644; entry->c.text.read = cs46xx_dsp_proc_symbol_table_read; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); @@ -826,7 +826,7 @@ int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip) if ((entry = snd_info_create_card_entry(card, "spos_modules", ins->proc_dsp_dir)) != NULL) { entry->content = SNDRV_INFO_CONTENT_TEXT; entry->private_data = chip; - entry->mode = S_IFREG | S_IRUGO | S_IWUSR; + entry->mode = S_IFREG | 0644; entry->c.text.read = cs46xx_dsp_proc_modules_read; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); @@ -838,7 +838,7 @@ int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip) if ((entry = snd_info_create_card_entry(card, "parameter", ins->proc_dsp_dir)) != NULL) { entry->content = SNDRV_INFO_CONTENT_TEXT; entry->private_data = chip; - entry->mode = S_IFREG | S_IRUGO | S_IWUSR; + entry->mode = S_IFREG | 0644; entry->c.text.read = cs46xx_dsp_proc_parameter_dump_read; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); @@ -850,7 +850,7 @@ int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip) if ((entry = snd_info_create_card_entry(card, "sample", ins->proc_dsp_dir)) != NULL) { entry->content = SNDRV_INFO_CONTENT_TEXT; entry->private_data = chip; - entry->mode = S_IFREG | S_IRUGO | S_IWUSR; + entry->mode = S_IFREG | 0644; entry->c.text.read = cs46xx_dsp_proc_sample_dump_read; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); @@ -862,7 +862,7 @@ int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip) if ((entry = snd_info_create_card_entry(card, "task_tree", ins->proc_dsp_dir)) != NULL) { entry->content = SNDRV_INFO_CONTENT_TEXT; entry->private_data = chip; - entry->mode = S_IFREG | S_IRUGO | S_IWUSR; + entry->mode = S_IFREG | 0644; entry->c.text.read = cs46xx_dsp_proc_task_tree_read; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); @@ -874,7 +874,7 @@ int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip) if ((entry = snd_info_create_card_entry(card, "scb_info", ins->proc_dsp_dir)) != NULL) { entry->content = SNDRV_INFO_CONTENT_TEXT; entry->private_data = chip; - entry->mode = S_IFREG | S_IRUGO | S_IWUSR; + entry->mode = S_IFREG | 0644; entry->c.text.read = cs46xx_dsp_proc_scb_read; if (snd_info_register(entry) < 0) { snd_info_free_entry(entry); diff --git a/sound/pci/cs46xx/dsp_spos_scb_lib.c b/sound/pci/cs46xx/dsp_spos_scb_lib.c index 7488e1b7a770..abb01ce66983 100644 --- a/sound/pci/cs46xx/dsp_spos_scb_lib.c +++ b/sound/pci/cs46xx/dsp_spos_scb_lib.c @@ -271,7 +271,7 @@ void cs46xx_dsp_proc_register_scb_desc (struct snd_cs46xx *chip, entry->content = SNDRV_INFO_CONTENT_TEXT; entry->private_data = scb_info; - entry->mode = S_IFREG | S_IRUGO | S_IWUSR; + entry->mode = S_IFREG | 0644; entry->c.text.read = cs46xx_dsp_proc_scb_info_read; diff --git a/sound/pci/ctxfi/cttimer.c b/sound/pci/ctxfi/cttimer.c index 08e874e9a7f6..2099e9ce441a 100644 --- a/sound/pci/ctxfi/cttimer.c +++ b/sound/pci/ctxfi/cttimer.c @@ -17,7 +17,7 @@ static bool use_system_timer; MODULE_PARM_DESC(use_system_timer, "Force to use system-timer"); -module_param(use_system_timer, bool, S_IRUGO); +module_param(use_system_timer, bool, 0444); struct ct_timer_ops { void (*init)(struct ct_timer_instance *); diff --git a/sound/pci/ctxfi/xfi.c b/sound/pci/ctxfi/xfi.c index f2f32779de98..b2874220be1b 100644 --- a/sound/pci/ctxfi/xfi.c +++ b/sound/pci/ctxfi/xfi.c @@ -26,9 +26,9 @@ MODULE_SUPPORTED_DEVICE("{{Creative Labs, Sound Blaster X-Fi}"); static unsigned int reference_rate = 48000; static unsigned int multiple = 2; MODULE_PARM_DESC(reference_rate, "Reference rate (default=48000)"); -module_param(reference_rate, uint, S_IRUGO); +module_param(reference_rate, uint, 0444); MODULE_PARM_DESC(multiple, "Rate multiplier (default=2)"); -module_param(multiple, uint, S_IRUGO); +module_param(multiple, uint, 0444); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c index 2c2b12a06177..611589cbdad6 100644 --- a/sound/pci/emu10k1/emu10k1x.c +++ b/sound/pci/emu10k1/emu10k1x.c @@ -1070,7 +1070,7 @@ static int snd_emu10k1x_proc_init(struct emu10k1x *emu) if(! snd_card_proc_new(emu->card, "emu10k1x_regs", &entry)) { snd_info_set_text_ops(entry, emu, snd_emu10k1x_proc_reg_read); entry->c.text.write = snd_emu10k1x_proc_reg_write; - entry->mode |= S_IWUSR; + entry->mode |= 0200; entry->private_data = emu; } diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 055227caa7ca..b57008031792 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -574,32 +574,32 @@ int snd_emu10k1_proc_init(struct snd_emu10k1 *emu) if (! snd_card_proc_new(emu->card, "io_regs", &entry)) { snd_info_set_text_ops(entry, emu, snd_emu_proc_io_reg_read); entry->c.text.write = snd_emu_proc_io_reg_write; - entry->mode |= S_IWUSR; + entry->mode |= 0200; } if (! snd_card_proc_new(emu->card, "ptr_regs00a", &entry)) { snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read00a); entry->c.text.write = snd_emu_proc_ptr_reg_write00; - entry->mode |= S_IWUSR; + entry->mode |= 0200; } if (! snd_card_proc_new(emu->card, "ptr_regs00b", &entry)) { snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read00b); entry->c.text.write = snd_emu_proc_ptr_reg_write00; - entry->mode |= S_IWUSR; + entry->mode |= 0200; } if (! snd_card_proc_new(emu->card, "ptr_regs20a", &entry)) { snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read20a); entry->c.text.write = snd_emu_proc_ptr_reg_write20; - entry->mode |= S_IWUSR; + entry->mode |= 0200; } if (! snd_card_proc_new(emu->card, "ptr_regs20b", &entry)) { snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read20b); entry->c.text.write = snd_emu_proc_ptr_reg_write20; - entry->mode |= S_IWUSR; + entry->mode |= 0200; } if (! snd_card_proc_new(emu->card, "ptr_regs20c", &entry)) { snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read20c); entry->c.text.write = snd_emu_proc_ptr_reg_write20; - entry->mode |= S_IWUSR; + entry->mode |= 0200; } #endif @@ -621,35 +621,35 @@ int snd_emu10k1_proc_init(struct snd_emu10k1 *emu) if (! snd_card_proc_new(emu->card, "fx8010_gpr", &entry)) { entry->content = SNDRV_INFO_CONTENT_DATA; entry->private_data = emu; - entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/; + entry->mode = S_IFREG | 0444 /*| S_IWUSR*/; entry->size = emu->audigy ? A_TOTAL_SIZE_GPR : TOTAL_SIZE_GPR; entry->c.ops = &snd_emu10k1_proc_ops_fx8010; } if (! snd_card_proc_new(emu->card, "fx8010_tram_data", &entry)) { entry->content = SNDRV_INFO_CONTENT_DATA; entry->private_data = emu; - entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/; + entry->mode = S_IFREG | 0444 /*| S_IWUSR*/; entry->size = emu->audigy ? A_TOTAL_SIZE_TANKMEM_DATA : TOTAL_SIZE_TANKMEM_DATA ; entry->c.ops = &snd_emu10k1_proc_ops_fx8010; } if (! snd_card_proc_new(emu->card, "fx8010_tram_addr", &entry)) { entry->content = SNDRV_INFO_CONTENT_DATA; entry->private_data = emu; - entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/; + entry->mode = S_IFREG | 0444 /*| S_IWUSR*/; entry->size = emu->audigy ? A_TOTAL_SIZE_TANKMEM_ADDR : TOTAL_SIZE_TANKMEM_ADDR ; entry->c.ops = &snd_emu10k1_proc_ops_fx8010; } if (! snd_card_proc_new(emu->card, "fx8010_code", &entry)) { entry->content = SNDRV_INFO_CONTENT_DATA; entry->private_data = emu; - entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/; + entry->mode = S_IFREG | 0444 /*| S_IWUSR*/; entry->size = emu->audigy ? A_TOTAL_SIZE_CODE : TOTAL_SIZE_CODE; entry->c.ops = &snd_emu10k1_proc_ops_fx8010; } if (! snd_card_proc_new(emu->card, "fx8010_acode", &entry)) { entry->content = SNDRV_INFO_CONTENT_TEXT; entry->private_data = emu; - entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/; + entry->mode = S_IFREG | 0444 /*| S_IWUSR*/; entry->c.text.read = snd_emu10k1_proc_acode_read; } return 0; diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 7d7eb1354eee..8840daf9c6a3 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -510,7 +510,7 @@ static int eld_proc_new(struct hdmi_spec_per_pin *per_pin, int index) snd_info_set_text_ops(entry, per_pin, print_eld_info); entry->c.text.write = write_eld_info; - entry->mode |= S_IWUSR; + entry->mode |= 0200; per_pin->proc_entry = entry; return 0; diff --git a/sound/pci/ice1712/pontis.c b/sound/pci/ice1712/pontis.c index 5101f40f6fbd..93b8cfc6636f 100644 --- a/sound/pci/ice1712/pontis.c +++ b/sound/pci/ice1712/pontis.c @@ -662,7 +662,7 @@ static void wm_proc_init(struct snd_ice1712 *ice) struct snd_info_entry *entry; if (! snd_card_proc_new(ice->card, "wm_codec", &entry)) { snd_info_set_text_ops(entry, ice, wm_proc_regs_read); - entry->mode |= S_IWUSR; + entry->mode |= 0200; entry->c.text.write = wm_proc_regs_write; } } diff --git a/sound/pci/ice1712/prodigy_hifi.c b/sound/pci/ice1712/prodigy_hifi.c index 8dabd4d0211d..d7366ade5a25 100644 --- a/sound/pci/ice1712/prodigy_hifi.c +++ b/sound/pci/ice1712/prodigy_hifi.c @@ -926,7 +926,7 @@ static void wm_proc_init(struct snd_ice1712 *ice) struct snd_info_entry *entry; if (!snd_card_proc_new(ice->card, "wm_codec", &entry)) { snd_info_set_text_ops(entry, ice, wm_proc_regs_read); - entry->mode |= S_IWUSR; + entry->mode |= 0200; entry->c.text.write = wm_proc_regs_write; } } diff --git a/sound/pci/lola/lola_proc.c b/sound/pci/lola/lola_proc.c index c241dc06dd92..904e3c4f4dfe 100644 --- a/sound/pci/lola/lola_proc.c +++ b/sound/pci/lola/lola_proc.c @@ -214,7 +214,7 @@ void lola_proc_debug_new(struct lola *chip) snd_info_set_text_ops(entry, chip, lola_proc_codec_read); if (!snd_card_proc_new(chip->card, "codec_rw", &entry)) { snd_info_set_text_ops(entry, chip, lola_proc_codec_rw_read); - entry->mode |= S_IWUSR; + entry->mode |= 0200; entry->c.text.write = lola_proc_codec_rw_write; } if (!snd_card_proc_new(chip->card, "regs", &entry)) diff --git a/sound/pci/pcxhr/pcxhr.c b/sound/pci/pcxhr/pcxhr.c index f9ae72f28ddc..e57da4036231 100644 --- a/sound/pci/pcxhr/pcxhr.c +++ b/sound/pci/pcxhr/pcxhr.c @@ -1465,7 +1465,7 @@ static void pcxhr_proc_init(struct snd_pcxhr *chip) !snd_card_proc_new(chip->card, "gpio", &entry)) { snd_info_set_text_ops(entry, chip, pcxhr_proc_gpio_read); entry->c.text.write = pcxhr_proc_gpo_write; - entry->mode |= S_IWUSR; + entry->mode |= 0200; } if (!snd_card_proc_new(chip->card, "ltc", &entry)) snd_info_set_text_ops(entry, chip, pcxhr_proc_ltc); diff --git a/sound/soc/codecs/cs43130.c b/sound/soc/codecs/cs43130.c index feca0a672976..80dc42197154 100644 --- a/sound/soc/codecs/cs43130.c +++ b/sound/soc/codecs/cs43130.c @@ -1733,10 +1733,10 @@ static ssize_t cs43130_show_ac_r(struct device *dev, return cs43130_show_ac(dev, buf, HP_RIGHT); } -static DEVICE_ATTR(hpload_dc_l, S_IRUGO, cs43130_show_dc_l, NULL); -static DEVICE_ATTR(hpload_dc_r, S_IRUGO, cs43130_show_dc_r, NULL); -static DEVICE_ATTR(hpload_ac_l, S_IRUGO, cs43130_show_ac_l, NULL); -static DEVICE_ATTR(hpload_ac_r, S_IRUGO, cs43130_show_ac_r, NULL); +static DEVICE_ATTR(hpload_dc_l, 0444, cs43130_show_dc_l, NULL); +static DEVICE_ATTR(hpload_dc_r, 0444, cs43130_show_dc_r, NULL); +static DEVICE_ATTR(hpload_ac_l, 0444, cs43130_show_ac_l, NULL); +static DEVICE_ATTR(hpload_ac_r, 0444, cs43130_show_ac_r, NULL); static struct reg_sequence hp_en_cal_seq[] = { {CS43130_INT_MASK_4, CS43130_INT_MASK_ALL}, diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 82b0927e6ed7..af062c4f4017 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -627,22 +627,21 @@ static void wm_adsp2_init_debugfs(struct wm_adsp *dsp, if (!root) goto err; - if (!debugfs_create_bool("booted", S_IRUGO, root, &dsp->booted)) + if (!debugfs_create_bool("booted", 0444, root, &dsp->booted)) goto err; - if (!debugfs_create_bool("running", S_IRUGO, root, &dsp->running)) + if (!debugfs_create_bool("running", 0444, root, &dsp->running)) goto err; - if (!debugfs_create_x32("fw_id", S_IRUGO, root, &dsp->fw_id)) + if (!debugfs_create_x32("fw_id", 0444, root, &dsp->fw_id)) goto err; - if (!debugfs_create_x32("fw_version", S_IRUGO, root, - &dsp->fw_id_version)) + if (!debugfs_create_x32("fw_version", 0444, root, &dsp->fw_id_version)) goto err; for (i = 0; i < ARRAY_SIZE(wm_adsp_debugfs_fops); ++i) { if (!debugfs_create_file(wm_adsp_debugfs_fops[i].name, - S_IRUGO, root, dsp, + 0444, root, dsp, &wm_adsp_debugfs_fops[i].fops)) goto err; } diff --git a/sound/soc/fsl/fsl_ssi_dbg.c b/sound/soc/fsl/fsl_ssi_dbg.c index 7aac63e2c561..0ff469c027dd 100644 --- a/sound/soc/fsl/fsl_ssi_dbg.c +++ b/sound/soc/fsl/fsl_ssi_dbg.c @@ -146,7 +146,7 @@ int fsl_ssi_debugfs_create(struct fsl_ssi_dbg *ssi_dbg, struct device *dev) if (!ssi_dbg->dbg_dir) return -ENOMEM; - ssi_dbg->dbg_stats = debugfs_create_file("stats", S_IRUGO, + ssi_dbg->dbg_stats = debugfs_create_file("stats", 0444, ssi_dbg->dbg_dir, ssi_dbg, &fsl_ssi_stats_ops); if (!ssi_dbg->dbg_stats) { diff --git a/sound/sound_core.c b/sound/sound_core.c index b4efb22db561..40ad000c2e3c 100644 --- a/sound/sound_core.c +++ b/sound/sound_core.c @@ -413,7 +413,7 @@ int register_sound_special_device(const struct file_operations *fops, int unit, break; } return sound_insert_unit(&chains[chain], fops, -1, unit, max_unit, - name, S_IRUSR | S_IWUSR, dev); + name, 0600, dev); } EXPORT_SYMBOL(register_sound_special_device); @@ -440,7 +440,7 @@ EXPORT_SYMBOL(register_sound_special); int register_sound_mixer(const struct file_operations *fops, int dev) { return sound_insert_unit(&chains[0], fops, dev, 0, 128, - "mixer", S_IRUSR | S_IWUSR, NULL); + "mixer", 0600, NULL); } EXPORT_SYMBOL(register_sound_mixer); @@ -468,7 +468,7 @@ EXPORT_SYMBOL(register_sound_mixer); int register_sound_dsp(const struct file_operations *fops, int dev) { return sound_insert_unit(&chains[3], fops, dev, 3, 131, - "dsp", S_IWUSR | S_IRUSR, NULL); + "dsp", 0600, NULL); } EXPORT_SYMBOL(register_sound_dsp); diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c index f0e713527e91..7609eceba1a2 100644 --- a/sound/sparc/dbri.c +++ b/sound/sparc/dbri.c @@ -2518,7 +2518,7 @@ static void snd_dbri_proc(struct snd_card *card) #ifdef DBRI_DEBUG if (!snd_card_proc_new(card, "debug", &entry)) { snd_info_set_text_ops(entry, dbri, dbri_debug_read); - entry->mode = S_IFREG | S_IRUGO; /* Readable only. */ + entry->mode = S_IFREG | 0444; /* Readable only. */ } #endif } -- cgit v1.2.3 From 3d6246173798fc7a81a69ad3fd0dd55fa1779068 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sun, 27 May 2018 22:23:12 +0100 Subject: ALSA: xen-front: remove redundant error check on ret The error for a -ve value in ret is redundant as all previous assignments to ret have an associated -ve check and hence it is impossible for ret to be less that zero at the point of the check. Remove this redundant error check. Detected by CoveritScan, CID#1469407 ("Logically Dead code") Signed-off-by: Colin Ian King Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/xen/xen_snd_front_evtchnl.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'sound') diff --git a/sound/xen/xen_snd_front_evtchnl.c b/sound/xen/xen_snd_front_evtchnl.c index d70a62e7f910..102d6e096cc8 100644 --- a/sound/xen/xen_snd_front_evtchnl.c +++ b/sound/xen/xen_snd_front_evtchnl.c @@ -351,8 +351,6 @@ int xen_snd_front_evtchnl_create_all(struct xen_snd_front_info *front_info, } } } - if (ret < 0) - goto fail; front_info->num_evt_pairs = num_streams; return 0; -- cgit v1.2.3 From 014cea591afac9b3dae61793446f83d5be634203 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sun, 27 May 2018 22:32:19 +0100 Subject: ALSA: xen-front: fix unsigned error check on return from to_sndif_format The negative error return from the call to to_sndif_format is being assigned to an unsigned 8 bit integer and hence the check for a negative value is always going to be false. Fix this by using ret as the error return and hence the negative error can be detected and assign the u8 sndif_format to ret if there is no error. Detected by CoverityScan, CID#1469385 ("Unsigned compared against 0") Signed-off-by: Colin Ian King Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/xen/xen_snd_front_alsa.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/xen/xen_snd_front_alsa.c b/sound/xen/xen_snd_front_alsa.c index 5041f83e98d2..5a2bd70a2fa1 100644 --- a/sound/xen/xen_snd_front_alsa.c +++ b/sound/xen/xen_snd_front_alsa.c @@ -466,13 +466,14 @@ static int alsa_prepare(struct snd_pcm_substream *substream) u8 sndif_format; int ret; - sndif_format = to_sndif_format(runtime->format); - if (sndif_format < 0) { + ret = to_sndif_format(runtime->format); + if (ret < 0) { dev_err(&stream->front_info->xb_dev->dev, "Unsupported sample format: %d\n", runtime->format); - return sndif_format; + return ret; } + sndif_format = ret; ret = xen_snd_front_stream_prepare(&stream->evt_pair->req, &stream->sh_buf, -- cgit v1.2.3 From 9f88058e7830d5943091e044e7b2d58773e31bca Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sun, 27 May 2018 13:55:01 +0200 Subject: ALSA: aloop: Reduced duplicated PCM ops definition The PCM ops defined for playback and capture are identical. Just use the single one for both. Signed-off-by: Takashi Iwai --- sound/drivers/aloop.c | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) (limited to 'sound') diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index eab7f594ebe7..78a2fdc38531 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -768,20 +768,7 @@ static int loopback_close(struct snd_pcm_substream *substream) return 0; } -static const struct snd_pcm_ops loopback_playback_ops = { - .open = loopback_open, - .close = loopback_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = loopback_hw_params, - .hw_free = loopback_hw_free, - .prepare = loopback_prepare, - .trigger = loopback_trigger, - .pointer = loopback_pointer, - .page = snd_pcm_lib_get_vmalloc_page, - .mmap = snd_pcm_lib_mmap_vmalloc, -}; - -static const struct snd_pcm_ops loopback_capture_ops = { +static const struct snd_pcm_ops loopback_pcm_ops = { .open = loopback_open, .close = loopback_close, .ioctl = snd_pcm_lib_ioctl, @@ -804,8 +791,8 @@ static int loopback_pcm_new(struct loopback *loopback, substreams, substreams, &pcm); if (err < 0) return err; - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &loopback_playback_ops); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &loopback_capture_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &loopback_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &loopback_pcm_ops); pcm->private_data = loopback; pcm->info_flags = 0; -- cgit v1.2.3 From 6fddc797878181c9bb16dff1034ad9de2b25902d Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sun, 27 May 2018 13:59:03 +0200 Subject: ALSA: usb-audio: Simplify PCM open/close callbacks The stream direction in open and close callbacks can be retrieved from substream->direction, hence we don't have to stick with the unique PCM ops hard-coded for each direction. Rewrite the common open/close callback functions. Signed-off-by: Takashi Iwai --- sound/usb/pcm.c | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) (limited to 'sound') diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 20bed1c7a312..d5b9c30d3bb1 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -1227,8 +1227,9 @@ rep_err: return err; } -static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction) +static int snd_usb_pcm_open(struct snd_pcm_substream *substream) { + int direction = substream->stream; struct snd_usb_stream *as = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_usb_substream *subs = &as->substream[direction]; @@ -1248,8 +1249,9 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction) return setup_hw_info(runtime, subs); } -static int snd_usb_pcm_close(struct snd_pcm_substream *substream, int direction) +static int snd_usb_pcm_close(struct snd_pcm_substream *substream) { + int direction = substream->stream; struct snd_usb_stream *as = snd_pcm_substream_chip(substream); struct snd_usb_substream *subs = &as->substream[direction]; @@ -1611,26 +1613,6 @@ static void retire_playback_urb(struct snd_usb_substream *subs, spin_unlock_irqrestore(&subs->lock, flags); } -static int snd_usb_playback_open(struct snd_pcm_substream *substream) -{ - return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_PLAYBACK); -} - -static int snd_usb_playback_close(struct snd_pcm_substream *substream) -{ - return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_PLAYBACK); -} - -static int snd_usb_capture_open(struct snd_pcm_substream *substream) -{ - return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_CAPTURE); -} - -static int snd_usb_capture_close(struct snd_pcm_substream *substream) -{ - return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_CAPTURE); -} - static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream, int cmd) { @@ -1692,8 +1674,8 @@ static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream } static const struct snd_pcm_ops snd_usb_playback_ops = { - .open = snd_usb_playback_open, - .close = snd_usb_playback_close, + .open = snd_usb_pcm_open, + .close = snd_usb_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = snd_usb_hw_params, .hw_free = snd_usb_hw_free, @@ -1705,8 +1687,8 @@ static const struct snd_pcm_ops snd_usb_playback_ops = { }; static const struct snd_pcm_ops snd_usb_capture_ops = { - .open = snd_usb_capture_open, - .close = snd_usb_capture_close, + .open = snd_usb_pcm_open, + .close = snd_usb_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = snd_usb_hw_params, .hw_free = snd_usb_hw_free, -- cgit v1.2.3 From e92be8146caf3ecd76f1211725d9ba47c239a77b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sun, 27 May 2018 15:09:15 +0200 Subject: ALSA: usb-audio: Move autoresume call at the end of open ... so that we can avoid the extra goto lines. Also beautify the code to follow the standard codex. No functional changes. Signed-off-by: Takashi Iwai --- sound/usb/pcm.c | 73 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 36 deletions(-) (limited to 'sound') diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index d5b9c30d3bb1..a66bc717b952 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -76,10 +76,9 @@ snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs, */ static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream) { - struct snd_usb_substream *subs; + struct snd_usb_substream *subs = substream->runtime->private_data; unsigned int hwptr_done; - subs = (struct snd_usb_substream *)substream->runtime->private_data; if (atomic_read(&subs->stream->chip->shutdown)) return SNDRV_PCM_POS_XRUN; spin_lock(&subs->lock); @@ -1172,9 +1171,6 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre pt = 125 * (1 << fp->datainterval); ptmin = min(ptmin, pt); } - err = snd_usb_autoresume(subs->stream->chip); - if (err < 0) - return err; param_period_time_if_needed = SNDRV_PCM_HW_PARAM_PERIOD_TIME; if (subs->speed == USB_SPEED_FULL) @@ -1183,30 +1179,37 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre if (ptmin == 1000) /* if period time doesn't go below 1 ms, no rules needed */ param_period_time_if_needed = -1; - snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, - ptmin, UINT_MAX); - - if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - hw_rule_rate, subs, - SNDRV_PCM_HW_PARAM_FORMAT, - SNDRV_PCM_HW_PARAM_CHANNELS, - param_period_time_if_needed, - -1)) < 0) - goto rep_err; - if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - hw_rule_channels, subs, - SNDRV_PCM_HW_PARAM_FORMAT, - SNDRV_PCM_HW_PARAM_RATE, - param_period_time_if_needed, - -1)) < 0) - goto rep_err; - if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, - hw_rule_format, subs, - SNDRV_PCM_HW_PARAM_RATE, - SNDRV_PCM_HW_PARAM_CHANNELS, - param_period_time_if_needed, - -1)) < 0) - goto rep_err; + + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + ptmin, UINT_MAX); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, subs, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_CHANNELS, + param_period_time_if_needed, + -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, subs, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_RATE, + param_period_time_if_needed, + -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + hw_rule_format, subs, + SNDRV_PCM_HW_PARAM_RATE, + SNDRV_PCM_HW_PARAM_CHANNELS, + param_period_time_if_needed, + -1); + if (err < 0) + return err; if (param_period_time_if_needed >= 0) { err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_TIME, @@ -1216,15 +1219,13 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre SNDRV_PCM_HW_PARAM_RATE, -1); if (err < 0) - goto rep_err; + return err; } - if ((err = snd_usb_pcm_check_knot(runtime, subs)) < 0) - goto rep_err; - return 0; + err = snd_usb_pcm_check_knot(runtime, subs); + if (err < 0) + return err; -rep_err: - snd_usb_autosuspend(subs->stream->chip); - return err; + return snd_usb_autoresume(subs->stream->chip); } static int snd_usb_pcm_open(struct snd_pcm_substream *substream) -- cgit v1.2.3 From f25ecf8f987d51be388e53de7b9e0e5815acc10b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sun, 27 May 2018 15:18:22 +0200 Subject: ALSA: usb-audio: Follow standard coding style Avoid if ((err = ...) style and expand to multiple lines instead. No change in the end result, but just the beautification. Signed-off-by: Takashi Iwai --- sound/usb/card.c | 3 ++- sound/usb/clock.c | 18 ++++++++++-------- sound/usb/mixer.c | 25 +++++++++++++++++-------- sound/usb/mixer_quirks.c | 3 ++- sound/usb/pcm.c | 18 ++++++++++-------- 5 files changed, 41 insertions(+), 26 deletions(-) (limited to 'sound') diff --git a/sound/usb/card.c b/sound/usb/card.c index f6c3c1cd591e..54c77d407a6d 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -508,7 +508,8 @@ static int snd_usb_audio_create(struct usb_interface *intf, INIT_LIST_HEAD(&chip->midi_list); INIT_LIST_HEAD(&chip->mixer_list); - if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { snd_usb_audio_free(chip); snd_card_free(card); return err; diff --git a/sound/usb/clock.c b/sound/usb/clock.c index 17673f37fcc8..c79749613fa6 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -443,10 +443,11 @@ static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface, data[0] = rate; data[1] = rate >> 8; data[2] = rate >> 16; - if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, - USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT, - UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, - data, sizeof(data))) < 0) { + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, + data, sizeof(data)); + if (err < 0) { dev_err(&dev->dev, "%d:%d: cannot set freq %d to ep %#x\n", iface, fmt->altsetting, rate, ep); return err; @@ -460,10 +461,11 @@ static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface, if (chip->sample_rate_read_error > 2) return 0; - if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR, - USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN, - UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, - data, sizeof(data))) < 0) { + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR, + USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, + data, sizeof(data)); + if (err < 0) { dev_err(&dev->dev, "%d:%d: cannot get freq at ep %#x\n", iface, fmt->altsetting, ep); chip->sample_rate_read_error++; diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index bf74e7edc92b..898afd3001ea 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -598,7 +598,8 @@ int snd_usb_mixer_add_control(struct usb_mixer_elem_list *list, while (snd_ctl_find_id(mixer->chip->card, &kctl->id)) kctl->id.index++; - if ((err = snd_ctl_add(mixer->chip->card, kctl)) < 0) { + err = snd_ctl_add(mixer->chip->card, kctl); + if (err < 0) { usb_audio_dbg(mixer->chip, "cannot add control (err = %d)\n", err); return err; @@ -1850,7 +1851,8 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, } /* parse the source unit */ - if ((err = parse_audio_unit(state, hdr->bSourceID)) < 0) + err = parse_audio_unit(state, hdr->bSourceID); + if (err < 0) return err; /* determine the input source type and name */ @@ -2270,7 +2272,8 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, } for (i = 0; i < num_ins; i++) { - if ((err = parse_audio_unit(state, desc->baSourceID[i])) < 0) + err = parse_audio_unit(state, desc->baSourceID[i]); + if (err < 0) return err; } @@ -2483,7 +2486,8 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, } for (i = 0; i < desc->bNrInPins; i++) { - if ((err = parse_audio_unit(state, desc->baSourceID[i])) < 0) + err = parse_audio_unit(state, desc->baSourceID[i]); + if (err < 0) return err; } @@ -3310,11 +3314,16 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, if (mixer->protocol == UAC_VERSION_3 && chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { - if ((err = snd_usb_mixer_controls_badd(mixer, ctrlif)) < 0) + err = snd_usb_mixer_controls_badd(mixer, ctrlif); + if (err < 0) + goto _error; + } else { + err = snd_usb_mixer_controls(mixer); + if (err < 0) + goto _error; + err = snd_usb_mixer_status_create(mixer); + if (err < 0) goto _error; - } else if ((err = snd_usb_mixer_controls(mixer)) < 0 || - (err = snd_usb_mixer_status_create(mixer)) < 0) { - goto _error; } err = create_keep_iface_ctl(mixer); if (err < 0) diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 1b94387e18b6..4149543f613e 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -1824,7 +1824,8 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) int err = 0; struct snd_info_entry *entry; - if ((err = snd_usb_soundblaster_remote_init(mixer)) < 0) + err = snd_usb_soundblaster_remote_init(mixer); + if (err < 0) return err; switch (mixer->chip->usb_id) { diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index a66bc717b952..897a2cbef6de 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -163,10 +163,11 @@ static int init_pitch_v1(struct snd_usb_audio *chip, int iface, ep = get_endpoint(alts, 0)->bEndpointAddress; data[0] = 1; - if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, - USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, - UAC_EP_CS_ATTR_PITCH_CONTROL << 8, ep, - data, sizeof(data))) < 0) { + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, + UAC_EP_CS_ATTR_PITCH_CONTROL << 8, ep, + data, sizeof(data)); + if (err < 0) { usb_audio_err(chip, "%d:%d: cannot set enable PITCH\n", iface, ep); return err; @@ -184,10 +185,11 @@ static int init_pitch_v2(struct snd_usb_audio *chip, int iface, int err; data[0] = 1; - if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR, - USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT, - UAC2_EP_CS_PITCH << 8, 0, - data, sizeof(data))) < 0) { + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR, + USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT, + UAC2_EP_CS_PITCH << 8, 0, + data, sizeof(data)); + if (err < 0) { usb_audio_err(chip, "%d:%d: cannot set enable PITCH (v2)\n", iface, fmt->altsetting); return err; -- cgit v1.2.3 From 011ae2bf06690c9fd6209537b4775855122f5c86 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sun, 27 May 2018 15:07:01 +0200 Subject: ALSA: usb-audio: Avoid lowlevel device object Simplify the device management by replacing the lowlevel device object allocation with the card->private_data. Nowadays there is almost no advantage by the lowlevel device, and with card->private_data, the code becomes cleaner. Signed-off-by: Takashi Iwai --- sound/usb/card.c | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) (limited to 'sound') diff --git a/sound/usb/card.c b/sound/usb/card.c index 54c77d407a6d..c80224807e8f 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -342,8 +342,9 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) * */ -static int snd_usb_audio_free(struct snd_usb_audio *chip) +static void snd_usb_audio_free(struct snd_card *card) { + struct snd_usb_audio *chip = card->private_data; struct snd_usb_endpoint *ep, *n; list_for_each_entry_safe(ep, n, &chip->ep_list, list) @@ -352,14 +353,6 @@ static int snd_usb_audio_free(struct snd_usb_audio *chip) mutex_destroy(&chip->mutex); if (!atomic_read(&chip->shutdown)) dev_set_drvdata(&chip->dev->dev, NULL); - kfree(chip); - return 0; -} - -static int snd_usb_audio_dev_free(struct snd_device *device) -{ - struct snd_usb_audio *chip = device->device_data; - return snd_usb_audio_free(chip); } static void usb_audio_make_shortname(struct usb_device *dev, @@ -459,9 +452,6 @@ static int snd_usb_audio_create(struct usb_interface *intf, struct snd_usb_audio *chip; int err; char component[14]; - static struct snd_device_ops ops = { - .dev_free = snd_usb_audio_dev_free, - }; *rchip = NULL; @@ -479,18 +469,13 @@ static int snd_usb_audio_create(struct usb_interface *intf, } err = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE, - 0, &card); + sizeof(*chip), &card); if (err < 0) { dev_err(&dev->dev, "cannot create card instance %d\n", idx); return err; } - chip = kzalloc(sizeof(*chip), GFP_KERNEL); - if (! chip) { - snd_card_free(card); - return -ENOMEM; - } - + chip = card->private_data; mutex_init(&chip->mutex); init_waitqueue_head(&chip->shutdown_wait); chip->index = idx; @@ -508,12 +493,7 @@ static int snd_usb_audio_create(struct usb_interface *intf, INIT_LIST_HEAD(&chip->midi_list); INIT_LIST_HEAD(&chip->mixer_list); - err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); - if (err < 0) { - snd_usb_audio_free(chip); - snd_card_free(card); - return err; - } + card->private_free = snd_usb_audio_free; strcpy(card->driver, "USB-Audio"); sprintf(component, "USB%04x:%04x", -- cgit v1.2.3 From 4c0eaac716d8e461584a4c8529cb00c72a563fa8 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 28 May 2018 17:59:57 +0200 Subject: ALSA: xen: ensure nul-terminated device name gcc-8 warns that pcm_instance->name is not necessarily terminated correctly if the input is more than 80 characters long or lacks a termination byte itself: In function 'strncpy', inlined from 'cfg_device' at sound/xen/xen_snd_front_cfg.c:399:3, inlined from 'xen_snd_front_cfg_card' at sound/xen/xen_snd_front_cfg.c:509:9: include/linux/string.h:254:9: error: '__builtin_strncpy' specified bound 80 equals destination size [-Werror=stringop-truncation] return __builtin_strncpy(p, q, size); Using strlcpy() instead of strncpy() makes this a bit safer. Fixes: fd3b36045c2c ("ALSA: xen-front: Read sound driver configuration from Xen store") Signed-off-by: Arnd Bergmann Reviewed-by: Oleksandr Andrushchenko Signed-off-by: Takashi Iwai --- sound/xen/xen_snd_front_cfg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/xen/xen_snd_front_cfg.c b/sound/xen/xen_snd_front_cfg.c index 38c7e1eefbb9..684b5f1d51ac 100644 --- a/sound/xen/xen_snd_front_cfg.c +++ b/sound/xen/xen_snd_front_cfg.c @@ -396,7 +396,7 @@ static int cfg_device(struct xen_snd_front_info *front_info, str = xenbus_read(XBT_NIL, device_path, XENSND_FIELD_DEVICE_NAME, NULL); if (!IS_ERR(str)) { - strncpy(pcm_instance->name, str, sizeof(pcm_instance->name)); + strlcpy(pcm_instance->name, str, sizeof(pcm_instance->name)); kfree(str); } -- cgit v1.2.3 From f91f1806530d065b90718598fbe2fad426732418 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 13 May 2018 14:48:13 +0100 Subject: ALSA: hda: Add Intel NUC5i7RY to the power_save blacklist Power-saving is causing a humming sound when active on the Intel NUC5i7RY, add it to the blacklist. BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=199607 Signed-off-by: Hans de Goede Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 40d50118f9f6..1ae1850b3bfd 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -2217,6 +2217,8 @@ static struct snd_pci_quirk power_save_blacklist[] = { /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ /* Note the P55A-UD3 and Z87-D3HP share the subsys id for the HDA dev */ SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte P55A-UD3 / Z87-D3HP", 0), + /* https://bugzilla.kernel.org/show_bug.cgi?id=199607 */ + SND_PCI_QUIRK(0x8086, 0x2057, "Intel NUC5i7RYB", 0), /* https://bugzilla.redhat.com/show_bug.cgi?id=1520902 */ SND_PCI_QUIRK(0x8086, 0x2068, "Intel NUC7i3BNB", 0), /* https://bugzilla.redhat.com/show_bug.cgi?id=1572975 */ -- cgit v1.2.3 From f274baa49be67dd8a9f318cd95da6ef9f565d06b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sun, 27 May 2018 13:01:17 +0200 Subject: ALSA: usb-audio: Allow non-vmalloc buffer for PCM buffers Currently, USB-audio driver allocates the PCM buffer via vmalloc(), as this serves merely as an intermediate buffer that is copied to each URB transfer buffer. This works well in general on x86, but on some archs this may result in cache coherency issues when mmap is used. OTOH, it works also on such arch unless mmap is used. This patch is a step for mitigating the inconvenience; a new module option "use_vmalloc" is provided so that user can choose to allocate the DMA coherent buffer instead of the existing vmalloc buffer. The drawback is that it'd be the standard dma_alloc_coherent() calls and the system would require contiguous pages on non-x86 archs. Note that it's a global option and not dynamically switchable since the buffer is pre-allocated at the probe time. In theory, it's possible to be switchable, but it'd be trickier and racier. As default use_vmalloc option is set to true, so that the old behavior is kept. For allowing the coherent mmap on ARM or MIPS, pass use_vmalloc=0 option explicitly. Reported-and-tested-by: Daniel Danzberger Signed-off-by: Takashi Iwai --- Documentation/sound/alsa-configuration.rst | 7 ++++ sound/usb/card.c | 4 ++ sound/usb/pcm.c | 59 +++++++++++++++++++++++++++--- sound/usb/pcm.h | 1 + sound/usb/stream.c | 2 + sound/usb/usbaudio.h | 2 + 6 files changed, 70 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index aed6b4fb8e46..b1052e18292d 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -2224,6 +2224,13 @@ quirk_alias Quirk alias list, pass strings like ``0123abcd:5678beef``, which applies the existing quirk for the device 5678:beef to a new device 0123:abcd. +use_vmalloc + Use vmalloc() for allocations of the PCM buffers (default: yes). + For architectures with non-coherent memory like ARM or MIPS, the + mmap access may give inconsistent results with vmalloc'ed + buffers. If mmap is used on such architectures, turn off this + option, so that the DMA-coherent buffers are allocated and used + instead. This module supports multiple devices, autoprobe and hotplugging. diff --git a/sound/usb/card.c b/sound/usb/card.c index c80224807e8f..a1ed798a1c6b 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -86,6 +86,8 @@ static bool ignore_ctl_error; static bool autoclock = true; static char *quirk_alias[SNDRV_CARDS]; +bool snd_usb_use_vmalloc = true; + module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for the USB audio adapter."); module_param_array(id, charp, NULL, 0444); @@ -105,6 +107,8 @@ module_param(autoclock, bool, 0444); MODULE_PARM_DESC(autoclock, "Enable auto-clock selection for UAC2 devices (default: yes)."); module_param_array(quirk_alias, charp, NULL, 0444); MODULE_PARM_DESC(quirk_alias, "Quirk aliases, e.g. 0123abcd:5678beef."); +module_param_named(use_vmalloc, snd_usb_use_vmalloc, bool, 0444); +MODULE_PARM_DESC(use_vmalloc, "Use vmalloc for PCM intermediate buffers (default: yes)."); /* * we keep the snd_usb_audio_t instances by ourselves for merging diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 897a2cbef6de..78d1cad08a0a 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -728,7 +728,11 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream, struct audioformat *fmt; int ret; - ret = snd_pcm_lib_alloc_vmalloc_buffer(substream, + if (snd_usb_use_vmalloc) + ret = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + else + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); if (ret < 0) return ret; @@ -781,7 +785,11 @@ static int snd_usb_hw_free(struct snd_pcm_substream *substream) snd_usb_endpoint_deactivate(subs->data_endpoint); snd_usb_unlock_shutdown(subs->stream->chip); } - return snd_pcm_lib_free_vmalloc_buffer(substream); + + if (snd_usb_use_vmalloc) + return snd_pcm_lib_free_vmalloc_buffer(substream); + else + return snd_pcm_lib_free_pages(substream); } /* @@ -1702,9 +1710,50 @@ static const struct snd_pcm_ops snd_usb_capture_ops = { .mmap = snd_pcm_lib_mmap_vmalloc, }; +static const struct snd_pcm_ops snd_usb_playback_dev_ops = { + .open = snd_usb_pcm_open, + .close = snd_usb_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_usb_hw_params, + .hw_free = snd_usb_hw_free, + .prepare = snd_usb_pcm_prepare, + .trigger = snd_usb_substream_playback_trigger, + .pointer = snd_usb_pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + +static const struct snd_pcm_ops snd_usb_capture_dev_ops = { + .open = snd_usb_pcm_open, + .close = snd_usb_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_usb_hw_params, + .hw_free = snd_usb_hw_free, + .prepare = snd_usb_pcm_prepare, + .trigger = snd_usb_substream_capture_trigger, + .pointer = snd_usb_pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream) { - snd_pcm_set_ops(pcm, stream, - stream == SNDRV_PCM_STREAM_PLAYBACK ? - &snd_usb_playback_ops : &snd_usb_capture_ops); + const struct snd_pcm_ops *ops; + + if (snd_usb_use_vmalloc) + ops = stream == SNDRV_PCM_STREAM_PLAYBACK ? + &snd_usb_playback_ops : &snd_usb_capture_ops; + else + ops = stream == SNDRV_PCM_STREAM_PLAYBACK ? + &snd_usb_playback_dev_ops : &snd_usb_capture_dev_ops; + snd_pcm_set_ops(pcm, stream, ops); +} + +void snd_usb_preallocate_buffer(struct snd_usb_substream *subs) +{ + struct snd_pcm *pcm = subs->stream->pcm; + struct snd_pcm_substream *s = pcm->streams[subs->direction].substream; + struct device *dev = subs->dev->bus->controller; + + if (!snd_usb_use_vmalloc) + snd_pcm_lib_preallocate_pages(s, SNDRV_DMA_TYPE_DEV_SG, + dev, 64*1024, 512*1024); } diff --git a/sound/usb/pcm.h b/sound/usb/pcm.h index 35740d5ef268..f77ec58bf1a1 100644 --- a/sound/usb/pcm.h +++ b/sound/usb/pcm.h @@ -10,6 +10,7 @@ void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream); int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface, struct usb_host_interface *alts, struct audioformat *fmt); +void snd_usb_preallocate_buffer(struct snd_usb_substream *subs); #endif /* __USBAUDIO_PCM_H */ diff --git a/sound/usb/stream.c b/sound/usb/stream.c index d16e1c23f4e9..729afd808cc4 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -106,6 +106,8 @@ static void snd_usb_init_substream(struct snd_usb_stream *as, subs->ep_num = fp->endpoint; if (fp->channels > subs->channels_max) subs->channels_max = fp->channels; + + snd_usb_preallocate_buffer(subs); } /* kctl callbacks for usb-audio channel maps */ diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 7b28cbde22c0..b9faeca645fd 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -127,4 +127,6 @@ struct snd_usb_audio_quirk { int snd_usb_lock_shutdown(struct snd_usb_audio *chip); void snd_usb_unlock_shutdown(struct snd_usb_audio *chip); +extern bool snd_usb_use_vmalloc; + #endif /* __USBAUDIO_H */ -- cgit v1.2.3 From 7f783bd5e215a014a3c98df64bef24c2dc736def Mon Sep 17 00:00:00 2001 From: Tom Briden Date: Sat, 25 Mar 2017 10:12:01 +0000 Subject: ALSA: hda/realtek - Fixup mute led on HP Spectre x360 This patch adds the mute LED control for HP Spectre x360 Kabylake model. The mute LED is controlled via VREF bits on NID 0x1b, so we need a new fixup function. Note that this doesn't fix the other issues like the missing speaker output on the machine. They will be addressed by later patches. Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=189331 Signed-off-by: Tom Briden Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 3af93841abe1..6369964c117a 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -3679,6 +3679,19 @@ static void alc269_fixup_hp_mute_led_mic2(struct hda_codec *codec, } } +static void alc269_fixup_hp_mute_led_mic3(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->mute_led_polarity = 0; + spec->mute_led_nid = 0x1b; + spec->gen.vmaster_mute.hook = alc269_fixup_mic_mute_hook; + spec->gen.vmaster_mute_enum = 1; + codec->power_filter = led_power_filter; + } +} + /* update LED status via GPIO */ static void alc_update_gpio_led(struct hda_codec *codec, unsigned int mask, bool enabled) @@ -5413,6 +5426,7 @@ enum { ALC269_FIXUP_HP_MUTE_LED, ALC269_FIXUP_HP_MUTE_LED_MIC1, ALC269_FIXUP_HP_MUTE_LED_MIC2, + ALC269_FIXUP_HP_MUTE_LED_MIC3, ALC269_FIXUP_HP_GPIO_LED, ALC269_FIXUP_HP_GPIO_MIC1_LED, ALC269_FIXUP_HP_LINE1_MIC1_LED, @@ -5672,6 +5686,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc269_fixup_hp_mute_led_mic2, }, + [ALC269_FIXUP_HP_MUTE_LED_MIC3] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_hp_mute_led_mic3, + }, [ALC269_FIXUP_HP_GPIO_LED] = { .type = HDA_FIXUP_FUNC, .v.func = alc269_fixup_hp_gpio_led, @@ -6494,6 +6512,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x2337, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x221c, "HP EliteBook 755 G2", ALC280_FIXUP_HP_HEADSET_MIC), SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC), + SND_PCI_QUIRK(0x103c, 0x827e, "HP x360", ALC269_FIXUP_HP_MUTE_LED_MIC3), SND_PCI_QUIRK(0x103c, 0x82bf, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x82c0, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), -- cgit v1.2.3 From 85c467dc03a283ada739acce89f071a40310d962 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 29 May 2018 11:38:38 +0200 Subject: ALSA: hda/realtek - Refactor alc269_fixup_hp_mute_led_mic*() Just a code refactoring to use the common helper for the all three functions. Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 6369964c117a..4a9ea34b11b5 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -3653,43 +3653,37 @@ static void alc269_fixup_hp_mute_led(struct hda_codec *codec, } } -static void alc269_fixup_hp_mute_led_mic1(struct hda_codec *codec, - const struct hda_fixup *fix, int action) +static void alc269_fixup_hp_mute_led_micx(struct hda_codec *codec, + const struct hda_fixup *fix, + int action, hda_nid_t pin) { struct alc_spec *spec = codec->spec; + if (action == HDA_FIXUP_ACT_PRE_PROBE) { spec->mute_led_polarity = 0; - spec->mute_led_nid = 0x18; + spec->mute_led_nid = pin; spec->gen.vmaster_mute.hook = alc269_fixup_mic_mute_hook; spec->gen.vmaster_mute_enum = 1; codec->power_filter = led_power_filter; } } +static void alc269_fixup_hp_mute_led_mic1(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc269_fixup_hp_mute_led_micx(codec, fix, action, 0x18); +} + static void alc269_fixup_hp_mute_led_mic2(struct hda_codec *codec, const struct hda_fixup *fix, int action) { - struct alc_spec *spec = codec->spec; - if (action == HDA_FIXUP_ACT_PRE_PROBE) { - spec->mute_led_polarity = 0; - spec->mute_led_nid = 0x19; - spec->gen.vmaster_mute.hook = alc269_fixup_mic_mute_hook; - spec->gen.vmaster_mute_enum = 1; - codec->power_filter = led_power_filter; - } + alc269_fixup_hp_mute_led_micx(codec, fix, action, 0x19); } static void alc269_fixup_hp_mute_led_mic3(struct hda_codec *codec, const struct hda_fixup *fix, int action) { - struct alc_spec *spec = codec->spec; - if (action == HDA_FIXUP_ACT_PRE_PROBE) { - spec->mute_led_polarity = 0; - spec->mute_led_nid = 0x1b; - spec->gen.vmaster_mute.hook = alc269_fixup_mic_mute_hook; - spec->gen.vmaster_mute_enum = 1; - codec->power_filter = led_power_filter; - } + alc269_fixup_hp_mute_led_micx(codec, fix, action, 0x1b); } /* update LED status via GPIO */ -- cgit v1.2.3 From bbf8ff6b1d2ae749d962fd9ff743164fe13abf5d Mon Sep 17 00:00:00 2001 From: Tom Briden Date: Tue, 29 May 2018 17:34:20 +0100 Subject: ALSA: hda/realtek - Fixup for HP x360 laptops with B&O speakers Added a new helper file for these fixups due to requiring a huge number of coefs being set to get the top speakers to work, as well as setting pin 0x17 for the top speakers and the correct input source of 0x17 for volume control [ Note: this is a revised work based on Tom's fixup patch with the replacement of the full COEF tables provided by Realtek. Also, the fixup function has a proper HDA_FIXUP_ACT_* handling now. The credit for the new COEF table goes to Kailang -- tiwai ] Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=189331 Cc: Kailang Yang Signed-off-by: Tom Briden Tested-by: Tom Briden Signed-off-by: Takashi Iwai --- sound/pci/hda/hp_x360_helper.c | 95 ++++++++++++++++++++++++++++++++++++++++++ sound/pci/hda/patch_realtek.c | 12 +++++- 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 sound/pci/hda/hp_x360_helper.c (limited to 'sound') diff --git a/sound/pci/hda/hp_x360_helper.c b/sound/pci/hda/hp_x360_helper.c new file mode 100644 index 000000000000..969542c57358 --- /dev/null +++ b/sound/pci/hda/hp_x360_helper.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Fixes for HP X360 laptops with top B&O speakers + * to be included from codec driver + */ + +static void alc295_fixup_hp_top_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const struct hda_pintbl pincfgs[] = { + { 0x17, 0x90170110 }, + { } + }; + static const struct coef_fw alc295_hp_speakers_coefs[] = { + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0000), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003f), WRITE_COEF(0x28, 0x1000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0004), WRITE_COEF(0x28, 0x0600), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x0006), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0xc0c0), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0008), WRITE_COEF(0x28, 0xb000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x002e), WRITE_COEF(0x28, 0x0800), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x00c1), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0x0320), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0039), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003b), WRITE_COEF(0x28, 0xffff), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003c), WRITE_COEF(0x28, 0xffd0), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003a), WRITE_COEF(0x28, 0x1dfe), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0080), WRITE_COEF(0x28, 0x0880), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003a), WRITE_COEF(0x28, 0x0dfe), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0018), WRITE_COEF(0x28, 0x0219), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x005d), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0x9142), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c0), WRITE_COEF(0x28, 0x01ce), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c1), WRITE_COEF(0x28, 0xed0c), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c2), WRITE_COEF(0x28, 0x1c00), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c3), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c4), WRITE_COEF(0x28, 0x0200), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c5), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c6), WRITE_COEF(0x28, 0x0399), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c7), WRITE_COEF(0x28, 0x2330), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c8), WRITE_COEF(0x28, 0x1e5d), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c9), WRITE_COEF(0x28, 0x6eff), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00ca), WRITE_COEF(0x28, 0x01c0), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00cb), WRITE_COEF(0x28, 0xed0c), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00cc), WRITE_COEF(0x28, 0x1c00), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00cd), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00ce), WRITE_COEF(0x28, 0x0200), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00cf), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00d0), WRITE_COEF(0x28, 0x0399), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00d1), WRITE_COEF(0x28, 0x2330), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00d2), WRITE_COEF(0x28, 0x1e5d), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00d3), WRITE_COEF(0x28, 0x6eff), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0062), WRITE_COEF(0x28, 0x8000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0063), WRITE_COEF(0x28, 0x5f5f), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0064), WRITE_COEF(0x28, 0x1000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0065), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0066), WRITE_COEF(0x28, 0x4004), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0067), WRITE_COEF(0x28, 0x0802), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0068), WRITE_COEF(0x28, 0x890f), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0069), WRITE_COEF(0x28, 0xe021), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0070), WRITE_COEF(0x28, 0x8012), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0071), WRITE_COEF(0x28, 0x3450), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0072), WRITE_COEF(0x28, 0x0123), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0073), WRITE_COEF(0x28, 0x4543), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0074), WRITE_COEF(0x28, 0x2100), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0075), WRITE_COEF(0x28, 0x4321), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0076), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0050), WRITE_COEF(0x28, 0x8200), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003a), WRITE_COEF(0x28, 0x1dfe), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0051), WRITE_COEF(0x28, 0x0707), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0052), WRITE_COEF(0x28, 0x4090), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x0090), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0x721f), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0012), WRITE_COEF(0x28, 0xebeb), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x009e), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0060), WRITE_COEF(0x28, 0x2213), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x0006), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003f), WRITE_COEF(0x28, 0x3000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0004), WRITE_COEF(0x28, 0x0500), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0040), WRITE_COEF(0x28, 0x800c), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0046), WRITE_COEF(0x28, 0xc22e), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x004b), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0050), WRITE_COEF(0x28, 0x82ec), WRITE_COEF(0x29, 0xb024), + }; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_apply_pincfgs(codec, pincfgs); + alc295_fixup_disable_dac3(codec, fix, action); + break; + case HDA_FIXUP_ACT_INIT: + alc_process_coef_fw(codec, alc295_hp_speakers_coefs); + break; + } +} diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 4a9ea34b11b5..240a1a43e048 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -5394,6 +5394,9 @@ static void alc274_fixup_bind_dacs(struct hda_codec *codec, /* for dell wmi mic mute led */ #include "dell_wmi_helper.c" +/* for alc295_fixup_hp_top_speakers */ +#include "hp_x360_helper.c" + enum { ALC269_FIXUP_SONY_VAIO, ALC275_FIXUP_SONY_VAIO_GPIO2, @@ -5514,6 +5517,7 @@ enum { ALC298_FIXUP_TPT470_DOCK, ALC255_FIXUP_DUMMY_LINEOUT_VERB, ALC255_FIXUP_DELL_HEADSET_MIC, + ALC295_FIXUP_HP_X360, }; static const struct hda_fixup alc269_fixups[] = { @@ -6387,6 +6391,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MIC }, + [ALC295_FIXUP_HP_X360] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc295_fixup_hp_top_speakers, + .chained = true, + .chain_id = ALC269_FIXUP_HP_MUTE_LED_MIC3 + } }; static const struct snd_pci_quirk alc269_fixup_tbl[] = { @@ -6506,7 +6516,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x2337, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x221c, "HP EliteBook 755 G2", ALC280_FIXUP_HP_HEADSET_MIC), SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC), - SND_PCI_QUIRK(0x103c, 0x827e, "HP x360", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x827e, "HP x360", ALC295_FIXUP_HP_X360), SND_PCI_QUIRK(0x103c, 0x82bf, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x82c0, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), -- cgit v1.2.3 From 986376b68dcc95bb7df60ad30c2353c1f7578fa5 Mon Sep 17 00:00:00 2001 From: Hui Wang Date: Wed, 30 May 2018 12:33:07 +0800 Subject: ALSA: hda/realtek - Enable mic-mute hotkey for several Lenovo AIOs We have several Lenovo AIOs like M810z, M820z and M920z, they have the same design for mic-mute hotkey and led and they use the same codec with the same pin configuration, so use the pin conf table to apply fix to all of them. Fixes: 29693efcea0f ("ALSA: hda - Fix micmute hotkey problem for a lenovo AIO machine") Cc: Signed-off-by: Hui Wang Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 240a1a43e048..d64dcb9a4c99 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -6603,7 +6603,6 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x312f, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION), SND_PCI_QUIRK(0x17aa, 0x3138, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION), SND_PCI_QUIRK(0x17aa, 0x313c, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION), - SND_PCI_QUIRK(0x17aa, 0x3112, "ThinkCentre AIO", ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY), SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI), SND_PCI_QUIRK(0x17aa, 0x3977, "IdeaPad S210", ALC283_FIXUP_INT_MIC), SND_PCI_QUIRK(0x17aa, 0x3978, "IdeaPad Y410P", ALC269_FIXUP_NO_SHUTUP), @@ -6775,6 +6774,11 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { {0x1b, 0x01111010}, {0x1e, 0x01451130}, {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0235, 0x17aa, "Lenovo", ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY, + {0x12, 0x90a60140}, + {0x14, 0x90170110}, + {0x19, 0x02a11030}, + {0x21, 0x02211020}), SND_HDA_PIN_QUIRK(0x10ec0236, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, {0x12, 0x90a60140}, {0x14, 0x90170110}, -- cgit v1.2.3 From e46dcbb17d790008a12b1c18f6235d03d1dd635b Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 30 May 2018 13:36:55 +0300 Subject: ALSA: xen-front: freeing an error pointer kfree() doesn't accept error pointers so I've set "str" to NULL on these paths. Fixes: fd3b36045c2c ("ALSA: xen-front: Read sound driver configuration from Xen store") Signed-off-by: Dan Carpenter Reviewed-by: Oleksandr Andrushchenko Signed-off-by: Takashi Iwai --- sound/xen/xen_snd_front_cfg.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound') diff --git a/sound/xen/xen_snd_front_cfg.c b/sound/xen/xen_snd_front_cfg.c index 684b5f1d51ac..eda077c8087a 100644 --- a/sound/xen/xen_snd_front_cfg.c +++ b/sound/xen/xen_snd_front_cfg.c @@ -306,6 +306,7 @@ static int cfg_get_stream_type(const char *path, int index, str = xenbus_read(XBT_NIL, stream_path, XENSND_FIELD_TYPE, NULL); if (IS_ERR(str)) { ret = PTR_ERR(str); + str = NULL; goto fail; } @@ -347,6 +348,7 @@ static int cfg_stream(struct xen_snd_front_info *front_info, str = xenbus_read(XBT_NIL, stream_path, XENSND_FIELD_TYPE, NULL); if (IS_ERR(str)) { ret = PTR_ERR(str); + str = NULL; goto fail; } -- cgit v1.2.3 From 0d5bcfc9974ad8ce0fc77a95a53ab704faa0dfb7 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 31 May 2018 09:25:07 +0300 Subject: ALSA: xen-front: fix a loop timeout We want the loop to exit when "to" is set to zero, but in the current code it's set to -1. Also I tweaked the indenting so it doesn't look like we're passing "--to" to xenbus_read_unsigned(). Fixes: cc3196ae197c ("ALSA: xen-front: Introduce Xen para-virtualized sound frontend driver") Signed-off-by: Dan Carpenter Reviewed-by: Oleksandr Andrushchenko Signed-off-by: Takashi Iwai --- sound/xen/xen_snd_front.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/xen/xen_snd_front.c b/sound/xen/xen_snd_front.c index c18973a9bc9b..b089b13b5160 100644 --- a/sound/xen/xen_snd_front.c +++ b/sound/xen/xen_snd_front.c @@ -334,7 +334,7 @@ static int xen_drv_remove(struct xenbus_device *dev) */ while ((xenbus_read_unsigned(front_info->xb_dev->otherend, "state", XenbusStateUnknown) != XenbusStateInitWait) && - to--) + --to) msleep(10); if (!to) { -- cgit v1.2.3 From 9ee92f5355f8264de20c8337823aff918c717aee Mon Sep 17 00:00:00 2001 From: Yisheng Xie Date: Thu, 31 May 2018 19:11:20 +0800 Subject: ALSA: oxygen: use match_string() helper match_string() returns the index of an array for a matching string, which can be used instead of open coded variant. Signed-off-by: Yisheng Xie Signed-off-by: Takashi Iwai --- sound/pci/oxygen/oxygen_mixer.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/pci/oxygen/oxygen_mixer.c b/sound/pci/oxygen/oxygen_mixer.c index 4ca12665ff73..81af21ac1439 100644 --- a/sound/pci/oxygen/oxygen_mixer.c +++ b/sound/pci/oxygen/oxygen_mixer.c @@ -1052,10 +1052,10 @@ static int add_controls(struct oxygen *chip, [CONTROL_CD_CAPTURE_SWITCH] = "CD Capture Switch", [CONTROL_AUX_CAPTURE_SWITCH] = "Aux Capture Switch", }; - unsigned int i, j; + unsigned int i; struct snd_kcontrol_new template; struct snd_kcontrol *ctl; - int err; + int j, err; for (i = 0; i < count; ++i) { template = controls[i]; @@ -1086,11 +1086,11 @@ static int add_controls(struct oxygen *chip, err = snd_ctl_add(chip->card, ctl); if (err < 0) return err; - for (j = 0; j < CONTROL_COUNT; ++j) - if (!strcmp(ctl->id.name, known_ctl_names[j])) { - chip->controls[j] = ctl; - ctl->private_free = oxygen_any_ctl_free; - } + j = match_string(known_ctl_names, CONTROL_COUNT, ctl->id.name); + if (j >= 0) { + chip->controls[j] = ctl; + ctl->private_free = oxygen_any_ctl_free; + } } return 0; } -- cgit v1.2.3 From a3aa60d511746bd6c0d0366d4eb90a7998bcde8b Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Thu, 31 May 2018 15:35:18 -0700 Subject: ALSA: hda - Handle kzalloc() failure in snd_hda_attach_pcm_stream() When 'kzalloc()' fails in 'snd_hda_attach_pcm_stream()', a new pcm instance is created without setting its operators via 'snd_pcm_set_ops()'. Following operations on the new pcm instance can trigger kernel null pointer dereferences and cause kernel oops. This bug was found with my work on building a gray-box fault-injection tool for linux-kernel-module binaries. A kernel null pointer dereference was confirmed from line 'substream->ops->open()' in function 'snd_pcm_open_substream()' in file 'sound/core/pcm_native.c'. This patch fixes the bug by calling 'snd_device_free()' in the error handling path of 'kzalloc()', which removes the new pcm instance from the snd card before returns with an error code. Signed-off-by: Bo Chen Cc: Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_controller.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c index d1eb14842340..a12e594d4e3b 100644 --- a/sound/pci/hda/hda_controller.c +++ b/sound/pci/hda/hda_controller.c @@ -748,8 +748,10 @@ int snd_hda_attach_pcm_stream(struct hda_bus *_bus, struct hda_codec *codec, return err; strlcpy(pcm->name, cpcm->name, sizeof(pcm->name)); apcm = kzalloc(sizeof(*apcm), GFP_KERNEL); - if (apcm == NULL) + if (apcm == NULL) { + snd_device_free(chip->card, pcm); return -ENOMEM; + } apcm->chip = chip; apcm->pcm = pcm; apcm->codec = codec; -- cgit v1.2.3 From ceec4684085a9e4dc60439d84ab47ce260444804 Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Fri, 1 Jun 2018 16:32:30 +0100 Subject: ALSA: pci/hda: Remove unused, broken, header file sound/pci/hda/local.h seems to be an earlier version of sound/hda/local.h; it was added at the same time but doesn't seem to have ever been used (within the git history). Most of its macros depend on a hdac_read_parm() function which is not defined anywhere. Signed-off-by: Ben Hutchings Signed-off-by: Takashi Iwai --- sound/pci/hda/local.h | 40 ---------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 sound/pci/hda/local.h (limited to 'sound') diff --git a/sound/pci/hda/local.h b/sound/pci/hda/local.h deleted file mode 100644 index 3b8b7d78f9e0..000000000000 --- a/sound/pci/hda/local.h +++ /dev/null @@ -1,40 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - */ - -#ifndef __HDAC_LOCAL_H -#define __HDAC_LOCAL_H - -int hdac_read_parm(struct hdac_device *codec, hda_nid_t nid, int parm); - -#define get_wcaps(codec, nid) \ - hdac_read_parm(codec, nid, AC_PAR_AUDIO_WIDGET_CAP) -/* get the widget type from widget capability bits */ -static inline int get_wcaps_type(unsigned int wcaps) -{ - if (!wcaps) - return -1; /* invalid type */ - return (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; -} - -#define get_pin_caps(codec, nid) \ - hdac_read_parm(codec, nid, AC_PAR_PIN_CAP) - -static inline -unsigned int get_pin_cfg(struct hdac_device *codec, hda_nid_t nid) -{ - unsigned int val; - - if (snd_hdac_read(codec, nid, AC_VERB_GET_CONFIG_DEFAULT, 0, &val)) - return -1; - return val; -} - -#define get_amp_caps(codec, nid, dir) \ - hdac_read_parm(codec, nid, (dir) == HDA_OUTPUT ? \ - AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP) - -#define get_power_caps(codec, nid) \ - hdac_read_parm(codec, nid, AC_PAR_POWER_STATE) - -#endif /* __HDAC_LOCAL_H */ -- cgit v1.2.3