]>
Commit | Line | Data |
---|---|---|
19325fed AY |
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 | } |