// SPDX-License-Identifier: GPL-2.0+ /* * virtio-snd: Virtio sound device * Copyright (C) 2021 OpenSynergy GmbH */ #include #include #include #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); }