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 */
32 struct 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 */
virtsnd_jack_get_label(struct virtio_jack * vjack)52 static 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 */
virtsnd_jack_get_type(struct virtio_jack * vjack)100 static 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 */
virtsnd_jack_parse_cfg(struct virtio_snd * snd)131 int 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(&vdev->dev, snd->njacks, sizeof(*snd->jacks),
143 GFP_KERNEL);
144 if (!snd->jacks)
145 return -ENOMEM;
146
147 info = kcalloc(snd->njacks, sizeof(*info), GFP_KERNEL);
148 if (!info)
149 return -ENOMEM;
150
151 rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_JACK_INFO, 0, snd->njacks,
152 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
166 on_exit:
167 kfree(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 */
virtsnd_jack_build_devs(struct virtio_snd * snd)179 int 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(snd->card, virtsnd_jack_get_label(vjack),
190 vjack->type, &vjack->jack, true, true);
191 if (rc)
192 return rc;
193
194 if (vjack->jack)
195 vjack->jack->private_data = vjack;
196
197 snd_jack_report(vjack->jack,
198 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 */
virtsnd_jack_event(struct virtio_snd * snd,struct virtio_snd_event * event)211 void 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(vjack->jack, vjack->connected ? vjack->type : 0);
233 }
234