1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * virtio-snd: Virtio sound device
4 * Copyright (C) 2021 OpenSynergy GmbH
5 */
6#include <linux/virtio_config.h>
7#include <sound/jack.h>
8#include <sound/hda_verbs.h>
9
10#include "virtio_card.h"
11
12/**
13 * DOC: Implementation Status
14 *
15 * At the moment jacks have a simple implementation and can only be used to
16 * receive notifications about a plugged in/out device.
17 *
18 * VIRTIO_SND_R_JACK_REMAP
19 * is not supported
20 */
21
22/**
23 * struct virtio_jack - VirtIO jack.
24 * @jack: Kernel jack control.
25 * @nid: Functional group node identifier.
26 * @features: Jack virtio feature bit map (1 << VIRTIO_SND_JACK_F_XXX).
27 * @defconf: Pin default configuration value.
28 * @caps: Pin capabilities value.
29 * @connected: Current jack connection status.
30 * @type: Kernel jack type (SND_JACK_XXX).
31 */
32struct virtio_jack {
33 struct snd_jack *jack;
34 u32 nid;
35 u32 features;
36 u32 defconf;
37 u32 caps;
38 bool connected;
39 int type;
40};
41
42/**
43 * virtsnd_jack_get_label() - Get the name string for the jack.
44 * @vjack: VirtIO jack.
45 *
46 * Returns the jack name based on the default pin configuration value (see HDA
47 * specification).
48 *
49 * Context: Any context.
50 * Return: Name string.
51 */
52static const char *virtsnd_jack_get_label(struct virtio_jack *vjack)
53{
54 unsigned int defconf = vjack->defconf;
55 unsigned int device =
56 (defconf & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT;
57 unsigned int location =
58 (defconf & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT;
59
60 switch (device) {
61 case AC_JACK_LINE_OUT:
62 return "Line Out";
63 case AC_JACK_SPEAKER:
64 return "Speaker";
65 case AC_JACK_HP_OUT:
66 return "Headphone";
67 case AC_JACK_CD:
68 return "CD";
69 case AC_JACK_SPDIF_OUT:
70 case AC_JACK_DIG_OTHER_OUT:
71 if (location == AC_JACK_LOC_HDMI)
72 return "HDMI Out";
73 else
74 return "SPDIF Out";
75 case AC_JACK_LINE_IN:
76 return "Line";
77 case AC_JACK_AUX:
78 return "Aux";
79 case AC_JACK_MIC_IN:
80 return "Mic";
81 case AC_JACK_SPDIF_IN:
82 return "SPDIF In";
83 case AC_JACK_DIG_OTHER_IN:
84 return "Digital In";
85 default:
86 return "Misc";
87 }
88}
89
90/**
91 * virtsnd_jack_get_type() - Get the type for the jack.
92 * @vjack: VirtIO jack.
93 *
94 * Returns the jack type based on the default pin configuration value (see HDA
95 * specification).
96 *
97 * Context: Any context.
98 * Return: SND_JACK_XXX value.
99 */
100static int virtsnd_jack_get_type(struct virtio_jack *vjack)
101{
102 unsigned int defconf = vjack->defconf;
103 unsigned int device =
104 (defconf & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT;
105
106 switch (device) {
107 case AC_JACK_LINE_OUT:
108 case AC_JACK_SPEAKER:
109 return SND_JACK_LINEOUT;
110 case AC_JACK_HP_OUT:
111 return SND_JACK_HEADPHONE;
112 case AC_JACK_SPDIF_OUT:
113 case AC_JACK_DIG_OTHER_OUT:
114 return SND_JACK_AVOUT;
115 case AC_JACK_MIC_IN:
116 return SND_JACK_MICROPHONE;
117 default:
118 return SND_JACK_LINEIN;
119 }
120}
121
122/**
123 * virtsnd_jack_parse_cfg() - Parse the jack configuration.
124 * @snd: VirtIO sound device.
125 *
126 * This function is called during initial device initialization.
127 *
128 * Context: Any context that permits to sleep.
129 * Return: 0 on success, -errno on failure.
130 */
131int virtsnd_jack_parse_cfg(struct virtio_snd *snd)
132{
133 struct virtio_device *vdev = snd->vdev;
134 struct virtio_snd_jack_info *info;
135 u32 i;
136 int rc;
137
138 virtio_cread_le(vdev, struct virtio_snd_config, jacks, &snd->njacks);
139 if (!snd->njacks)
140 return 0;
141
142 snd->jacks = devm_kcalloc(dev: &vdev->dev, n: snd->njacks, size: sizeof(*snd->jacks),
143 GFP_KERNEL);
144 if (!snd->jacks)
145 return -ENOMEM;
146
147 info = kcalloc(n: snd->njacks, size: sizeof(*info), GFP_KERNEL);
148 if (!info)
149 return -ENOMEM;
150
151 rc = virtsnd_ctl_query_info(snd, command: VIRTIO_SND_R_JACK_INFO, start_id: 0, count: snd->njacks,
152 size: sizeof(*info), info);
153 if (rc)
154 goto on_exit;
155
156 for (i = 0; i < snd->njacks; ++i) {
157 struct virtio_jack *vjack = &snd->jacks[i];
158
159 vjack->nid = le32_to_cpu(info[i].hdr.hda_fn_nid);
160 vjack->features = le32_to_cpu(info[i].features);
161 vjack->defconf = le32_to_cpu(info[i].hda_reg_defconf);
162 vjack->caps = le32_to_cpu(info[i].hda_reg_caps);
163 vjack->connected = info[i].connected;
164 }
165
166on_exit:
167 kfree(objp: info);
168
169 return rc;
170}
171
172/**
173 * virtsnd_jack_build_devs() - Build ALSA controls for jacks.
174 * @snd: VirtIO sound device.
175 *
176 * Context: Any context that permits to sleep.
177 * Return: 0 on success, -errno on failure.
178 */
179int virtsnd_jack_build_devs(struct virtio_snd *snd)
180{
181 u32 i;
182 int rc;
183
184 for (i = 0; i < snd->njacks; ++i) {
185 struct virtio_jack *vjack = &snd->jacks[i];
186
187 vjack->type = virtsnd_jack_get_type(vjack);
188
189 rc = snd_jack_new(card: snd->card, id: virtsnd_jack_get_label(vjack),
190 type: vjack->type, jack: &vjack->jack, initial_kctl: true, phantom_jack: true);
191 if (rc)
192 return rc;
193
194 if (vjack->jack)
195 vjack->jack->private_data = vjack;
196
197 snd_jack_report(jack: vjack->jack,
198 status: vjack->connected ? vjack->type : 0);
199 }
200
201 return 0;
202}
203
204/**
205 * virtsnd_jack_event() - Handle the jack event notification.
206 * @snd: VirtIO sound device.
207 * @event: VirtIO sound event.
208 *
209 * Context: Interrupt context.
210 */
211void virtsnd_jack_event(struct virtio_snd *snd, struct virtio_snd_event *event)
212{
213 u32 jack_id = le32_to_cpu(event->data);
214 struct virtio_jack *vjack;
215
216 if (jack_id >= snd->njacks)
217 return;
218
219 vjack = &snd->jacks[jack_id];
220
221 switch (le32_to_cpu(event->hdr.code)) {
222 case VIRTIO_SND_EVT_JACK_CONNECTED:
223 vjack->connected = true;
224 break;
225 case VIRTIO_SND_EVT_JACK_DISCONNECTED:
226 vjack->connected = false;
227 break;
228 default:
229 return;
230 }
231
232 snd_jack_report(jack: vjack->jack, status: vjack->connected ? vjack->type : 0);
233}
234

source code of linux/sound/virtio/virtio_jack.c