Commit | Line | Data |
---|---|---|
da607e19 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
31514bfb | 2 | /* |
f3a0e32a | 3 | * oxfw-spkr.c - a part of driver for OXFW970/971 based devices |
31514bfb TS |
4 | * |
5 | * Copyright (c) Clemens Ladisch <clemens@ladisch.de> | |
31514bfb TS |
6 | */ |
7 | ||
8 | #include "oxfw.h" | |
9 | ||
40540de5 TS |
10 | struct fw_spkr { |
11 | bool mute; | |
12 | s16 volume[6]; | |
13 | s16 volume_min; | |
14 | s16 volume_max; | |
3e2f4570 TS |
15 | |
16 | unsigned int mixer_channels; | |
17 | u8 mute_fb_id; | |
18 | u8 volume_fb_id; | |
40540de5 TS |
19 | }; |
20 | ||
31514bfb TS |
21 | enum control_action { CTL_READ, CTL_WRITE }; |
22 | enum control_attribute { | |
23 | CTL_MIN = 0x02, | |
24 | CTL_MAX = 0x03, | |
25 | CTL_CURRENT = 0x10, | |
26 | }; | |
27 | ||
eab8e4e4 TS |
28 | static int avc_audio_feature_mute(struct fw_unit *unit, u8 fb_id, bool *value, |
29 | enum control_action action) | |
31514bfb TS |
30 | { |
31 | u8 *buf; | |
32 | u8 response_ok; | |
33 | int err; | |
34 | ||
35 | buf = kmalloc(11, GFP_KERNEL); | |
36 | if (!buf) | |
37 | return -ENOMEM; | |
38 | ||
39 | if (action == CTL_READ) { | |
40 | buf[0] = 0x01; /* AV/C, STATUS */ | |
41 | response_ok = 0x0c; /* STABLE */ | |
42 | } else { | |
43 | buf[0] = 0x00; /* AV/C, CONTROL */ | |
44 | response_ok = 0x09; /* ACCEPTED */ | |
45 | } | |
46 | buf[1] = 0x08; /* audio unit 0 */ | |
47 | buf[2] = 0xb8; /* FUNCTION BLOCK */ | |
48 | buf[3] = 0x81; /* function block type: feature */ | |
eab8e4e4 | 49 | buf[4] = fb_id; /* function block ID */ |
31514bfb TS |
50 | buf[5] = 0x10; /* control attribute: current */ |
51 | buf[6] = 0x02; /* selector length */ | |
52 | buf[7] = 0x00; /* audio channel number */ | |
53 | buf[8] = 0x01; /* control selector: mute */ | |
54 | buf[9] = 0x01; /* control data length */ | |
55 | if (action == CTL_READ) | |
56 | buf[10] = 0xff; | |
57 | else | |
58 | buf[10] = *value ? 0x70 : 0x60; | |
59 | ||
eab8e4e4 | 60 | err = fcp_avc_transaction(unit, buf, 11, buf, 11, 0x3fe); |
31514bfb TS |
61 | if (err < 0) |
62 | goto error; | |
63 | if (err < 11) { | |
eab8e4e4 | 64 | dev_err(&unit->device, "short FCP response\n"); |
31514bfb TS |
65 | err = -EIO; |
66 | goto error; | |
67 | } | |
68 | if (buf[0] != response_ok) { | |
eab8e4e4 | 69 | dev_err(&unit->device, "mute command failed\n"); |
31514bfb TS |
70 | err = -EIO; |
71 | goto error; | |
72 | } | |
73 | if (action == CTL_READ) | |
74 | *value = buf[10] == 0x70; | |
75 | ||
76 | err = 0; | |
77 | ||
78 | error: | |
79 | kfree(buf); | |
80 | ||
81 | return err; | |
82 | } | |
83 | ||
eab8e4e4 TS |
84 | static int avc_audio_feature_volume(struct fw_unit *unit, u8 fb_id, s16 *value, |
85 | unsigned int channel, | |
86 | enum control_attribute attribute, | |
87 | enum control_action action) | |
31514bfb TS |
88 | { |
89 | u8 *buf; | |
90 | u8 response_ok; | |
91 | int err; | |
92 | ||
93 | buf = kmalloc(12, GFP_KERNEL); | |
94 | if (!buf) | |
95 | return -ENOMEM; | |
96 | ||
97 | if (action == CTL_READ) { | |
98 | buf[0] = 0x01; /* AV/C, STATUS */ | |
99 | response_ok = 0x0c; /* STABLE */ | |
100 | } else { | |
101 | buf[0] = 0x00; /* AV/C, CONTROL */ | |
102 | response_ok = 0x09; /* ACCEPTED */ | |
103 | } | |
104 | buf[1] = 0x08; /* audio unit 0 */ | |
105 | buf[2] = 0xb8; /* FUNCTION BLOCK */ | |
106 | buf[3] = 0x81; /* function block type: feature */ | |
eab8e4e4 | 107 | buf[4] = fb_id; /* function block ID */ |
31514bfb TS |
108 | buf[5] = attribute; /* control attribute */ |
109 | buf[6] = 0x02; /* selector length */ | |
110 | buf[7] = channel; /* audio channel number */ | |
111 | buf[8] = 0x02; /* control selector: volume */ | |
112 | buf[9] = 0x02; /* control data length */ | |
113 | if (action == CTL_READ) { | |
114 | buf[10] = 0xff; | |
115 | buf[11] = 0xff; | |
116 | } else { | |
117 | buf[10] = *value >> 8; | |
118 | buf[11] = *value; | |
119 | } | |
120 | ||
eab8e4e4 | 121 | err = fcp_avc_transaction(unit, buf, 12, buf, 12, 0x3fe); |
31514bfb TS |
122 | if (err < 0) |
123 | goto error; | |
124 | if (err < 12) { | |
eab8e4e4 | 125 | dev_err(&unit->device, "short FCP response\n"); |
31514bfb TS |
126 | err = -EIO; |
127 | goto error; | |
128 | } | |
129 | if (buf[0] != response_ok) { | |
eab8e4e4 | 130 | dev_err(&unit->device, "volume command failed\n"); |
31514bfb TS |
131 | err = -EIO; |
132 | goto error; | |
133 | } | |
134 | if (action == CTL_READ) | |
135 | *value = (buf[10] << 8) | buf[11]; | |
136 | ||
137 | err = 0; | |
138 | ||
139 | error: | |
140 | kfree(buf); | |
141 | ||
142 | return err; | |
143 | } | |
144 | ||
29aa09ac | 145 | static int spkr_mute_get(struct snd_kcontrol *control, |
31514bfb TS |
146 | struct snd_ctl_elem_value *value) |
147 | { | |
148 | struct snd_oxfw *oxfw = control->private_data; | |
40540de5 | 149 | struct fw_spkr *spkr = oxfw->spec; |
31514bfb | 150 | |
40540de5 | 151 | value->value.integer.value[0] = !spkr->mute; |
31514bfb TS |
152 | |
153 | return 0; | |
154 | } | |
155 | ||
29aa09ac | 156 | static int spkr_mute_put(struct snd_kcontrol *control, |
31514bfb TS |
157 | struct snd_ctl_elem_value *value) |
158 | { | |
159 | struct snd_oxfw *oxfw = control->private_data; | |
40540de5 | 160 | struct fw_spkr *spkr = oxfw->spec; |
31514bfb TS |
161 | bool mute; |
162 | int err; | |
163 | ||
164 | mute = !value->value.integer.value[0]; | |
165 | ||
40540de5 | 166 | if (mute == spkr->mute) |
31514bfb TS |
167 | return 0; |
168 | ||
3e2f4570 TS |
169 | err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &mute, |
170 | CTL_WRITE); | |
31514bfb TS |
171 | if (err < 0) |
172 | return err; | |
40540de5 | 173 | spkr->mute = mute; |
31514bfb TS |
174 | |
175 | return 1; | |
176 | } | |
177 | ||
29aa09ac | 178 | static int spkr_volume_info(struct snd_kcontrol *control, |
31514bfb TS |
179 | struct snd_ctl_elem_info *info) |
180 | { | |
181 | struct snd_oxfw *oxfw = control->private_data; | |
40540de5 | 182 | struct fw_spkr *spkr = oxfw->spec; |
31514bfb TS |
183 | |
184 | info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | |
3e2f4570 | 185 | info->count = spkr->mixer_channels; |
40540de5 TS |
186 | info->value.integer.min = spkr->volume_min; |
187 | info->value.integer.max = spkr->volume_max; | |
31514bfb TS |
188 | |
189 | return 0; | |
190 | } | |
191 | ||
192 | static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 }; | |
193 | ||
29aa09ac | 194 | static int spkr_volume_get(struct snd_kcontrol *control, |
31514bfb TS |
195 | struct snd_ctl_elem_value *value) |
196 | { | |
197 | struct snd_oxfw *oxfw = control->private_data; | |
40540de5 | 198 | struct fw_spkr *spkr = oxfw->spec; |
31514bfb TS |
199 | unsigned int i; |
200 | ||
3e2f4570 | 201 | for (i = 0; i < spkr->mixer_channels; ++i) |
40540de5 | 202 | value->value.integer.value[channel_map[i]] = spkr->volume[i]; |
31514bfb TS |
203 | |
204 | return 0; | |
205 | } | |
206 | ||
29aa09ac | 207 | static int spkr_volume_put(struct snd_kcontrol *control, |
31514bfb TS |
208 | struct snd_ctl_elem_value *value) |
209 | { | |
210 | struct snd_oxfw *oxfw = control->private_data; | |
40540de5 | 211 | struct fw_spkr *spkr = oxfw->spec; |
31514bfb TS |
212 | unsigned int i, changed_channels; |
213 | bool equal_values = true; | |
214 | s16 volume; | |
215 | int err; | |
216 | ||
3e2f4570 | 217 | for (i = 0; i < spkr->mixer_channels; ++i) { |
40540de5 TS |
218 | if (value->value.integer.value[i] < spkr->volume_min || |
219 | value->value.integer.value[i] > spkr->volume_max) | |
31514bfb TS |
220 | return -EINVAL; |
221 | if (value->value.integer.value[i] != | |
222 | value->value.integer.value[0]) | |
223 | equal_values = false; | |
224 | } | |
225 | ||
226 | changed_channels = 0; | |
3e2f4570 | 227 | for (i = 0; i < spkr->mixer_channels; ++i) |
31514bfb | 228 | if (value->value.integer.value[channel_map[i]] != |
40540de5 | 229 | spkr->volume[i]) |
31514bfb TS |
230 | changed_channels |= 1 << (i + 1); |
231 | ||
232 | if (equal_values && changed_channels != 0) | |
233 | changed_channels = 1 << 0; | |
234 | ||
3e2f4570 | 235 | for (i = 0; i <= spkr->mixer_channels; ++i) { |
31514bfb TS |
236 | volume = value->value.integer.value[channel_map[i ? i - 1 : 0]]; |
237 | if (changed_channels & (1 << i)) { | |
eab8e4e4 | 238 | err = avc_audio_feature_volume(oxfw->unit, |
3e2f4570 | 239 | spkr->volume_fb_id, &volume, |
eab8e4e4 | 240 | i, CTL_CURRENT, CTL_WRITE); |
31514bfb TS |
241 | if (err < 0) |
242 | return err; | |
243 | } | |
244 | if (i > 0) | |
40540de5 | 245 | spkr->volume[i - 1] = volume; |
31514bfb TS |
246 | } |
247 | ||
248 | return changed_channels != 0; | |
249 | } | |
250 | ||
3e2f4570 | 251 | int snd_oxfw_add_spkr(struct snd_oxfw *oxfw, bool is_lacie) |
31514bfb TS |
252 | { |
253 | static const struct snd_kcontrol_new controls[] = { | |
254 | { | |
255 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | |
256 | .name = "PCM Playback Switch", | |
257 | .info = snd_ctl_boolean_mono_info, | |
29aa09ac TS |
258 | .get = spkr_mute_get, |
259 | .put = spkr_mute_put, | |
31514bfb TS |
260 | }, |
261 | { | |
262 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | |
263 | .name = "PCM Playback Volume", | |
29aa09ac TS |
264 | .info = spkr_volume_info, |
265 | .get = spkr_volume_get, | |
266 | .put = spkr_volume_put, | |
31514bfb TS |
267 | }, |
268 | }; | |
40540de5 | 269 | struct fw_spkr *spkr; |
31514bfb TS |
270 | unsigned int i, first_ch; |
271 | int err; | |
272 | ||
72bc8c43 TS |
273 | spkr = devm_kzalloc(&oxfw->card->card_dev, sizeof(struct fw_spkr), |
274 | GFP_KERNEL); | |
275 | if (!spkr) | |
40540de5 TS |
276 | return -ENOMEM; |
277 | oxfw->spec = spkr; | |
278 | ||
3e2f4570 TS |
279 | if (is_lacie) { |
280 | spkr->mixer_channels = 1; | |
281 | spkr->mute_fb_id = 0x01; | |
282 | spkr->volume_fb_id = 0x01; | |
283 | } else { | |
284 | spkr->mixer_channels = 6; | |
285 | spkr->mute_fb_id = 0x01; | |
286 | spkr->volume_fb_id = 0x02; | |
287 | } | |
288 | ||
289 | err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, | |
290 | &spkr->volume_min, 0, CTL_MIN, CTL_READ); | |
31514bfb TS |
291 | if (err < 0) |
292 | return err; | |
3e2f4570 TS |
293 | err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, |
294 | &spkr->volume_max, 0, CTL_MAX, CTL_READ); | |
31514bfb TS |
295 | if (err < 0) |
296 | return err; | |
297 | ||
3e2f4570 TS |
298 | err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &spkr->mute, |
299 | CTL_READ); | |
31514bfb TS |
300 | if (err < 0) |
301 | return err; | |
302 | ||
3e2f4570 TS |
303 | first_ch = spkr->mixer_channels == 1 ? 0 : 1; |
304 | for (i = 0; i < spkr->mixer_channels; ++i) { | |
305 | err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, | |
306 | &spkr->volume[i], first_ch + i, | |
307 | CTL_CURRENT, CTL_READ); | |
31514bfb TS |
308 | if (err < 0) |
309 | return err; | |
310 | } | |
311 | ||
312 | for (i = 0; i < ARRAY_SIZE(controls); ++i) { | |
313 | err = snd_ctl_add(oxfw->card, | |
314 | snd_ctl_new1(&controls[i], oxfw)); | |
315 | if (err < 0) | |
316 | return err; | |
317 | } | |
318 | ||
319 | return 0; | |
320 | } |