]>
Commit | Line | Data |
---|---|---|
ca61a41f AY |
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(&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 | */ | |
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 | */ | |
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 | } |