162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * virtio-snd: Virtio sound device 462306a36Sopenharmony_ci * Copyright (C) 2021 OpenSynergy GmbH 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci#include <linux/virtio_config.h> 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include "virtio_card.h" 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci/* VirtIO->ALSA channel position map */ 1162306a36Sopenharmony_cistatic const u8 g_v2a_position_map[] = { 1262306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_NONE] = SNDRV_CHMAP_UNKNOWN, 1362306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_NA] = SNDRV_CHMAP_NA, 1462306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_MONO] = SNDRV_CHMAP_MONO, 1562306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_FL] = SNDRV_CHMAP_FL, 1662306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_FR] = SNDRV_CHMAP_FR, 1762306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_RL] = SNDRV_CHMAP_RL, 1862306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_RR] = SNDRV_CHMAP_RR, 1962306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_FC] = SNDRV_CHMAP_FC, 2062306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_LFE] = SNDRV_CHMAP_LFE, 2162306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_SL] = SNDRV_CHMAP_SL, 2262306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_SR] = SNDRV_CHMAP_SR, 2362306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_RC] = SNDRV_CHMAP_RC, 2462306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_FLC] = SNDRV_CHMAP_FLC, 2562306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_FRC] = SNDRV_CHMAP_FRC, 2662306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_RLC] = SNDRV_CHMAP_RLC, 2762306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_RRC] = SNDRV_CHMAP_RRC, 2862306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_FLW] = SNDRV_CHMAP_FLW, 2962306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_FRW] = SNDRV_CHMAP_FRW, 3062306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_FLH] = SNDRV_CHMAP_FLH, 3162306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_FCH] = SNDRV_CHMAP_FCH, 3262306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_FRH] = SNDRV_CHMAP_FRH, 3362306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_TC] = SNDRV_CHMAP_TC, 3462306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_TFL] = SNDRV_CHMAP_TFL, 3562306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_TFR] = SNDRV_CHMAP_TFR, 3662306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_TFC] = SNDRV_CHMAP_TFC, 3762306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_TRL] = SNDRV_CHMAP_TRL, 3862306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_TRR] = SNDRV_CHMAP_TRR, 3962306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_TRC] = SNDRV_CHMAP_TRC, 4062306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_TFLC] = SNDRV_CHMAP_TFLC, 4162306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_TFRC] = SNDRV_CHMAP_TFRC, 4262306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_TSL] = SNDRV_CHMAP_TSL, 4362306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_TSR] = SNDRV_CHMAP_TSR, 4462306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_LLFE] = SNDRV_CHMAP_LLFE, 4562306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_RLFE] = SNDRV_CHMAP_RLFE, 4662306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_BC] = SNDRV_CHMAP_BC, 4762306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_BLC] = SNDRV_CHMAP_BLC, 4862306a36Sopenharmony_ci [VIRTIO_SND_CHMAP_BRC] = SNDRV_CHMAP_BRC 4962306a36Sopenharmony_ci}; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci/** 5262306a36Sopenharmony_ci * virtsnd_chmap_parse_cfg() - Parse the channel map configuration. 5362306a36Sopenharmony_ci * @snd: VirtIO sound device. 5462306a36Sopenharmony_ci * 5562306a36Sopenharmony_ci * This function is called during initial device initialization. 5662306a36Sopenharmony_ci * 5762306a36Sopenharmony_ci * Context: Any context that permits to sleep. 5862306a36Sopenharmony_ci * Return: 0 on success, -errno on failure. 5962306a36Sopenharmony_ci */ 6062306a36Sopenharmony_ciint virtsnd_chmap_parse_cfg(struct virtio_snd *snd) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci struct virtio_device *vdev = snd->vdev; 6362306a36Sopenharmony_ci u32 i; 6462306a36Sopenharmony_ci int rc; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci virtio_cread_le(vdev, struct virtio_snd_config, chmaps, &snd->nchmaps); 6762306a36Sopenharmony_ci if (!snd->nchmaps) 6862306a36Sopenharmony_ci return 0; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci snd->chmaps = devm_kcalloc(&vdev->dev, snd->nchmaps, 7162306a36Sopenharmony_ci sizeof(*snd->chmaps), GFP_KERNEL); 7262306a36Sopenharmony_ci if (!snd->chmaps) 7362306a36Sopenharmony_ci return -ENOMEM; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_CHMAP_INFO, 0, 7662306a36Sopenharmony_ci snd->nchmaps, sizeof(*snd->chmaps), 7762306a36Sopenharmony_ci snd->chmaps); 7862306a36Sopenharmony_ci if (rc) 7962306a36Sopenharmony_ci return rc; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci /* Count the number of channel maps per each PCM device/stream. */ 8262306a36Sopenharmony_ci for (i = 0; i < snd->nchmaps; ++i) { 8362306a36Sopenharmony_ci struct virtio_snd_chmap_info *info = &snd->chmaps[i]; 8462306a36Sopenharmony_ci u32 nid = le32_to_cpu(info->hdr.hda_fn_nid); 8562306a36Sopenharmony_ci struct virtio_pcm *vpcm; 8662306a36Sopenharmony_ci struct virtio_pcm_stream *vs; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci vpcm = virtsnd_pcm_find_or_create(snd, nid); 8962306a36Sopenharmony_ci if (IS_ERR(vpcm)) 9062306a36Sopenharmony_ci return PTR_ERR(vpcm); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci switch (info->direction) { 9362306a36Sopenharmony_ci case VIRTIO_SND_D_OUTPUT: 9462306a36Sopenharmony_ci vs = &vpcm->streams[SNDRV_PCM_STREAM_PLAYBACK]; 9562306a36Sopenharmony_ci break; 9662306a36Sopenharmony_ci case VIRTIO_SND_D_INPUT: 9762306a36Sopenharmony_ci vs = &vpcm->streams[SNDRV_PCM_STREAM_CAPTURE]; 9862306a36Sopenharmony_ci break; 9962306a36Sopenharmony_ci default: 10062306a36Sopenharmony_ci dev_err(&vdev->dev, 10162306a36Sopenharmony_ci "chmap #%u: unknown direction (%u)\n", i, 10262306a36Sopenharmony_ci info->direction); 10362306a36Sopenharmony_ci return -EINVAL; 10462306a36Sopenharmony_ci } 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci vs->nchmaps++; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci return 0; 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci/** 11362306a36Sopenharmony_ci * virtsnd_chmap_add_ctls() - Create an ALSA control for channel maps. 11462306a36Sopenharmony_ci * @pcm: ALSA PCM device. 11562306a36Sopenharmony_ci * @direction: PCM stream direction (SNDRV_PCM_STREAM_XXX). 11662306a36Sopenharmony_ci * @vs: VirtIO PCM stream. 11762306a36Sopenharmony_ci * 11862306a36Sopenharmony_ci * Context: Any context. 11962306a36Sopenharmony_ci * Return: 0 on success, -errno on failure. 12062306a36Sopenharmony_ci */ 12162306a36Sopenharmony_cistatic int virtsnd_chmap_add_ctls(struct snd_pcm *pcm, int direction, 12262306a36Sopenharmony_ci struct virtio_pcm_stream *vs) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci u32 i; 12562306a36Sopenharmony_ci int max_channels = 0; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci for (i = 0; i < vs->nchmaps; i++) 12862306a36Sopenharmony_ci if (max_channels < vs->chmaps[i].channels) 12962306a36Sopenharmony_ci max_channels = vs->chmaps[i].channels; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci return snd_pcm_add_chmap_ctls(pcm, direction, vs->chmaps, max_channels, 13262306a36Sopenharmony_ci 0, NULL); 13362306a36Sopenharmony_ci} 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci/** 13662306a36Sopenharmony_ci * virtsnd_chmap_build_devs() - Build ALSA controls for channel maps. 13762306a36Sopenharmony_ci * @snd: VirtIO sound device. 13862306a36Sopenharmony_ci * 13962306a36Sopenharmony_ci * Context: Any context. 14062306a36Sopenharmony_ci * Return: 0 on success, -errno on failure. 14162306a36Sopenharmony_ci */ 14262306a36Sopenharmony_ciint virtsnd_chmap_build_devs(struct virtio_snd *snd) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci struct virtio_device *vdev = snd->vdev; 14562306a36Sopenharmony_ci struct virtio_pcm *vpcm; 14662306a36Sopenharmony_ci struct virtio_pcm_stream *vs; 14762306a36Sopenharmony_ci u32 i; 14862306a36Sopenharmony_ci int rc; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci /* Allocate channel map elements per each PCM device/stream. */ 15162306a36Sopenharmony_ci list_for_each_entry(vpcm, &snd->pcm_list, list) { 15262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(vpcm->streams); ++i) { 15362306a36Sopenharmony_ci vs = &vpcm->streams[i]; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci if (!vs->nchmaps) 15662306a36Sopenharmony_ci continue; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci vs->chmaps = devm_kcalloc(&vdev->dev, vs->nchmaps + 1, 15962306a36Sopenharmony_ci sizeof(*vs->chmaps), 16062306a36Sopenharmony_ci GFP_KERNEL); 16162306a36Sopenharmony_ci if (!vs->chmaps) 16262306a36Sopenharmony_ci return -ENOMEM; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci vs->nchmaps = 0; 16562306a36Sopenharmony_ci } 16662306a36Sopenharmony_ci } 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci /* Initialize channel maps per each PCM device/stream. */ 16962306a36Sopenharmony_ci for (i = 0; i < snd->nchmaps; ++i) { 17062306a36Sopenharmony_ci struct virtio_snd_chmap_info *info = &snd->chmaps[i]; 17162306a36Sopenharmony_ci unsigned int channels = info->channels; 17262306a36Sopenharmony_ci unsigned int ch; 17362306a36Sopenharmony_ci struct snd_pcm_chmap_elem *chmap; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci vpcm = virtsnd_pcm_find(snd, le32_to_cpu(info->hdr.hda_fn_nid)); 17662306a36Sopenharmony_ci if (IS_ERR(vpcm)) 17762306a36Sopenharmony_ci return PTR_ERR(vpcm); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci if (info->direction == VIRTIO_SND_D_OUTPUT) 18062306a36Sopenharmony_ci vs = &vpcm->streams[SNDRV_PCM_STREAM_PLAYBACK]; 18162306a36Sopenharmony_ci else 18262306a36Sopenharmony_ci vs = &vpcm->streams[SNDRV_PCM_STREAM_CAPTURE]; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci chmap = &vs->chmaps[vs->nchmaps++]; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci if (channels > ARRAY_SIZE(chmap->map)) 18762306a36Sopenharmony_ci channels = ARRAY_SIZE(chmap->map); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci chmap->channels = channels; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci for (ch = 0; ch < channels; ++ch) { 19262306a36Sopenharmony_ci u8 position = info->positions[ch]; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci if (position >= ARRAY_SIZE(g_v2a_position_map)) 19562306a36Sopenharmony_ci return -EINVAL; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci chmap->map[ch] = g_v2a_position_map[position]; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci } 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci /* Create an ALSA control per each PCM device/stream. */ 20262306a36Sopenharmony_ci list_for_each_entry(vpcm, &snd->pcm_list, list) { 20362306a36Sopenharmony_ci if (!vpcm->pcm) 20462306a36Sopenharmony_ci continue; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(vpcm->streams); ++i) { 20762306a36Sopenharmony_ci vs = &vpcm->streams[i]; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci if (!vs->nchmaps) 21062306a36Sopenharmony_ci continue; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci rc = virtsnd_chmap_add_ctls(vpcm->pcm, i, vs); 21362306a36Sopenharmony_ci if (rc) 21462306a36Sopenharmony_ci return rc; 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci return 0; 21962306a36Sopenharmony_ci} 220