.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * Line 6 Pod HD |
---|
3 | 4 | * |
---|
4 | 5 | * Copyright (C) 2011 Stefan Hajnoczi <stefanha@gmail.com> |
---|
5 | 6 | * Copyright (C) 2015 Andrej Krutak <dev@andree.sk> |
---|
6 | 7 | * Copyright (C) 2017 Hans P. Moller <hmoller@uc.cl> |
---|
7 | | - * |
---|
8 | | - * This program is free software; you can redistribute it and/or |
---|
9 | | - * modify it under the terms of the GNU General Public License as |
---|
10 | | - * published by the Free Software Foundation, version 2. |
---|
11 | | - * |
---|
12 | 8 | */ |
---|
13 | 9 | |
---|
14 | 10 | #include <linux/usb.h> |
---|
15 | 11 | #include <linux/slab.h> |
---|
16 | 12 | #include <linux/module.h> |
---|
17 | 13 | #include <sound/core.h> |
---|
| 14 | +#include <sound/control.h> |
---|
18 | 15 | #include <sound/pcm.h> |
---|
19 | 16 | |
---|
20 | 17 | #include "driver.h" |
---|
.. | .. |
---|
22 | 19 | |
---|
23 | 20 | #define PODHD_STARTUP_DELAY 500 |
---|
24 | 21 | |
---|
25 | | -/* |
---|
26 | | - * Stages of POD startup procedure |
---|
27 | | - */ |
---|
28 | | -enum { |
---|
29 | | - PODHD_STARTUP_INIT = 1, |
---|
30 | | - PODHD_STARTUP_SCHEDULE_WORKQUEUE, |
---|
31 | | - PODHD_STARTUP_SETUP, |
---|
32 | | - PODHD_STARTUP_LAST = PODHD_STARTUP_SETUP - 1 |
---|
33 | | -}; |
---|
34 | | - |
---|
35 | 22 | enum { |
---|
36 | 23 | LINE6_PODHD300, |
---|
37 | 24 | LINE6_PODHD400, |
---|
38 | | - LINE6_PODHD500_0, |
---|
39 | | - LINE6_PODHD500_1, |
---|
| 25 | + LINE6_PODHD500, |
---|
40 | 26 | LINE6_PODX3, |
---|
41 | 27 | LINE6_PODX3LIVE, |
---|
42 | 28 | LINE6_PODHD500X, |
---|
.. | .. |
---|
47 | 33 | /* Generic Line 6 USB data */ |
---|
48 | 34 | struct usb_line6 line6; |
---|
49 | 35 | |
---|
50 | | - /* Timer for device initialization */ |
---|
51 | | - struct timer_list startup_timer; |
---|
52 | | - |
---|
53 | | - /* Work handler for device initialization */ |
---|
54 | | - struct work_struct startup_work; |
---|
55 | | - |
---|
56 | | - /* Current progress in startup procedure */ |
---|
57 | | - int startup_progress; |
---|
58 | | - |
---|
59 | 36 | /* Serial number of device */ |
---|
60 | 37 | u32 serial_number; |
---|
61 | 38 | |
---|
62 | 39 | /* Firmware version */ |
---|
63 | 40 | int firmware_version; |
---|
| 41 | + |
---|
| 42 | + /* Monitor level */ |
---|
| 43 | + int monitor_level; |
---|
64 | 44 | }; |
---|
65 | 45 | |
---|
66 | | -static struct snd_ratden podhd_ratden = { |
---|
| 46 | +#define line6_to_podhd(x) container_of(x, struct usb_line6_podhd, line6) |
---|
| 47 | + |
---|
| 48 | +static const struct snd_ratden podhd_ratden = { |
---|
67 | 49 | .num_min = 48000, |
---|
68 | 50 | .num_max = 48000, |
---|
69 | 51 | .num_step = 1, |
---|
.. | .. |
---|
158 | 140 | }; |
---|
159 | 141 | static struct usb_driver podhd_driver; |
---|
160 | 142 | |
---|
161 | | -static void podhd_startup_start_workqueue(struct timer_list *t); |
---|
162 | | -static void podhd_startup_workqueue(struct work_struct *work); |
---|
163 | | -static int podhd_startup_finalize(struct usb_line6_podhd *pod); |
---|
164 | | - |
---|
165 | 143 | static ssize_t serial_number_show(struct device *dev, |
---|
166 | 144 | struct device_attribute *attr, char *buf) |
---|
167 | 145 | { |
---|
.. | .. |
---|
202 | 180 | * audio nor bulk interfaces to work. |
---|
203 | 181 | */ |
---|
204 | 182 | |
---|
205 | | -static void podhd_startup(struct usb_line6_podhd *pod) |
---|
206 | | -{ |
---|
207 | | - CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_INIT); |
---|
208 | | - |
---|
209 | | - /* delay startup procedure: */ |
---|
210 | | - line6_start_timer(&pod->startup_timer, PODHD_STARTUP_DELAY, |
---|
211 | | - podhd_startup_start_workqueue); |
---|
212 | | -} |
---|
213 | | - |
---|
214 | | -static void podhd_startup_start_workqueue(struct timer_list *t) |
---|
215 | | -{ |
---|
216 | | - struct usb_line6_podhd *pod = from_timer(pod, t, startup_timer); |
---|
217 | | - |
---|
218 | | - CHECK_STARTUP_PROGRESS(pod->startup_progress, |
---|
219 | | - PODHD_STARTUP_SCHEDULE_WORKQUEUE); |
---|
220 | | - |
---|
221 | | - /* schedule work for global work queue: */ |
---|
222 | | - schedule_work(&pod->startup_work); |
---|
223 | | -} |
---|
224 | | - |
---|
225 | 183 | static int podhd_dev_start(struct usb_line6_podhd *pod) |
---|
226 | 184 | { |
---|
227 | 185 | int ret; |
---|
228 | | - u8 *init_bytes; |
---|
| 186 | + u8 init_bytes[8]; |
---|
229 | 187 | int i; |
---|
230 | 188 | struct usb_device *usbdev = pod->line6.usbdev; |
---|
231 | 189 | |
---|
232 | | - init_bytes = kmalloc(8, GFP_KERNEL); |
---|
233 | | - if (!init_bytes) |
---|
234 | | - return -ENOMEM; |
---|
235 | | - |
---|
236 | | - ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), |
---|
| 190 | + ret = usb_control_msg_send(usbdev, 0, |
---|
237 | 191 | 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, |
---|
238 | 192 | 0x11, 0, |
---|
239 | | - NULL, 0, LINE6_TIMEOUT); |
---|
240 | | - if (ret < 0) { |
---|
| 193 | + NULL, 0, LINE6_TIMEOUT, GFP_KERNEL); |
---|
| 194 | + if (ret) { |
---|
241 | 195 | dev_err(pod->line6.ifcdev, "read request failed (error %d)\n", ret); |
---|
242 | 196 | goto exit; |
---|
243 | 197 | } |
---|
244 | 198 | |
---|
245 | 199 | /* NOTE: looks like some kind of ping message */ |
---|
246 | | - ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, |
---|
| 200 | + ret = usb_control_msg_recv(usbdev, 0, 0x67, |
---|
247 | 201 | USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, |
---|
248 | 202 | 0x11, 0x0, |
---|
249 | | - init_bytes, 3, LINE6_TIMEOUT); |
---|
250 | | - if (ret < 0) { |
---|
| 203 | + init_bytes, 3, LINE6_TIMEOUT, GFP_KERNEL); |
---|
| 204 | + if (ret) { |
---|
251 | 205 | dev_err(pod->line6.ifcdev, |
---|
252 | 206 | "receive length failed (error %d)\n", ret); |
---|
253 | 207 | goto exit; |
---|
.. | .. |
---|
262 | 216 | goto exit; |
---|
263 | 217 | } |
---|
264 | 218 | |
---|
265 | | - ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), |
---|
| 219 | + ret = usb_control_msg_send(usbdev, 0, |
---|
266 | 220 | USB_REQ_SET_FEATURE, |
---|
267 | 221 | USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_DIR_OUT, |
---|
268 | 222 | 1, 0, |
---|
269 | | - NULL, 0, LINE6_TIMEOUT); |
---|
| 223 | + NULL, 0, LINE6_TIMEOUT, GFP_KERNEL); |
---|
270 | 224 | exit: |
---|
271 | | - kfree(init_bytes); |
---|
272 | 225 | return ret; |
---|
273 | 226 | } |
---|
274 | 227 | |
---|
275 | | -static void podhd_startup_workqueue(struct work_struct *work) |
---|
| 228 | +static void podhd_startup(struct usb_line6 *line6) |
---|
276 | 229 | { |
---|
277 | | - struct usb_line6_podhd *pod = |
---|
278 | | - container_of(work, struct usb_line6_podhd, startup_work); |
---|
279 | | - |
---|
280 | | - CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_SETUP); |
---|
| 230 | + struct usb_line6_podhd *pod = line6_to_podhd(line6); |
---|
281 | 231 | |
---|
282 | 232 | podhd_dev_start(pod); |
---|
283 | 233 | line6_read_serial_number(&pod->line6, &pod->serial_number); |
---|
284 | | - |
---|
285 | | - podhd_startup_finalize(pod); |
---|
286 | | -} |
---|
287 | | - |
---|
288 | | -static int podhd_startup_finalize(struct usb_line6_podhd *pod) |
---|
289 | | -{ |
---|
290 | | - struct usb_line6 *line6 = &pod->line6; |
---|
291 | | - |
---|
292 | | - /* ALSA audio interface: */ |
---|
293 | | - return snd_card_register(line6->card); |
---|
| 234 | + if (snd_card_register(line6->card)) |
---|
| 235 | + dev_err(line6->ifcdev, "Failed to register POD HD card.\n"); |
---|
294 | 236 | } |
---|
295 | 237 | |
---|
296 | 238 | static void podhd_disconnect(struct usb_line6 *line6) |
---|
297 | 239 | { |
---|
298 | | - struct usb_line6_podhd *pod = (struct usb_line6_podhd *)line6; |
---|
| 240 | + struct usb_line6_podhd *pod = line6_to_podhd(line6); |
---|
299 | 241 | |
---|
300 | 242 | if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO) { |
---|
301 | 243 | struct usb_interface *intf; |
---|
302 | | - |
---|
303 | | - del_timer_sync(&pod->startup_timer); |
---|
304 | | - cancel_work_sync(&pod->startup_work); |
---|
305 | 244 | |
---|
306 | 245 | intf = usb_ifnum_to_if(line6->usbdev, |
---|
307 | 246 | pod->line6.properties->ctrl_if); |
---|
.. | .. |
---|
310 | 249 | } |
---|
311 | 250 | } |
---|
312 | 251 | |
---|
| 252 | +static const unsigned int float_zero_to_one_lookup[] = { |
---|
| 253 | +0x00000000, 0x3c23d70a, 0x3ca3d70a, 0x3cf5c28f, 0x3d23d70a, 0x3d4ccccd, |
---|
| 254 | +0x3d75c28f, 0x3d8f5c29, 0x3da3d70a, 0x3db851ec, 0x3dcccccd, 0x3de147ae, |
---|
| 255 | +0x3df5c28f, 0x3e051eb8, 0x3e0f5c29, 0x3e19999a, 0x3e23d70a, 0x3e2e147b, |
---|
| 256 | +0x3e3851ec, 0x3e428f5c, 0x3e4ccccd, 0x3e570a3d, 0x3e6147ae, 0x3e6b851f, |
---|
| 257 | +0x3e75c28f, 0x3e800000, 0x3e851eb8, 0x3e8a3d71, 0x3e8f5c29, 0x3e947ae1, |
---|
| 258 | +0x3e99999a, 0x3e9eb852, 0x3ea3d70a, 0x3ea8f5c3, 0x3eae147b, 0x3eb33333, |
---|
| 259 | +0x3eb851ec, 0x3ebd70a4, 0x3ec28f5c, 0x3ec7ae14, 0x3ecccccd, 0x3ed1eb85, |
---|
| 260 | +0x3ed70a3d, 0x3edc28f6, 0x3ee147ae, 0x3ee66666, 0x3eeb851f, 0x3ef0a3d7, |
---|
| 261 | +0x3ef5c28f, 0x3efae148, 0x3f000000, 0x3f028f5c, 0x3f051eb8, 0x3f07ae14, |
---|
| 262 | +0x3f0a3d71, 0x3f0ccccd, 0x3f0f5c29, 0x3f11eb85, 0x3f147ae1, 0x3f170a3d, |
---|
| 263 | +0x3f19999a, 0x3f1c28f6, 0x3f1eb852, 0x3f2147ae, 0x3f23d70a, 0x3f266666, |
---|
| 264 | +0x3f28f5c3, 0x3f2b851f, 0x3f2e147b, 0x3f30a3d7, 0x3f333333, 0x3f35c28f, |
---|
| 265 | +0x3f3851ec, 0x3f3ae148, 0x3f3d70a4, 0x3f400000, 0x3f428f5c, 0x3f451eb8, |
---|
| 266 | +0x3f47ae14, 0x3f4a3d71, 0x3f4ccccd, 0x3f4f5c29, 0x3f51eb85, 0x3f547ae1, |
---|
| 267 | +0x3f570a3d, 0x3f59999a, 0x3f5c28f6, 0x3f5eb852, 0x3f6147ae, 0x3f63d70a, |
---|
| 268 | +0x3f666666, 0x3f68f5c3, 0x3f6b851f, 0x3f6e147b, 0x3f70a3d7, 0x3f733333, |
---|
| 269 | +0x3f75c28f, 0x3f7851ec, 0x3f7ae148, 0x3f7d70a4, 0x3f800000 |
---|
| 270 | +}; |
---|
| 271 | + |
---|
| 272 | +static void podhd_set_monitor_level(struct usb_line6_podhd *podhd, int value) |
---|
| 273 | +{ |
---|
| 274 | + unsigned int fl; |
---|
| 275 | + static const unsigned char msg[16] = { |
---|
| 276 | + /* Chunk is 0xc bytes (without first word) */ |
---|
| 277 | + 0x0c, 0x00, |
---|
| 278 | + /* First chunk in the message */ |
---|
| 279 | + 0x01, 0x00, |
---|
| 280 | + /* Message size is 2 4-byte words */ |
---|
| 281 | + 0x02, 0x00, |
---|
| 282 | + /* Unknown */ |
---|
| 283 | + 0x04, 0x41, |
---|
| 284 | + /* Unknown */ |
---|
| 285 | + 0x04, 0x00, 0x13, 0x00, |
---|
| 286 | + /* Volume, LE float32, 0.0 - 1.0 */ |
---|
| 287 | + 0x00, 0x00, 0x00, 0x00 |
---|
| 288 | + }; |
---|
| 289 | + unsigned char *buf; |
---|
| 290 | + |
---|
| 291 | + buf = kmemdup(msg, sizeof(msg), GFP_KERNEL); |
---|
| 292 | + if (!buf) |
---|
| 293 | + return; |
---|
| 294 | + |
---|
| 295 | + if (value < 0) |
---|
| 296 | + value = 0; |
---|
| 297 | + |
---|
| 298 | + if (value >= ARRAY_SIZE(float_zero_to_one_lookup)) |
---|
| 299 | + value = ARRAY_SIZE(float_zero_to_one_lookup) - 1; |
---|
| 300 | + |
---|
| 301 | + fl = float_zero_to_one_lookup[value]; |
---|
| 302 | + |
---|
| 303 | + buf[12] = (fl >> 0) & 0xff; |
---|
| 304 | + buf[13] = (fl >> 8) & 0xff; |
---|
| 305 | + buf[14] = (fl >> 16) & 0xff; |
---|
| 306 | + buf[15] = (fl >> 24) & 0xff; |
---|
| 307 | + |
---|
| 308 | + line6_send_raw_message(&podhd->line6, buf, sizeof(msg)); |
---|
| 309 | + kfree(buf); |
---|
| 310 | + |
---|
| 311 | + podhd->monitor_level = value; |
---|
| 312 | +} |
---|
| 313 | + |
---|
| 314 | +/* control info callback */ |
---|
| 315 | +static int snd_podhd_control_monitor_info(struct snd_kcontrol *kcontrol, |
---|
| 316 | + struct snd_ctl_elem_info *uinfo) |
---|
| 317 | +{ |
---|
| 318 | + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
---|
| 319 | + uinfo->count = 1; |
---|
| 320 | + uinfo->value.integer.min = 0; |
---|
| 321 | + uinfo->value.integer.max = 100; |
---|
| 322 | + uinfo->value.integer.step = 1; |
---|
| 323 | + return 0; |
---|
| 324 | +} |
---|
| 325 | + |
---|
| 326 | +/* control get callback */ |
---|
| 327 | +static int snd_podhd_control_monitor_get(struct snd_kcontrol *kcontrol, |
---|
| 328 | + struct snd_ctl_elem_value *ucontrol) |
---|
| 329 | +{ |
---|
| 330 | + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); |
---|
| 331 | + struct usb_line6_podhd *podhd = line6_to_podhd(line6pcm->line6); |
---|
| 332 | + |
---|
| 333 | + ucontrol->value.integer.value[0] = podhd->monitor_level; |
---|
| 334 | + return 0; |
---|
| 335 | +} |
---|
| 336 | + |
---|
| 337 | +/* control put callback */ |
---|
| 338 | +static int snd_podhd_control_monitor_put(struct snd_kcontrol *kcontrol, |
---|
| 339 | + struct snd_ctl_elem_value *ucontrol) |
---|
| 340 | +{ |
---|
| 341 | + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); |
---|
| 342 | + struct usb_line6_podhd *podhd = line6_to_podhd(line6pcm->line6); |
---|
| 343 | + |
---|
| 344 | + if (ucontrol->value.integer.value[0] == podhd->monitor_level) |
---|
| 345 | + return 0; |
---|
| 346 | + |
---|
| 347 | + podhd_set_monitor_level(podhd, ucontrol->value.integer.value[0]); |
---|
| 348 | + return 1; |
---|
| 349 | +} |
---|
| 350 | + |
---|
| 351 | +/* control definition */ |
---|
| 352 | +static const struct snd_kcontrol_new podhd_control_monitor = { |
---|
| 353 | + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
---|
| 354 | + .name = "Monitor Playback Volume", |
---|
| 355 | + .index = 0, |
---|
| 356 | + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, |
---|
| 357 | + .info = snd_podhd_control_monitor_info, |
---|
| 358 | + .get = snd_podhd_control_monitor_get, |
---|
| 359 | + .put = snd_podhd_control_monitor_put |
---|
| 360 | +}; |
---|
| 361 | + |
---|
313 | 362 | /* |
---|
314 | 363 | Try to init POD HD device. |
---|
315 | 364 | */ |
---|
.. | .. |
---|
317 | 366 | const struct usb_device_id *id) |
---|
318 | 367 | { |
---|
319 | 368 | int err; |
---|
320 | | - struct usb_line6_podhd *pod = (struct usb_line6_podhd *) line6; |
---|
| 369 | + struct usb_line6_podhd *pod = line6_to_podhd(line6); |
---|
321 | 370 | struct usb_interface *intf; |
---|
322 | 371 | |
---|
323 | 372 | line6->disconnect = podhd_disconnect; |
---|
324 | | - |
---|
325 | | - timer_setup(&pod->startup_timer, NULL, 0); |
---|
326 | | - INIT_WORK(&pod->startup_work, podhd_startup_workqueue); |
---|
| 373 | + line6->startup = podhd_startup; |
---|
327 | 374 | |
---|
328 | 375 | if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { |
---|
329 | 376 | /* claim the data interface */ |
---|
.. | .. |
---|
360 | 407 | return err; |
---|
361 | 408 | } |
---|
362 | 409 | |
---|
| 410 | + if (pod->line6.properties->capabilities & LINE6_CAP_HWMON_CTL) { |
---|
| 411 | + podhd_set_monitor_level(pod, 100); |
---|
| 412 | + err = snd_ctl_add(line6->card, |
---|
| 413 | + snd_ctl_new1(&podhd_control_monitor, |
---|
| 414 | + line6->line6pcm)); |
---|
| 415 | + if (err < 0) |
---|
| 416 | + return err; |
---|
| 417 | + } |
---|
| 418 | + |
---|
363 | 419 | if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO)) { |
---|
364 | 420 | /* register USB audio system directly */ |
---|
365 | | - return podhd_startup_finalize(pod); |
---|
| 421 | + return snd_card_register(line6->card); |
---|
366 | 422 | } |
---|
367 | 423 | |
---|
368 | 424 | /* init device and delay registering */ |
---|
369 | | - podhd_startup(pod); |
---|
| 425 | + schedule_delayed_work(&line6->startup_work, |
---|
| 426 | + msecs_to_jiffies(PODHD_STARTUP_DELAY)); |
---|
370 | 427 | return 0; |
---|
371 | 428 | } |
---|
372 | 429 | |
---|
.. | .. |
---|
378 | 435 | /* TODO: no need to alloc data interfaces when only audio is used */ |
---|
379 | 436 | { LINE6_DEVICE(0x5057), .driver_info = LINE6_PODHD300 }, |
---|
380 | 437 | { LINE6_DEVICE(0x5058), .driver_info = LINE6_PODHD400 }, |
---|
381 | | - { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500_0 }, |
---|
382 | | - { LINE6_IF_NUM(0x414D, 1), .driver_info = LINE6_PODHD500_1 }, |
---|
| 438 | + { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500 }, |
---|
383 | 439 | { LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 }, |
---|
384 | 440 | { LINE6_IF_NUM(0x414B, 0), .driver_info = LINE6_PODX3LIVE }, |
---|
385 | 441 | { LINE6_IF_NUM(0x4159, 0), .driver_info = LINE6_PODHD500X }, |
---|
.. | .. |
---|
412 | 468 | .ep_audio_r = 0x82, |
---|
413 | 469 | .ep_audio_w = 0x01, |
---|
414 | 470 | }, |
---|
415 | | - [LINE6_PODHD500_0] = { |
---|
| 471 | + [LINE6_PODHD500] = { |
---|
416 | 472 | .id = "PODHD500", |
---|
417 | 473 | .name = "POD HD500", |
---|
418 | | - .capabilities = LINE6_CAP_PCM |
---|
419 | | - | LINE6_CAP_HWMON, |
---|
420 | | - .altsetting = 0, |
---|
421 | | - .ep_ctrl_r = 0x81, |
---|
422 | | - .ep_ctrl_w = 0x01, |
---|
423 | | - .ep_audio_r = 0x86, |
---|
424 | | - .ep_audio_w = 0x02, |
---|
425 | | - }, |
---|
426 | | - [LINE6_PODHD500_1] = { |
---|
427 | | - .id = "PODHD500", |
---|
428 | | - .name = "POD HD500", |
---|
429 | | - .capabilities = LINE6_CAP_PCM |
---|
430 | | - | LINE6_CAP_HWMON, |
---|
| 474 | + .capabilities = LINE6_CAP_PCM | LINE6_CAP_CONTROL |
---|
| 475 | + | LINE6_CAP_HWMON | LINE6_CAP_HWMON_CTL, |
---|
431 | 476 | .altsetting = 1, |
---|
| 477 | + .ctrl_if = 1, |
---|
432 | 478 | .ep_ctrl_r = 0x81, |
---|
433 | 479 | .ep_ctrl_w = 0x01, |
---|
434 | 480 | .ep_audio_r = 0x86, |
---|