Merge tag 'sound-5.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai...
[sfrench/cifs-2.6.git] / sound / virtio / virtio_jack.c
diff --git a/sound/virtio/virtio_jack.c b/sound/virtio/virtio_jack.c
new file mode 100644 (file)
index 0000000..c69f1dc
--- /dev/null
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * virtio-snd: Virtio sound device
+ * Copyright (C) 2021 OpenSynergy GmbH
+ */
+#include <linux/virtio_config.h>
+#include <sound/jack.h>
+#include <sound/hda_verbs.h>
+
+#include "virtio_card.h"
+
+/**
+ * DOC: Implementation Status
+ *
+ * At the moment jacks have a simple implementation and can only be used to
+ * receive notifications about a plugged in/out device.
+ *
+ * VIRTIO_SND_R_JACK_REMAP
+ *   is not supported
+ */
+
+/**
+ * struct virtio_jack - VirtIO jack.
+ * @jack: Kernel jack control.
+ * @nid: Functional group node identifier.
+ * @features: Jack virtio feature bit map (1 << VIRTIO_SND_JACK_F_XXX).
+ * @defconf: Pin default configuration value.
+ * @caps: Pin capabilities value.
+ * @connected: Current jack connection status.
+ * @type: Kernel jack type (SND_JACK_XXX).
+ */
+struct virtio_jack {
+       struct snd_jack *jack;
+       u32 nid;
+       u32 features;
+       u32 defconf;
+       u32 caps;
+       bool connected;
+       int type;
+};
+
+/**
+ * virtsnd_jack_get_label() - Get the name string for the jack.
+ * @vjack: VirtIO jack.
+ *
+ * Returns the jack name based on the default pin configuration value (see HDA
+ * specification).
+ *
+ * Context: Any context.
+ * Return: Name string.
+ */
+static const char *virtsnd_jack_get_label(struct virtio_jack *vjack)
+{
+       unsigned int defconf = vjack->defconf;
+       unsigned int device =
+               (defconf & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT;
+       unsigned int location =
+               (defconf & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT;
+
+       switch (device) {
+       case AC_JACK_LINE_OUT:
+               return "Line Out";
+       case AC_JACK_SPEAKER:
+               return "Speaker";
+       case AC_JACK_HP_OUT:
+               return "Headphone";
+       case AC_JACK_CD:
+               return "CD";
+       case AC_JACK_SPDIF_OUT:
+       case AC_JACK_DIG_OTHER_OUT:
+               if (location == AC_JACK_LOC_HDMI)
+                       return "HDMI Out";
+               else
+                       return "SPDIF Out";
+       case AC_JACK_LINE_IN:
+               return "Line";
+       case AC_JACK_AUX:
+               return "Aux";
+       case AC_JACK_MIC_IN:
+               return "Mic";
+       case AC_JACK_SPDIF_IN:
+               return "SPDIF In";
+       case AC_JACK_DIG_OTHER_IN:
+               return "Digital In";
+       default:
+               return "Misc";
+       }
+}
+
+/**
+ * virtsnd_jack_get_type() - Get the type for the jack.
+ * @vjack: VirtIO jack.
+ *
+ * Returns the jack type based on the default pin configuration value (see HDA
+ * specification).
+ *
+ * Context: Any context.
+ * Return: SND_JACK_XXX value.
+ */
+static int virtsnd_jack_get_type(struct virtio_jack *vjack)
+{
+       unsigned int defconf = vjack->defconf;
+       unsigned int device =
+               (defconf & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT;
+
+       switch (device) {
+       case AC_JACK_LINE_OUT:
+       case AC_JACK_SPEAKER:
+               return SND_JACK_LINEOUT;
+       case AC_JACK_HP_OUT:
+               return SND_JACK_HEADPHONE;
+       case AC_JACK_SPDIF_OUT:
+       case AC_JACK_DIG_OTHER_OUT:
+               return SND_JACK_AVOUT;
+       case AC_JACK_MIC_IN:
+               return SND_JACK_MICROPHONE;
+       default:
+               return SND_JACK_LINEIN;
+       }
+}
+
+/**
+ * virtsnd_jack_parse_cfg() - Parse the jack configuration.
+ * @snd: VirtIO sound device.
+ *
+ * This function is called during initial device initialization.
+ *
+ * Context: Any context that permits to sleep.
+ * Return: 0 on success, -errno on failure.
+ */
+int virtsnd_jack_parse_cfg(struct virtio_snd *snd)
+{
+       struct virtio_device *vdev = snd->vdev;
+       struct virtio_snd_jack_info *info;
+       u32 i;
+       int rc;
+
+       virtio_cread_le(vdev, struct virtio_snd_config, jacks, &snd->njacks);
+       if (!snd->njacks)
+               return 0;
+
+       snd->jacks = devm_kcalloc(&vdev->dev, snd->njacks, sizeof(*snd->jacks),
+                                 GFP_KERNEL);
+       if (!snd->jacks)
+               return -ENOMEM;
+
+       info = kcalloc(snd->njacks, sizeof(*info), GFP_KERNEL);
+       if (!info)
+               return -ENOMEM;
+
+       rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_JACK_INFO, 0, snd->njacks,
+                                   sizeof(*info), info);
+       if (rc)
+               goto on_exit;
+
+       for (i = 0; i < snd->njacks; ++i) {
+               struct virtio_jack *vjack = &snd->jacks[i];
+
+               vjack->nid = le32_to_cpu(info[i].hdr.hda_fn_nid);
+               vjack->features = le32_to_cpu(info[i].features);
+               vjack->defconf = le32_to_cpu(info[i].hda_reg_defconf);
+               vjack->caps = le32_to_cpu(info[i].hda_reg_caps);
+               vjack->connected = info[i].connected;
+       }
+
+on_exit:
+       kfree(info);
+
+       return rc;
+}
+
+/**
+ * virtsnd_jack_build_devs() - Build ALSA controls for jacks.
+ * @snd: VirtIO sound device.
+ *
+ * Context: Any context that permits to sleep.
+ * Return: 0 on success, -errno on failure.
+ */
+int virtsnd_jack_build_devs(struct virtio_snd *snd)
+{
+       u32 i;
+       int rc;
+
+       for (i = 0; i < snd->njacks; ++i) {
+               struct virtio_jack *vjack = &snd->jacks[i];
+
+               vjack->type = virtsnd_jack_get_type(vjack);
+
+               rc = snd_jack_new(snd->card, virtsnd_jack_get_label(vjack),
+                                 vjack->type, &vjack->jack, true, true);
+               if (rc)
+                       return rc;
+
+               if (vjack->jack)
+                       vjack->jack->private_data = vjack;
+
+               snd_jack_report(vjack->jack,
+                               vjack->connected ? vjack->type : 0);
+       }
+
+       return 0;
+}
+
+/**
+ * virtsnd_jack_event() - Handle the jack event notification.
+ * @snd: VirtIO sound device.
+ * @event: VirtIO sound event.
+ *
+ * Context: Interrupt context.
+ */
+void virtsnd_jack_event(struct virtio_snd *snd, struct virtio_snd_event *event)
+{
+       u32 jack_id = le32_to_cpu(event->data);
+       struct virtio_jack *vjack;
+
+       if (jack_id >= snd->njacks)
+               return;
+
+       vjack = &snd->jacks[jack_id];
+
+       switch (le32_to_cpu(event->hdr.code)) {
+       case VIRTIO_SND_EVT_JACK_CONNECTED:
+               vjack->connected = true;
+               break;
+       case VIRTIO_SND_EVT_JACK_DISCONNECTED:
+               vjack->connected = false;
+               break;
+       default:
+               return;
+       }
+
+       snd_jack_report(vjack->jack, vjack->connected ? vjack->type : 0);
+}