Anton Yakovlev | 19325fe | 2021-03-02 17:47:08 +0100 | [diff] [blame] | 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 | } |