xref: /linux/sound/virtio/virtio_jack.c (revision 4b132aacb0768ac1e652cf517097ea6f237214b9)
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 }
234