162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * virtio-snd: Virtio sound device 462306a36Sopenharmony_ci * Copyright (C) 2021 OpenSynergy GmbH 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci#include <linux/virtio_config.h> 762306a36Sopenharmony_ci#include <sound/jack.h> 862306a36Sopenharmony_ci#include <sound/hda_verbs.h> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include "virtio_card.h" 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci/** 1362306a36Sopenharmony_ci * DOC: Implementation Status 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * At the moment jacks have a simple implementation and can only be used to 1662306a36Sopenharmony_ci * receive notifications about a plugged in/out device. 1762306a36Sopenharmony_ci * 1862306a36Sopenharmony_ci * VIRTIO_SND_R_JACK_REMAP 1962306a36Sopenharmony_ci * is not supported 2062306a36Sopenharmony_ci */ 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci/** 2362306a36Sopenharmony_ci * struct virtio_jack - VirtIO jack. 2462306a36Sopenharmony_ci * @jack: Kernel jack control. 2562306a36Sopenharmony_ci * @nid: Functional group node identifier. 2662306a36Sopenharmony_ci * @features: Jack virtio feature bit map (1 << VIRTIO_SND_JACK_F_XXX). 2762306a36Sopenharmony_ci * @defconf: Pin default configuration value. 2862306a36Sopenharmony_ci * @caps: Pin capabilities value. 2962306a36Sopenharmony_ci * @connected: Current jack connection status. 3062306a36Sopenharmony_ci * @type: Kernel jack type (SND_JACK_XXX). 3162306a36Sopenharmony_ci */ 3262306a36Sopenharmony_cistruct virtio_jack { 3362306a36Sopenharmony_ci struct snd_jack *jack; 3462306a36Sopenharmony_ci u32 nid; 3562306a36Sopenharmony_ci u32 features; 3662306a36Sopenharmony_ci u32 defconf; 3762306a36Sopenharmony_ci u32 caps; 3862306a36Sopenharmony_ci bool connected; 3962306a36Sopenharmony_ci int type; 4062306a36Sopenharmony_ci}; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci/** 4362306a36Sopenharmony_ci * virtsnd_jack_get_label() - Get the name string for the jack. 4462306a36Sopenharmony_ci * @vjack: VirtIO jack. 4562306a36Sopenharmony_ci * 4662306a36Sopenharmony_ci * Returns the jack name based on the default pin configuration value (see HDA 4762306a36Sopenharmony_ci * specification). 4862306a36Sopenharmony_ci * 4962306a36Sopenharmony_ci * Context: Any context. 5062306a36Sopenharmony_ci * Return: Name string. 5162306a36Sopenharmony_ci */ 5262306a36Sopenharmony_cistatic const char *virtsnd_jack_get_label(struct virtio_jack *vjack) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci unsigned int defconf = vjack->defconf; 5562306a36Sopenharmony_ci unsigned int device = 5662306a36Sopenharmony_ci (defconf & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT; 5762306a36Sopenharmony_ci unsigned int location = 5862306a36Sopenharmony_ci (defconf & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci switch (device) { 6162306a36Sopenharmony_ci case AC_JACK_LINE_OUT: 6262306a36Sopenharmony_ci return "Line Out"; 6362306a36Sopenharmony_ci case AC_JACK_SPEAKER: 6462306a36Sopenharmony_ci return "Speaker"; 6562306a36Sopenharmony_ci case AC_JACK_HP_OUT: 6662306a36Sopenharmony_ci return "Headphone"; 6762306a36Sopenharmony_ci case AC_JACK_CD: 6862306a36Sopenharmony_ci return "CD"; 6962306a36Sopenharmony_ci case AC_JACK_SPDIF_OUT: 7062306a36Sopenharmony_ci case AC_JACK_DIG_OTHER_OUT: 7162306a36Sopenharmony_ci if (location == AC_JACK_LOC_HDMI) 7262306a36Sopenharmony_ci return "HDMI Out"; 7362306a36Sopenharmony_ci else 7462306a36Sopenharmony_ci return "SPDIF Out"; 7562306a36Sopenharmony_ci case AC_JACK_LINE_IN: 7662306a36Sopenharmony_ci return "Line"; 7762306a36Sopenharmony_ci case AC_JACK_AUX: 7862306a36Sopenharmony_ci return "Aux"; 7962306a36Sopenharmony_ci case AC_JACK_MIC_IN: 8062306a36Sopenharmony_ci return "Mic"; 8162306a36Sopenharmony_ci case AC_JACK_SPDIF_IN: 8262306a36Sopenharmony_ci return "SPDIF In"; 8362306a36Sopenharmony_ci case AC_JACK_DIG_OTHER_IN: 8462306a36Sopenharmony_ci return "Digital In"; 8562306a36Sopenharmony_ci default: 8662306a36Sopenharmony_ci return "Misc"; 8762306a36Sopenharmony_ci } 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci/** 9162306a36Sopenharmony_ci * virtsnd_jack_get_type() - Get the type for the jack. 9262306a36Sopenharmony_ci * @vjack: VirtIO jack. 9362306a36Sopenharmony_ci * 9462306a36Sopenharmony_ci * Returns the jack type based on the default pin configuration value (see HDA 9562306a36Sopenharmony_ci * specification). 9662306a36Sopenharmony_ci * 9762306a36Sopenharmony_ci * Context: Any context. 9862306a36Sopenharmony_ci * Return: SND_JACK_XXX value. 9962306a36Sopenharmony_ci */ 10062306a36Sopenharmony_cistatic int virtsnd_jack_get_type(struct virtio_jack *vjack) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci unsigned int defconf = vjack->defconf; 10362306a36Sopenharmony_ci unsigned int device = 10462306a36Sopenharmony_ci (defconf & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci switch (device) { 10762306a36Sopenharmony_ci case AC_JACK_LINE_OUT: 10862306a36Sopenharmony_ci case AC_JACK_SPEAKER: 10962306a36Sopenharmony_ci return SND_JACK_LINEOUT; 11062306a36Sopenharmony_ci case AC_JACK_HP_OUT: 11162306a36Sopenharmony_ci return SND_JACK_HEADPHONE; 11262306a36Sopenharmony_ci case AC_JACK_SPDIF_OUT: 11362306a36Sopenharmony_ci case AC_JACK_DIG_OTHER_OUT: 11462306a36Sopenharmony_ci return SND_JACK_AVOUT; 11562306a36Sopenharmony_ci case AC_JACK_MIC_IN: 11662306a36Sopenharmony_ci return SND_JACK_MICROPHONE; 11762306a36Sopenharmony_ci default: 11862306a36Sopenharmony_ci return SND_JACK_LINEIN; 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci/** 12362306a36Sopenharmony_ci * virtsnd_jack_parse_cfg() - Parse the jack configuration. 12462306a36Sopenharmony_ci * @snd: VirtIO sound device. 12562306a36Sopenharmony_ci * 12662306a36Sopenharmony_ci * This function is called during initial device initialization. 12762306a36Sopenharmony_ci * 12862306a36Sopenharmony_ci * Context: Any context that permits to sleep. 12962306a36Sopenharmony_ci * Return: 0 on success, -errno on failure. 13062306a36Sopenharmony_ci */ 13162306a36Sopenharmony_ciint virtsnd_jack_parse_cfg(struct virtio_snd *snd) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci struct virtio_device *vdev = snd->vdev; 13462306a36Sopenharmony_ci struct virtio_snd_jack_info *info; 13562306a36Sopenharmony_ci u32 i; 13662306a36Sopenharmony_ci int rc; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci virtio_cread_le(vdev, struct virtio_snd_config, jacks, &snd->njacks); 13962306a36Sopenharmony_ci if (!snd->njacks) 14062306a36Sopenharmony_ci return 0; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci snd->jacks = devm_kcalloc(&vdev->dev, snd->njacks, sizeof(*snd->jacks), 14362306a36Sopenharmony_ci GFP_KERNEL); 14462306a36Sopenharmony_ci if (!snd->jacks) 14562306a36Sopenharmony_ci return -ENOMEM; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci info = kcalloc(snd->njacks, sizeof(*info), GFP_KERNEL); 14862306a36Sopenharmony_ci if (!info) 14962306a36Sopenharmony_ci return -ENOMEM; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_JACK_INFO, 0, snd->njacks, 15262306a36Sopenharmony_ci sizeof(*info), info); 15362306a36Sopenharmony_ci if (rc) 15462306a36Sopenharmony_ci goto on_exit; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci for (i = 0; i < snd->njacks; ++i) { 15762306a36Sopenharmony_ci struct virtio_jack *vjack = &snd->jacks[i]; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci vjack->nid = le32_to_cpu(info[i].hdr.hda_fn_nid); 16062306a36Sopenharmony_ci vjack->features = le32_to_cpu(info[i].features); 16162306a36Sopenharmony_ci vjack->defconf = le32_to_cpu(info[i].hda_reg_defconf); 16262306a36Sopenharmony_ci vjack->caps = le32_to_cpu(info[i].hda_reg_caps); 16362306a36Sopenharmony_ci vjack->connected = info[i].connected; 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cion_exit: 16762306a36Sopenharmony_ci kfree(info); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci return rc; 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci/** 17362306a36Sopenharmony_ci * virtsnd_jack_build_devs() - Build ALSA controls for jacks. 17462306a36Sopenharmony_ci * @snd: VirtIO sound device. 17562306a36Sopenharmony_ci * 17662306a36Sopenharmony_ci * Context: Any context that permits to sleep. 17762306a36Sopenharmony_ci * Return: 0 on success, -errno on failure. 17862306a36Sopenharmony_ci */ 17962306a36Sopenharmony_ciint virtsnd_jack_build_devs(struct virtio_snd *snd) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci u32 i; 18262306a36Sopenharmony_ci int rc; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci for (i = 0; i < snd->njacks; ++i) { 18562306a36Sopenharmony_ci struct virtio_jack *vjack = &snd->jacks[i]; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci vjack->type = virtsnd_jack_get_type(vjack); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci rc = snd_jack_new(snd->card, virtsnd_jack_get_label(vjack), 19062306a36Sopenharmony_ci vjack->type, &vjack->jack, true, true); 19162306a36Sopenharmony_ci if (rc) 19262306a36Sopenharmony_ci return rc; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci if (vjack->jack) 19562306a36Sopenharmony_ci vjack->jack->private_data = vjack; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci snd_jack_report(vjack->jack, 19862306a36Sopenharmony_ci vjack->connected ? vjack->type : 0); 19962306a36Sopenharmony_ci } 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci return 0; 20262306a36Sopenharmony_ci} 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci/** 20562306a36Sopenharmony_ci * virtsnd_jack_event() - Handle the jack event notification. 20662306a36Sopenharmony_ci * @snd: VirtIO sound device. 20762306a36Sopenharmony_ci * @event: VirtIO sound event. 20862306a36Sopenharmony_ci * 20962306a36Sopenharmony_ci * Context: Interrupt context. 21062306a36Sopenharmony_ci */ 21162306a36Sopenharmony_civoid virtsnd_jack_event(struct virtio_snd *snd, struct virtio_snd_event *event) 21262306a36Sopenharmony_ci{ 21362306a36Sopenharmony_ci u32 jack_id = le32_to_cpu(event->data); 21462306a36Sopenharmony_ci struct virtio_jack *vjack; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci if (jack_id >= snd->njacks) 21762306a36Sopenharmony_ci return; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci vjack = &snd->jacks[jack_id]; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci switch (le32_to_cpu(event->hdr.code)) { 22262306a36Sopenharmony_ci case VIRTIO_SND_EVT_JACK_CONNECTED: 22362306a36Sopenharmony_ci vjack->connected = true; 22462306a36Sopenharmony_ci break; 22562306a36Sopenharmony_ci case VIRTIO_SND_EVT_JACK_DISCONNECTED: 22662306a36Sopenharmony_ci vjack->connected = false; 22762306a36Sopenharmony_ci break; 22862306a36Sopenharmony_ci default: 22962306a36Sopenharmony_ci return; 23062306a36Sopenharmony_ci } 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci snd_jack_report(vjack->jack, vjack->connected ? vjack->type : 0); 23362306a36Sopenharmony_ci} 234