Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /*
0003  * virtio-snd: Virtio sound device
0004  * Copyright (C) 2021 OpenSynergy GmbH
0005  */
0006 #include <linux/virtio_config.h>
0007 
0008 #include "virtio_card.h"
0009 
0010 /* VirtIO->ALSA channel position map */
0011 static const u8 g_v2a_position_map[] = {
0012     [VIRTIO_SND_CHMAP_NONE] = SNDRV_CHMAP_UNKNOWN,
0013     [VIRTIO_SND_CHMAP_NA] = SNDRV_CHMAP_NA,
0014     [VIRTIO_SND_CHMAP_MONO] = SNDRV_CHMAP_MONO,
0015     [VIRTIO_SND_CHMAP_FL] = SNDRV_CHMAP_FL,
0016     [VIRTIO_SND_CHMAP_FR] = SNDRV_CHMAP_FR,
0017     [VIRTIO_SND_CHMAP_RL] = SNDRV_CHMAP_RL,
0018     [VIRTIO_SND_CHMAP_RR] = SNDRV_CHMAP_RR,
0019     [VIRTIO_SND_CHMAP_FC] = SNDRV_CHMAP_FC,
0020     [VIRTIO_SND_CHMAP_LFE] = SNDRV_CHMAP_LFE,
0021     [VIRTIO_SND_CHMAP_SL] = SNDRV_CHMAP_SL,
0022     [VIRTIO_SND_CHMAP_SR] = SNDRV_CHMAP_SR,
0023     [VIRTIO_SND_CHMAP_RC] = SNDRV_CHMAP_RC,
0024     [VIRTIO_SND_CHMAP_FLC] = SNDRV_CHMAP_FLC,
0025     [VIRTIO_SND_CHMAP_FRC] = SNDRV_CHMAP_FRC,
0026     [VIRTIO_SND_CHMAP_RLC] = SNDRV_CHMAP_RLC,
0027     [VIRTIO_SND_CHMAP_RRC] = SNDRV_CHMAP_RRC,
0028     [VIRTIO_SND_CHMAP_FLW] = SNDRV_CHMAP_FLW,
0029     [VIRTIO_SND_CHMAP_FRW] = SNDRV_CHMAP_FRW,
0030     [VIRTIO_SND_CHMAP_FLH] = SNDRV_CHMAP_FLH,
0031     [VIRTIO_SND_CHMAP_FCH] = SNDRV_CHMAP_FCH,
0032     [VIRTIO_SND_CHMAP_FRH] = SNDRV_CHMAP_FRH,
0033     [VIRTIO_SND_CHMAP_TC] = SNDRV_CHMAP_TC,
0034     [VIRTIO_SND_CHMAP_TFL] = SNDRV_CHMAP_TFL,
0035     [VIRTIO_SND_CHMAP_TFR] = SNDRV_CHMAP_TFR,
0036     [VIRTIO_SND_CHMAP_TFC] = SNDRV_CHMAP_TFC,
0037     [VIRTIO_SND_CHMAP_TRL] = SNDRV_CHMAP_TRL,
0038     [VIRTIO_SND_CHMAP_TRR] = SNDRV_CHMAP_TRR,
0039     [VIRTIO_SND_CHMAP_TRC] = SNDRV_CHMAP_TRC,
0040     [VIRTIO_SND_CHMAP_TFLC] = SNDRV_CHMAP_TFLC,
0041     [VIRTIO_SND_CHMAP_TFRC] = SNDRV_CHMAP_TFRC,
0042     [VIRTIO_SND_CHMAP_TSL] = SNDRV_CHMAP_TSL,
0043     [VIRTIO_SND_CHMAP_TSR] = SNDRV_CHMAP_TSR,
0044     [VIRTIO_SND_CHMAP_LLFE] = SNDRV_CHMAP_LLFE,
0045     [VIRTIO_SND_CHMAP_RLFE] = SNDRV_CHMAP_RLFE,
0046     [VIRTIO_SND_CHMAP_BC] = SNDRV_CHMAP_BC,
0047     [VIRTIO_SND_CHMAP_BLC] = SNDRV_CHMAP_BLC,
0048     [VIRTIO_SND_CHMAP_BRC] = SNDRV_CHMAP_BRC
0049 };
0050 
0051 /**
0052  * virtsnd_chmap_parse_cfg() - Parse the channel map configuration.
0053  * @snd: VirtIO sound device.
0054  *
0055  * This function is called during initial device initialization.
0056  *
0057  * Context: Any context that permits to sleep.
0058  * Return: 0 on success, -errno on failure.
0059  */
0060 int virtsnd_chmap_parse_cfg(struct virtio_snd *snd)
0061 {
0062     struct virtio_device *vdev = snd->vdev;
0063     u32 i;
0064     int rc;
0065 
0066     virtio_cread_le(vdev, struct virtio_snd_config, chmaps, &snd->nchmaps);
0067     if (!snd->nchmaps)
0068         return 0;
0069 
0070     snd->chmaps = devm_kcalloc(&vdev->dev, snd->nchmaps,
0071                    sizeof(*snd->chmaps), GFP_KERNEL);
0072     if (!snd->chmaps)
0073         return -ENOMEM;
0074 
0075     rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_CHMAP_INFO, 0,
0076                     snd->nchmaps, sizeof(*snd->chmaps),
0077                     snd->chmaps);
0078     if (rc)
0079         return rc;
0080 
0081     /* Count the number of channel maps per each PCM device/stream. */
0082     for (i = 0; i < snd->nchmaps; ++i) {
0083         struct virtio_snd_chmap_info *info = &snd->chmaps[i];
0084         u32 nid = le32_to_cpu(info->hdr.hda_fn_nid);
0085         struct virtio_pcm *vpcm;
0086         struct virtio_pcm_stream *vs;
0087 
0088         vpcm = virtsnd_pcm_find_or_create(snd, nid);
0089         if (IS_ERR(vpcm))
0090             return PTR_ERR(vpcm);
0091 
0092         switch (info->direction) {
0093         case VIRTIO_SND_D_OUTPUT:
0094             vs = &vpcm->streams[SNDRV_PCM_STREAM_PLAYBACK];
0095             break;
0096         case VIRTIO_SND_D_INPUT:
0097             vs = &vpcm->streams[SNDRV_PCM_STREAM_CAPTURE];
0098             break;
0099         default:
0100             dev_err(&vdev->dev,
0101                 "chmap #%u: unknown direction (%u)\n", i,
0102                 info->direction);
0103             return -EINVAL;
0104         }
0105 
0106         vs->nchmaps++;
0107     }
0108 
0109     return 0;
0110 }
0111 
0112 /**
0113  * virtsnd_chmap_add_ctls() - Create an ALSA control for channel maps.
0114  * @pcm: ALSA PCM device.
0115  * @direction: PCM stream direction (SNDRV_PCM_STREAM_XXX).
0116  * @vs: VirtIO PCM stream.
0117  *
0118  * Context: Any context.
0119  * Return: 0 on success, -errno on failure.
0120  */
0121 static int virtsnd_chmap_add_ctls(struct snd_pcm *pcm, int direction,
0122                   struct virtio_pcm_stream *vs)
0123 {
0124     u32 i;
0125     int max_channels = 0;
0126 
0127     for (i = 0; i < vs->nchmaps; i++)
0128         if (max_channels < vs->chmaps[i].channels)
0129             max_channels = vs->chmaps[i].channels;
0130 
0131     return snd_pcm_add_chmap_ctls(pcm, direction, vs->chmaps, max_channels,
0132                       0, NULL);
0133 }
0134 
0135 /**
0136  * virtsnd_chmap_build_devs() - Build ALSA controls for channel maps.
0137  * @snd: VirtIO sound device.
0138  *
0139  * Context: Any context.
0140  * Return: 0 on success, -errno on failure.
0141  */
0142 int virtsnd_chmap_build_devs(struct virtio_snd *snd)
0143 {
0144     struct virtio_device *vdev = snd->vdev;
0145     struct virtio_pcm *vpcm;
0146     struct virtio_pcm_stream *vs;
0147     u32 i;
0148     int rc;
0149 
0150     /* Allocate channel map elements per each PCM device/stream. */
0151     list_for_each_entry(vpcm, &snd->pcm_list, list) {
0152         for (i = 0; i < ARRAY_SIZE(vpcm->streams); ++i) {
0153             vs = &vpcm->streams[i];
0154 
0155             if (!vs->nchmaps)
0156                 continue;
0157 
0158             vs->chmaps = devm_kcalloc(&vdev->dev, vs->nchmaps + 1,
0159                           sizeof(*vs->chmaps),
0160                           GFP_KERNEL);
0161             if (!vs->chmaps)
0162                 return -ENOMEM;
0163 
0164             vs->nchmaps = 0;
0165         }
0166     }
0167 
0168     /* Initialize channel maps per each PCM device/stream. */
0169     for (i = 0; i < snd->nchmaps; ++i) {
0170         struct virtio_snd_chmap_info *info = &snd->chmaps[i];
0171         unsigned int channels = info->channels;
0172         unsigned int ch;
0173         struct snd_pcm_chmap_elem *chmap;
0174 
0175         vpcm = virtsnd_pcm_find(snd, le32_to_cpu(info->hdr.hda_fn_nid));
0176         if (IS_ERR(vpcm))
0177             return PTR_ERR(vpcm);
0178 
0179         if (info->direction == VIRTIO_SND_D_OUTPUT)
0180             vs = &vpcm->streams[SNDRV_PCM_STREAM_PLAYBACK];
0181         else
0182             vs = &vpcm->streams[SNDRV_PCM_STREAM_CAPTURE];
0183 
0184         chmap = &vs->chmaps[vs->nchmaps++];
0185 
0186         if (channels > ARRAY_SIZE(chmap->map))
0187             channels = ARRAY_SIZE(chmap->map);
0188 
0189         chmap->channels = channels;
0190 
0191         for (ch = 0; ch < channels; ++ch) {
0192             u8 position = info->positions[ch];
0193 
0194             if (position >= ARRAY_SIZE(g_v2a_position_map))
0195                 return -EINVAL;
0196 
0197             chmap->map[ch] = g_v2a_position_map[position];
0198         }
0199     }
0200 
0201     /* Create an ALSA control per each PCM device/stream. */
0202     list_for_each_entry(vpcm, &snd->pcm_list, list) {
0203         if (!vpcm->pcm)
0204             continue;
0205 
0206         for (i = 0; i < ARRAY_SIZE(vpcm->streams); ++i) {
0207             vs = &vpcm->streams[i];
0208 
0209             if (!vs->nchmaps)
0210                 continue;
0211 
0212             rc = virtsnd_chmap_add_ctls(vpcm->pcm, i, vs);
0213             if (rc)
0214                 return rc;
0215         }
0216     }
0217 
0218     return 0;
0219 }