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 | */ |
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 | */ |
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 | */ |
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(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 | |
166 | on_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 | */ |
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(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 | */ |
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(jack: vjack->jack, status: vjack->connected ? vjack->type : 0); |
233 | } |
234 | |