|  | // 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); | 
|  | } |