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(dev: &vdev->dev, n: snd->nchmaps, |
71 | size: sizeof(*snd->chmaps), GFP_KERNEL); |
72 | if (!snd->chmaps) |
73 | return -ENOMEM; |
74 | |
75 | rc = virtsnd_ctl_query_info(snd, command: VIRTIO_SND_R_CHMAP_INFO, start_id: 0, |
76 | count: snd->nchmaps, size: sizeof(*snd->chmaps), |
77 | info: 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(ptr: vpcm)) |
90 | return PTR_ERR(ptr: 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, stream: direction, chmap: vs->chmaps, max_channels, |
132 | private_value: 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(dev: &vdev->dev, n: vs->nchmaps + 1, |
159 | size: 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(ptr: vpcm)) |
177 | return PTR_ERR(ptr: 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(pcm: vpcm->pcm, direction: i, vs); |
213 | if (rc) |
214 | return rc; |
215 | } |
216 | } |
217 | |
218 | return 0; |
219 | } |
220 | |