blob: acbfe1c49b4dc9148c48d788ccd4f8c2111961c6 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * The DSP56001 Device Driver, saviour of the Free World(tm)
3 *
4 * Authors: Fredrik Noring <noring@nocrew.org>
5 * lars brinkhoff <lars@nocrew.org>
6 * Tomas Berndtsson <tomas@nocrew.org>
7 *
8 * First version May 1996
9 *
10 * History:
11 * 97-01-29 Tomas Berndtsson,
12 * Integrated with Linux 2.1.21 kernel sources.
13 * 97-02-15 Tomas Berndtsson,
14 * Fixed for kernel 2.1.26
15 *
16 * BUGS:
17 * Hmm... there must be something here :)
18 *
19 * Copyright (C) 1996,1997 Fredrik Noring, lars brinkhoff & Tomas Berndtsson
20 *
21 * This file is subject to the terms and conditions of the GNU General Public
22 * License. See the file COPYING in the main directory of this archive
23 * for more details.
24 */
25
26#include <linux/module.h>
27#include <linux/slab.h> /* for kmalloc() and kfree() */
Linus Torvalds1da177e2005-04-16 15:20:36 -070028#include <linux/major.h>
29#include <linux/types.h>
30#include <linux/errno.h>
31#include <linux/delay.h> /* guess what */
32#include <linux/fs.h>
33#include <linux/mm.h>
34#include <linux/init.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070035#include <linux/device.h>
36
37#include <asm/atarihw.h>
38#include <asm/traps.h>
39#include <asm/uaccess.h> /* For put_user and get_user */
40
41#include <asm/dsp56k.h>
42
43/* minor devices */
44#define DSP56K_DEV_56001 0 /* The only device so far */
45
46#define TIMEOUT 10 /* Host port timeout in number of tries */
47#define MAXIO 2048 /* Maximum number of words before sleep */
48#define DSP56K_MAX_BINARY_LENGTH (3*64*1024)
49
50#define DSP56K_TX_INT_ON dsp56k_host_interface.icr |= DSP56K_ICR_TREQ
51#define DSP56K_RX_INT_ON dsp56k_host_interface.icr |= DSP56K_ICR_RREQ
52#define DSP56K_TX_INT_OFF dsp56k_host_interface.icr &= ~DSP56K_ICR_TREQ
53#define DSP56K_RX_INT_OFF dsp56k_host_interface.icr &= ~DSP56K_ICR_RREQ
54
55#define DSP56K_TRANSMIT (dsp56k_host_interface.isr & DSP56K_ISR_TXDE)
56#define DSP56K_RECEIVE (dsp56k_host_interface.isr & DSP56K_ISR_RXDF)
57
58#define handshake(count, maxio, timeout, ENABLE, f) \
59{ \
60 long i, t, m; \
61 while (count > 0) { \
62 m = min_t(unsigned long, count, maxio); \
63 for (i = 0; i < m; i++) { \
64 for (t = 0; t < timeout && !ENABLE; t++) \
65 msleep(20); \
66 if(!ENABLE) \
67 return -EIO; \
68 f; \
69 } \
70 count -= m; \
71 if (m == maxio) msleep(20); \
72 } \
73}
74
75#define tx_wait(n) \
76{ \
77 int t; \
78 for(t = 0; t < n && !DSP56K_TRANSMIT; t++) \
79 msleep(10); \
80 if(!DSP56K_TRANSMIT) { \
81 return -EIO; \
82 } \
83}
84
85#define rx_wait(n) \
86{ \
87 int t; \
88 for(t = 0; t < n && !DSP56K_RECEIVE; t++) \
89 msleep(10); \
90 if(!DSP56K_RECEIVE) { \
91 return -EIO; \
92 } \
93}
94
95/* DSP56001 bootstrap code */
96static char bootstrap[] = {
97 0x0c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
98 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
99 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
100 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
101 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
102 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
103 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
104 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
105 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
106 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
107 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
108 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
109 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
110 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
111 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
112 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
113 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
114 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
115 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
116 0x00, 0x00, 0x60, 0xf4, 0x00, 0x00, 0x00, 0x4f, 0x61, 0xf4,
117 0x00, 0x00, 0x7e, 0xa9, 0x06, 0x2e, 0x80, 0x00, 0x00, 0x47,
118 0x07, 0xd8, 0x84, 0x07, 0x59, 0x84, 0x08, 0xf4, 0xa8, 0x00,
119 0x00, 0x04, 0x08, 0xf4, 0xbf, 0x00, 0x0c, 0x00, 0x00, 0xfe,
120 0xb8, 0x0a, 0xf0, 0x80, 0x00, 0x7e, 0xa9, 0x08, 0xf4, 0xa0,
121 0x00, 0x00, 0x01, 0x08, 0xf4, 0xbe, 0x00, 0x00, 0x00, 0x0a,
122 0xa9, 0x80, 0x00, 0x7e, 0xad, 0x08, 0x4e, 0x2b, 0x44, 0xf4,
123 0x00, 0x00, 0x00, 0x03, 0x44, 0xf4, 0x45, 0x00, 0x00, 0x01,
124 0x0e, 0xa0, 0x00, 0x0a, 0xa9, 0x80, 0x00, 0x7e, 0xb5, 0x08,
125 0x50, 0x2b, 0x0a, 0xa9, 0x80, 0x00, 0x7e, 0xb8, 0x08, 0x46,
126 0x2b, 0x44, 0xf4, 0x45, 0x00, 0x00, 0x02, 0x0a, 0xf0, 0xaa,
127 0x00, 0x7e, 0xc9, 0x20, 0x00, 0x45, 0x0a, 0xf0, 0xaa, 0x00,
128 0x7e, 0xd0, 0x06, 0xc6, 0x00, 0x00, 0x7e, 0xc6, 0x0a, 0xa9,
129 0x80, 0x00, 0x7e, 0xc4, 0x08, 0x58, 0x6b, 0x0a, 0xf0, 0x80,
130 0x00, 0x7e, 0xad, 0x06, 0xc6, 0x00, 0x00, 0x7e, 0xcd, 0x0a,
131 0xa9, 0x80, 0x00, 0x7e, 0xcb, 0x08, 0x58, 0xab, 0x0a, 0xf0,
132 0x80, 0x00, 0x7e, 0xad, 0x06, 0xc6, 0x00, 0x00, 0x7e, 0xd4,
133 0x0a, 0xa9, 0x80, 0x00, 0x7e, 0xd2, 0x08, 0x58, 0xeb, 0x0a,
134 0xf0, 0x80, 0x00, 0x7e, 0xad};
135static int sizeof_bootstrap = 375;
136
137
138static struct dsp56k_device {
139 long in_use;
140 long maxio, timeout;
141 int tx_wsize, rx_wsize;
142} dsp56k;
143
gregkh@suse.deca8eca62005-03-23 09:53:09 -0800144static struct class *dsp56k_class;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700145
146static int dsp56k_reset(void)
147{
148 u_char status;
149
150 /* Power down the DSP */
151 sound_ym.rd_data_reg_sel = 14;
152 status = sound_ym.rd_data_reg_sel & 0xef;
153 sound_ym.wd_data = status;
154 sound_ym.wd_data = status | 0x10;
155
156 udelay(10);
157
158 /* Power up the DSP */
159 sound_ym.rd_data_reg_sel = 14;
160 sound_ym.wd_data = sound_ym.rd_data_reg_sel & 0xef;
161
162 return 0;
163}
164
Al Virod85f6892006-01-12 01:06:31 -0800165static int dsp56k_upload(u_char __user *bin, int len)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700166{
167 int i;
168 u_char *p;
169
170 dsp56k_reset();
171
172 p = bootstrap;
173 for (i = 0; i < sizeof_bootstrap/3; i++) {
174 /* tx_wait(10); */
175 dsp56k_host_interface.data.b[1] = *p++;
176 dsp56k_host_interface.data.b[2] = *p++;
177 dsp56k_host_interface.data.b[3] = *p++;
178 }
179 for (; i < 512; i++) {
180 /* tx_wait(10); */
181 dsp56k_host_interface.data.b[1] = 0;
182 dsp56k_host_interface.data.b[2] = 0;
183 dsp56k_host_interface.data.b[3] = 0;
184 }
185
186 for (i = 0; i < len; i++) {
187 tx_wait(10);
188 get_user(dsp56k_host_interface.data.b[1], bin++);
189 get_user(dsp56k_host_interface.data.b[2], bin++);
190 get_user(dsp56k_host_interface.data.b[3], bin++);
191 }
192
193 tx_wait(10);
194 dsp56k_host_interface.data.l = 3; /* Magic execute */
195
196 return 0;
197}
198
Al Virod85f6892006-01-12 01:06:31 -0800199static ssize_t dsp56k_read(struct file *file, char __user *buf, size_t count,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700200 loff_t *ppos)
201{
Josef Sipeka7113a92006-12-08 02:36:55 -0800202 struct inode *inode = file->f_path.dentry->d_inode;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700203 int dev = iminor(inode) & 0x0f;
204
205 switch(dev)
206 {
207 case DSP56K_DEV_56001:
208 {
209
210 long n;
211
212 /* Don't do anything if nothing is to be done */
213 if (!count) return 0;
214
215 n = 0;
216 switch (dsp56k.rx_wsize) {
217 case 1: /* 8 bit */
218 {
219 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
220 put_user(dsp56k_host_interface.data.b[3], buf+n++));
221 return n;
222 }
223 case 2: /* 16 bit */
224 {
Al Virod85f6892006-01-12 01:06:31 -0800225 short __user *data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700226
227 count /= 2;
Al Virod85f6892006-01-12 01:06:31 -0800228 data = (short __user *) buf;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700229 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
230 put_user(dsp56k_host_interface.data.w[1], data+n++));
231 return 2*n;
232 }
233 case 3: /* 24 bit */
234 {
235 count /= 3;
236 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
237 put_user(dsp56k_host_interface.data.b[1], buf+n++);
238 put_user(dsp56k_host_interface.data.b[2], buf+n++);
239 put_user(dsp56k_host_interface.data.b[3], buf+n++));
240 return 3*n;
241 }
242 case 4: /* 32 bit */
243 {
Al Virod85f6892006-01-12 01:06:31 -0800244 long __user *data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700245
246 count /= 4;
Al Virod85f6892006-01-12 01:06:31 -0800247 data = (long __user *) buf;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700248 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
249 put_user(dsp56k_host_interface.data.l, data+n++));
250 return 4*n;
251 }
252 }
253 return -EFAULT;
254 }
255
256 default:
257 printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
258 return -ENXIO;
259 }
260}
261
Al Virod85f6892006-01-12 01:06:31 -0800262static ssize_t dsp56k_write(struct file *file, const char __user *buf, size_t count,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700263 loff_t *ppos)
264{
Josef Sipeka7113a92006-12-08 02:36:55 -0800265 struct inode *inode = file->f_path.dentry->d_inode;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700266 int dev = iminor(inode) & 0x0f;
267
268 switch(dev)
269 {
270 case DSP56K_DEV_56001:
271 {
272 long n;
273
274 /* Don't do anything if nothing is to be done */
275 if (!count) return 0;
276
277 n = 0;
278 switch (dsp56k.tx_wsize) {
279 case 1: /* 8 bit */
280 {
281 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
282 get_user(dsp56k_host_interface.data.b[3], buf+n++));
283 return n;
284 }
285 case 2: /* 16 bit */
286 {
Al Virod85f6892006-01-12 01:06:31 -0800287 const short __user *data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700288
289 count /= 2;
Al Virod85f6892006-01-12 01:06:31 -0800290 data = (const short __user *)buf;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700291 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
292 get_user(dsp56k_host_interface.data.w[1], data+n++));
293 return 2*n;
294 }
295 case 3: /* 24 bit */
296 {
297 count /= 3;
298 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
299 get_user(dsp56k_host_interface.data.b[1], buf+n++);
300 get_user(dsp56k_host_interface.data.b[2], buf+n++);
301 get_user(dsp56k_host_interface.data.b[3], buf+n++));
302 return 3*n;
303 }
304 case 4: /* 32 bit */
305 {
Al Virod85f6892006-01-12 01:06:31 -0800306 const long __user *data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700307
308 count /= 4;
Al Virod85f6892006-01-12 01:06:31 -0800309 data = (const long __user *)buf;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700310 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
311 get_user(dsp56k_host_interface.data.l, data+n++));
312 return 4*n;
313 }
314 }
315
316 return -EFAULT;
317 }
318 default:
319 printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
320 return -ENXIO;
321 }
322}
323
324static int dsp56k_ioctl(struct inode *inode, struct file *file,
325 unsigned int cmd, unsigned long arg)
326{
327 int dev = iminor(inode) & 0x0f;
Al Virod85f6892006-01-12 01:06:31 -0800328 void __user *argp = (void __user *)arg;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700329
330 switch(dev)
331 {
332 case DSP56K_DEV_56001:
333
334 switch(cmd) {
335 case DSP56K_UPLOAD:
336 {
Al Virod85f6892006-01-12 01:06:31 -0800337 char __user *bin;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700338 int r, len;
Al Virod85f6892006-01-12 01:06:31 -0800339 struct dsp56k_upload __user *binary = argp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700340
341 if(get_user(len, &binary->len) < 0)
342 return -EFAULT;
343 if(get_user(bin, &binary->bin) < 0)
344 return -EFAULT;
345
346 if (len == 0) {
347 return -EINVAL; /* nothing to upload?!? */
348 }
349 if (len > DSP56K_MAX_BINARY_LENGTH) {
350 return -EINVAL;
351 }
352
353 r = dsp56k_upload(bin, len);
354 if (r < 0) {
355 return r;
356 }
357
358 break;
359 }
360 case DSP56K_SET_TX_WSIZE:
361 if (arg > 4 || arg < 1)
362 return -EINVAL;
363 dsp56k.tx_wsize = (int) arg;
364 break;
365 case DSP56K_SET_RX_WSIZE:
366 if (arg > 4 || arg < 1)
367 return -EINVAL;
368 dsp56k.rx_wsize = (int) arg;
369 break;
370 case DSP56K_HOST_FLAGS:
371 {
372 int dir, out, status;
Al Virod85f6892006-01-12 01:06:31 -0800373 struct dsp56k_host_flags __user *hf = argp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700374
375 if(get_user(dir, &hf->dir) < 0)
376 return -EFAULT;
377 if(get_user(out, &hf->out) < 0)
378 return -EFAULT;
379
380 if ((dir & 0x1) && (out & 0x1))
381 dsp56k_host_interface.icr |= DSP56K_ICR_HF0;
382 else if (dir & 0x1)
383 dsp56k_host_interface.icr &= ~DSP56K_ICR_HF0;
384 if ((dir & 0x2) && (out & 0x2))
385 dsp56k_host_interface.icr |= DSP56K_ICR_HF1;
386 else if (dir & 0x2)
387 dsp56k_host_interface.icr &= ~DSP56K_ICR_HF1;
388
389 status = 0;
390 if (dsp56k_host_interface.icr & DSP56K_ICR_HF0) status |= 0x1;
391 if (dsp56k_host_interface.icr & DSP56K_ICR_HF1) status |= 0x2;
392 if (dsp56k_host_interface.isr & DSP56K_ISR_HF2) status |= 0x4;
393 if (dsp56k_host_interface.isr & DSP56K_ISR_HF3) status |= 0x8;
394
395 return put_user(status, &hf->status);
396 }
397 case DSP56K_HOST_CMD:
398 if (arg > 31 || arg < 0)
399 return -EINVAL;
400 dsp56k_host_interface.cvr = (u_char)((arg & DSP56K_CVR_HV_MASK) |
401 DSP56K_CVR_HC);
402 break;
403 default:
404 return -EINVAL;
405 }
406 return 0;
407
408 default:
409 printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
410 return -ENXIO;
411 }
412}
413
414/* As of 2.1.26 this should be dsp56k_poll,
415 * but how do I then check device minor number?
416 * Do I need this function at all???
417 */
418#if 0
419static unsigned int dsp56k_poll(struct file *file, poll_table *wait)
420{
Josef Sipeka7113a92006-12-08 02:36:55 -0800421 int dev = iminor(file->f_path.dentry->d_inode) & 0x0f;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700422
423 switch(dev)
424 {
425 case DSP56K_DEV_56001:
426 /* poll_wait(file, ???, wait); */
427 return POLLIN | POLLRDNORM | POLLOUT;
428
429 default:
430 printk("DSP56k driver: Unknown minor device: %d\n", dev);
431 return 0;
432 }
433}
434#endif
435
436static int dsp56k_open(struct inode *inode, struct file *file)
437{
438 int dev = iminor(inode) & 0x0f;
439
440 switch(dev)
441 {
442 case DSP56K_DEV_56001:
443
444 if (test_and_set_bit(0, &dsp56k.in_use))
445 return -EBUSY;
446
447 dsp56k.timeout = TIMEOUT;
448 dsp56k.maxio = MAXIO;
449 dsp56k.rx_wsize = dsp56k.tx_wsize = 4;
450
451 DSP56K_TX_INT_OFF;
452 DSP56K_RX_INT_OFF;
453
454 /* Zero host flags */
455 dsp56k_host_interface.icr &= ~DSP56K_ICR_HF0;
456 dsp56k_host_interface.icr &= ~DSP56K_ICR_HF1;
457
458 break;
459
460 default:
461 return -ENODEV;
462 }
463
464 return 0;
465}
466
467static int dsp56k_release(struct inode *inode, struct file *file)
468{
469 int dev = iminor(inode) & 0x0f;
470
471 switch(dev)
472 {
473 case DSP56K_DEV_56001:
474 clear_bit(0, &dsp56k.in_use);
475 break;
476 default:
477 printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
478 return -ENXIO;
479 }
480
481 return 0;
482}
483
Arjan van de Ven62322d22006-07-03 00:24:21 -0700484static const struct file_operations dsp56k_fops = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700485 .owner = THIS_MODULE,
486 .read = dsp56k_read,
487 .write = dsp56k_write,
488 .ioctl = dsp56k_ioctl,
489 .open = dsp56k_open,
490 .release = dsp56k_release,
491};
492
493
494/****** Init and module functions ******/
495
496static char banner[] __initdata = KERN_INFO "DSP56k driver installed\n";
497
498static int __init dsp56k_init_driver(void)
499{
500 int err = 0;
501
502 if(!MACH_IS_ATARI || !ATARIHW_PRESENT(DSP56K)) {
503 printk("DSP56k driver: Hardware not present\n");
504 return -ENODEV;
505 }
506
507 if(register_chrdev(DSP56K_MAJOR, "dsp56k", &dsp56k_fops)) {
508 printk("DSP56k driver: Unable to register driver\n");
509 return -ENODEV;
510 }
gregkh@suse.deca8eca62005-03-23 09:53:09 -0800511 dsp56k_class = class_create(THIS_MODULE, "dsp56k");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700512 if (IS_ERR(dsp56k_class)) {
513 err = PTR_ERR(dsp56k_class);
514 goto out_chrdev;
515 }
tonyj@suse.de07c015e2007-08-07 22:28:44 -0700516 device_create(dsp56k_class, NULL, MKDEV(DSP56K_MAJOR, 0), "dsp56k");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700517
Linus Torvalds1da177e2005-04-16 15:20:36 -0700518 printk(banner);
519 goto out;
520
Linus Torvalds1da177e2005-04-16 15:20:36 -0700521out_chrdev:
522 unregister_chrdev(DSP56K_MAJOR, "dsp56k");
523out:
524 return err;
525}
526module_init(dsp56k_init_driver);
527
528static void __exit dsp56k_cleanup_driver(void)
529{
tonyj@suse.de07c015e2007-08-07 22:28:44 -0700530 device_destroy(dsp56k_class, MKDEV(DSP56K_MAJOR, 0));
gregkh@suse.deca8eca62005-03-23 09:53:09 -0800531 class_destroy(dsp56k_class);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700532 unregister_chrdev(DSP56K_MAJOR, "dsp56k");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700533}
534module_exit(dsp56k_cleanup_driver);
535
536MODULE_LICENSE("GPL");