MOXA linux-2.6.x / linux-2.6.9-uc0 from sdlinux-moxaart.tgz
[linux-2.6.9-moxart.git] / drivers / mmc.old / moxasd.c
blobdee68c381a3e526309b461abffaf83c2dd87495a
1 /*
2 * linux/drivers/mmc/moxasd.c - Moxa CPU SD/MMC driver
4 * Copyright (C) 2005 Moxa Tech., All Rights Reserved.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
11 #include <linux/config.h>
12 #include <asm/arch/cpe/cpe.h>
13 #include <asm/arch/cpe_int.h>
14 #include <linux/module.h>
15 #include <linux/init.h>
16 #include <linux/ioport.h>
17 #include <linux/device.h>
18 #include <linux/delay.h>
19 #include <linux/interrupt.h>
20 #include <linux/blkdev.h>
21 #include <linux/dma-mapping.h>
22 #include <linux/mmc/host.h>
23 #include <linux/sched.h>
24 #include <linux/delay.h>
25 #include <linux/mmc/protocol.h>
27 #include <asm/dma.h>
28 #include <asm/io.h>
29 #include <asm/irq.h>
30 #include <asm/sizes.h>
31 #include <asm/arch/gpio.h>
33 #include "moxasd.h"
35 //#define MSD_SUPPORT_GET_CLOCK 1
37 #define MSD_RETRY_COUNT 1000
38 #define DMA_BUFFER_SIZE 65536
40 //#define CONFIG_MMC_DEBUG
41 #ifdef CONFIG_MMC_DEBUG
42 #define DBG(x...) printk(x)
43 #else
44 #define DBG(x...)
45 #endif
47 struct moxasd_host {
48 struct mmc_host *mmc;
49 spinlock_t lock;
50 moxasd_reg *reg;
51 apb_dma_priv *dma;
52 #ifdef MSD_SUPPORT_GET_CLOCK
53 unsigned int sysclk;
54 #endif
55 struct mmc_request *mrq;
56 struct mmc_data *data;
58 struct scatterlist *cur_sg; /* Current SG entry */
59 unsigned int num_sg; /* Number of entries left */
60 void *mapped_sg; /* vaddr of mapped sg */
61 unsigned int remain; /* Data left in curren entry */
62 int size; /* Total size of transfer */
64 struct tasklet_struct card_change_tasklet;
65 struct tasklet_struct fifo_run_tasklet;
68 static inline void moxasd_init_sg(struct moxasd_host* host, struct mmc_data* data)
70 // Get info. about SG list from data structure.
71 host->cur_sg = data->sg;
72 host->num_sg = data->sg_len;
74 host->remain = host->cur_sg->length;
75 #if 1 // add by Victor Yu. 07-04-2007
76 if ( host->remain > host->size )
77 host->remain = host->size;
78 host->mapped_sg = NULL;
79 #endif
80 data->error = MMC_ERR_NONE;
83 static inline void moxasd_next_sg(struct moxasd_host* host)
85 #if 1 // add by Victor Yu. 07-04-2007
86 struct mmc_data *data=host->data;
87 #endif
89 // Skip to next SG entry.
90 host->cur_sg++;
91 host->num_sg--;
93 // Any entries left?
94 if (host->num_sg > 0) {
95 host->remain = host->cur_sg->length;
96 #if 1 // add by Victor Yu. 07-04-2007
98 int remain;
99 remain = host->size - data->bytes_xfered;
100 if ( remain > 0 && remain < host->remain ) {
101 host->remain = remain;
104 #endif
108 static inline char *moxasd_kmap_sg(struct moxasd_host* host)
110 host->mapped_sg = kmap_atomic(host->cur_sg->page, KM_BIO_SRC_IRQ);
111 return host->mapped_sg + host->cur_sg->offset;
114 static void moxasd_do_fifo(struct moxasd_host *host, struct mmc_data *data)
116 char *buffer;
117 int wcnt, i;
119 #if 1 // add by Victor Yu. 07-06-2007
120 if ( host->mapped_sg ) {
121 kunmap_atomic(host->mapped_sg, KM_BIO_SRC_IRQ);
122 moxasd_next_sg(host);
124 #endif
125 if ( host->size <= data->bytes_xfered ) {
126 return;
128 buffer = moxasd_kmap_sg(host);
129 if ( host->size > MSD_FIFO_LENB && host->dma ) {
130 apb_dma_conf_param param;
131 param.size = host->remain;
132 param.burst_mode = APB_DMAB_BURST_MODE;
133 param.data_width = APB_DMAB_DATA_WIDTH_4;
134 if ( data->flags & MMC_DATA_WRITE ) {
135 param.source_addr = (unsigned int)buffer;
136 param.dest_addr = (unsigned int)&host->reg->data_window;
137 param.dest_inc = APB_DMAB_DEST_INC_0;
138 param.source_inc = APB_DMAB_DEST_INC_4_16;
139 param.dest_sel = APB_DMAB_DEST_APB;
140 param.source_sel = APB_DMAB_SOURCE_AHB;
141 } else {
142 param.dest_addr = (unsigned int)buffer;
143 param.source_addr = (unsigned int)&host->reg->data_window;
144 param.source_inc = APB_DMAB_DEST_INC_0;
145 param.dest_inc = APB_DMAB_DEST_INC_4_16;
146 param.source_sel = APB_DMAB_DEST_APB;
147 param.dest_sel = APB_DMAB_SOURCE_AHB;
149 data->bytes_xfered += host->remain;
150 apb_dma_conf(host->dma, &param);
151 apb_dma_enable(host->dma);
152 } else {
153 wcnt = host->remain >> 2;
154 if ( data->flags & MMC_DATA_WRITE ) {
155 for ( i=0; i<wcnt; i++, buffer+=4 )
156 writel(*(unsigned int *)buffer, &host->reg->data_window);
157 } else {
158 for ( i=0; i<wcnt; i++, buffer+=4 )
159 *(unsigned int *)buffer = readl(&host->reg->data_window);
161 wcnt <<= 2;
162 host->remain -= wcnt;
163 data->bytes_xfered += wcnt;
165 #if 0 // mask by Victor Yu. 07-06-2007
166 kunmap_atomic(host->mapped_sg, KM_BIO_SRC_IRQ);
167 moxasd_next_sg(host);
168 #endif
171 static void moxasd_request_done(struct moxasd_host *host)
173 struct mmc_request *mrq=host->mrq;
175 if ( mrq == NULL ) {
176 return;
178 host->mrq = NULL;
179 host->data = NULL;
180 mmc_request_done(host->mmc, mrq);
183 static void moxasd_prepare_data(struct moxasd_host *host, struct mmc_data *data)
185 unsigned int timeout, datactrl;
186 //unsigned long flags;
188 host->data = data;
190 // initialize the data size
191 host->size = data->blocks << data->blksz_bits;
192 moxasd_init_sg(host, data);
194 // initialize the timeout value
195 timeout = (host->mmc->f_max/1000) * (data->timeout_ns/1000);
196 timeout *= 2;
198 // initialize the data control
199 datactrl = (data->blksz_bits & MSD_BLK_SIZE_MASK) | MSD_DATA_EN;
200 if ( data->flags & MMC_DATA_WRITE ) {
201 datactrl |= MSD_DATA_WRITE;
203 if ( host->size > MSD_FIFO_LENB && host->dma ) {
204 datactrl |= MSD_DMA_EN;
206 writel(timeout, &host->reg->data_timer);
207 writel(host->size, &host->reg->data_length);
208 writel(datactrl, &host->reg->data_control);
209 //printk("DCR:%x data_length:%x data->blocks:%x host->remain:%x\n",host->reg->data_control,host->reg->data_length,data->blocks, host->remain);//johnson
210 if ( host->size > MSD_FIFO_LENB && host->dma ) {
211 writel(MSD_INT_CARD_CHANGE, &host->reg->interrupt_mask);
212 moxasd_do_fifo(host, data);
213 //tasklet_schedule(&host->fifo_run_tasklet);
214 } else {
215 writel(MSD_INT_FIFO_URUN|MSD_INT_FIFO_ORUN|MSD_INT_CARD_CHANGE, &host->reg->interrupt_mask);
219 static void moxasd_send_command(struct moxasd_host *host, struct mmc_command *cmd)
221 unsigned int status, cmdctrl;
222 int retry=0;
224 // first clear status
225 writel(MSD_CLR_RSP_TIMEOUT|MSD_CLR_RSP_CRC_OK|MSD_CLR_RSP_CRC_FAIL|MSD_CLR_CMD_SENT, &host->reg->clear);
227 // write argument
228 writel(cmd->arg, &host->reg->argument);
230 // write command
231 cmdctrl = cmd->opcode & MSD_CMD_IDX_MASK;
232 if ( cmdctrl == SD_APP_SET_BUS_WIDTH ||
233 cmdctrl == SD_APP_OP_COND ||
234 cmdctrl == SD_APP_SEND_SCR ) // this is SD application specific command
235 cmdctrl |= MSD_APP_CMD;
236 if ( cmd->flags & MMC_RSP_LONG )
237 cmdctrl |= (MSD_LONG_RSP|MSD_NEED_RSP);
238 if ( cmd->flags & MMC_RSP_SHORT )
239 cmdctrl |= MSD_NEED_RSP;
240 writel(cmdctrl|MSD_CMD_EN, &host->reg->command);
242 // wait response
243 while ( retry++ < MSD_RETRY_COUNT ) {
244 status = readl(&host->reg->status);
245 if ( status & MSD_CARD_DETECT ) { // card is removed
246 cmd->error = MMC_ERR_TIMEOUT;
247 return;
249 if ( cmdctrl & MSD_NEED_RSP ) {
250 if ( status & MSD_RSP_TIMEOUT ) {
251 writel(MSD_CLR_RSP_TIMEOUT, &host->reg->clear);
252 cmd->error = MMC_ERR_TIMEOUT;
253 return;
255 #if 0
256 if ( status & MSD_RSP_CRC_FAIL ) {
257 #else
258 if ( (cmd->flags&MMC_RSP_CRC) && (status&MSD_RSP_CRC_FAIL) ) {
259 #endif
260 writel(MSD_CLR_RSP_CRC_FAIL, &host->reg->clear);
261 cmd->error = MMC_ERR_BADCRC;
262 return;
264 if ( status & MSD_RSP_CRC_OK ) {
265 writel(MSD_CLR_RSP_CRC_OK, &host->reg->clear);
266 // read response
267 cmd->resp[0] = readl(&host->reg->response0);
268 cmd->resp[1] = readl(&host->reg->response1);
269 cmd->resp[2] = readl(&host->reg->response2);
270 cmd->resp[3] = readl(&host->reg->response3);
271 cmd->error = MMC_ERR_NONE;
272 return;
274 } else {
275 if ( status & MSD_CMD_SENT ) {
276 writel(MSD_CLR_CMD_SENT, &host->reg->clear);
277 cmd->error = MMC_ERR_NONE;
278 return;
283 cmd->error = MMC_ERR_TIMEOUT;
284 // cmd->error = MMC_ERR_NONE;
288 static irqreturn_t moxasd_irq(int irq, void *devid, struct pt_regs *regs)
290 struct moxasd_host *host=devid;
291 unsigned int status;
293 // get the interrupt status
294 status = readl(&host->reg->status);
296 // acknowledge the interurpt
297 if ( status & MSD_CARD_CHANGE ) { // has card inserted or removed
298 tasklet_schedule(&host->card_change_tasklet);
301 if ( status & (MSD_FIFO_ORUN|MSD_FIFO_URUN) ) {
302 #if 1 // add by Victor Yu. 07-09-2007
303 writel(status & (MSD_FIFO_ORUN|MSD_FIFO_URUN), &host->reg->clear);
304 #endif
305 tasklet_schedule(&host->fifo_run_tasklet);
308 return IRQ_HANDLED;
311 static void moxasd_fifo_run(unsigned long param)
313 struct moxasd_host *host;
314 struct mmc_data *data;
315 unsigned long flags;
317 spin_lock_irqsave(&host->lock, flags);
318 host = (struct moxasd_host *)param;
319 data = host->data;
320 if ( host->mrq == NULL || data == NULL ) {
321 spin_unlock_irqrestore(&host->lock, flags);
322 return;
324 moxasd_do_fifo(host, data);
325 if ( host->size <= data->bytes_xfered ) {
326 #if 1 // mask by Victor Yu. 07-04-2007
327 unsigned int status;
328 while ( 1 ) {
329 status = readl(&host->reg->status);
330 if ( status & (MSD_DATA_CRC_OK|MSD_DATA_CRC_FAIL|MSD_DATA_END) )
331 break;
332 current->state = TASK_INTERRUPTIBLE;
333 schedule_timeout(5);
335 if ( status & MSD_DATA_CRC_OK ) {
336 writel(MSD_CLR_DATA_CRC_OK, &host->reg->clear);
338 if ( status & MSD_DATA_CRC_FAIL ) {
339 writel(MSD_CLR_DATA_CRC_FAIL, &host->reg->clear);
340 data->error = MMC_ERR_TIMEOUT;
342 if ( status & MSD_DATA_END ) {
343 writel(MSD_CLR_DATA_END, &host->reg->clear);
345 #endif
346 //moxasd_do_fifo(host, data);
347 if ( data->stop ) {
348 moxasd_send_command(host, data->stop);
350 } else {
351 #if 0 // mask by Victor Yu. 07-10-2007
352 moxasd_do_fifo(host, data);
353 if ( host->size <= MSD_FIFO_LENB ) {
354 //tasklet_schedule(&host->fifo_run_tasklet);
355 moxasd_request_done(host);
357 #endif
358 spin_unlock_irqrestore(&host->lock, flags);
359 return;
362 moxasd_request_done(host);
363 spin_unlock_irqrestore(&host->lock, flags);
366 static void moxasd_card_change(unsigned long param)
368 struct moxasd_host *host=(struct moxasd_host *)param;
369 unsigned int status;
370 int delay;
371 unsigned long flags;
373 spin_lock_irqsave(&host->lock, flags);
374 udelay(3000); // to wait the hardware stably for card inserted or removed
375 status = readl(&host->reg->status);
376 if ( status & MSD_CARD_DETECT ) { // card removed
377 printk("Moxa CPU SD/MMC card is removed.\n");
378 delay = 0;
379 if ( host->data ) {
380 if ( host->dma && host->size > MSD_FIFO_LENB )
381 apb_dma_disable(host->dma);
382 host->size = host->data->bytes_xfered;
383 moxasd_do_fifo(host, host->data);
384 host->data->error = MMC_ERR_TIMEOUT;
385 moxasd_request_done(host);
386 //spin_unlock_irqrestore(&host->lock, flags);
387 //moxasd_fifo_run(*(unsigned long *)host);
388 //spin_lock_irqsave(&host->lock, flags);
390 } else { // card inserted
391 printk("Moxa CPU SD/MMC card is inserted.\n");
392 if ( readl(&host->reg->clock_control) & MSD_CLK_SD ) { // SD
393 host->mmc->f_max = 25000000;
395 host->mmc->mode = MMC_MODE_SD;
396 } else {
397 host->mmc->f_max = 20000000;
398 host->mmc->mode = MMC_MODE_MMC;
400 delay = 500;
402 writel(MSD_CLR_CARD_CHANGE, &host->reg->clear);
403 spin_unlock_irqrestore(&host->lock, flags);
404 mmc_detect_change(host->mmc, msecs_to_jiffies(delay));
407 static void moxasd_dma_irq(void *param)
409 struct moxasd_host *host=(struct moxasd_host *)param;
411 if ( host )
412 tasklet_schedule(&host->fifo_run_tasklet);
415 static void moxasd_request(struct mmc_host *mmc, struct mmc_request *mrq)
417 struct moxasd_host *host=mmc_priv(mmc);
418 struct mmc_command *cmd;
419 unsigned long flags;
421 spin_lock_irqsave(&host->lock, flags);
422 host->mrq = mrq;
423 cmd = mrq->cmd;
425 // if no card inserted, return timeout error
426 if ( readl(&host->reg->status) & MSD_CARD_DETECT ) { // card is removed
427 cmd->error = MMC_ERR_TIMEOUT;
428 goto request_done;
431 // request include data or not
432 if ( cmd->data ) {
433 moxasd_prepare_data(host, cmd->data);
436 // do request command
437 moxasd_send_command(host, cmd);
439 if ( cmd->data && cmd->error == MMC_ERR_NONE ) {
440 spin_unlock_irqrestore(&host->lock, flags);
441 return;
445 request_done:
446 moxasd_request_done(host);
447 spin_unlock_irqrestore(&host->lock, flags);
450 #define MIN_POWER (MMC_VDD_360 - MSD_SD_POWER_MASK)
451 static void moxasd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
453 struct moxasd_host *host=mmc_priv(mmc);
454 unsigned long flags;
456 spin_lock_irqsave(&host->lock, flags);
457 if (ios->clock) {
458 int div;
459 #ifdef MSD_SUPPORT_GET_CLOCK
460 div = (host->sysclk / (host->mmc->f_max * 2)) - 1;
461 #else
462 div = (APB_CLK / (host->mmc->f_max * 2)) - 1;
463 #endif
464 if ( div > MSD_CLK_DIV_MASK )
465 div = MSD_CLK_DIV_MASK;
466 else if ( div < 0 )
467 div = 0;
468 if ( host->mmc->mode == MMC_MODE_SD )
469 div |= MSD_CLK_SD;
470 writel(div, &host->reg->clock_control);
471 } else if ( !(readl(&host->reg->clock_control) & MSD_CLK_DIS) ) {
473 * Ensure that the clock is off.
475 writel(readl(&host->reg->clock_control)|MSD_CLK_DIS, &host->reg->clock_control);
478 if ( ios->power_mode == MMC_POWER_OFF ) {
479 writel(readl(&host->reg->power_control)&~MSD_SD_POWER_ON, &host->reg->power_control);
480 } else {
481 unsigned short power;
483 if ( ios->vdd < MIN_POWER )
484 power = 0;
485 else
486 power = ios->vdd - MIN_POWER;
487 writel(MSD_SD_POWER_ON|(unsigned int)power, &host->reg->power_control);
490 #if 1
491 if ( ios->bus_width == MMC_BUS_WIDTH_1 ) {
492 writel(MSD_SINGLE_BUS, &host->reg->bus_width);
493 } else {
494 writel(MSD_WIDE_BUS, &host->reg->bus_width);
496 #endif
497 spin_unlock_irqrestore(&host->lock, flags);
501 * To check write protect or not. Return 0 for none, 1 for write protect.
503 static int moxasd_get_ro(struct mmc_host *mmc)
505 struct moxasd_host *host=mmc_priv(mmc);
507 if ( readl(&host->reg->status) & MSD_WRITE_PROT )
508 return 1;
509 else
510 return 0;
513 static struct mmc_host_ops moxasd_ops = {
514 .request = moxasd_request,
515 .set_ios = moxasd_set_ios,
516 .get_ro = moxasd_get_ro,
519 static int moxasd_probe(struct device *dev)
521 struct mmc_host *mmc;
522 struct moxasd_host *host=NULL;
523 int ret;
525 mmc = mmc_alloc_host(sizeof(struct moxasd_host), dev);
526 if (!mmc) {
527 ret = -ENOMEM;
528 goto out;
531 mmc->ops = &moxasd_ops;
532 mmc->f_min = 400000;
533 mmc->f_max = 25000000;
535 mmc->mode = MMC_MODE_SD;
536 #if 1
537 mmc->ocr_avail = 0xffff00; // support 2.0v - 3.6v power
538 #else
539 mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
540 mmc->caps = MMC_CAP_4_BIT_DATA;
541 mmc->max_hw_segs = 128;
542 mmc->max_phys_segs = 128;
543 mmc->max_sectors = 128;
544 mmc->max_seg_size = mmc->max_sectors * 512;
545 #endif
547 host = mmc_priv(mmc);
548 host->mmc = mmc;
549 spin_lock_init(&host->lock);
550 tasklet_init(&host->card_change_tasklet, moxasd_card_change, (unsigned long)host);
551 tasklet_init(&host->fifo_run_tasklet, moxasd_fifo_run, (unsigned long)host);
552 host->reg = (moxasd_reg *)CPE_SD_VA_BASE;
553 host->dma = apb_dma_alloc(APB_DMA_SD_REQ_NO);
554 if ( host->dma ) {
555 apb_dma_set_irq(host->dma, moxasd_dma_irq, host);
558 #ifdef MSD_SUPPORT_GET_CLOCK
559 // get system clock
561 unsigned int mul, val, div;
562 mul = (*(volatile unsigned int *)(CPE_PMU_VA_BASE+0x30) >> 3) & 0x1ff;
563 val = (*(volatile unsigned int *)(CPE_PMU_VA_BASE+0x0c) >> 4) & 0x7;
564 switch ( val ) {
565 case 0 : div = 2; break;
566 case 1 : div = 3; break;
567 case 2 : div = 4; break;
568 case 3 : div = 6; break;
569 case 4 : div = 8; break;
570 default : div = 2; break;
572 host->sysclk = (38684*mul + 10000) / (div * 10000);
573 host->sysclk = (host->sysclk * 1000000) / 2;
575 #endif
577 // change I/O multiplexing to SD, so the GPIO 17-10 will be fail
578 mcpu_gpio_mp_clear(0xff<<10);
581 * Ensure that the host controller is shut down, and setup
582 * with our defaults.
584 writel(0, &host->reg->interrupt_mask); // disable all interrupt
585 writel(MSD_SDC_RST, &host->reg->command); // reset chip
586 while ( readl(&host->reg->command) & MSD_SDC_RST); // wait for reset finished
587 writel(0, &host->reg->interrupt_mask); // disable all interrupt
589 // to check any card inserted or not
590 if ( !(readl(&host->reg->status) & MSD_CARD_DETECT) ) { // is inserted
591 if ( readl(&host->reg->clock_control) & MSD_CLK_SD ) { // is SD card
592 mmc->f_max = 25000000;
593 mmc->mode = MMC_MODE_SD;
594 } else { // is MMC card
595 mmc->f_max = 20000000;
596 mmc->mode = MMC_MODE_MMC;
600 mmc->caps = MMC_CAP_4_BIT_DATA;
601 writel(MSD_WIDE_BUS, &host->reg->bus_width);
603 cpe_int_set_irq(IRQ_SD, EDGE, H_ACTIVE);
604 ret = request_irq(IRQ_SD, moxasd_irq, SA_INTERRUPT, "MOXASD", host);
605 if (ret)
606 goto out;
608 writel(MSD_INT_CARD_CHANGE, &host->reg->interrupt_mask);
609 dev_set_drvdata(dev, mmc);
610 mmc_add_host(mmc);
612 return 0;
614 out:
615 if (mmc)
616 mmc_free_host(mmc);
618 return ret;
621 static int moxasd_remove(struct device *dev)
623 struct mmc_host *mmc=dev_get_drvdata(dev);
625 dev_set_drvdata(dev, NULL);
627 if (mmc) {
628 struct moxasd_host *host=mmc_priv(mmc);
630 mmc_remove_host(mmc);
632 // stop SD/MMC
633 if ( host->dma ) {
634 apb_dma_disable(host->dma);
635 apb_dma_release_irq(host->dma);
636 apb_dma_release(host->dma);
638 writel(0, &host->reg->interrupt_mask);
639 writel(0, &host->reg->power_control);
640 writel(readl(&host->reg->clock_control)|MSD_CLK_DIS, &host->reg->clock_control);
642 free_irq(IRQ_SD, host);
643 tasklet_kill(&host->card_change_tasklet);
644 tasklet_kill(&host->fifo_run_tasklet);
646 mmc_free_host(mmc);
648 return 0;
651 static struct platform_device moxasd_device = {
652 .name = "moxart-sd",
653 .id = -1,
656 static struct device_driver moxasd_driver = {
657 .name = "moxart-sd",
658 .bus = &platform_bus_type,
659 .probe = moxasd_probe,
660 .remove = moxasd_remove,
663 static int __init moxasd_init(void)
665 int ret;
667 printk("Moxa CPU SD/MMC Device Driver V1.0 initialize ");
668 platform_device_register(&moxasd_device);
669 ret = driver_register(&moxasd_driver);
670 if ( ret ) {
671 printk("fail !\n");
672 platform_device_unregister(&moxasd_device);
673 } else {
674 printk("OK.\n");
676 return ret;
679 static void __exit moxasd_exit(void)
681 platform_device_unregister(&moxasd_device);
682 driver_unregister(&moxasd_driver);
685 module_init(moxasd_init);
686 module_exit(moxasd_exit);
688 MODULE_DESCRIPTION("Moxa CPU SD/Multimedia Card Interface Driver");
689 MODULE_LICENSE("GPL");