@ -13,16 +13,15 @@
# include <linux/delay.h>
# include <media/tuner.h>
# include <linux/mutex.h>
# include "tuner-driver .h"
# include "tuner-i2c .h"
# include "tuner-xc2028.h"
# include <linux/dvb/frontend.h>
# include "dvb_frontend.h"
/* digital TV standards */
# define V4L2_STD_DTV_6MHZ ((v4l2_std_id)0x04000000)
# define V4L2_STD_DTV_7MHZ ((v4l2_std_id)0x08000000)
# define V4L2_STD_DTV_8MHZ ((v4l2_std_id)0x10000000)
# define PREFIX "xc2028 "
static LIST_HEAD ( xc2028_list ) ;
/* Firmwares used on tm5600/tm6000 + xc2028/xc3028 */
@ -40,6 +39,15 @@ static const char *firmware_DK = "tm_xc3028_DK_PAL_MTS.fw";
static const char * firmware_MN = " tm_xc3028_MN_BTSC.fw " ;
struct xc2028_data {
struct list_head xc2028_list ;
struct tuner_i2c_props i2c_props ;
int ( * tuner_callback ) ( void * dev ,
int command , int arg ) ;
struct device * dev ;
void * video_dev ;
int count ;
u32 frequency ;
v4l2_std_id firm_type ; /* video stds supported
by current firmware */
fe_bandwidth_t bandwidth ; /* Firmware bandwidth:
@ -48,61 +56,64 @@ struct xc2028_data {
were loaded ? */
enum tuner_mode mode ;
struct i2c_client * i2c_client ;
struct mutex lock ;
struct mutex lock ;
} ;
# define i2c_send(rc,c,buf,size) \
if ( size ! = ( rc = i2c_mast er_send( c , buf , size ) ) ) \
tuner_warn ( " i2c output error: rc = %d (should be %d) \n " , \
# define i2c_send(rc, priv, buf, size) \
if ( size ! = ( rc = tuner_i2c_xf er_send( & priv - > i2c_props , buf , size ) ) ) \
tuner_info ( " i2c output error: rc = %d (should be %d) \n " , \
rc , ( int ) size ) ;
# define i2c_rcv(rc,c,buf,size) \
if ( size ! = ( rc = i2c_mast er_recv( c , buf , size ) ) ) \
tuner_warn ( " i2c input error: rc = %d (should be %d) \n " , \
# define i2c_rcv(rc, priv, buf, size) \
if ( size ! = ( rc = tuner_i2c_xf er_recv( & priv - > i2c_props , buf , size ) ) ) \
tuner_info ( " i2c input error: rc = %d (should be %d) \n " , \
rc , ( int ) size ) ;
# define send_seq(c , data...) \
# define send_seq(priv , data...) \
{ int rc ; \
const static u8 _val [ ] = data ; \
static u8 _val [ ] = data ; \
if ( sizeof ( _val ) ! = \
( rc = i2c_master_send \
( c , _val , sizeof ( _val ) ) ) ) { \
printk ( KERN_ERR " Error on line %d: %d \n " , __LINE__ , rc ) ; \
return ; \
( rc = tuner_i2c_xfer_send ( & priv - > i2c_props , \
_val , sizeof ( _val ) ) ) ) { \
tuner_info ( " Error on line %d: %d \n " , __LINE__ , rc ) ; \
return - EINVAL ; \
} \
msleep ( 10 ) ; \
}
static int xc2028_get_reg ( struct i2c_client * c , u16 reg )
static int xc2028_get_reg ( struct xc2028_data * priv , u16 reg )
{
int rc ;
unsigned char buf [ 1 ] ;
struct tuner * t = i2c_get_clientdata ( c ) ;
tuner_info ( " %s called \n " , __FUNCTION__ ) ;
buf [ 0 ] = reg ;
i2c_send ( rc , c , buf , sizeof ( buf ) ) ;
i2c_send ( rc , priv , buf , sizeof ( buf ) ) ;
if ( rc < 0 )
return rc ;
i2c_rcv ( rc , c , buf , 2 ) ;
i2c_rcv ( rc , priv , buf , 2 ) ;
if ( rc < 0 )
return rc ;
return ( buf [ 1 ] ) | ( buf [ 0 ] < < 8 ) ;
}
static int load_firmware ( struct i2c_client * c , const char * name )
static int load_firmware ( struct dvb_frontend * fe , const char * name )
{
struct xc2028_data * priv = fe - > tuner_priv ;
const struct firmware * fw = NULL ;
struct tuner * t = i2c_get_clientdata ( c ) ;
unsigned char * p , * endp ;
int len = 0 , rc = 0 ;
static const char firmware_ver [ ] = " tm6000/xcv v1 " ;
tuner_info ( " xc2028: Loading firmware %s \n " , name ) ;
rc = request_firmware ( & fw , name , & c - > dev ) ;
tuner_info ( " %s called \n " , __FUNCTION__ ) ;
tuner_info ( " Loading firmware %s \n " , name ) ;
rc = request_firmware ( & fw , name , priv - > dev ) ;
if ( rc < 0 ) {
if ( rc = = - ENOENT )
tuner_info ( " Error: firmware %s not found. \n " , name ) ;
@ -138,7 +149,7 @@ static int load_firmware (struct i2c_client *c, const char *name)
while ( p < endp ) {
if ( ( * p ) & 0x80 ) {
/* Special callback command received */
rc = t - > tuner_callback ( c - > adapter - > algo_data ,
rc = priv - > tuner_callback ( priv - > video_dev ,
XC2028_TUNER_RESET , ( * p ) & 0x7f ) ;
if ( rc < 0 ) {
tuner_info ( " Error at RESET code %d \n " ,
@ -162,7 +173,7 @@ static int load_firmware (struct i2c_client *c, const char *name)
goto err ;
}
i2c_send ( rc , c , p , len ) ;
i2c_send ( rc , priv , p , len ) ;
if ( rc < 0 )
goto err ;
p + = len ;
@ -179,171 +190,173 @@ err:
return rc ;
}
static int check_firmware ( struct i2c_client * c , enum tuner_mode new_mode ,
static int check_firmware ( struct dvb_frontend * fe , enum tuner_mode new_mode ,
v4l2_std_id std ,
fe_bandwidth_t bandwidth )
{
struct xc2028_data * priv = fe - > tuner_priv ;
int rc , version ;
struct tuner * t = i2c_get_clientdata ( c ) ;
struct xc2028_data * xc2028 = t - > priv ;
const char * name ;
int change_digital_bandwidth ;
if ( ! t - > tuner_callback ) {
printk ( KERN_ERR " xc2028: need tuner_callback to load firmware \n " ) ;
return - EINVAL ;
}
tuner_info ( " %s called \n " , __FUNCTION__ ) ;
printk ( KERN_INFO " xc2028: I am in mode %u and I should switch to mode %i\n " ,
xc2028 - > mode , new_mode ) ;
tuner_info ( " I am in mode %u and I should switch to mode %i \n " ,
priv - > mode , new_mode ) ;
/* first of all, determine whether we have switched the mode */
if ( new_mode ! = xc2028 - > mode ) {
xc2028 - > mode = new_mode ;
xc2028 - > need_load_generic = 1 ;
if ( new_mode ! = priv - > mode ) {
priv - > mode = new_mode ;
priv - > need_load_generic = 1 ;
}
change_digital_bandwidth = ( xc2028 - > mode = = T_DIGITAL_TV
& & bandwidth ! = xc2028 - > bandwidth ) ? 1 : 0 ;
tuner_info ( " xc2028: old bandwidth %u, new bandwidth %u\n " , xc2028 - > bandwidth ,
change_digital_bandwidth = ( priv - > mode = = T_DIGITAL_TV
& & bandwidth ! = priv - > bandwidth ) ? 1 : 0 ;
tuner_info ( " old bandwidth %u, new bandwidth %u \n " , priv - > bandwidth ,
bandwidth ) ;
if ( xc2028 - > need_load_generic ) {
if ( xc2028 - > bandwidth = = 8 )
if ( priv - > need_load_generic ) {
if ( priv - > bandwidth = = 8 )
name = firmware_8MHZ_INIT0 ;
else
name = firmware_INIT0 ;
/* Reset is needed before loading firmware */
rc = t - > tuner_callback ( c - > adapter - > algo_data ,
XC2028_TUNER_RESET , 0 ) ;
rc = priv - > tuner_callback ( priv - > video_dev ,
XC2028_TUNER_RESET , 0 ) ;
if ( rc < 0 )
return rc ;
rc = load_firmware ( c , name ) ;
rc = load_firmware ( fe , name ) ;
if ( rc < 0 )
return rc ;
xc2028 - > need_load_generic = 0 ;
xc2028 - > firm_type = 0 ;
if ( xc2028 - > mode = = T_DIGITAL_TV ) {
priv - > need_load_generic = 0 ;
priv - > firm_type = 0 ;
if ( priv - > mode = = T_DIGITAL_TV ) {
change_digital_bandwidth = 1 ;
}
}
tuner_info ( " xc2028: I should change bandwidth %u\n " ,
tuner_info ( " I should change bandwidth %u \n " ,
change_digital_bandwidth ) ;
/* FIXME: t->std makes no sense here */
if ( change_digital_bandwidth ) {
switch ( bandwidth ) {
case BANDWIDTH_8_MHZ :
t - > std = V4L2_STD_DTV_8MHZ ;
std = V4L2_STD_DTV_8MHZ ;
break ;
case BANDWIDTH_7_MHZ :
t - > std = V4L2_STD_DTV_7MHZ ;
std = V4L2_STD_DTV_7MHZ ;
break ;
case BANDWIDTH_6_MHZ :
t - > std = V4L2_STD_DTV_6MHZ ;
std = V4L2_STD_DTV_6MHZ ;
break ;
default :
tuner_info ( " error: bandwidth not supported. \n " ) ;
} ;
xc2028 - > bandwidth = bandwidth ;
priv - > bandwidth = bandwidth ;
}
if ( xc2028 - > firm_type & t - > std ) {
if ( priv - > firm_type & std ) {
tuner_info ( " xc3028: no need to load a std-specific firmware. \n " ) ;
return 0 ;
}
rc = load_firmware ( c , firmware_INIT1 ) ;
rc = load_firmware ( fe , firmware_INIT1 ) ;
if ( t - > std & V4L2_STD_MN )
if ( std & V4L2_STD_MN )
name = firmware_MN ;
else if ( t - > std & V4L2_STD_DTV_6MHZ )
else if ( std & V4L2_STD_DTV_6MHZ )
name = firmware_6M ;
else if ( t - > std & V4L2_STD_DTV_7MHZ )
else if ( std & V4L2_STD_DTV_7MHZ )
name = firmware_7M ;
else if ( t - > std & V4L2_STD_DTV_8MHZ )
else if ( std & V4L2_STD_DTV_8MHZ )
name = firmware_8M ;
else if ( t - > std & V4L2_STD_PAL_B )
else if ( std & V4L2_STD_PAL_B )
name = firmware_B ;
else
name = firmware_DK ;
tuner_info ( " xc2028: loading firmware named %s.\n " , name ) ;
rc = load_firmware ( c , name ) ;
tuner_info ( " loading firmware named %s. \n " , name ) ;
rc = load_firmware ( fe , name ) ;
if ( rc < 0 )
return rc ;
version = xc2028_get_reg ( c , 0x4 ) ;
version = xc2028_get_reg ( priv , 0x4 ) ;
tuner_info ( " Firmware version is %d.%d \n " ,
( version > > 4 ) & 0x0f , ( version ) & 0x0f ) ;
xc2028 - > firm_type = t - > std ;
priv - > firm_type = std ;
return 0 ;
}
static int xc2028_signal ( struct i2c_client * c )
static int xc2028_signal ( struct dvb_frontend * fe , u16 * strength )
{
struct tuner * t = i2c_get_clientdata ( c ) ;
struct xc2028_data * xc2028 = t - > priv ;
struct xc2028_data * priv = fe - > tuner_priv ;
int frq_lock , signal = 0 ;
mutex_lock ( & xc2028 - > lock ) ;
tuner_info ( " %s called \n " , __FUNCTION__ ) ;
printk ( KERN_INFO " xc2028: %s called \n " , __FUNCTION__ ) ;
mutex_lock ( & priv - > lock ) ;
frq_lock = xc2028_get_reg ( c , 0x2 ) ;
* strength = 0 ;
frq_lock = xc2028_get_reg ( priv , 0x2 ) ;
if ( frq_lock < = 0 )
goto ret ;
/* Frequency is locked. Return signal quality */
signal = xc2028_get_reg ( c , 0x40 ) ;
signal = xc2028_get_reg ( priv , 0x40 ) ;
if ( signal < = 0 ) {
signal = frq_lock ;
}
ret :
mutex_unlock ( & xc2028 - > lock ) ;
mutex_unlock ( & priv - > lock ) ;
* strength = signal ;
return signal ;
return 0 ;
}
# define DIV 15625
static void generic_set_tv_freq ( struct i2c_client * c , u32 freq /* in Hz */ ,
enum tuner_mode new_mode , fe_bandwidth_t bandwidth )
static int generic_set_tv_freq ( struct dvb_frontend * fe , u32 freq /* in Hz */ ,
enum tuner_mode new_mode ,
v4l2_std_id std ,
fe_bandwidth_t bandwidth )
{
int rc ;
struct xc2028_data * priv = fe - > tuner_priv ;
int rc = - EINVAL ;
unsigned char buf [ 5 ] ;
struct tuner * t = i2c_get_clientdata ( c ) ;
u32 div , offset = 0 ;
tuner_info ( " %s called \n " , __FUNCTION__ ) ;
/* HACK: It seems that specific firmware need to be reloaded
when freq is changed */
struct xc2028_data * xc2028 = t - > priv ;
mutex_lock ( & xc2028 - > lock ) ;
mutex_lock ( & priv - > lock ) ;
xc2028 - > firm_type = 0 ;
priv - > firm_type = 0 ;
/* Reset GPIO 1 */
if ( t - > tuner_callback ) {
rc = t - > tuner_callback ( c - > adapter - > algo_data ,
XC2028_TUNER_RESET , 0 ) ;
if ( rc < 0 )
goto ret ;
}
rc = priv - > tuner_callback ( priv - > video_dev , XC2028_TUNER_RESET , 0 ) ;
if ( rc < 0 )
goto ret ;
msleep ( 10 ) ;
printk ( " xc3028: should set frequency %d kHz)\n " , freq / 1000 ) ;
tuner_info ( " should set frequency %d kHz) \n " , freq / 1000 ) ;
if ( check_firmware ( c , new_mode , bandwidth ) < 0 )
if ( check_firmware ( fe , new_mode , std , bandwidth ) < 0 )
goto ret ;
if ( new_mode = = T_DIGITAL_TV )
@ -352,13 +365,10 @@ static void generic_set_tv_freq(struct i2c_client *c, u32 freq /* in Hz */,
div = ( freq - offset + DIV / 2 ) / DIV ;
/* CMD= Set frequency */
send_seq ( c , { 0x00 , 0x02 , 0x00 , 0x00 } ) ;
if ( t - > tuner_callback ) {
rc = t - > tuner_callback ( c - > adapter - > algo_data ,
XC2028_RESET_CLK , 1 ) ;
if ( rc < 0 )
goto ret ;
}
send_seq ( priv , { 0x00 , 0x02 , 0x00 , 0x00 } ) ;
rc = priv - > tuner_callback ( priv - > video_dev , XC2028_RESET_CLK , 1 ) ;
if ( rc < 0 )
goto ret ;
msleep ( 10 ) ;
@ -368,121 +378,82 @@ static void generic_set_tv_freq(struct i2c_client *c, u32 freq /* in Hz */,
buf [ 3 ] = 0xff & ( div ) ;
buf [ 4 ] = 0 ;
i2c_send ( rc , c , buf , sizeof ( buf ) ) ;
i2c_send ( rc , priv , buf , sizeof ( buf ) ) ;
if ( rc < 0 )
goto ret ;
msleep ( 100 ) ;
priv - > frequency = freq ;
printk ( " divider= %02x %02x %02x %02x (freq=%d.%02d) \n " ,
buf [ 1 ] , buf [ 2 ] , buf [ 3 ] , buf [ 4 ] ,
freq / 16 , freq % 16 * 100 / 16 ) ;
freq / 1000000 , ( freq % 1000000 ) / 10000 ) ;
ret :
mutex_unlock ( & xc2028 - > lock ) ;
}
rc = 0 ;
ret :
mutex_unlock ( & priv - > lock ) ;
static void set_tv_freq ( struct i2c_client * c , unsigned int freq )
{
printk ( KERN_INFO " xc2028: %s called \n " , __FUNCTION__ ) ;
generic_set_tv_freq ( c , freq * 62500l , T_ANALOG_TV ,
BANDWIDTH_8_MHZ /* unimportant */ ) ;
return rc ;
}
static void xc2028_release ( struct i2c_client * c )
static int xc2028_set_tv_freq ( struct dvb_frontend * fe ,
struct analog_parameters * p )
{
struct tuner * t = i2c_get_clientdata ( c ) ;
struct xc2028_data * priv = fe - > tuner_priv ;
kfree ( t - > priv ) ;
t - > priv = NULL ;
}
static struct tuner_operations tea5767_tuner_ops = {
. set_tv_freq = set_tv_freq ,
. has_signal = xc2028_signal ,
. release = xc2028_release ,
// .is_stereo = xc2028_stereo,
} ;
tuner_info ( " %s called \n " , __FUNCTION__ ) ;
return generic_set_tv_freq ( fe , 62500l * p - > frequency , T_ANALOG_TV ,
p - > std ,
BANDWIDTH_8_MHZ /* NOT USED */ ) ;
}
static int init = 0 ;
int xc2028_tuner_init ( struct i2c_client * c )
static int xc2028_set_params ( struct dvb_frontend * fe ,
struct dvb_frontend_parameters * p )
{
struct tuner * t = i2c_get_clientdata ( c ) ;
int version = xc2028_get_reg ( c , 0x4 ) ;
int prd_id = xc2028_get_reg ( c , 0x8 ) ;
struct xc2028_data * xc2028 ;
struct xc2028_data * priv = fe - > tuner_priv ;
tuner_info ( " Xcv2028/3028 init called! \n " ) ;
tuner_info ( " %s called \n " , __FUNCTION__ ) ;
if ( init ) {
printk ( KERN_ERR " Module already initialized! \n " ) ;
return 0 ;
/* FIXME: Only OFDM implemented */
if ( fe - > ops . info . type ! = FE_OFDM ) {
tuner_info ( " DTV type not implemented. \n " ) ;
return - EINVAL ;
}
init + + ;
xc2028 = kzalloc ( sizeof ( * xc2028 ) , GFP_KERNEL ) ;
if ( ! xc2028 )
return - ENOMEM ;
t - > priv = xc2028 ;
xc2028 - > bandwidth = BANDWIDTH_6_MHZ ;
xc2028 - > need_load_generic = 1 ;
xc2028 - > mode = T_UNINITIALIZED ;
return generic_set_tv_freq ( fe , p - > frequency , T_DIGITAL_TV ,
0 , /* NOT USED */
p - > u . ofdm . bandwidth ) ;
mutex_init ( & xc2028 - > lock ) ;
/* FIXME: Check where t->priv will be freed */
if ( version < 0 )
version = 0 ;
if ( prd_id < 0 )
prd_id = 0 ;
strlcpy ( c - > name , " xc2028 " , sizeof ( c - > name ) ) ;
tuner_info ( " type set to %d (%s, hw ver=%d.%d, fw ver=%d.%d, id=0x%04x) \n " ,
t - > type , c - > name ,
( version > > 12 ) & 0x0f , ( version > > 8 ) & 0x0f ,
( version > > 4 ) & 0x0f , ( version ) & 0x0f , prd_id ) ;
memcpy ( & t - > ops , & tea5767_tuner_ops , sizeof ( struct tuner_operations ) ) ;
return 0 ;
}
static int xc3028_set_params ( struct dvb_frontend * fe ,
struct dvb_frontend_parameters * p )
static int xc2028_dvb_release ( struct dvb_frontend * fe )
{
struct i2c_client * c = fe - > tuner_priv ;
struct xc2028_data * priv = fe - > tuner_priv ;
tuner_info ( " %s called \n " , __FUNCTION__ ) ;
printk ( KERN_INFO " xc2028: %s called \n " , __FUNCTION__ ) ;
priv - > count - - ;
generic_set_tv_freq ( c , p - > frequency , T_DIGITAL_TV ,
p - > u . ofdm . bandwidth ) ;
if ( ! priv - > count )
kfree ( priv ) ;
return 0 ;
}
static int xc3028_dvb_release ( struct dvb_frontend * fe )
static int xc2028_get_frequency ( struct dvb_frontend * fe , u32 * frequency )
{
printk ( KERN_INFO " xc2028: %s called \n " , __FUNCTION__ ) ;
struct xc2028_data * priv = fe - > tuner_priv ;
fe - > tuner_priv = NULL ;
tuner_info ( " %s called \n " , __FUNCTION__ ) ;
return 0 ;
}
static int xc3028_dvb_init ( struct dvb_frontend * fe )
{
printk ( KERN_INFO " xc2028: %s called \n " , __FUNCTION__ ) ;
* frequency = priv - > frequency ;
return 0 ;
}
static const struct dvb_tuner_ops xc3 028_dvb_tuner_ops = {
static const struct dvb_tuner_ops xc2028_dvb_tuner_ops = {
. info = {
. name = " Xceive XC3028 " ,
. frequency_min = 42000000 ,
@ -490,33 +461,74 @@ static const struct dvb_tuner_ops xc3028_dvb_tuner_ops = {
. frequency_step = 50000 ,
} ,
. release = xc3028_dvb_release ,
. init = xc3028_dvb_init ,
. set_analog_params = xc2028_set_tv_freq ,
. release = xc2028_dvb_release ,
. get_frequency = xc2028_get_frequency ,
. get_rf_strength = xc2028_signal ,
. set_params = xc2028_set_params ,
// int (*sleep)(struct dvb_frontend *fe);
/** This is for simple PLLs - set all parameters in one go. */
. set_params = xc3028_set_params ,
/** This is support for demods like the mt352 - fills out the supplied buffer with what to write. */
// int (*calc_regs)(struct dvb_frontend *fe, struct dvb_frontend_parameters *p, u8 *buf, int buf_len);
// int (*get_frequency)(struct dvb_frontend *fe, u32 *frequency);
// int (*get_bandwidth)(struct dvb_frontend *fe, u32 *bandwidth);
// int (*get_status)(struct dvb_frontend *fe, u32 *status);
} ;
int xc2028_attach ( struct i2c_client * c , struct dvb_frontend * fe )
int xc2028_attach ( struct dvb_frontend * fe , struct i2c_adapter * i2c_adap ,
u8 i2c_addr , struct device * dev , void * video_dev ,
int ( * tuner_callback ) ( void * dev , int command , int arg ) )
{
fe - > tuner_priv = c ;
struct xc2028_data * priv ;
memcpy ( & fe - > ops . tuner_ops , & xc3028_dvb_tuner_ops , sizeof ( fe - > ops . tuner_ops ) ) ;
printk ( KERN_INFO PREFIX " Xcv2028/3028 init called! \n " ) ;
return 0 ;
}
if ( NULL = = dev )
return - ENODEV ;
if ( NULL = = video_dev )
return - ENODEV ;
if ( ! tuner_callback ) {
printk ( KERN_ERR PREFIX " No tuner callback! \n " ) ;
return - EINVAL ;
}
list_for_each_entry ( priv , & xc2028_list , xc2028_list ) {
if ( priv - > dev = = dev ) {
dev = NULL ;
priv - > count + + ;
}
}
if ( dev ) {
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( priv = = NULL )
return - ENOMEM ;
fe - > tuner_priv = priv ;
priv - > bandwidth = BANDWIDTH_6_MHZ ;
priv - > need_load_generic = 1 ;
priv - > mode = T_UNINITIALIZED ;
priv - > i2c_props . addr = i2c_addr ;
priv - > i2c_props . adap = i2c_adap ;
priv - > dev = dev ;
priv - > video_dev = video_dev ;
priv - > tuner_callback = tuner_callback ;
mutex_init ( & priv - > lock ) ;
list_add_tail ( & priv - > xc2028_list , & xc2028_list ) ;
}
memcpy ( & fe - > ops . tuner_ops , & xc2028_dvb_tuner_ops ,
sizeof ( xc2028_dvb_tuner_ops ) ) ;
tuner_info ( " type set to %s \n " , " XCeive xc2028/xc3028 tuner " ) ;
return 0 ;
}
EXPORT_SYMBOL ( xc2028_attach ) ;
MODULE_DESCRIPTION ( " Xceive xc2028/xc3028 tuner driver " ) ;
MODULE_AUTHOR ( " Mauro Carvalho Chehab <mchehab@infradead.org> " ) ;
MODULE_LICENSE ( " GPL " ) ;