Commit | Line | Data |
---|---|---|
705ececd | 1 | /* |
e1a164d7 | 2 | * Line6 Linux USB driver - 0.9.1beta |
705ececd | 3 | * |
1027f476 | 4 | * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) |
705ececd MG |
5 | * |
6 | * This program is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU General Public License as | |
8 | * published by the Free Software Foundation, version 2. | |
9 | * | |
10 | */ | |
11 | ||
5a0e3ad6 | 12 | #include <linux/slab.h> |
1027f476 MG |
13 | #include <linux/wait.h> |
14 | #include <sound/control.h> | |
5a0e3ad6 | 15 | |
705ececd MG |
16 | #include "audio.h" |
17 | #include "capture.h" | |
1027f476 | 18 | #include "driver.h" |
705ececd MG |
19 | #include "playback.h" |
20 | #include "pod.h" | |
21 | ||
705ececd | 22 | #define POD_SYSEX_CODE 3 |
e1a164d7 | 23 | #define POD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */ |
705ececd | 24 | |
e1a164d7 | 25 | /* *INDENT-OFF* */ |
705ececd MG |
26 | |
27 | enum { | |
705ececd MG |
28 | POD_SYSEX_SAVE = 0x24, |
29 | POD_SYSEX_SYSTEM = 0x56, | |
30 | POD_SYSEX_SYSTEMREQ = 0x57, | |
31 | /* POD_SYSEX_UPDATE = 0x6c, */ /* software update! */ | |
32 | POD_SYSEX_STORE = 0x71, | |
33 | POD_SYSEX_FINISH = 0x72, | |
34 | POD_SYSEX_DUMPMEM = 0x73, | |
35 | POD_SYSEX_DUMP = 0x74, | |
36 | POD_SYSEX_DUMPREQ = 0x75 | |
0a1eb4e8 SH |
37 | |
38 | /* dumps entire internal memory of PODxt Pro */ | |
39 | /* POD_SYSEX_DUMPMEM2 = 0x76 */ | |
705ececd MG |
40 | }; |
41 | ||
42 | enum { | |
43 | POD_monitor_level = 0x04, | |
1027f476 | 44 | POD_system_invalid = 0x10000 |
705ececd MG |
45 | }; |
46 | ||
e1a164d7 MG |
47 | /* *INDENT-ON* */ |
48 | ||
705ececd MG |
49 | enum { |
50 | POD_DUMP_MEMORY = 2 | |
51 | }; | |
52 | ||
53 | enum { | |
54 | POD_BUSY_READ, | |
55 | POD_BUSY_WRITE, | |
56 | POD_CHANNEL_DIRTY, | |
57 | POD_SAVE_PRESSED, | |
58 | POD_BUSY_MIDISEND | |
59 | }; | |
60 | ||
705ececd MG |
61 | static struct snd_ratden pod_ratden = { |
62 | .num_min = 78125, | |
63 | .num_max = 78125, | |
64 | .num_step = 1, | |
65 | .den = 2 | |
66 | }; | |
67 | ||
68 | static struct line6_pcm_properties pod_pcm_properties = { | |
1027f476 | 69 | .snd_line6_playback_hw = { |
e1a164d7 MG |
70 | .info = (SNDRV_PCM_INFO_MMAP | |
71 | SNDRV_PCM_INFO_INTERLEAVED | | |
72 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | |
73 | SNDRV_PCM_INFO_MMAP_VALID | | |
74 | SNDRV_PCM_INFO_PAUSE | | |
1027f476 | 75 | #ifdef CONFIG_PM |
e1a164d7 | 76 | SNDRV_PCM_INFO_RESUME | |
1027f476 | 77 | #endif |
e1a164d7 MG |
78 | SNDRV_PCM_INFO_SYNC_START), |
79 | .formats = SNDRV_PCM_FMTBIT_S24_3LE, | |
80 | .rates = SNDRV_PCM_RATE_KNOT, | |
81 | .rate_min = 39062, | |
82 | .rate_max = 39063, | |
83 | .channels_min = 2, | |
84 | .channels_max = 2, | |
85 | .buffer_bytes_max = 60000, | |
86 | .period_bytes_min = 64, | |
87 | .period_bytes_max = 8192, | |
88 | .periods_min = 1, | |
89 | .periods_max = 1024}, | |
1027f476 | 90 | .snd_line6_capture_hw = { |
e1a164d7 MG |
91 | .info = (SNDRV_PCM_INFO_MMAP | |
92 | SNDRV_PCM_INFO_INTERLEAVED | | |
93 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | |
94 | SNDRV_PCM_INFO_MMAP_VALID | | |
1027f476 | 95 | #ifdef CONFIG_PM |
e1a164d7 | 96 | SNDRV_PCM_INFO_RESUME | |
1027f476 | 97 | #endif |
e1a164d7 MG |
98 | SNDRV_PCM_INFO_SYNC_START), |
99 | .formats = SNDRV_PCM_FMTBIT_S24_3LE, | |
100 | .rates = SNDRV_PCM_RATE_KNOT, | |
101 | .rate_min = 39062, | |
102 | .rate_max = 39063, | |
103 | .channels_min = 2, | |
104 | .channels_max = 2, | |
105 | .buffer_bytes_max = 60000, | |
106 | .period_bytes_min = 64, | |
107 | .period_bytes_max = 8192, | |
108 | .periods_min = 1, | |
109 | .periods_max = 1024}, | |
705ececd | 110 | .snd_line6_rates = { |
e1a164d7 MG |
111 | .nrats = 1, |
112 | .rats = &pod_ratden}, | |
705ececd MG |
113 | .bytes_per_frame = POD_BYTES_PER_FRAME |
114 | }; | |
115 | ||
e1a164d7 | 116 | static const char pod_version_header[] = { |
1027f476 MG |
117 | 0xf2, 0x7e, 0x7f, 0x06, 0x02 |
118 | }; | |
119 | ||
1027f476 MG |
120 | /* forward declarations: */ |
121 | static void pod_startup2(unsigned long data); | |
122 | static void pod_startup3(struct usb_line6_pod *pod); | |
705ececd | 123 | |
e1a164d7 MG |
124 | static char *pod_alloc_sysex_buffer(struct usb_line6_pod *pod, int code, |
125 | int size) | |
705ececd | 126 | { |
e1a164d7 MG |
127 | return line6_alloc_sysex_buffer(&pod->line6, POD_SYSEX_CODE, code, |
128 | size); | |
705ececd MG |
129 | } |
130 | ||
705ececd MG |
131 | /* |
132 | Process a completely received message. | |
133 | */ | |
1027f476 | 134 | void line6_pod_process_message(struct usb_line6_pod *pod) |
705ececd MG |
135 | { |
136 | const unsigned char *buf = pod->line6.buffer_message; | |
137 | ||
138 | /* filter messages by type */ | |
0fdef36a | 139 | switch (buf[0] & 0xf0) { |
705ececd MG |
140 | case LINE6_PARAM_CHANGE: |
141 | case LINE6_PROGRAM_CHANGE: | |
142 | case LINE6_SYSEX_BEGIN: | |
e1a164d7 | 143 | break; /* handle these further down */ |
705ececd MG |
144 | |
145 | default: | |
e1a164d7 | 146 | return; /* ignore all others */ |
705ececd MG |
147 | } |
148 | ||
149 | /* process all remaining messages */ | |
0fdef36a | 150 | switch (buf[0]) { |
705ececd | 151 | case LINE6_PARAM_CHANGE | LINE6_CHANNEL_DEVICE: |
705ececd | 152 | case LINE6_PARAM_CHANGE | LINE6_CHANNEL_HOST: |
705ececd MG |
153 | break; |
154 | ||
155 | case LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_DEVICE: | |
156 | case LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_HOST: | |
705ececd MG |
157 | break; |
158 | ||
159 | case LINE6_SYSEX_BEGIN | LINE6_CHANNEL_DEVICE: | |
160 | case LINE6_SYSEX_BEGIN | LINE6_CHANNEL_UNKNOWN: | |
0a1eb4e8 SH |
161 | if (memcmp(buf + 1, line6_midi_id, |
162 | sizeof(line6_midi_id)) == 0) { | |
0fdef36a | 163 | switch (buf[5]) { |
705ececd | 164 | case POD_SYSEX_DUMP: |
705ececd MG |
165 | break; |
166 | ||
e1a164d7 MG |
167 | case POD_SYSEX_SYSTEM:{ |
168 | short value = | |
169 | ((int)buf[7] << 12) | ((int)buf[8] | |
170 | << 8) | | |
171 | ((int)buf[9] << 4) | (int)buf[10]; | |
705ececd | 172 | |
99c54e98 | 173 | if (buf[6] == POD_monitor_level) |
2c35dc21 | 174 | pod->monitor_level = value; |
e1a164d7 MG |
175 | break; |
176 | } | |
705ececd MG |
177 | |
178 | case POD_SYSEX_FINISH: | |
179 | /* do we need to respond to this? */ | |
180 | break; | |
181 | ||
182 | case POD_SYSEX_SAVE: | |
705ececd MG |
183 | break; |
184 | ||
705ececd | 185 | case POD_SYSEX_STORE: |
e00d33cb SH |
186 | dev_dbg(pod->line6.ifcdev, |
187 | "message %02X not yet implemented\n", | |
188 | buf[5]); | |
705ececd MG |
189 | break; |
190 | ||
191 | default: | |
e00d33cb SH |
192 | dev_dbg(pod->line6.ifcdev, |
193 | "unknown sysex message %02X\n", | |
194 | buf[5]); | |
705ececd | 195 | } |
e1a164d7 MG |
196 | } else |
197 | if (memcmp | |
198 | (buf, pod_version_header, | |
199 | sizeof(pod_version_header)) == 0) { | |
200 | pod->firmware_version = | |
201 | buf[13] * 100 + buf[14] * 10 + buf[15]; | |
202 | pod->device_id = | |
203 | ((int)buf[8] << 16) | ((int)buf[9] << 8) | (int) | |
204 | buf[10]; | |
09fda10a | 205 | pod_startup3(pod); |
0fdef36a | 206 | } else |
e00d33cb | 207 | dev_dbg(pod->line6.ifcdev, "unknown sysex header\n"); |
705ececd MG |
208 | |
209 | break; | |
210 | ||
211 | case LINE6_SYSEX_END: | |
212 | break; | |
213 | ||
214 | default: | |
e00d33cb SH |
215 | dev_dbg(pod->line6.ifcdev, "POD: unknown message %02X\n", |
216 | buf[0]); | |
705ececd MG |
217 | } |
218 | } | |
219 | ||
705ececd MG |
220 | /* |
221 | Transmit PODxt Pro control parameter. | |
222 | */ | |
e1a164d7 | 223 | void line6_pod_transmit_parameter(struct usb_line6_pod *pod, int param, |
5b9bd2ad | 224 | u8 value) |
705ececd | 225 | { |
79038f61 | 226 | line6_transmit_parameter(&pod->line6, param, value); |
705ececd MG |
227 | } |
228 | ||
705ececd | 229 | /* |
1027f476 | 230 | Send system parameter (from integer). |
705ececd | 231 | */ |
e1a164d7 MG |
232 | static int pod_set_system_param_int(struct usb_line6_pod *pod, int value, |
233 | int code) | |
705ececd MG |
234 | { |
235 | char *sysex; | |
236 | static const int size = 5; | |
705ececd | 237 | |
705ececd | 238 | sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_SYSTEM, size); |
0fdef36a | 239 | if (!sysex) |
1027f476 | 240 | return -ENOMEM; |
705ececd MG |
241 | sysex[SYSEX_DATA_OFS] = code; |
242 | sysex[SYSEX_DATA_OFS + 1] = (value >> 12) & 0x0f; | |
e1a164d7 MG |
243 | sysex[SYSEX_DATA_OFS + 2] = (value >> 8) & 0x0f; |
244 | sysex[SYSEX_DATA_OFS + 3] = (value >> 4) & 0x0f; | |
245 | sysex[SYSEX_DATA_OFS + 4] = (value) & 0x0f; | |
705ececd MG |
246 | line6_send_sysex_message(&pod->line6, sysex, size); |
247 | kfree(sysex); | |
1027f476 MG |
248 | return 0; |
249 | } | |
250 | ||
705ececd MG |
251 | /* |
252 | "read" request on "serial_number" special file. | |
253 | */ | |
77491e52 GKH |
254 | static ssize_t pod_get_serial_number(struct device *dev, |
255 | struct device_attribute *attr, char *buf) | |
705ececd MG |
256 | { |
257 | struct usb_interface *interface = to_usb_interface(dev); | |
258 | struct usb_line6_pod *pod = usb_get_intfdata(interface); | |
259 | return sprintf(buf, "%d\n", pod->serial_number); | |
260 | } | |
261 | ||
262 | /* | |
263 | "read" request on "firmware_version" special file. | |
264 | */ | |
77491e52 GKH |
265 | static ssize_t pod_get_firmware_version(struct device *dev, |
266 | struct device_attribute *attr, | |
267 | char *buf) | |
705ececd MG |
268 | { |
269 | struct usb_interface *interface = to_usb_interface(dev); | |
270 | struct usb_line6_pod *pod = usb_get_intfdata(interface); | |
0fdef36a GKH |
271 | return sprintf(buf, "%d.%02d\n", pod->firmware_version / 100, |
272 | pod->firmware_version % 100); | |
705ececd MG |
273 | } |
274 | ||
275 | /* | |
276 | "read" request on "device_id" special file. | |
277 | */ | |
77491e52 GKH |
278 | static ssize_t pod_get_device_id(struct device *dev, |
279 | struct device_attribute *attr, char *buf) | |
705ececd MG |
280 | { |
281 | struct usb_interface *interface = to_usb_interface(dev); | |
282 | struct usb_line6_pod *pod = usb_get_intfdata(interface); | |
283 | return sprintf(buf, "%d\n", pod->device_id); | |
284 | } | |
285 | ||
1027f476 MG |
286 | /* |
287 | POD startup procedure. | |
288 | This is a sequence of functions with special requirements (e.g., must | |
289 | not run immediately after initialization, must not run in interrupt | |
290 | context). After the last one has finished, the device is ready to use. | |
291 | */ | |
292 | ||
293 | static void pod_startup1(struct usb_line6_pod *pod) | |
294 | { | |
e1a164d7 | 295 | CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_INIT); |
1027f476 MG |
296 | |
297 | /* delay startup procedure: */ | |
e1a164d7 MG |
298 | line6_start_timer(&pod->startup_timer, POD_STARTUP_DELAY, pod_startup2, |
299 | (unsigned long)pod); | |
1027f476 MG |
300 | } |
301 | ||
302 | static void pod_startup2(unsigned long data) | |
303 | { | |
304 | struct usb_line6_pod *pod = (struct usb_line6_pod *)data; | |
1027f476 | 305 | struct usb_line6 *line6 = &pod->line6; |
e1a164d7 | 306 | CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_VERSIONREQ); |
1027f476 MG |
307 | |
308 | /* request firmware version: */ | |
309 | line6_version_request_async(line6); | |
705ececd MG |
310 | } |
311 | ||
09fda10a | 312 | static void pod_startup3(struct usb_line6_pod *pod) |
1027f476 | 313 | { |
e1a164d7 | 314 | CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_WORKQUEUE); |
1027f476 MG |
315 | |
316 | /* schedule work for global work queue: */ | |
317 | schedule_work(&pod->startup_work); | |
318 | } | |
319 | ||
09fda10a | 320 | static void pod_startup4(struct work_struct *work) |
1027f476 | 321 | { |
e1a164d7 MG |
322 | struct usb_line6_pod *pod = |
323 | container_of(work, struct usb_line6_pod, startup_work); | |
1027f476 MG |
324 | struct usb_line6 *line6 = &pod->line6; |
325 | ||
e1a164d7 | 326 | CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_SETUP); |
1027f476 MG |
327 | |
328 | /* serial number: */ | |
329 | line6_read_serial_number(&pod->line6, &pod->serial_number); | |
330 | ||
331 | /* ALSA audio interface: */ | |
332 | line6_register_audio(line6); | |
1027f476 MG |
333 | } |
334 | ||
705ececd | 335 | /* POD special files: */ |
705ececd | 336 | static DEVICE_ATTR(device_id, S_IRUGO, pod_get_device_id, line6_nop_write); |
e1a164d7 MG |
337 | static DEVICE_ATTR(firmware_version, S_IRUGO, pod_get_firmware_version, |
338 | line6_nop_write); | |
e1a164d7 MG |
339 | static DEVICE_ATTR(serial_number, S_IRUGO, pod_get_serial_number, |
340 | line6_nop_write); | |
705ececd | 341 | |
1027f476 MG |
342 | /* control info callback */ |
343 | static int snd_pod_control_monitor_info(struct snd_kcontrol *kcontrol, | |
344 | struct snd_ctl_elem_info *uinfo) | |
345 | { | |
346 | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | |
347 | uinfo->count = 1; | |
348 | uinfo->value.integer.min = 0; | |
349 | uinfo->value.integer.max = 65535; | |
350 | return 0; | |
351 | } | |
352 | ||
353 | /* control get callback */ | |
354 | static int snd_pod_control_monitor_get(struct snd_kcontrol *kcontrol, | |
355 | struct snd_ctl_elem_value *ucontrol) | |
356 | { | |
357 | struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); | |
358 | struct usb_line6_pod *pod = (struct usb_line6_pod *)line6pcm->line6; | |
2c35dc21 | 359 | ucontrol->value.integer.value[0] = pod->monitor_level; |
1027f476 MG |
360 | return 0; |
361 | } | |
362 | ||
363 | /* control put callback */ | |
364 | static int snd_pod_control_monitor_put(struct snd_kcontrol *kcontrol, | |
365 | struct snd_ctl_elem_value *ucontrol) | |
366 | { | |
367 | struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); | |
368 | struct usb_line6_pod *pod = (struct usb_line6_pod *)line6pcm->line6; | |
369 | ||
2c35dc21 | 370 | if (ucontrol->value.integer.value[0] == pod->monitor_level) |
1027f476 MG |
371 | return 0; |
372 | ||
2c35dc21 | 373 | pod->monitor_level = ucontrol->value.integer.value[0]; |
e1a164d7 MG |
374 | pod_set_system_param_int(pod, ucontrol->value.integer.value[0], |
375 | POD_monitor_level); | |
1027f476 MG |
376 | return 1; |
377 | } | |
378 | ||
379 | /* control definition */ | |
380 | static struct snd_kcontrol_new pod_control_monitor = { | |
381 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | |
382 | .name = "Monitor Playback Volume", | |
383 | .index = 0, | |
384 | .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | |
385 | .info = snd_pod_control_monitor_info, | |
386 | .get = snd_pod_control_monitor_get, | |
387 | .put = snd_pod_control_monitor_put | |
388 | }; | |
389 | ||
705ececd MG |
390 | /* |
391 | POD destructor. | |
392 | */ | |
393 | static void pod_destruct(struct usb_interface *interface) | |
394 | { | |
395 | struct usb_line6_pod *pod = usb_get_intfdata(interface); | |
705ececd | 396 | |
0fdef36a GKH |
397 | if (pod == NULL) |
398 | return; | |
188e6645 | 399 | line6_cleanup_audio(&pod->line6); |
705ececd | 400 | |
e1a164d7 MG |
401 | del_timer(&pod->startup_timer); |
402 | cancel_work_sync(&pod->startup_work); | |
705ececd MG |
403 | } |
404 | ||
405 | /* | |
406 | Create sysfs entries. | |
407 | */ | |
b702ed25 | 408 | static int pod_create_files2(struct device *dev) |
705ececd MG |
409 | { |
410 | int err; | |
411 | ||
705ececd | 412 | CHECK_RETURN(device_create_file(dev, &dev_attr_device_id)); |
705ececd | 413 | CHECK_RETURN(device_create_file(dev, &dev_attr_firmware_version)); |
705ececd | 414 | CHECK_RETURN(device_create_file(dev, &dev_attr_serial_number)); |
705ececd MG |
415 | return 0; |
416 | } | |
417 | ||
418 | /* | |
1027f476 | 419 | Try to init POD device. |
705ececd | 420 | */ |
e1a164d7 MG |
421 | static int pod_try_init(struct usb_interface *interface, |
422 | struct usb_line6_pod *pod) | |
705ececd MG |
423 | { |
424 | int err; | |
425 | struct usb_line6 *line6 = &pod->line6; | |
426 | ||
e1a164d7 | 427 | init_timer(&pod->startup_timer); |
09fda10a | 428 | INIT_WORK(&pod->startup_work, pod_startup4); |
e1a164d7 | 429 | |
0fdef36a GKH |
430 | if ((interface == NULL) || (pod == NULL)) |
431 | return -ENODEV; | |
705ececd | 432 | |
705ececd | 433 | /* create sysfs entries: */ |
0fdef36a | 434 | err = pod_create_files2(&interface->dev); |
027360c5 | 435 | if (err < 0) |
705ececd | 436 | return err; |
705ececd MG |
437 | |
438 | /* initialize audio system: */ | |
0fdef36a | 439 | err = line6_init_audio(line6); |
027360c5 | 440 | if (err < 0) |
705ececd | 441 | return err; |
705ececd MG |
442 | |
443 | /* initialize MIDI subsystem: */ | |
0fdef36a | 444 | err = line6_init_midi(line6); |
027360c5 | 445 | if (err < 0) |
705ececd | 446 | return err; |
705ececd MG |
447 | |
448 | /* initialize PCM subsystem: */ | |
0fdef36a | 449 | err = line6_init_pcm(line6, &pod_pcm_properties); |
027360c5 | 450 | if (err < 0) |
705ececd | 451 | return err; |
705ececd | 452 | |
1027f476 | 453 | /* register monitor control: */ |
027360c5 GKH |
454 | err = snd_ctl_add(line6->card, |
455 | snd_ctl_new1(&pod_control_monitor, line6->line6pcm)); | |
456 | if (err < 0) | |
705ececd | 457 | return err; |
705ececd | 458 | |
1027f476 | 459 | /* |
e1a164d7 MG |
460 | When the sound card is registered at this point, the PODxt Live |
461 | displays "Invalid Code Error 07", so we do it later in the event | |
462 | handler. | |
463 | */ | |
1027f476 | 464 | |
0fdef36a | 465 | if (pod->line6.properties->capabilities & LINE6_BIT_CONTROL) { |
2c35dc21 | 466 | pod->monitor_level = POD_system_invalid; |
1027f476 MG |
467 | |
468 | /* initiate startup procedure: */ | |
469 | pod_startup1(pod); | |
705ececd MG |
470 | } |
471 | ||
472 | return 0; | |
473 | } | |
474 | ||
1027f476 MG |
475 | /* |
476 | Init POD device (and clean up in case of failure). | |
477 | */ | |
478 | int line6_pod_init(struct usb_interface *interface, struct usb_line6_pod *pod) | |
479 | { | |
480 | int err = pod_try_init(interface, pod); | |
481 | ||
027360c5 | 482 | if (err < 0) |
1027f476 | 483 | pod_destruct(interface); |
1027f476 MG |
484 | |
485 | return err; | |
486 | } | |
487 | ||
705ececd MG |
488 | /* |
489 | POD device disconnected. | |
490 | */ | |
1027f476 | 491 | void line6_pod_disconnect(struct usb_interface *interface) |
705ececd MG |
492 | { |
493 | struct usb_line6_pod *pod; | |
494 | ||
0fdef36a GKH |
495 | if (interface == NULL) |
496 | return; | |
705ececd MG |
497 | pod = usb_get_intfdata(interface); |
498 | ||
0fdef36a | 499 | if (pod != NULL) { |
705ececd MG |
500 | struct snd_line6_pcm *line6pcm = pod->line6.line6pcm; |
501 | struct device *dev = &interface->dev; | |
502 | ||
027360c5 | 503 | if (line6pcm != NULL) |
1027f476 | 504 | line6_pcm_disconnect(line6pcm); |
705ececd | 505 | |
0fdef36a | 506 | if (dev != NULL) { |
705ececd | 507 | /* remove sysfs entries: */ |
705ececd | 508 | device_remove_file(dev, &dev_attr_device_id); |
705ececd | 509 | device_remove_file(dev, &dev_attr_firmware_version); |
705ececd | 510 | device_remove_file(dev, &dev_attr_serial_number); |
705ececd MG |
511 | } |
512 | } | |
513 | ||
514 | pod_destruct(interface); | |
515 | } |