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: Anton Yakovlev <anton.yakovlev@opensynergy.com> Link: https://lore.kernel.org/r/20210302164709.3142702-8-anton.yakovlev@opensynergy.com Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
parent
da76e9f3e4
commit
ca61a41f38
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue