forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
233 lines
5.3 KiB
233 lines
5.3 KiB
// 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); |
|
}
|
|
|