blob: 06f2dbf17710770e5829d464e81cfb48d5e8367c [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() */
28#include <linux/sched.h> /* for struct wait_queue etc */
29#include <linux/major.h>
30#include <linux/types.h>
31#include <linux/errno.h>
32#include <linux/delay.h> /* guess what */
33#include <linux/fs.h>
34#include <linux/mm.h>
35#include <linux/init.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070036#include <linux/smp_lock.h>
37#include <linux/device.h>
38
39#include <asm/atarihw.h>
40#include <asm/traps.h>
41#include <asm/uaccess.h> /* For put_user and get_user */
42
43#include <asm/dsp56k.h>
44
45/* minor devices */
46#define DSP56K_DEV_56001 0 /* The only device so far */
47
48#define TIMEOUT 10 /* Host port timeout in number of tries */
49#define MAXIO 2048 /* Maximum number of words before sleep */
50#define DSP56K_MAX_BINARY_LENGTH (3*64*1024)
51
52#define DSP56K_TX_INT_ON dsp56k_host_interface.icr |= DSP56K_ICR_TREQ
53#define DSP56K_RX_INT_ON dsp56k_host_interface.icr |= DSP56K_ICR_RREQ
54#define DSP56K_TX_INT_OFF dsp56k_host_interface.icr &= ~DSP56K_ICR_TREQ
55#define DSP56K_RX_INT_OFF dsp56k_host_interface.icr &= ~DSP56K_ICR_RREQ
56
57#define DSP56K_TRANSMIT (dsp56k_host_interface.isr & DSP56K_ISR_TXDE)
58#define DSP56K_RECEIVE (dsp56k_host_interface.isr & DSP56K_ISR_RXDF)
59
60#define handshake(count, maxio, timeout, ENABLE, f) \
61{ \
62 long i, t, m; \
63 while (count > 0) { \
64 m = min_t(unsigned long, count, maxio); \
65 for (i = 0; i < m; i++) { \
66 for (t = 0; t < timeout && !ENABLE; t++) \
67 msleep(20); \
68 if(!ENABLE) \
69 return -EIO; \
70 f; \
71 } \
72 count -= m; \
73 if (m == maxio) msleep(20); \
74 } \
75}
76
77#define tx_wait(n) \
78{ \
79 int t; \
80 for(t = 0; t < n && !DSP56K_TRANSMIT; t++) \
81 msleep(10); \
82 if(!DSP56K_TRANSMIT) { \
83 return -EIO; \
84 } \
85}
86
87#define rx_wait(n) \
88{ \
89 int t; \
90 for(t = 0; t < n && !DSP56K_RECEIVE; t++) \
91 msleep(10); \
92 if(!DSP56K_RECEIVE) { \
93 return -EIO; \
94 } \
95}
96
97/* DSP56001 bootstrap code */
98static char bootstrap[] = {
99 0x0c, 0x00, 0x40, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
117 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
118 0x00, 0x00, 0x60, 0xf4, 0x00, 0x00, 0x00, 0x4f, 0x61, 0xf4,
119 0x00, 0x00, 0x7e, 0xa9, 0x06, 0x2e, 0x80, 0x00, 0x00, 0x47,
120 0x07, 0xd8, 0x84, 0x07, 0x59, 0x84, 0x08, 0xf4, 0xa8, 0x00,
121 0x00, 0x04, 0x08, 0xf4, 0xbf, 0x00, 0x0c, 0x00, 0x00, 0xfe,
122 0xb8, 0x0a, 0xf0, 0x80, 0x00, 0x7e, 0xa9, 0x08, 0xf4, 0xa0,
123 0x00, 0x00, 0x01, 0x08, 0xf4, 0xbe, 0x00, 0x00, 0x00, 0x0a,
124 0xa9, 0x80, 0x00, 0x7e, 0xad, 0x08, 0x4e, 0x2b, 0x44, 0xf4,
125 0x00, 0x00, 0x00, 0x03, 0x44, 0xf4, 0x45, 0x00, 0x00, 0x01,
126 0x0e, 0xa0, 0x00, 0x0a, 0xa9, 0x80, 0x00, 0x7e, 0xb5, 0x08,
127 0x50, 0x2b, 0x0a, 0xa9, 0x80, 0x00, 0x7e, 0xb8, 0x08, 0x46,
128 0x2b, 0x44, 0xf4, 0x45, 0x00, 0x00, 0x02, 0x0a, 0xf0, 0xaa,
129 0x00, 0x7e, 0xc9, 0x20, 0x00, 0x45, 0x0a, 0xf0, 0xaa, 0x00,
130 0x7e, 0xd0, 0x06, 0xc6, 0x00, 0x00, 0x7e, 0xc6, 0x0a, 0xa9,
131 0x80, 0x00, 0x7e, 0xc4, 0x08, 0x58, 0x6b, 0x0a, 0xf0, 0x80,
132 0x00, 0x7e, 0xad, 0x06, 0xc6, 0x00, 0x00, 0x7e, 0xcd, 0x0a,
133 0xa9, 0x80, 0x00, 0x7e, 0xcb, 0x08, 0x58, 0xab, 0x0a, 0xf0,
134 0x80, 0x00, 0x7e, 0xad, 0x06, 0xc6, 0x00, 0x00, 0x7e, 0xd4,
135 0x0a, 0xa9, 0x80, 0x00, 0x7e, 0xd2, 0x08, 0x58, 0xeb, 0x0a,
136 0xf0, 0x80, 0x00, 0x7e, 0xad};
137static int sizeof_bootstrap = 375;
138
139
140static struct dsp56k_device {
141 long in_use;
142 long maxio, timeout;
143 int tx_wsize, rx_wsize;
144} dsp56k;
145
gregkh@suse.deca8eca62005-03-23 09:53:09 -0800146static struct class *dsp56k_class;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700147
148static int dsp56k_reset(void)
149{
150 u_char status;
151
152 /* Power down the DSP */
153 sound_ym.rd_data_reg_sel = 14;
154 status = sound_ym.rd_data_reg_sel & 0xef;
155 sound_ym.wd_data = status;
156 sound_ym.wd_data = status | 0x10;
157
158 udelay(10);
159
160 /* Power up the DSP */
161 sound_ym.rd_data_reg_sel = 14;
162 sound_ym.wd_data = sound_ym.rd_data_reg_sel & 0xef;
163
164 return 0;
165}
166
Al Virod85f6892006-01-12 01:06:31 -0800167static int dsp56k_upload(u_char __user *bin, int len)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700168{
169 int i;
170 u_char *p;
171
172 dsp56k_reset();
173
174 p = bootstrap;
175 for (i = 0; i < sizeof_bootstrap/3; i++) {
176 /* tx_wait(10); */
177 dsp56k_host_interface.data.b[1] = *p++;
178 dsp56k_host_interface.data.b[2] = *p++;
179 dsp56k_host_interface.data.b[3] = *p++;
180 }
181 for (; i < 512; i++) {
182 /* tx_wait(10); */
183 dsp56k_host_interface.data.b[1] = 0;
184 dsp56k_host_interface.data.b[2] = 0;
185 dsp56k_host_interface.data.b[3] = 0;
186 }
187
188 for (i = 0; i < len; i++) {
189 tx_wait(10);
190 get_user(dsp56k_host_interface.data.b[1], bin++);
191 get_user(dsp56k_host_interface.data.b[2], bin++);
192 get_user(dsp56k_host_interface.data.b[3], bin++);
193 }
194
195 tx_wait(10);
196 dsp56k_host_interface.data.l = 3; /* Magic execute */
197
198 return 0;
199}
200
Al Virod85f6892006-01-12 01:06:31 -0800201static ssize_t dsp56k_read(struct file *file, char __user *buf, size_t count,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700202 loff_t *ppos)
203{
Josef Sipeka7113a92006-12-08 02:36:55 -0800204 struct inode *inode = file->f_path.dentry->d_inode;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700205 int dev = iminor(inode) & 0x0f;
206
207 switch(dev)
208 {
209 case DSP56K_DEV_56001:
210 {
211
212 long n;
213
214 /* Don't do anything if nothing is to be done */
215 if (!count) return 0;
216
217 n = 0;
218 switch (dsp56k.rx_wsize) {
219 case 1: /* 8 bit */
220 {
221 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
222 put_user(dsp56k_host_interface.data.b[3], buf+n++));
223 return n;
224 }
225 case 2: /* 16 bit */
226 {
Al Virod85f6892006-01-12 01:06:31 -0800227 short __user *data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700228
229 count /= 2;
Al Virod85f6892006-01-12 01:06:31 -0800230 data = (short __user *) buf;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700231 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
232 put_user(dsp56k_host_interface.data.w[1], data+n++));
233 return 2*n;
234 }
235 case 3: /* 24 bit */
236 {
237 count /= 3;
238 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
239 put_user(dsp56k_host_interface.data.b[1], buf+n++);
240 put_user(dsp56k_host_interface.data.b[2], buf+n++);
241 put_user(dsp56k_host_interface.data.b[3], buf+n++));
242 return 3*n;
243 }
244 case 4: /* 32 bit */
245 {
Al Virod85f6892006-01-12 01:06:31 -0800246 long __user *data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700247
248 count /= 4;
Al Virod85f6892006-01-12 01:06:31 -0800249 data = (long __user *) buf;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700250 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
251 put_user(dsp56k_host_interface.data.l, data+n++));
252 return 4*n;
253 }
254 }
255 return -EFAULT;
256 }
257
258 default:
259 printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
260 return -ENXIO;
261 }
262}
263
Al Virod85f6892006-01-12 01:06:31 -0800264static ssize_t dsp56k_write(struct file *file, const char __user *buf, size_t count,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700265 loff_t *ppos)
266{
Josef Sipeka7113a92006-12-08 02:36:55 -0800267 struct inode *inode = file->f_path.dentry->d_inode;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700268 int dev = iminor(inode) & 0x0f;
269
270 switch(dev)
271 {
272 case DSP56K_DEV_56001:
273 {
274 long n;
275
276 /* Don't do anything if nothing is to be done */
277 if (!count) return 0;
278
279 n = 0;
280 switch (dsp56k.tx_wsize) {
281 case 1: /* 8 bit */
282 {
283 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
284 get_user(dsp56k_host_interface.data.b[3], buf+n++));
285 return n;
286 }
287 case 2: /* 16 bit */
288 {
Al Virod85f6892006-01-12 01:06:31 -0800289 const short __user *data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700290
291 count /= 2;
Al Virod85f6892006-01-12 01:06:31 -0800292 data = (const short __user *)buf;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700293 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
294 get_user(dsp56k_host_interface.data.w[1], data+n++));
295 return 2*n;
296 }
297 case 3: /* 24 bit */
298 {
299 count /= 3;
300 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
301 get_user(dsp56k_host_interface.data.b[1], buf+n++);
302 get_user(dsp56k_host_interface.data.b[2], buf+n++);
303 get_user(dsp56k_host_interface.data.b[3], buf+n++));
304 return 3*n;
305 }
306 case 4: /* 32 bit */
307 {
Al Virod85f6892006-01-12 01:06:31 -0800308 const long __user *data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700309
310 count /= 4;
Al Virod85f6892006-01-12 01:06:31 -0800311 data = (const long __user *)buf;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700312 handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
313 get_user(dsp56k_host_interface.data.l, data+n++));
314 return 4*n;
315 }
316 }
317
318 return -EFAULT;
319 }
320 default:
321 printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
322 return -ENXIO;
323 }
324}
325
326static int dsp56k_ioctl(struct inode *inode, struct file *file,
327 unsigned int cmd, unsigned long arg)
328{
329 int dev = iminor(inode) & 0x0f;
Al Virod85f6892006-01-12 01:06:31 -0800330 void __user *argp = (void __user *)arg;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700331
332 switch(dev)
333 {
334 case DSP56K_DEV_56001:
335
336 switch(cmd) {
337 case DSP56K_UPLOAD:
338 {
Al Virod85f6892006-01-12 01:06:31 -0800339 char __user *bin;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700340 int r, len;
Al Virod85f6892006-01-12 01:06:31 -0800341 struct dsp56k_upload __user *binary = argp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700342
343 if(get_user(len, &binary->len) < 0)
344 return -EFAULT;
345 if(get_user(bin, &binary->bin) < 0)
346 return -EFAULT;
347
348 if (len == 0) {
349 return -EINVAL; /* nothing to upload?!? */
350 }
351 if (len > DSP56K_MAX_BINARY_LENGTH) {
352 return -EINVAL;
353 }
354
355 r = dsp56k_upload(bin, len);
356 if (r < 0) {
357 return r;
358 }
359
360 break;
361 }
362 case DSP56K_SET_TX_WSIZE:
363 if (arg > 4 || arg < 1)
364 return -EINVAL;
365 dsp56k.tx_wsize = (int) arg;
366 break;
367 case DSP56K_SET_RX_WSIZE:
368 if (arg > 4 || arg < 1)
369 return -EINVAL;
370 dsp56k.rx_wsize = (int) arg;
371 break;
372 case DSP56K_HOST_FLAGS:
373 {
374 int dir, out, status;
Al Virod85f6892006-01-12 01:06:31 -0800375 struct dsp56k_host_flags __user *hf = argp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700376
377 if(get_user(dir, &hf->dir) < 0)
378 return -EFAULT;
379 if(get_user(out, &hf->out) < 0)
380 return -EFAULT;
381
382 if ((dir & 0x1) && (out & 0x1))
383 dsp56k_host_interface.icr |= DSP56K_ICR_HF0;
384 else if (dir & 0x1)
385 dsp56k_host_interface.icr &= ~DSP56K_ICR_HF0;
386 if ((dir & 0x2) && (out & 0x2))
387 dsp56k_host_interface.icr |= DSP56K_ICR_HF1;
388 else if (dir & 0x2)
389 dsp56k_host_interface.icr &= ~DSP56K_ICR_HF1;
390
391 status = 0;
392 if (dsp56k_host_interface.icr & DSP56K_ICR_HF0) status |= 0x1;
393 if (dsp56k_host_interface.icr & DSP56K_ICR_HF1) status |= 0x2;
394 if (dsp56k_host_interface.isr & DSP56K_ISR_HF2) status |= 0x4;
395 if (dsp56k_host_interface.isr & DSP56K_ISR_HF3) status |= 0x8;
396
397 return put_user(status, &hf->status);
398 }
399 case DSP56K_HOST_CMD:
400 if (arg > 31 || arg < 0)
401 return -EINVAL;
402 dsp56k_host_interface.cvr = (u_char)((arg & DSP56K_CVR_HV_MASK) |
403 DSP56K_CVR_HC);
404 break;
405 default:
406 return -EINVAL;
407 }
408 return 0;
409
410 default:
411 printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
412 return -ENXIO;
413 }
414}
415
416/* As of 2.1.26 this should be dsp56k_poll,
417 * but how do I then check device minor number?
418 * Do I need this function at all???
419 */
420#if 0
421static unsigned int dsp56k_poll(struct file *file, poll_table *wait)
422{
Josef Sipeka7113a92006-12-08 02:36:55 -0800423 int dev = iminor(file->f_path.dentry->d_inode) & 0x0f;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700424
425 switch(dev)
426 {
427 case DSP56K_DEV_56001:
428 /* poll_wait(file, ???, wait); */
429 return POLLIN | POLLRDNORM | POLLOUT;
430
431 default:
432 printk("DSP56k driver: Unknown minor device: %d\n", dev);
433 return 0;
434 }
435}
436#endif
437
438static int dsp56k_open(struct inode *inode, struct file *file)
439{
440 int dev = iminor(inode) & 0x0f;
441
442 switch(dev)
443 {
444 case DSP56K_DEV_56001:
445
446 if (test_and_set_bit(0, &dsp56k.in_use))
447 return -EBUSY;
448
449 dsp56k.timeout = TIMEOUT;
450 dsp56k.maxio = MAXIO;
451 dsp56k.rx_wsize = dsp56k.tx_wsize = 4;
452
453 DSP56K_TX_INT_OFF;
454 DSP56K_RX_INT_OFF;
455
456 /* Zero host flags */
457 dsp56k_host_interface.icr &= ~DSP56K_ICR_HF0;
458 dsp56k_host_interface.icr &= ~DSP56K_ICR_HF1;
459
460 break;
461
462 default:
463 return -ENODEV;
464 }
465
466 return 0;
467}
468
469static int dsp56k_release(struct inode *inode, struct file *file)
470{
471 int dev = iminor(inode) & 0x0f;
472
473 switch(dev)
474 {
475 case DSP56K_DEV_56001:
476 clear_bit(0, &dsp56k.in_use);
477 break;
478 default:
479 printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
480 return -ENXIO;
481 }
482
483 return 0;
484}
485
Arjan van de Ven62322d22006-07-03 00:24:21 -0700486static const struct file_operations dsp56k_fops = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700487 .owner = THIS_MODULE,
488 .read = dsp56k_read,
489 .write = dsp56k_write,
490 .ioctl = dsp56k_ioctl,
491 .open = dsp56k_open,
492 .release = dsp56k_release,
493};
494
495
496/****** Init and module functions ******/
497
498static char banner[] __initdata = KERN_INFO "DSP56k driver installed\n";
499
500static int __init dsp56k_init_driver(void)
501{
502 int err = 0;
503
504 if(!MACH_IS_ATARI || !ATARIHW_PRESENT(DSP56K)) {
505 printk("DSP56k driver: Hardware not present\n");
506 return -ENODEV;
507 }
508
509 if(register_chrdev(DSP56K_MAJOR, "dsp56k", &dsp56k_fops)) {
510 printk("DSP56k driver: Unable to register driver\n");
511 return -ENODEV;
512 }
gregkh@suse.deca8eca62005-03-23 09:53:09 -0800513 dsp56k_class = class_create(THIS_MODULE, "dsp56k");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700514 if (IS_ERR(dsp56k_class)) {
515 err = PTR_ERR(dsp56k_class);
516 goto out_chrdev;
517 }
Greg Kroah-Hartman53f46542005-10-27 22:25:43 -0700518 class_device_create(dsp56k_class, NULL, MKDEV(DSP56K_MAJOR, 0), NULL, "dsp56k");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700519
Linus Torvalds1da177e2005-04-16 15:20:36 -0700520 printk(banner);
521 goto out;
522
Linus Torvalds1da177e2005-04-16 15:20:36 -0700523out_chrdev:
524 unregister_chrdev(DSP56K_MAJOR, "dsp56k");
525out:
526 return err;
527}
528module_init(dsp56k_init_driver);
529
530static void __exit dsp56k_cleanup_driver(void)
531{
gregkh@suse.deca8eca62005-03-23 09:53:09 -0800532 class_device_destroy(dsp56k_class, MKDEV(DSP56K_MAJOR, 0));
533 class_destroy(dsp56k_class);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700534 unregister_chrdev(DSP56K_MAJOR, "dsp56k");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700535}
536module_exit(dsp56k_cleanup_driver);
537
538MODULE_LICENSE("GPL");