Merge tag 'asoc-fix-v5.13-rc4' of https://git.kernel.org/pub/scm/linux/kernel/git...
[platform/kernel/linux-rpi.git] / sound / virtio / virtio_chmap.c
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 }