1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * virtio-snd: Virtio sound device 4 * Copyright (C) 2021 OpenSynergy GmbH 5 */ 6 #include <linux/virtio_config.h> 7 8 #include "virtio_card.h" 9 10 /* VirtIO->ALSA channel position map */ 11 static const u8 g_v2a_position_map[] = { 12 [VIRTIO_SND_CHMAP_NONE] = SNDRV_CHMAP_UNKNOWN, 13 [VIRTIO_SND_CHMAP_NA] = SNDRV_CHMAP_NA, 14 [VIRTIO_SND_CHMAP_MONO] = SNDRV_CHMAP_MONO, 15 [VIRTIO_SND_CHMAP_FL] = SNDRV_CHMAP_FL, 16 [VIRTIO_SND_CHMAP_FR] = SNDRV_CHMAP_FR, 17 [VIRTIO_SND_CHMAP_RL] = SNDRV_CHMAP_RL, 18 [VIRTIO_SND_CHMAP_RR] = SNDRV_CHMAP_RR, 19 [VIRTIO_SND_CHMAP_FC] = SNDRV_CHMAP_FC, 20 [VIRTIO_SND_CHMAP_LFE] = SNDRV_CHMAP_LFE, 21 [VIRTIO_SND_CHMAP_SL] = SNDRV_CHMAP_SL, 22 [VIRTIO_SND_CHMAP_SR] = SNDRV_CHMAP_SR, 23 [VIRTIO_SND_CHMAP_RC] = SNDRV_CHMAP_RC, 24 [VIRTIO_SND_CHMAP_FLC] = SNDRV_CHMAP_FLC, 25 [VIRTIO_SND_CHMAP_FRC] = SNDRV_CHMAP_FRC, 26 [VIRTIO_SND_CHMAP_RLC] = SNDRV_CHMAP_RLC, 27 [VIRTIO_SND_CHMAP_RRC] = SNDRV_CHMAP_RRC, 28 [VIRTIO_SND_CHMAP_FLW] = SNDRV_CHMAP_FLW, 29 [VIRTIO_SND_CHMAP_FRW] = SNDRV_CHMAP_FRW, 30 [VIRTIO_SND_CHMAP_FLH] = SNDRV_CHMAP_FLH, 31 [VIRTIO_SND_CHMAP_FCH] = SNDRV_CHMAP_FCH, 32 [VIRTIO_SND_CHMAP_FRH] = SNDRV_CHMAP_FRH, 33 [VIRTIO_SND_CHMAP_TC] = SNDRV_CHMAP_TC, 34 [VIRTIO_SND_CHMAP_TFL] = SNDRV_CHMAP_TFL, 35 [VIRTIO_SND_CHMAP_TFR] = SNDRV_CHMAP_TFR, 36 [VIRTIO_SND_CHMAP_TFC] = SNDRV_CHMAP_TFC, 37 [VIRTIO_SND_CHMAP_TRL] = SNDRV_CHMAP_TRL, 38 [VIRTIO_SND_CHMAP_TRR] = SNDRV_CHMAP_TRR, 39 [VIRTIO_SND_CHMAP_TRC] = SNDRV_CHMAP_TRC, 40 [VIRTIO_SND_CHMAP_TFLC] = SNDRV_CHMAP_TFLC, 41 [VIRTIO_SND_CHMAP_TFRC] = SNDRV_CHMAP_TFRC, 42 [VIRTIO_SND_CHMAP_TSL] = SNDRV_CHMAP_TSL, 43 [VIRTIO_SND_CHMAP_TSR] = SNDRV_CHMAP_TSR, 44 [VIRTIO_SND_CHMAP_LLFE] = SNDRV_CHMAP_LLFE, 45 [VIRTIO_SND_CHMAP_RLFE] = SNDRV_CHMAP_RLFE, 46 [VIRTIO_SND_CHMAP_BC] = SNDRV_CHMAP_BC, 47 [VIRTIO_SND_CHMAP_BLC] = SNDRV_CHMAP_BLC, 48 [VIRTIO_SND_CHMAP_BRC] = SNDRV_CHMAP_BRC 49 }; 50 51 /** 52 * virtsnd_chmap_parse_cfg() - Parse the channel map configuration. 53 * @snd: VirtIO sound device. 54 * 55 * This function is called during initial device initialization. 56 * 57 * Context: Any context that permits to sleep. 58 * Return: 0 on success, -errno on failure. 59 */ 60 int virtsnd_chmap_parse_cfg(struct virtio_snd *snd) 61 { 62 struct virtio_device *vdev = snd->vdev; 63 u32 i; 64 int rc; 65 66 virtio_cread_le(vdev, struct virtio_snd_config, chmaps, &snd->nchmaps); 67 if (!snd->nchmaps) 68 return 0; 69 70 snd->chmaps = devm_kcalloc(&vdev->dev, snd->nchmaps, 71 sizeof(*snd->chmaps), GFP_KERNEL); 72 if (!snd->chmaps) 73 return -ENOMEM; 74 75 rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_CHMAP_INFO, 0, 76 snd->nchmaps, sizeof(*snd->chmaps), 77 snd->chmaps); 78 if (rc) 79 return rc; 80 81 /* Count the number of channel maps per each PCM device/stream. */ 82 for (i = 0; i < snd->nchmaps; ++i) { 83 struct virtio_snd_chmap_info *info = &snd->chmaps[i]; 84 u32 nid = le32_to_cpu(info->hdr.hda_fn_nid); 85 struct virtio_pcm *vpcm; 86 struct virtio_pcm_stream *vs; 87 88 vpcm = virtsnd_pcm_find_or_create(snd, nid); 89 if (IS_ERR(vpcm)) 90 return PTR_ERR(vpcm); 91 92 switch (info->direction) { 93 case VIRTIO_SND_D_OUTPUT: 94 vs = &vpcm->streams[SNDRV_PCM_STREAM_PLAYBACK]; 95 break; 96 case VIRTIO_SND_D_INPUT: 97 vs = &vpcm->streams[SNDRV_PCM_STREAM_CAPTURE]; 98 break; 99 default: 100 dev_err(&vdev->dev, 101 "chmap #%u: unknown direction (%u)\n", i, 102 info->direction); 103 return -EINVAL; 104 } 105 106 vs->nchmaps++; 107 } 108 109 return 0; 110 } 111 112 /** 113 * virtsnd_chmap_add_ctls() - Create an ALSA control for channel maps. 114 * @pcm: ALSA PCM device. 115 * @direction: PCM stream direction (SNDRV_PCM_STREAM_XXX). 116 * @vs: VirtIO PCM stream. 117 * 118 * Context: Any context. 119 * Return: 0 on success, -errno on failure. 120 */ 121 static int virtsnd_chmap_add_ctls(struct snd_pcm *pcm, int direction, 122 struct virtio_pcm_stream *vs) 123 { 124 u32 i; 125 int max_channels = 0; 126 127 for (i = 0; i < vs->nchmaps; i++) 128 if (max_channels < vs->chmaps[i].channels) 129 max_channels = vs->chmaps[i].channels; 130 131 return snd_pcm_add_chmap_ctls(pcm, direction, vs->chmaps, max_channels, 132 0, NULL); 133 } 134 135 /** 136 * virtsnd_chmap_build_devs() - Build ALSA controls for channel maps. 137 * @snd: VirtIO sound device. 138 * 139 * Context: Any context. 140 * Return: 0 on success, -errno on failure. 141 */ 142 int virtsnd_chmap_build_devs(struct virtio_snd *snd) 143 { 144 struct virtio_device *vdev = snd->vdev; 145 struct virtio_pcm *vpcm; 146 struct virtio_pcm_stream *vs; 147 u32 i; 148 int rc; 149 150 /* Allocate channel map elements per each PCM device/stream. */ 151 list_for_each_entry(vpcm, &snd->pcm_list, list) { 152 for (i = 0; i < ARRAY_SIZE(vpcm->streams); ++i) { 153 vs = &vpcm->streams[i]; 154 155 if (!vs->nchmaps) 156 continue; 157 158 vs->chmaps = devm_kcalloc(&vdev->dev, vs->nchmaps + 1, 159 sizeof(*vs->chmaps), 160 GFP_KERNEL); 161 if (!vs->chmaps) 162 return -ENOMEM; 163 164 vs->nchmaps = 0; 165 } 166 } 167 168 /* Initialize channel maps per each PCM device/stream. */ 169 for (i = 0; i < snd->nchmaps; ++i) { 170 struct virtio_snd_chmap_info *info = &snd->chmaps[i]; 171 unsigned int channels = info->channels; 172 unsigned int ch; 173 struct snd_pcm_chmap_elem *chmap; 174 175 vpcm = virtsnd_pcm_find(snd, le32_to_cpu(info->hdr.hda_fn_nid)); 176 if (IS_ERR(vpcm)) 177 return PTR_ERR(vpcm); 178 179 if (info->direction == VIRTIO_SND_D_OUTPUT) 180 vs = &vpcm->streams[SNDRV_PCM_STREAM_PLAYBACK]; 181 else 182 vs = &vpcm->streams[SNDRV_PCM_STREAM_CAPTURE]; 183 184 chmap = &vs->chmaps[vs->nchmaps++]; 185 186 if (channels > ARRAY_SIZE(chmap->map)) 187 channels = ARRAY_SIZE(chmap->map); 188 189 chmap->channels = channels; 190 191 for (ch = 0; ch < channels; ++ch) { 192 u8 position = info->positions[ch]; 193 194 if (position >= ARRAY_SIZE(g_v2a_position_map)) 195 return -EINVAL; 196 197 chmap->map[ch] = g_v2a_position_map[position]; 198 } 199 } 200 201 /* Create an ALSA control per each PCM device/stream. */ 202 list_for_each_entry(vpcm, &snd->pcm_list, list) { 203 if (!vpcm->pcm) 204 continue; 205 206 for (i = 0; i < ARRAY_SIZE(vpcm->streams); ++i) { 207 vs = &vpcm->streams[i]; 208 209 if (!vs->nchmaps) 210 continue; 211 212 rc = virtsnd_chmap_add_ctls(vpcm->pcm, i, vs); 213 if (rc) 214 return rc; 215 } 216 } 217 218 return 0; 219 } 220