2005-04-16 15:20:36 -07:00
/*
*
* keyboard input driver for i2c IR remote controls
*
* Copyright ( c ) 2000 - 2003 Gerd Knorr < kraxel @ bytesex . org >
* modified for PixelView ( BT878P + W / FM ) by
* Michal Kochanowicz < mkochano @ pld . org . pl >
* Christoph Bartelmus < lirc @ bartelmus . de >
* modified for KNC ONE TV Station / Anubis Typhoon TView Tuner by
* Ulrich Mueller < ulrich . mueller42 @ web . de >
2005-11-08 21:37:21 -08:00
* modified for em2820 based USB TV tuners by
* Markus Rechberger < mrechberger @ gmail . com >
2007-08-24 01:02:32 -03:00
* modified for DViCO Fusion HDTV 5 RT GOLD by
* Chaogui Zhang < czhang1974 @ gmail . com >
2008-10-13 08:37:06 -03:00
* modified for MSI TV @ nywhere Plus by
* Henry Wong < henry @ stuffedcow . net >
* Mark Schultz < n9xmj @ yahoo . com >
* Brian Rogers < brian_rogers @ comcast . net >
2009-02-12 03:43:11 -03:00
* modified for AVerMedia Cardbus by
* Oldrich Jedlicka < oldium . pro @ seznam . cz >
2005-04-16 15:20:36 -07:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/string.h>
# include <linux/timer.h>
# include <linux/delay.h>
# include <linux/errno.h>
# include <linux/slab.h>
# include <linux/i2c.h>
# include <linux/workqueue.h>
2005-12-12 00:37:28 -08:00
2010-11-17 13:28:38 -03:00
# include <media/rc-core.h>
2005-11-08 21:37:32 -08:00
# include <media/ir-kbd-i2c.h>
2005-11-08 21:37:21 -08:00
2005-04-16 15:20:36 -07:00
/* ----------------------------------------------------------------------- */
/* insmod parameters */
static int debug ;
module_param ( debug , int , 0644 ) ; /* debug level (0,1,2) */
2006-01-23 09:34:06 -02:00
2010-03-12 21:18:14 -03:00
# define MODULE_NAME "ir-kbd-i2c"
2005-04-16 15:20:36 -07:00
# define dprintk(level, fmt, arg...) if (debug >= level) \
2010-03-12 21:18:14 -03:00
printk ( KERN_DEBUG MODULE_NAME " : " fmt , # # arg )
2005-04-16 15:20:36 -07:00
/* ----------------------------------------------------------------------- */
2007-06-25 14:34:06 -03:00
static int get_key_haup_common ( struct IR_i2c * ir , u32 * ir_key , u32 * ir_raw ,
int size , int offset )
2005-04-16 15:20:36 -07:00
{
2007-06-25 14:34:06 -03:00
unsigned char buf [ 6 ] ;
2008-09-22 00:54:59 -03:00
int start , range , toggle , dev , code , ircode ;
2005-04-16 15:20:36 -07:00
/* poll IR chip */
2009-05-13 16:48:50 -03:00
if ( size ! = i2c_master_recv ( ir - > c , buf , size ) )
2005-04-16 15:20:36 -07:00
return - EIO ;
/* split rc5 data block ... */
2007-06-25 14:34:06 -03:00
start = ( buf [ offset ] > > 7 ) & 1 ;
range = ( buf [ offset ] > > 6 ) & 1 ;
toggle = ( buf [ offset ] > > 5 ) & 1 ;
dev = buf [ offset ] & 0x1f ;
code = ( buf [ offset + 1 ] > > 2 ) & 0x3f ;
2005-04-16 15:20:36 -07:00
2006-09-26 16:39:00 -03:00
/* rc5 has two start bits
* the first bit must be one
* the second bit defines the command range ( 1 = 0 - 63 , 0 = 64 - 127 )
*/
if ( ! start )
2005-04-16 15:20:36 -07:00
/* no key pressed */
return 0 ;
2008-09-22 00:54:59 -03:00
/*
* Hauppauge remotes ( black / silver ) always use
* specific device ids . If we do not filter the
* device ids then messages destined for devices
* such as TVs ( id = 0 ) will get through causing
* mis - fired events .
*
* We also filter out invalid key presses which
* produce annoying debug log entries .
*/
ircode = ( start < < 12 ) | ( toggle < < 11 ) | ( dev < < 6 ) | code ;
if ( ( ircode & 0x1fff ) = = 0x1fff )
/* invalid key press */
return 0 ;
2006-09-26 16:39:00 -03:00
if ( ! range )
code + = 64 ;
dprintk ( 1 , " ir hauppauge (rc5): s%d r%d t%d dev=%d code=%d \n " ,
start , range , toggle , dev , code ) ;
2005-04-16 15:20:36 -07:00
/* return key */
2011-03-04 17:30:17 -03:00
* ir_key = ( dev < < 8 ) | code ;
2008-09-22 00:54:59 -03:00
* ir_raw = ircode ;
2005-04-16 15:20:36 -07:00
return 1 ;
}
2009-07-28 11:41:35 -03:00
static int get_key_haup ( struct IR_i2c * ir , u32 * ir_key , u32 * ir_raw )
2007-06-25 14:34:06 -03:00
{
return get_key_haup_common ( ir , ir_key , ir_raw , 3 , 0 ) ;
}
2009-07-28 11:41:35 -03:00
static int get_key_haup_xvr ( struct IR_i2c * ir , u32 * ir_key , u32 * ir_raw )
2007-06-25 14:34:06 -03:00
{
2011-01-20 18:31:18 -03:00
int ret ;
unsigned char buf [ 1 ] = { 0 } ;
/*
* This is the same apparent " are you ready? " poll command observed
* watching Windows driver traffic and implemented in lirc_zilog . With
* this added , we get far saner remote behavior with z8 chips on usb
* connected devices , even with the default polling interval of 100 ms .
*/
ret = i2c_master_send ( ir - > c , buf , 1 ) ;
if ( ret ! = 1 )
return ( ret < 0 ) ? ret : - EINVAL ;
2007-06-25 14:34:06 -03:00
return get_key_haup_common ( ir , ir_key , ir_raw , 6 , 3 ) ;
}
2005-11-08 21:37:32 -08:00
static int get_key_pixelview ( struct IR_i2c * ir , u32 * ir_key , u32 * ir_raw )
2005-04-16 15:20:36 -07:00
{
2005-11-08 21:37:43 -08:00
unsigned char b ;
2005-04-16 15:20:36 -07:00
/* poll IR chip */
2009-05-13 16:48:50 -03:00
if ( 1 ! = i2c_master_recv ( ir - > c , & b , 1 ) ) {
2005-04-16 15:20:36 -07:00
dprintk ( 1 , " read error \n " ) ;
return - EIO ;
}
* ir_key = b ;
* ir_raw = b ;
return 1 ;
}
2007-08-24 01:07:12 -03:00
static int get_key_fusionhdtv ( struct IR_i2c * ir , u32 * ir_key , u32 * ir_raw )
2007-08-24 01:02:32 -03:00
{
unsigned char buf [ 4 ] ;
/* poll IR chip */
2009-05-13 16:48:50 -03:00
if ( 4 ! = i2c_master_recv ( ir - > c , buf , 4 ) ) {
2007-08-24 01:02:32 -03:00
dprintk ( 1 , " read error \n " ) ;
return - EIO ;
}
if ( buf [ 0 ] ! = 0 | | buf [ 1 ] ! = 0 | | buf [ 2 ] ! = 0 | | buf [ 3 ] ! = 0 )
2008-04-08 23:20:00 -03:00
dprintk ( 2 , " %s: 0x%2x 0x%2x 0x%2x 0x%2x \n " , __func__ ,
2007-08-24 01:02:32 -03:00
buf [ 0 ] , buf [ 1 ] , buf [ 2 ] , buf [ 3 ] ) ;
/* no key pressed or signal from other ir remote */
if ( buf [ 0 ] ! = 0x1 | | buf [ 1 ] ! = 0xfe )
return 0 ;
* ir_key = buf [ 2 ] ;
* ir_raw = ( buf [ 2 ] < < 8 ) | buf [ 3 ] ;
return 1 ;
}
2005-11-08 21:37:32 -08:00
static int get_key_knc1 ( struct IR_i2c * ir , u32 * ir_key , u32 * ir_raw )
2005-04-16 15:20:36 -07:00
{
unsigned char b ;
/* poll IR chip */
2009-05-13 16:48:50 -03:00
if ( 1 ! = i2c_master_recv ( ir - > c , & b , 1 ) ) {
2005-04-16 15:20:36 -07:00
dprintk ( 1 , " read error \n " ) ;
return - EIO ;
}
/* it seems that 0xFE indicates that a button is still hold
2005-11-08 21:37:36 -08:00
down , while 0xff indicates that no button is hold
down . 0xfe sequences are sometimes interrupted by 0xFF */
2005-04-16 15:20:36 -07:00
dprintk ( 2 , " key %02x \n " , b ) ;
2005-11-08 21:37:36 -08:00
if ( b = = 0xff )
2005-04-16 15:20:36 -07:00
return 0 ;
2005-11-08 21:37:36 -08:00
if ( b = = 0xfe )
2005-04-16 15:20:36 -07:00
/* keep old data */
return 1 ;
* ir_key = b ;
* ir_raw = b ;
return 1 ;
}
2009-02-12 03:43:11 -03:00
static int get_key_avermedia_cardbus ( struct IR_i2c * ir ,
u32 * ir_key , u32 * ir_raw )
{
unsigned char subaddr , key , keygroup ;
2009-05-13 16:48:50 -03:00
struct i2c_msg msg [ ] = { { . addr = ir - > c - > addr , . flags = 0 ,
2009-02-12 03:43:11 -03:00
. buf = & subaddr , . len = 1 } ,
2009-05-13 16:48:50 -03:00
{ . addr = ir - > c - > addr , . flags = I2C_M_RD ,
2009-02-12 03:43:11 -03:00
. buf = & key , . len = 1 } } ;
subaddr = 0x0d ;
2009-05-13 16:48:50 -03:00
if ( 2 ! = i2c_transfer ( ir - > c - > adapter , msg , 2 ) ) {
2009-02-12 03:43:11 -03:00
dprintk ( 1 , " read error \n " ) ;
return - EIO ;
}
if ( key = = 0xff )
return 0 ;
subaddr = 0x0b ;
msg [ 1 ] . buf = & keygroup ;
2009-05-13 16:48:50 -03:00
if ( 2 ! = i2c_transfer ( ir - > c - > adapter , msg , 2 ) ) {
2009-02-12 03:43:11 -03:00
dprintk ( 1 , " read error \n " ) ;
return - EIO ;
}
if ( keygroup = = 0xff )
return 0 ;
dprintk ( 1 , " read key 0x%02x/0x%02x \n " , key , keygroup ) ;
if ( keygroup < 2 | | keygroup > 3 ) {
/* Only a warning */
dprintk ( 1 , " warning: invalid key group 0x%02x for key 0x%02x \n " ,
keygroup , key ) ;
}
key | = ( keygroup & 1 ) < < 6 ;
* ir_key = key ;
* ir_raw = key ;
return 1 ;
}
2005-04-16 15:20:36 -07:00
/* ----------------------------------------------------------------------- */
2012-01-09 22:28:13 -02:00
static int ir_key_poll ( struct IR_i2c * ir )
2005-04-16 15:20:36 -07:00
{
static u32 ir_key , ir_raw ;
int rc ;
2011-01-12 13:50:01 -03:00
dprintk ( 3 , " %s \n " , __func__ ) ;
2005-04-16 15:20:36 -07:00
rc = ir - > get_key ( ir , & ir_key , & ir_raw ) ;
if ( rc < 0 ) {
dprintk ( 2 , " error \n " ) ;
2012-01-09 22:28:13 -02:00
return rc ;
2005-04-16 15:20:36 -07:00
}
2011-01-12 13:50:01 -03:00
if ( rc ) {
dprintk ( 1 , " %s: keycode = 0x%04x \n " , __func__ , ir_key ) ;
2010-11-17 13:53:11 -03:00
rc_keydown ( ir - > rc , ir_key , 0 ) ;
2011-01-12 13:50:01 -03:00
}
2012-01-09 22:28:13 -02:00
return 0 ;
2005-04-16 15:20:36 -07:00
}
2006-11-22 14:57:56 +00:00
static void ir_work ( struct work_struct * work )
2005-04-16 15:20:36 -07:00
{
2012-01-09 22:28:13 -02:00
int rc ;
2009-03-07 07:43:43 -03:00
struct IR_i2c * ir = container_of ( work , struct IR_i2c , work . work ) ;
2007-05-21 11:51:11 -03:00
2012-01-09 22:28:13 -02:00
rc = ir_key_poll ( ir ) ;
if ( rc = = - ENODEV ) {
rc_unregister_device ( ir - > rc ) ;
ir - > rc = NULL ;
return ;
}
2010-09-23 01:23:10 -03:00
schedule_delayed_work ( & ir - > work , msecs_to_jiffies ( ir - > polling_interval ) ) ;
2005-04-16 15:20:36 -07:00
}
/* ----------------------------------------------------------------------- */
2009-05-13 16:48:50 -03:00
static int ir_probe ( struct i2c_client * client , const struct i2c_device_id * id )
2005-04-16 15:20:36 -07:00
{
2010-04-02 20:01:00 -03:00
char * ir_codes = NULL ;
2009-05-13 16:50:11 -03:00
const char * name = NULL ;
2010-11-17 14:20:52 -03:00
u64 rc_type = RC_TYPE_UNKNOWN ;
2005-12-12 00:37:27 -08:00
struct IR_i2c * ir ;
2010-11-12 09:02:40 -03:00
struct rc_dev * rc = NULL ;
2009-05-13 16:48:50 -03:00
struct i2c_adapter * adap = client - > adapter ;
unsigned short addr = client - > addr ;
2006-11-20 10:23:04 -03:00
int err ;
2005-04-16 15:20:36 -07:00
2010-11-12 09:02:40 -03:00
ir = kzalloc ( sizeof ( struct IR_i2c ) , GFP_KERNEL ) ;
if ( ! ir )
return - ENOMEM ;
2005-09-15 02:01:53 -05:00
2009-05-13 16:48:50 -03:00
ir - > c = client ;
2010-09-23 01:23:10 -03:00
ir - > polling_interval = DEFAULT_POLLING_INTERVAL ;
2009-05-13 16:48:50 -03:00
i2c_set_clientdata ( client , ir ) ;
2006-01-09 15:25:21 -02:00
2005-04-16 15:20:36 -07:00
switch ( addr ) {
case 0x64 :
name = " Pixelview " ;
ir - > get_key = get_key_pixelview ;
2010-11-17 14:20:52 -03:00
rc_type = RC_TYPE_OTHER ;
2010-04-02 20:01:00 -03:00
ir_codes = RC_MAP_EMPTY ;
2005-04-16 15:20:36 -07:00
break ;
case 0x18 :
2009-11-13 05:48:24 -03:00
case 0x1f :
2005-04-16 15:20:36 -07:00
case 0x1a :
name = " Hauppauge " ;
ir - > get_key = get_key_haup ;
2010-11-17 14:20:52 -03:00
rc_type = RC_TYPE_RC5 ;
2011-01-24 22:23:08 -03:00
ir_codes = RC_MAP_HAUPPAUGE ;
2005-04-16 15:20:36 -07:00
break ;
case 0x30 :
2005-11-08 21:37:32 -08:00
name = " KNC One " ;
ir - > get_key = get_key_knc1 ;
2010-11-17 14:20:52 -03:00
rc_type = RC_TYPE_OTHER ;
2010-04-02 20:01:00 -03:00
ir_codes = RC_MAP_EMPTY ;
2005-04-16 15:20:36 -07:00
break ;
2007-08-24 01:02:32 -03:00
case 0x6b :
2007-08-24 01:07:12 -03:00
name = " FusionHDTV " ;
ir - > get_key = get_key_fusionhdtv ;
2010-11-17 14:20:52 -03:00
rc_type = RC_TYPE_RC5 ;
2010-04-02 20:01:00 -03:00
ir_codes = RC_MAP_FUSIONHDTV_MCE ;
2007-08-24 01:02:32 -03:00
break ;
2009-02-12 03:43:11 -03:00
case 0x40 :
name = " AVerMedia Cardbus remote " ;
ir - > get_key = get_key_avermedia_cardbus ;
2010-11-17 14:20:52 -03:00
rc_type = RC_TYPE_OTHER ;
2010-04-02 20:01:00 -03:00
ir_codes = RC_MAP_AVERMEDIA_CARDBUS ;
2009-02-12 03:43:11 -03:00
break ;
2011-01-16 15:45:32 -03:00
case 0x71 :
name = " Hauppauge/Zilog Z8 " ;
ir - > get_key = get_key_haup_xvr ;
rc_type = RC_TYPE_RC5 ;
2011-01-24 22:23:08 -03:00
ir_codes = RC_MAP_HAUPPAUGE ;
2011-01-16 15:45:32 -03:00
break ;
2005-04-16 15:20:36 -07:00
}
2009-05-13 16:49:32 -03:00
/* Let the caller override settings */
if ( client - > dev . platform_data ) {
const struct IR_i2c_init_data * init_data =
client - > dev . platform_data ;
ir_codes = init_data - > ir_codes ;
2010-11-12 09:02:40 -03:00
rc = init_data - > rc_dev ;
2009-05-13 16:49:32 -03:00
name = init_data - > name ;
2009-07-28 11:44:05 -03:00
if ( init_data - > type )
2010-11-17 14:20:52 -03:00
rc_type = init_data - > type ;
2009-07-28 11:44:05 -03:00
2010-09-23 01:23:10 -03:00
if ( init_data - > polling_interval )
ir - > polling_interval = init_data - > polling_interval ;
2009-07-28 11:44:05 -03:00
switch ( init_data - > internal_get_key_func ) {
case IR_KBD_GET_KEY_CUSTOM :
/* The bridge driver provided us its own function */
ir - > get_key = init_data - > get_key ;
break ;
case IR_KBD_GET_KEY_PIXELVIEW :
ir - > get_key = get_key_pixelview ;
break ;
case IR_KBD_GET_KEY_HAUP :
ir - > get_key = get_key_haup ;
break ;
case IR_KBD_GET_KEY_KNC1 :
ir - > get_key = get_key_knc1 ;
break ;
case IR_KBD_GET_KEY_FUSIONHDTV :
ir - > get_key = get_key_fusionhdtv ;
break ;
case IR_KBD_GET_KEY_HAUP_XVR :
ir - > get_key = get_key_haup_xvr ;
break ;
case IR_KBD_GET_KEY_AVERMEDIA_CARDBUS :
ir - > get_key = get_key_avermedia_cardbus ;
break ;
}
2009-05-13 16:49:32 -03:00
}
2010-11-12 09:02:40 -03:00
if ( ! rc ) {
/*
* If platform_data doesn ' t specify rc_dev , initilize it
* internally
*/
rc = rc_allocate_device ( ) ;
if ( ! rc ) {
err = - ENOMEM ;
goto err_out_free ;
}
}
ir - > rc = rc ;
2009-05-13 16:50:11 -03:00
/* Make sure we are all setup before going on */
2010-11-17 14:20:52 -03:00
if ( ! name | | ! ir - > get_key | | ! rc_type | | ! ir_codes ) {
2009-11-24 12:06:52 -03:00
dprintk ( 1 , " : Unsupported device at address 0x%02x \n " ,
2009-05-13 16:50:11 -03:00
addr ) ;
err = - ENODEV ;
goto err_out_free ;
}
2005-11-08 21:37:56 -08:00
/* Sets name */
2009-05-13 16:48:07 -03:00
snprintf ( ir - > name , sizeof ( ir - > name ) , " i2c IR (%s) " , name ) ;
2006-11-20 10:23:04 -03:00
ir - > ir_codes = ir_codes ;
2005-11-08 21:37:32 -08:00
2005-11-08 21:37:56 -08:00
snprintf ( ir - > phys , sizeof ( ir - > phys ) , " %s/%s/ir0 " ,
2009-05-13 16:48:50 -03:00
dev_name ( & adap - > dev ) ,
dev_name ( & client - > dev ) ) ;
2005-11-08 21:37:56 -08:00
2010-11-12 09:02:40 -03:00
/*
* Initialize input_dev fields
* It doesn ' t make sense to allow overriding them via platform_data
*/
2010-10-29 16:08:23 -03:00
rc - > input_id . bustype = BUS_I2C ;
rc - > input_phys = ir - > phys ;
2010-11-12 09:02:40 -03:00
rc - > input_name = ir - > name ;
/*
* Initialize the other fields of rc_dev
*/
rc - > map_name = ir - > ir_codes ;
2010-11-17 14:20:52 -03:00
rc - > allowed_protos = rc_type ;
2010-11-12 09:02:40 -03:00
if ( ! rc - > driver_name )
rc - > driver_name = MODULE_NAME ;
2005-09-15 02:01:53 -05:00
2010-10-29 16:08:23 -03:00
err = rc_register_device ( rc ) ;
2006-11-20 10:23:04 -03:00
if ( err )
2009-05-13 16:48:50 -03:00
goto err_out_free ;
2006-11-20 10:23:04 -03:00
2010-03-12 21:18:14 -03:00
printk ( MODULE_NAME " : %s detected at %s [%s] \n " ,
2010-10-29 16:08:23 -03:00
ir - > name , ir - > phys , adap - > name ) ;
2005-04-16 15:20:36 -07:00
/* start polling via eventd */
2009-03-07 07:43:43 -03:00
INIT_DELAYED_WORK ( & ir - > work , ir_work ) ;
schedule_delayed_work ( & ir - > work , 0 ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
2006-11-20 10:23:04 -03:00
err_out_free :
2010-11-12 09:02:40 -03:00
/* Only frees rc if it were allocated internally */
2010-10-29 16:08:23 -03:00
rc_free_device ( rc ) ;
2006-11-20 10:23:04 -03:00
kfree ( ir ) ;
return err ;
2005-04-16 15:20:36 -07:00
}
2009-05-13 16:48:50 -03:00
static int ir_remove ( struct i2c_client * client )
2005-04-16 15:20:36 -07:00
{
2005-11-08 21:37:43 -08:00
struct IR_i2c * ir = i2c_get_clientdata ( client ) ;
2005-04-16 15:20:36 -07:00
/* kill outstanding polls */
2009-03-07 07:43:43 -03:00
cancel_delayed_work_sync ( & ir - > work ) ;
2005-04-16 15:20:36 -07:00
2009-05-13 16:48:50 -03:00
/* unregister device */
2012-01-09 22:28:13 -02:00
if ( ir - > rc )
rc_unregister_device ( ir - > rc ) ;
2005-04-16 15:20:36 -07:00
/* free memory */
kfree ( ir ) ;
return 0 ;
}
2009-05-13 16:48:50 -03:00
static const struct i2c_device_id ir_kbd_id [ ] = {
/* Generic entry for any IR receiver */
{ " ir_video " , 0 } ,
2009-07-28 11:50:14 -03:00
/* IR device specific entries should be added here */
{ " ir_rx_z8f0811_haup " , 0 } ,
2010-12-28 22:47:46 -03:00
{ " ir_rx_z8f0811_hdpvr " , 0 } ,
2009-05-13 16:48:50 -03:00
{ }
} ;
2009-02-12 03:43:11 -03:00
2012-02-12 06:56:32 -03:00
static struct i2c_driver ir_kbd_driver = {
2009-05-13 16:48:50 -03:00
. driver = {
. name = " ir-kbd-i2c " ,
} ,
. probe = ir_probe ,
. remove = ir_remove ,
. id_table = ir_kbd_id ,
} ;
2005-04-16 15:20:36 -07:00
2012-02-12 06:56:32 -03:00
module_i2c_driver ( ir_kbd_driver ) ;
2005-04-16 15:20:36 -07:00
/* ----------------------------------------------------------------------- */
MODULE_AUTHOR ( " Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, Ulrich Mueller " ) ;
MODULE_DESCRIPTION ( " input driver for i2c IR remote controls " ) ;
MODULE_LICENSE ( " GPL " ) ;