summaryrefslogtreecommitdiffstats
path: root/sound/virtio/virtio_jack.c
blob: c69f1dcdcc845638bb123a04e022d09fc211faff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
// SPDX-License-Identifier: GPL-2.0+
/*
 * virtio-snd: Virtio sound device
 * Copyright (C) 2021 OpenSynergy GmbH
 */
#include <linux/virtio_config.h>
#include <sound/jack.h>
#include <sound/hda_verbs.h>

#include "virtio_card.h"

/**
 * DOC: Implementation Status
 *
 * At the moment jacks have a simple implementation and can only be used to
 * receive notifications about a plugged in/out device.
 *
 * VIRTIO_SND_R_JACK_REMAP
 *   is not supported
 */

/**
 * struct virtio_jack - VirtIO jack.
 * @jack: Kernel jack control.
 * @nid: Functional group node identifier.
 * @features: Jack virtio feature bit map (1 << VIRTIO_SND_JACK_F_XXX).
 * @defconf: Pin default configuration value.
 * @caps: Pin capabilities value.
 * @connected: Current jack connection status.
 * @type: Kernel jack type (SND_JACK_XXX).
 */
struct virtio_jack {
	struct snd_jack *jack;
	u32 nid;
	u32 features;
	u32 defconf;
	u32 caps;
	bool connected;
	int type;
};

/**
 * virtsnd_jack_get_label() - Get the name string for the jack.
 * @vjack: VirtIO jack.
 *
 * Returns the jack name based on the default pin configuration value (see HDA
 * specification).
 *
 * Context: Any context.
 * Return: Name string.
 */
static const char *virtsnd_jack_get_label(struct virtio_jack *vjack)
{
	unsigned int defconf = vjack->defconf;
	unsigned int device =
		(defconf & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT;
	unsigned int location =
		(defconf & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT;

	switch (device) {
	case AC_JACK_LINE_OUT:
		return "Line Out";
	case AC_JACK_SPEAKER:
		return "Speaker";
	case AC_JACK_HP_OUT:
		return "Headphone";
	case AC_JACK_CD:
		return "CD";
	case AC_JACK_SPDIF_OUT:
	case AC_JACK_DIG_OTHER_OUT:
		if (location == AC_JACK_LOC_HDMI)
			return "HDMI Out";
		else
			return "SPDIF Out";
	case AC_JACK_LINE_IN:
		return "Line";
	case AC_JACK_AUX:
		return "Aux";
	case AC_JACK_MIC_IN:
		return "Mic";
	case AC_JACK_SPDIF_IN:
		return "SPDIF In";
	case AC_JACK_DIG_OTHER_IN:
		return "Digital In";
	default:
		return "Misc";
	}
}

/**
 * virtsnd_jack_get_type() - Get the type for the jack.
 * @vjack: VirtIO jack.
 *
 * Returns the jack type based on the default pin configuration value (see HDA
 * specification).
 *
 * Context: Any context.
 * Return: SND_JACK_XXX value.
 */
static int virtsnd_jack_get_type(struct virtio_jack *vjack)
{
	unsigned int defconf = vjack->defconf;
	unsigned int device =
		(defconf & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT;

	switch (device) {
	case AC_JACK_LINE_OUT:
	case AC_JACK_SPEAKER:
		return SND_JACK_LINEOUT;
	case AC_JACK_HP_OUT:
		return SND_JACK_HEADPHONE;
	case AC_JACK_SPDIF_OUT:
	case AC_JACK_DIG_OTHER_OUT:
		return SND_JACK_AVOUT;
	case AC_JACK_MIC_IN:
		return SND_JACK_MICROPHONE;
	default:
		return SND_JACK_LINEIN;
	}
}

/**
 * virtsnd_jack_parse_cfg() - Parse the jack configuration.
 * @snd: VirtIO sound device.
 *
 * This function is called during initial device initialization.
 *
 * Context: Any context that permits to sleep.
 * Return: 0 on success, -errno on failure.
 */
int virtsnd_jack_parse_cfg(struct virtio_snd *snd)
{
	struct virtio_device *vdev = snd->vdev;
	struct virtio_snd_jack_info *info;
	u32 i;
	int rc;

	virtio_cread_le(vdev, struct virtio_snd_config, jacks, &snd->njacks);
	if (!snd->njacks)
		return 0;

	snd->jacks = devm_kcalloc(&vdev->dev, snd->njacks, sizeof(*snd->jacks),
				  GFP_KERNEL);
	if (!snd->jacks)
		return -ENOMEM;

	info = kcalloc(snd->njacks, sizeof(*info), GFP_KERNEL);
	if (!info)
		return -ENOMEM;

	rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_JACK_INFO, 0, snd->njacks,
				    sizeof(*info), info);
	if (rc)
		goto on_exit;

	for (i = 0; i < snd->njacks; ++i) {
		struct virtio_jack *vjack = &snd->jacks[i];

		vjack->nid = le32_to_cpu(info[i].hdr.hda_fn_nid);
		vjack->features = le32_to_cpu(info[i].features);
		vjack->defconf = le32_to_cpu(info[i].hda_reg_defconf);
		vjack->caps = le32_to_cpu(info[i].hda_reg_caps);
		vjack->connected = info[i].connected;
	}

on_exit:
	kfree(info);

	return rc;
}

/**
 * virtsnd_jack_build_devs() - Build ALSA controls for jacks.
 * @snd: VirtIO sound device.
 *
 * Context: Any context that permits to sleep.
 * Return: 0 on success, -errno on failure.
 */
int virtsnd_jack_build_devs(struct virtio_snd *snd)
{
	u32 i;
	int rc;

	for (i = 0; i < snd->njacks; ++i) {
		struct virtio_jack *vjack = &snd->jacks[i];

		vjack->type = virtsnd_jack_get_type(vjack);

		rc = snd_jack_new(snd->card, virtsnd_jack_get_label(vjack),
				  vjack->type, &vjack->jack, true, true);
		if (rc)
			return rc;

		if (vjack->jack)
			vjack->jack->private_data = vjack;

		snd_jack_report(vjack->jack,
				vjack->connected ? vjack->type : 0);
	}

	return 0;
}

/**
 * virtsnd_jack_event() - Handle the jack event notification.
 * @snd: VirtIO sound device.
 * @event: VirtIO sound event.
 *
 * Context: Interrupt context.
 */
void virtsnd_jack_event(struct virtio_snd *snd, struct virtio_snd_event *event)
{
	u32 jack_id = le32_to_cpu(event->data);
	struct virtio_jack *vjack;

	if (jack_id >= snd->njacks)
		return;

	vjack = &snd->jacks[jack_id];

	switch (le32_to_cpu(event->hdr.code)) {
	case VIRTIO_SND_EVT_JACK_CONNECTED:
		vjack->connected = true;
		break;
	case VIRTIO_SND_EVT_JACK_DISCONNECTED:
		vjack->connected = false;
		break;
	default:
		return;
	}

	snd_jack_report(vjack->jack, vjack->connected ? vjack->type : 0);
}