summaryrefslogtreecommitdiff
path: root/packages/linux/linux-rp-2.6.22/tosa-keyboard-r19.patch
diff options
context:
space:
mode:
Diffstat (limited to 'packages/linux/linux-rp-2.6.22/tosa-keyboard-r19.patch')
-rw-r--r--packages/linux/linux-rp-2.6.22/tosa-keyboard-r19.patch514
1 files changed, 0 insertions, 514 deletions
diff --git a/packages/linux/linux-rp-2.6.22/tosa-keyboard-r19.patch b/packages/linux/linux-rp-2.6.22/tosa-keyboard-r19.patch
deleted file mode 100644
index 948c27fdce..0000000000
--- a/packages/linux/linux-rp-2.6.22/tosa-keyboard-r19.patch
+++ /dev/null
@@ -1,514 +0,0 @@
- drivers/input/keyboard/Kconfig | 12 -
- drivers/input/keyboard/Makefile | 1
- drivers/input/keyboard/tosakbd.c | 467 +++++++++++++++++++++++++++++++++++++++
- 3 files changed, 479 insertions(+), 1 deletion(-)
-
-Index: git/drivers/input/keyboard/Kconfig
-===================================================================
---- git.orig/drivers/input/keyboard/Kconfig 2006-10-31 16:08:57.000000000 +0000
-+++ git/drivers/input/keyboard/Kconfig 2006-11-07 22:13:10.000000000 +0000
-@@ -148,12 +148,22 @@ config KEYBOARD_SPITZ
- depends on PXA_SHARPSL
- default y
- help
-- Say Y here to enable the keyboard on the Sharp Zaurus SL-C1000,
-+ Say Y here to enable the keyboard on the Sharp Zaurus SL-C1000,
- SL-C3000 and Sl-C3100 series of PDAs.
-
- To compile this driver as a module, choose M here: the
- module will be called spitzkbd.
-
-+config KEYBOARD_TOSA
-+ tristate "Tosa keyboard"
-+ depends on PXA_SHARPSL
-+ default y
-+ help
-+ Say Y here to enable the keyboard on the Sharp Zaurus SL-6000x (Tosa)
-+
-+ To compile this driver as a module, choose M here: the
-+ module will be called tosakbd.
-+
- config KEYBOARD_AMIGA
- tristate "Amiga keyboard"
- depends on AMIGA
-Index: git/drivers/input/keyboard/Makefile
-===================================================================
---- git.orig/drivers/input/keyboard/Makefile 2006-10-31 16:08:57.000000000 +0000
-+++ git/drivers/input/keyboard/Makefile 2006-11-07 22:13:10.000000000 +0000
-@@ -17,3 +17,4 @@ obj-$(CONFIG_KEYBOARD_SPITZ) += spitzkb
- obj-$(CONFIG_KEYBOARD_AAED2000) += aaed2000_kbd.o
- obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o
- obj-$(CONFIG_KEYBOARD_ASIC3) += asic3_keys.o
-+obj-$(CONFIG_KEYBOARD_TOSA) += tosakbd.o
-Index: git/drivers/input/keyboard/tosakbd.c
-===================================================================
---- /dev/null 1970-01-01 00:00:00.000000000 +0000
-+++ git/drivers/input/keyboard/tosakbd.c 2006-11-07 23:27:19.000000000 +0000
-@@ -0,0 +1,467 @@
-+/*
-+ * Keyboard driver for Sharp Tosa models (SL-6000x)
-+ *
-+ * Copyright (c) 2005 Dirk Opfer
-+ *
-+ * Based on xtkbd.c/locomkbd.c/corgikbd.c
-+ *
-+ * This program is free software; you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License version 2 as
-+ * published by the Free Software Foundation.
-+ *
-+ */
-+
-+#include <linux/delay.h>
-+#include <linux/platform_device.h>
-+#include <linux/init.h>
-+#include <linux/input.h>
-+#include <linux/interrupt.h>
-+#include <linux/jiffies.h>
-+#include <linux/module.h>
-+#include <linux/slab.h>
-+
-+#include <asm/arch/tosa.h>
-+#include <asm/arch/hardware.h>
-+#include <asm/arch/pxa-regs.h>
-+
-+
-+#define TOSA_KEY_STROBE_NUM (11)
-+#define TOSA_KEY_SENSE_NUM (7)
-+
-+#define KEYMASK_ON (0x1<<0)
-+#define KEYMASK_REC (0x1<<1)
-+#define KEYMASK_SYNC (0x1<<2)
-+
-+#define KB_ROWS 7
-+#define KB_COLS 11
-+#define KB_ROWMASK(r) (1 << (r))
-+#define SCANCODE(r,c) ( ((r)<<4) + (c) + 1 )
-+#define NR_SCANCODES (SCANCODE(KB_ROWS-1,KB_COLS)+1+1)
-+
-+#define SCAN_INTERVAL (HZ/10)
-+#define HP_SCAN_INTERVAL (150) /* ms */
-+#define HP_STABLE_COUNT 2
-+
-+#define TOSA_KEY_CALENDER KEY_F1
-+#define TOSA_KEY_ADDRESS KEY_F2
-+#define TOSA_KEY_FN KEY_F3
-+#define TOSA_KEY_CANCEL KEY_F4
-+#define TOSA_KEY_OFF KEY_SUSPEND
-+#define TOSA_KEY_CENTER KEY_F5
-+#define TOSA_KEY_REC KEY_F6
-+#define TOSA_KEY_LIGHT KEY_F7
-+#define TOSA_KEY_RECORD KEY_F8
-+#define TOSA_KEY_HOME KEY_F9
-+#define TOSA_KEY_MAIL KEY_F10
-+#define TOSA_KEY_OK KEY_F11
-+#define TOSA_KEY_MENU KEY_F12
-+#define TOSA_KEY_SYNC KEY_F13
-+
-+#define GET_ROWS_STATUS(c) ((GPLR2 & TOSA_GPIO_ALL_SENSE_BIT) >> TOSA_GPIO_ALL_SENSE_RSHIFT)
-+#define KB_DISCHARGE_DELAY 10
-+#define KB_ACTIVATE_DELAY 10
-+
-+
-+static unsigned char tosakbd_keycode[NR_SCANCODES] = {
-+ 0, /* 0 */
-+ 0, KEY_W, 0, 0, 0, KEY_K, KEY_BACKSPACE, KEY_P, 0, 0, 0, TOSA_KEY_OFF, 0, 0, 0, 0, /*1 - 16*/
-+ KEY_Q, KEY_E, KEY_T, KEY_Y, 0, KEY_O, KEY_I, KEY_COMMA, 0, 0, 0, TOSA_KEY_RECORD, 0, 0, 0, 0, /*17 - 32*/
-+ KEY_A, KEY_D, KEY_G, KEY_U, 0, KEY_L, KEY_ENTER, KEY_DOT, 0, 0, 0, TOSA_KEY_SYNC, 0, 0, 0, 0, /*33 - 48*/
-+ KEY_Z, KEY_C, KEY_V, KEY_J, TOSA_KEY_ADDRESS, TOSA_KEY_CANCEL, TOSA_KEY_CENTER, TOSA_KEY_OK, KEY_LEFTSHIFT, 0 , 0,0 , 0, 0, 0, 0, /*49 - 64*/
-+ KEY_S, KEY_R, KEY_B, KEY_N, TOSA_KEY_CALENDER, TOSA_KEY_HOME, TOSA_KEY_REC, TOSA_KEY_LIGHT, 0, KEY_RIGHTSHIFT, 0, 0, 0, 0, 0, 0, /*65 - 80*/
-+ KEY_TAB, KEY_SLASH, KEY_H, KEY_M, TOSA_KEY_MENU, 0, KEY_UP, 0, 0, 0, TOSA_KEY_FN, 0, 0, 0, 0, 0, /*81 - 96*/
-+ KEY_X, KEY_F, KEY_SPACE, KEY_APOSTROPHE, TOSA_KEY_MAIL, KEY_LEFT, KEY_DOWN, KEY_RIGHT, 0, 0, 0, 0, 0, /*97 - 109*/
-+};
-+
-+struct tosakbd {
-+ unsigned char keycode[ARRAY_SIZE(tosakbd_keycode)];
-+ struct input_dev *input;
-+
-+ spinlock_t lock;
-+ struct timer_list timer;
-+ struct timer_list hptimer;
-+
-+ int hp_state;
-+ int hp_count;
-+
-+ unsigned int suspended;
-+ unsigned long suspend_jiffies;
-+};
-+
-+/* Helper functions for reading the keyboard matrix
-+ * Note: We should really be using pxa_gpio_mode to alter GPDR but it
-+ * requires a function call per GPIO bit which is excessive
-+ * when we need to access 12 bits at once, multiple times.
-+ * These functions must be called within local_irq_save()/local_irq_restore()
-+ * or similar.
-+ */
-+static inline void tosakbd_discharge_all(void)
-+{
-+ /* STROBE All HiZ */
-+ GPCR1 = TOSA_GPIO_HIGH_STROBE_BIT;
-+ GPDR1 &= ~TOSA_GPIO_HIGH_STROBE_BIT;
-+ GPCR2 = TOSA_GPIO_LOW_STROBE_BIT;
-+ GPDR2 &= ~TOSA_GPIO_LOW_STROBE_BIT;
-+}
-+
-+static inline void tosakbd_activate_all(void)
-+{
-+ /* STROBE ALL -> High */
-+ GPSR1 = TOSA_GPIO_HIGH_STROBE_BIT;
-+ GPDR1 |= TOSA_GPIO_HIGH_STROBE_BIT;
-+ GPSR2 = TOSA_GPIO_LOW_STROBE_BIT;
-+ GPDR2 |= TOSA_GPIO_LOW_STROBE_BIT;
-+
-+ udelay(KB_DISCHARGE_DELAY);
-+
-+ /* STATE CLEAR */
-+ GEDR2 |= TOSA_GPIO_ALL_SENSE_BIT;
-+}
-+
-+static inline void tosakbd_activate_col(int col)
-+{
-+ if (col<=5) {
-+ /* STROBE col -> High, not col -> HiZ */
-+ GPSR1 = TOSA_GPIO_STROBE_BIT(col);
-+ GPDR1 = (GPDR1 & ~TOSA_GPIO_HIGH_STROBE_BIT) | TOSA_GPIO_STROBE_BIT(col);
-+ } else {
-+ /* STROBE col -> High, not col -> HiZ */
-+ GPSR2 = TOSA_GPIO_STROBE_BIT(col);
-+ GPDR2 = (GPDR2 & ~TOSA_GPIO_LOW_STROBE_BIT) | TOSA_GPIO_STROBE_BIT(col);
-+ }
-+}
-+
-+static inline void tosakbd_reset_col(int col)
-+{
-+ if (col<=5) {
-+ /* STROBE col -> Low */
-+ GPCR1 = TOSA_GPIO_STROBE_BIT(col);
-+ /* STROBE col -> out, not col -> HiZ */
-+ GPDR1 = (GPDR1 & ~TOSA_GPIO_HIGH_STROBE_BIT) | TOSA_GPIO_STROBE_BIT(col);
-+ } else {
-+ /* STROBE col -> Low */
-+ GPCR2 = TOSA_GPIO_STROBE_BIT(col);
-+ /* STROBE col -> out, not col -> HiZ */
-+ GPDR2 = (GPDR2 & ~TOSA_GPIO_LOW_STROBE_BIT) | TOSA_GPIO_STROBE_BIT(col);
-+ }
-+}
-+
-+/*
-+ * Read the GPIOs for POWER, RECORD and SYNC
-+ */
-+static int read_port_key_status_raw(void)
-+{
-+ int val=0;
-+
-+ /* Power key */
-+ if ((GPLR0 & GPIO_bit(TOSA_GPIO_ON_KEY))==0)
-+ val |= KEYMASK_ON;
-+ /* Record key */
-+ if ((GPLR0 & GPIO_bit(TOSA_GPIO_RECORD_BTN))==0)
-+ val |= KEYMASK_REC;
-+ /* Sync key */
-+ if ((GPLR0 & GPIO_bit(TOSA_GPIO_SYNC))==0)
-+ val |= KEYMASK_SYNC;
-+ return val;
-+}
-+
-+
-+/*
-+ * The tosa keyboard only generates interrupts when a key is pressed.
-+ * So when a key is pressed, we enable a timer. This timer scans the
-+ * keyboard, and this is how we detect when the key is released.
-+ */
-+
-+/* Scan the hardware keyboard and push any changes up through the input layer */
-+static void tosakbd_scankeyboard(struct tosakbd *tosakbd_data)
-+{
-+ unsigned int row, col, rowd;
-+ unsigned long flags;
-+ unsigned int num_pressed = 0;
-+
-+ if (tosakbd_data->suspended)
-+ return;
-+
-+ spin_lock_irqsave(&tosakbd_data->lock, flags);
-+
-+ for (col = 0; col < KB_COLS; col++) {
-+ /*
-+ * Discharge the output driver capacitatance
-+ * in the keyboard matrix. (Yes it is significant..)
-+ */
-+ tosakbd_discharge_all();
-+ udelay(KB_DISCHARGE_DELAY);
-+
-+ tosakbd_activate_col( col);
-+ udelay(KB_ACTIVATE_DELAY);
-+
-+ rowd = GET_ROWS_STATUS(col);
-+
-+ for (row = 0; row < KB_ROWS; row++) {
-+ unsigned int scancode, pressed;
-+ scancode = SCANCODE(row, col);
-+ pressed = rowd & KB_ROWMASK(row);
-+ input_report_key(tosakbd_data->input, tosakbd_data->keycode[scancode], pressed);
-+ if (pressed)
-+ num_pressed++;
-+ }
-+
-+ tosakbd_reset_col(col);
-+ }
-+
-+ tosakbd_activate_all();
-+
-+ rowd = read_port_key_status_raw();
-+
-+ for (row = 0; row < 3; row++ ) {
-+ unsigned int scancode, pressed;
-+ scancode = SCANCODE(row, KB_COLS);
-+ pressed = rowd & KB_ROWMASK(row);
-+ input_report_key(tosakbd_data->input, tosakbd_data->keycode[scancode], pressed);
-+ if (pressed)
-+ num_pressed++;
-+
-+ if (pressed && (tosakbd_data->keycode[scancode] == TOSA_KEY_OFF)
-+ && time_after(jiffies, tosakbd_data->suspend_jiffies + msecs_to_jiffies(1000))) {
-+ input_event(tosakbd_data->input, EV_PWR, TOSA_KEY_OFF, 1);
-+ tosakbd_data->suspend_jiffies = jiffies;
-+ }
-+ }
-+
-+ input_sync(tosakbd_data->input);
-+
-+ /* if any keys are pressed, enable the timer */
-+ if (num_pressed)
-+ mod_timer(&tosakbd_data->timer, jiffies + SCAN_INTERVAL);
-+
-+ spin_unlock_irqrestore(&tosakbd_data->lock, flags);
-+}
-+
-+/*
-+ * tosa keyboard interrupt handler.
-+ */
-+static irqreturn_t tosakbd_interrupt(int irq, void *dev_id)
-+{
-+ struct tosakbd *tosakbd_data = dev_id;
-+
-+ if (!timer_pending(&tosakbd_data->timer))
-+ {
-+ /** wait chattering delay **/
-+ udelay(20);
-+ tosakbd_scankeyboard(tosakbd_data);
-+ }
-+
-+ return IRQ_HANDLED;
-+}
-+
-+/*
-+ * tosa timer checking for released keys
-+ */
-+static void tosakbd_timer_callback(unsigned long data)
-+{
-+ struct tosakbd *tosakbd_data = (struct tosakbd *) data;
-+ tosakbd_scankeyboard(tosakbd_data);
-+}
-+
-+/*
-+ * The headphone generates an interrupt.
-+ * We debounce the switche and pass them to the input system.
-+ */
-+
-+static irqreturn_t tosakbd_hp_isr(int irq, void *dev_id)
-+{
-+ struct tosakbd *tosakbd_data = dev_id;
-+
-+ if (!timer_pending(&tosakbd_data->hptimer))
-+ mod_timer(&tosakbd_data->hptimer, jiffies + msecs_to_jiffies(HP_SCAN_INTERVAL));
-+
-+ return IRQ_HANDLED;
-+}
-+
-+static void tosakbd_hp_timer(unsigned long data)
-+{
-+ struct tosakbd *tosakbd_data = (struct tosakbd *) data;
-+ unsigned long state;
-+ unsigned long flags;
-+
-+ state = (GPLR(TOSA_GPIO_EAR_IN) & GPIO_bit(TOSA_GPIO_EAR_IN));
-+ if (state != tosakbd_data->hp_state) {
-+ tosakbd_data->hp_count = 0;
-+ tosakbd_data->hp_state = state;
-+ } else if (tosakbd_data->hp_count < HP_STABLE_COUNT) {
-+ tosakbd_data->hp_count++;
-+ }
-+
-+ if (tosakbd_data->hp_count >= HP_STABLE_COUNT) {
-+ spin_lock_irqsave(&tosakbd_data->lock, flags);
-+
-+ input_report_switch(tosakbd_data->input, SW_HEADPHONE_INSERT, ((GPLR(TOSA_GPIO_EAR_IN) & GPIO_bit(TOSA_GPIO_EAR_IN)) == 0));
-+ input_sync(tosakbd_data->input);
-+
-+ spin_unlock_irqrestore(&tosakbd_data->lock, flags);
-+ } else {
-+ mod_timer(&tosakbd_data->hptimer, jiffies + msecs_to_jiffies(HP_SCAN_INTERVAL));
-+ }
-+}
-+
-+#ifdef CONFIG_PM
-+static int tosakbd_suspend(struct platform_device *dev, pm_message_t state)
-+{
-+ struct tosakbd *tosakbd = platform_get_drvdata(dev);
-+
-+ tosakbd->suspended = 1;
-+
-+ return 0;
-+}
-+
-+static int tosakbd_resume(struct platform_device *dev)
-+{
-+ struct tosakbd *tosakbd = platform_get_drvdata(dev);
-+
-+ /* Upon resume, ignore the suspend key for a short while */
-+ tosakbd->suspend_jiffies = jiffies;
-+ tosakbd->suspended = 0;
-+
-+ return 0;
-+}
-+#else
-+#define tosakbd_suspend NULL
-+#define tosakbd_resume NULL
-+#endif
-+
-+static int __init tosakbd_probe(struct platform_device *pdev) {
-+
-+ int i;
-+ struct tosakbd *tosakbd;
-+ struct input_dev *input_dev;
-+
-+ tosakbd = kzalloc(sizeof(struct tosakbd), GFP_KERNEL);
-+ if (!tosakbd)
-+ return -ENOMEM;
-+
-+ input_dev = input_allocate_device();
-+ if (!input_dev) {
-+ kfree(tosakbd);
-+ return -ENOMEM;
-+ }
-+
-+ platform_set_drvdata(pdev,tosakbd);
-+
-+ spin_lock_init(&tosakbd->lock);
-+
-+ /* Init Keyboard rescan timer */
-+ init_timer(&tosakbd->timer);
-+ tosakbd->timer.function = tosakbd_timer_callback;
-+ tosakbd->timer.data = (unsigned long) tosakbd;
-+
-+ /* Init Headphone Timer */
-+ init_timer(&tosakbd->hptimer);
-+ tosakbd->hptimer.function = tosakbd_hp_timer;
-+ tosakbd->hptimer.data = (unsigned long) tosakbd;
-+
-+ tosakbd->suspend_jiffies = jiffies;
-+
-+ tosakbd->input = input_dev;
-+
-+ input_dev->private = tosakbd;
-+ input_dev->name = "Tosa Keyboard";
-+ input_dev->phys = "tosakbd/input0";
-+ input_dev->cdev.dev = &pdev->dev;
-+
-+ input_dev->id.bustype = BUS_HOST;
-+ input_dev->id.vendor = 0x0001;
-+ input_dev->id.product = 0x0001;
-+ input_dev->id.version = 0x0100;
-+
-+ input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP) | BIT(EV_PWR) | BIT(EV_SW);
-+ input_dev->keycode = tosakbd->keycode;
-+ input_dev->keycodesize = sizeof(unsigned char);
-+ input_dev->keycodemax = ARRAY_SIZE(tosakbd_keycode);
-+
-+ memcpy(tosakbd->keycode, tosakbd_keycode, sizeof(tosakbd->keycode));
-+ for (i = 0; i < ARRAY_SIZE(tosakbd_keycode); i++)
-+ set_bit(tosakbd->keycode[i], input_dev->keybit);
-+ clear_bit(0, input_dev->keybit);
-+ set_bit(SW_HEADPHONE_INSERT, input_dev->swbit);
-+
-+ input_register_device(tosakbd->input);
-+
-+ /* Setup sense interrupts - RisingEdge Detect, sense lines as inputs */
-+ for (i = 0; i < TOSA_KEY_SENSE_NUM; i++) {
-+ pxa_gpio_mode( TOSA_GPIO_KEY_SENSE(i) | GPIO_IN);
-+ if (request_irq(TOSA_IRQ_GPIO_KEY_SENSE(i), tosakbd_interrupt,
-+ IRQF_DISABLED | IRQF_TRIGGER_RISING, "tosakbd", tosakbd)) {
-+ printk("tosakbd: Can't get IRQ: %d !\n", i);
-+ }
-+ }
-+
-+ /* Set Strobe lines as outputs - set high */
-+ for (i = 0; i < TOSA_KEY_STROBE_NUM; i++) {
-+ pxa_gpio_mode( TOSA_GPIO_KEY_STROBE(i) | GPIO_OUT | GPIO_DFLT_HIGH);
-+ }
-+
-+ // Power&Rec Button
-+ pxa_gpio_mode( TOSA_GPIO_ON_KEY | GPIO_IN);
-+ pxa_gpio_mode( TOSA_GPIO_RECORD_BTN | GPIO_IN);
-+ pxa_gpio_mode( TOSA_GPIO_SYNC | GPIO_IN);
-+ pxa_gpio_mode( TOSA_GPIO_EAR_IN | GPIO_IN);
-+
-+ if (request_irq(TOSA_IRQ_GPIO_ON_KEY, tosakbd_interrupt, IRQF_DISABLED | IRQF_TRIGGER_FALLING, "On key", tosakbd) ||
-+ request_irq(TOSA_IRQ_GPIO_RECORD_BTN, tosakbd_interrupt, IRQF_DISABLED | IRQF_TRIGGER_FALLING, "Record key", tosakbd) ||
-+ request_irq(TOSA_IRQ_GPIO_SYNC, tosakbd_interrupt, IRQF_DISABLED | IRQF_TRIGGER_FALLING, "Sync key", tosakbd) ||
-+ request_irq(TOSA_IRQ_GPIO_EAR_IN, tosakbd_hp_isr, IRQF_DISABLED | IRQF_TRIGGER_FALLING, "HP in", tosakbd)) {
-+ printk("Could not allocate KEYBD IRQ!\n");
-+ }
-+
-+ printk(KERN_INFO "input: Tosa Keyboard Registered\n");
-+
-+ return 0;
-+}
-+
-+static int tosakbd_remove(struct platform_device *dev) {
-+
-+ int i;
-+ struct tosakbd *tosakbd = platform_get_drvdata(dev);
-+
-+ for (i = 0; i < TOSA_KEY_SENSE_NUM; i++)
-+ free_irq(TOSA_IRQ_GPIO_KEY_SENSE(i),tosakbd);
-+
-+ free_irq(TOSA_IRQ_GPIO_ON_KEY,tosakbd);
-+ free_irq(TOSA_IRQ_GPIO_RECORD_BTN,tosakbd);
-+ free_irq(TOSA_IRQ_GPIO_SYNC,tosakbd);
-+
-+ del_timer_sync(&tosakbd->timer);
-+
-+ input_unregister_device(tosakbd->input);
-+
-+ kfree(tosakbd);
-+
-+ return 0;
-+}
-+
-+static struct platform_driver tosakbd_driver = {
-+ .probe = tosakbd_probe,
-+ .remove = tosakbd_remove,
-+ .suspend = tosakbd_suspend,
-+ .resume = tosakbd_resume,
-+ .driver = {
-+ .name = "tosa-keyboard",
-+ },
-+};
-+
-+static int __devinit tosakbd_init(void)
-+{
-+ return platform_driver_register(&tosakbd_driver);
-+}
-+
-+static void __exit tosakbd_exit(void)
-+{
-+ platform_driver_unregister(&tosakbd_driver);
-+}
-+
-+module_init(tosakbd_init);
-+module_exit(tosakbd_exit);
-+
-+MODULE_AUTHOR("Dirk Opfer <Dirk@Opfer-Online.de>");
-+MODULE_DESCRIPTION("Tosa Keyboard Driver");
-+MODULE_LICENSE("GPLv2");