Commit ca61a41f authored by Anton Yakovlev's avatar Anton Yakovlev Committed by Takashi Iwai
Browse files

ALSA: virtio: introduce jack support



Enumerate all available jacks and create ALSA controls.

At the moment jacks have a simple implementation and can only be used
to receive notifications about a plugged in/out device.

Signed-off-by: default avatarAnton Yakovlev <anton.yakovlev@opensynergy.com>
Link: https://lore.kernel.org/r/20210302164709.3142702-8-anton.yakovlev@opensynergy.com


Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent da76e9f3
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ obj-$(CONFIG_SND_VIRTIO) += virtio_snd.o
virtio_snd-objs := \
	virtio_card.o \
	virtio_ctl_msg.o \
	virtio_jack.o \
	virtio_pcm.o \
	virtio_pcm_msg.o \
	virtio_pcm_ops.o
+14 −0
Original line number Diff line number Diff line
@@ -56,6 +56,10 @@ static void virtsnd_event_dispatch(struct virtio_snd *snd,
				   struct virtio_snd_event *event)
{
	switch (le32_to_cpu(event->hdr.code)) {
	case VIRTIO_SND_EVT_JACK_CONNECTED:
	case VIRTIO_SND_EVT_JACK_DISCONNECTED:
		virtsnd_jack_event(snd, event);
		break;
	case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED:
	case VIRTIO_SND_EVT_PCM_XRUN:
		virtsnd_pcm_event(snd, event);
@@ -219,10 +223,20 @@ static int virtsnd_build_devs(struct virtio_snd *snd)
			 VIRTIO_SND_CARD_NAME " at %s/%s",
			 dev_name(dev->parent), dev_name(dev));

	rc = virtsnd_jack_parse_cfg(snd);
	if (rc)
		return rc;

	rc = virtsnd_pcm_parse_cfg(snd);
	if (rc)
		return rc;

	if (snd->njacks) {
		rc = virtsnd_jack_build_devs(snd);
		if (rc)
			return rc;
	}

	if (snd->nsubstreams) {
		rc = virtsnd_pcm_build_devs(snd);
		if (rc)
+12 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
#define VIRTIO_SND_CARD_NAME	"VirtIO SoundCard"
#define VIRTIO_SND_PCM_NAME	"VirtIO PCM"

struct virtio_jack;
struct virtio_pcm_substream;

/**
@@ -38,6 +39,8 @@ struct virtio_snd_queue {
 * @ctl_msgs: Pending control request list.
 * @event_msgs: Device events.
 * @pcm_list: VirtIO PCM device list.
 * @jacks: VirtIO jacks.
 * @njacks: Number of jacks.
 * @substreams: VirtIO PCM substreams.
 * @nsubstreams: Number of PCM substreams.
 */
@@ -48,6 +51,8 @@ struct virtio_snd {
	struct list_head ctl_msgs;
	struct virtio_snd_event *event_msgs;
	struct list_head pcm_list;
	struct virtio_jack *jacks;
	u32 njacks;
	struct virtio_pcm_substream *substreams;
	u32 nsubstreams;
};
@@ -88,4 +93,11 @@ virtsnd_pcm_queue(struct virtio_pcm_substream *vss)
		return virtsnd_rx_queue(vss->snd);
}

int virtsnd_jack_parse_cfg(struct virtio_snd *snd);

int virtsnd_jack_build_devs(struct virtio_snd *snd);

void virtsnd_jack_event(struct virtio_snd *snd,
			struct virtio_snd_event *event);

#endif /* VIRTIO_SND_CARD_H */
+233 −0
Original line number Diff line number Diff line
// 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);
}