aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/sound/virtio/virtio_jack.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/virtio/virtio_jack.c')
-rw-r--r--sound/virtio/virtio_jack.c233
1 files changed, 233 insertions, 0 deletions
diff --git a/sound/virtio/virtio_jack.c b/sound/virtio/virtio_jack.c
new file mode 100644
index 000000000000..c69f1dcdcc84
--- /dev/null
+++ b/sound/virtio/virtio_jack.c
@@ -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);
+}