2005-04-17 02:20:36 +04:00
/*
* cycx_drv . c Cyclom 2 X Support Module .
*
* This module is a library of common hardware specific
* functions used by the Cyclades Cyclom 2 X sync card .
*
* Author : Arnaldo Carvalho de Melo < acme @ conectiva . com . br >
*
* Copyright : ( c ) 1998 - 2003 Arnaldo Carvalho de Melo
*
* Based on sdladrv . c by Gene Kozin < genek @ compuserve . com >
*
* 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 .
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
* 1999 / 11 / 11 acme set_current_state ( TASK_INTERRUPTIBLE ) , code
* cleanup
* 1999 / 11 / 08 acme init_cyc2x deleted , doing nothing
* 1999 / 11 / 06 acme back to read [ bw ] , write [ bw ] and memcpy_to and
* fromio to use dpmbase ioremaped
* 1999 / 10 / 26 acme use isa_read [ bw ] , isa_write [ bw ] & isa_memcpy_to
* & fromio
* 1999 / 10 / 23 acme cleanup to only supports cyclom2x : all the other
* boards are no longer manufactured by cyclades ,
* if someone wants to support them . . . be my guest !
* 1999 / 05 / 28 acme cycx_intack & cycx_intde gone for good
* 1999 / 05 / 18 acme lots of unlogged work , submitting to Linus . . .
* 1999 / 01 / 03 acme more judicious use of data types
* 1999 / 01 / 03 acme judicious use of data types : >
* cycx_inten trying to reset pending interrupts
* from cyclom 2 x - I think this isn ' t the way to
* go , but for now . . .
* 1999 / 01 / 02 acme cycx_intack ok , I think there ' s nothing to do
* to ack an int in cycx_drv . c , only handle it in
* cyx_isr ( or in the other protocols : cyp_isr ,
* cyf_isr , when they get implemented .
* Dec 31 , 1998 acme cycx_data_boot & cycx_code_boot fixed , crossing
* fingers to see x25_configure in cycx_x25 . c
* work . . . : )
* Dec 26 , 1998 acme load implementation fixed , seems to work ! : )
* cycx_2x_dpmbase_options with all the possible
* DPM addresses ( 20 ) .
* cycx_intr implemented ( test this ! )
* general code cleanup
* Dec 8 , 1998 Ivan Passos Cyclom - 2 X firmware load implementation .
* Aug 8 , 1998 acme Initial version .
*/
# include <linux/init.h> /* __init */
# include <linux/module.h>
# include <linux/kernel.h> /* printk(), and other useful stuff */
# include <linux/stddef.h> /* offsetof(), etc. */
# include <linux/errno.h> /* return codes */
# include <linux/cycx_drv.h> /* API definitions */
# include <linux/cycx_cfm.h> /* CYCX firmware module definitions */
2005-06-27 20:20:04 +04:00
# include <linux/delay.h> /* udelay, msleep_interruptible */
2005-04-17 02:20:36 +04:00
# include <asm/io.h> /* read[wl], write[wl], ioremap, iounmap */
# define MOD_VERSION 0
# define MOD_RELEASE 6
MODULE_AUTHOR ( " Arnaldo Carvalho de Melo " ) ;
MODULE_DESCRIPTION ( " Cyclom 2x Sync Card Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
/* Hardware-specific functions */
static int load_cyc2x ( struct cycx_hw * hw , struct cycx_firmware * cfm , u32 len ) ;
static void cycx_bootcfg ( struct cycx_hw * hw ) ;
static int reset_cyc2x ( void __iomem * addr ) ;
static int detect_cyc2x ( void __iomem * addr ) ;
/* Miscellaneous functions */
static int get_option_index ( long * optlist , long optval ) ;
static u16 checksum ( u8 * buf , u32 len ) ;
# define wait_cyc(addr) cycx_exec(addr + CMD_OFFSET)
/* Global Data */
/* private data */
static char modname [ ] = " cycx_drv " ;
static char fullname [ ] = " Cyclom 2X Support Module " ;
static char copyright [ ] = " (c) 1998-2003 Arnaldo Carvalho de Melo "
" <acme@conectiva.com.br> " ;
/* Hardware configuration options.
* These are arrays of configuration options used by verification routines .
* The first element of each array is its size ( i . e . number of options ) .
*/
static long cyc2x_dpmbase_options [ ] = {
20 ,
0xA0000 , 0xA4000 , 0xA8000 , 0xAC000 , 0xB0000 , 0xB4000 , 0xB8000 ,
0xBC000 , 0xC0000 , 0xC4000 , 0xC8000 , 0xCC000 , 0xD0000 , 0xD4000 ,
0xD8000 , 0xDC000 , 0xE0000 , 0xE4000 , 0xE8000 , 0xEC000
} ;
static long cycx_2x_irq_options [ ] = { 7 , 3 , 5 , 9 , 10 , 11 , 12 , 15 } ;
/* Kernel Loadable Module Entry Points */
/* Module 'insert' entry point.
* o print announcement
* o initialize static data
*
* Return : 0 Ok
* < 0 error .
* Context : process */
2005-09-10 10:17:28 +04:00
static int __init cycx_drv_init ( void )
2005-04-17 02:20:36 +04:00
{
printk ( KERN_INFO " %s v%u.%u %s \n " , fullname , MOD_VERSION , MOD_RELEASE ,
copyright ) ;
return 0 ;
}
/* Module 'remove' entry point.
* o release all remaining system resources */
2005-09-10 10:17:28 +04:00
static void cycx_drv_cleanup ( void )
2005-04-17 02:20:36 +04:00
{
}
/* Kernel APIs */
/* Set up adapter.
* o detect adapter type
* o verify hardware configuration options
* o check for hardware conflicts
* o set up adapter shared memory
* o test adapter memory
* o load firmware
* Return : 0 ok .
* < 0 error */
EXPORT_SYMBOL ( cycx_setup ) ;
int cycx_setup ( struct cycx_hw * hw , void * cfm , u32 len , unsigned long dpmbase )
{
int err ;
/* Verify IRQ configuration options */
if ( ! get_option_index ( cycx_2x_irq_options , hw - > irq ) ) {
printk ( KERN_ERR " %s: IRQ %d is invalid! \n " , modname , hw - > irq ) ;
return - EINVAL ;
}
/* Setup adapter dual-port memory window and test memory */
if ( ! dpmbase ) {
printk ( KERN_ERR " %s: you must specify the dpm address! \n " ,
modname ) ;
return - EINVAL ;
} else if ( ! get_option_index ( cyc2x_dpmbase_options , dpmbase ) ) {
printk ( KERN_ERR " %s: memory address 0x%lX is invalid! \n " ,
modname , dpmbase ) ;
return - EINVAL ;
}
hw - > dpmbase = ioremap ( dpmbase , CYCX_WINDOWSIZE ) ;
hw - > dpmsize = CYCX_WINDOWSIZE ;
if ( ! detect_cyc2x ( hw - > dpmbase ) ) {
printk ( KERN_ERR " %s: adapter Cyclom 2X not found at "
" address 0x%lX! \n " , modname , dpmbase ) ;
return - EINVAL ;
}
printk ( KERN_INFO " %s: found Cyclom 2X card at address 0x%lX. \n " ,
modname , dpmbase ) ;
/* Load firmware. If loader fails then shut down adapter */
err = load_cyc2x ( hw , cfm , len ) ;
if ( err )
cycx_down ( hw ) ; /* shutdown adapter */
return err ;
}
EXPORT_SYMBOL ( cycx_down ) ;
int cycx_down ( struct cycx_hw * hw )
{
iounmap ( hw - > dpmbase ) ;
return 0 ;
}
/* Enable interrupt generation. */
2005-09-10 10:17:28 +04:00
static void cycx_inten ( struct cycx_hw * hw )
2005-04-17 02:20:36 +04:00
{
writeb ( 0 , hw - > dpmbase ) ;
}
/* Generate an interrupt to adapter's CPU. */
EXPORT_SYMBOL ( cycx_intr ) ;
void cycx_intr ( struct cycx_hw * hw )
{
writew ( 0 , hw - > dpmbase + GEN_CYCX_INTR ) ;
}
/* Execute Adapter Command.
* o Set exec flag .
* o Busy - wait until flag is reset . */
EXPORT_SYMBOL ( cycx_exec ) ;
int cycx_exec ( void __iomem * addr )
{
u16 i = 0 ;
/* wait till addr content is zeroed */
while ( readw ( addr ) ) {
udelay ( 1000 ) ;
if ( + + i > 50 )
return - 1 ;
}
return 0 ;
}
/* Read absolute adapter memory.
* Transfer data from adapter ' s memory to data buffer . */
EXPORT_SYMBOL ( cycx_peek ) ;
int cycx_peek ( struct cycx_hw * hw , u32 addr , void * buf , u32 len )
{
if ( len = = 1 )
* ( u8 * ) buf = readb ( hw - > dpmbase + addr ) ;
else
memcpy_fromio ( buf , hw - > dpmbase + addr , len ) ;
return 0 ;
}
/* Write Absolute Adapter Memory.
* Transfer data from data buffer to adapter ' s memory . */
EXPORT_SYMBOL ( cycx_poke ) ;
int cycx_poke ( struct cycx_hw * hw , u32 addr , void * buf , u32 len )
{
if ( len = = 1 )
writeb ( * ( u8 * ) buf , hw - > dpmbase + addr ) ;
else
memcpy_toio ( hw - > dpmbase + addr , buf , len ) ;
return 0 ;
}
/* Hardware-Specific Functions */
/* Load Aux Routines */
/* Reset board hardware.
return 1 if memory exists at addr and 0 if not . */
static int memory_exists ( void __iomem * addr )
{
int tries = 0 ;
for ( ; tries < 3 ; tries + + ) {
writew ( TEST_PATTERN , addr + 0x10 ) ;
if ( readw ( addr + 0x10 ) = = TEST_PATTERN )
if ( readw ( addr + 0x10 ) = = TEST_PATTERN )
return 1 ;
2005-06-27 20:20:04 +04:00
msleep_interruptible ( 1 * 1000 ) ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
}
/* Load reset code. */
static void reset_load ( void __iomem * addr , u8 * buffer , u32 cnt )
{
void __iomem * pt_code = addr + RESET_OFFSET ;
u16 i ; /*, j; */
for ( i = 0 ; i < cnt ; i + + ) {
/* for (j = 0 ; j < 50 ; j++); Delay - FIXME busy waiting... */
writeb ( * buffer + + , pt_code + + ) ;
}
}
/* Load buffer using boot interface.
* o copy data from buffer to Cyclom - X memory
* o wait for reset code to copy it to right portion of memory */
static int buffer_load ( void __iomem * addr , u8 * buffer , u32 cnt )
{
memcpy_toio ( addr + DATA_OFFSET , buffer , cnt ) ;
writew ( GEN_BOOT_DAT , addr + CMD_OFFSET ) ;
return wait_cyc ( addr ) ;
}
/* Set up entry point and kick start Cyclom-X CPU. */
static void cycx_start ( void __iomem * addr )
{
/* put in 0x30 offset the jump instruction to the code entry point */
writeb ( 0xea , addr + 0x30 ) ;
writeb ( 0x00 , addr + 0x31 ) ;
writeb ( 0xc4 , addr + 0x32 ) ;
writeb ( 0x00 , addr + 0x33 ) ;
writeb ( 0x00 , addr + 0x34 ) ;
/* cmd to start executing code */
writew ( GEN_START , addr + CMD_OFFSET ) ;
}
/* Load and boot reset code. */
static void cycx_reset_boot ( void __iomem * addr , u8 * code , u32 len )
{
void __iomem * pt_start = addr + START_OFFSET ;
writeb ( 0xea , pt_start + + ) ; /* jmp to f000:3f00 */
writeb ( 0x00 , pt_start + + ) ;
writeb ( 0xfc , pt_start + + ) ;
writeb ( 0x00 , pt_start + + ) ;
writeb ( 0xf0 , pt_start ) ;
reset_load ( addr , code , len ) ;
/* 80186 was in hold, go */
writeb ( 0 , addr + START_CPU ) ;
2005-06-27 20:20:04 +04:00
msleep_interruptible ( 1 * 1000 ) ;
2005-04-17 02:20:36 +04:00
}
/* Load data.bin file through boot (reset) interface. */
static int cycx_data_boot ( void __iomem * addr , u8 * code , u32 len )
{
void __iomem * pt_boot_cmd = addr + CMD_OFFSET ;
u32 i ;
2008-02-03 16:42:53 +03:00
/* boot buffer length */
2005-04-17 02:20:36 +04:00
writew ( CFM_LOAD_BUFSZ , pt_boot_cmd + sizeof ( u16 ) ) ;
writew ( GEN_DEFPAR , pt_boot_cmd ) ;
if ( wait_cyc ( addr ) < 0 )
return - 1 ;
writew ( 0 , pt_boot_cmd + sizeof ( u16 ) ) ;
writew ( 0x4000 , pt_boot_cmd + 2 * sizeof ( u16 ) ) ;
writew ( GEN_SET_SEG , pt_boot_cmd ) ;
if ( wait_cyc ( addr ) < 0 )
return - 1 ;
for ( i = 0 ; i < len ; i + = CFM_LOAD_BUFSZ )
if ( buffer_load ( addr , code + i ,
min_t ( u32 , CFM_LOAD_BUFSZ , ( len - i ) ) ) < 0 ) {
printk ( KERN_ERR " %s: Error !! \n " , modname ) ;
return - 1 ;
}
return 0 ;
}
/* Load code.bin file through boot (reset) interface. */
static int cycx_code_boot ( void __iomem * addr , u8 * code , u32 len )
{
void __iomem * pt_boot_cmd = addr + CMD_OFFSET ;
u32 i ;
2008-02-03 16:42:53 +03:00
/* boot buffer length */
2005-04-17 02:20:36 +04:00
writew ( CFM_LOAD_BUFSZ , pt_boot_cmd + sizeof ( u16 ) ) ;
writew ( GEN_DEFPAR , pt_boot_cmd ) ;
if ( wait_cyc ( addr ) < 0 )
return - 1 ;
writew ( 0x0000 , pt_boot_cmd + sizeof ( u16 ) ) ;
writew ( 0xc400 , pt_boot_cmd + 2 * sizeof ( u16 ) ) ;
writew ( GEN_SET_SEG , pt_boot_cmd ) ;
if ( wait_cyc ( addr ) < 0 )
return - 1 ;
for ( i = 0 ; i < len ; i + = CFM_LOAD_BUFSZ )
if ( buffer_load ( addr , code + i ,
min_t ( u32 , CFM_LOAD_BUFSZ , ( len - i ) ) ) ) {
printk ( KERN_ERR " %s: Error !! \n " , modname ) ;
return - 1 ;
}
return 0 ;
}
/* Load adapter from the memory image of the CYCX firmware module.
* o verify firmware integrity and compatibility
* o start adapter up */
static int load_cyc2x ( struct cycx_hw * hw , struct cycx_firmware * cfm , u32 len )
{
int i , j ;
struct cycx_fw_header * img_hdr ;
u8 * reset_image ,
* data_image ,
* code_image ;
void __iomem * pt_cycld = hw - > dpmbase + 0x400 ;
u16 cksum ;
/* Announce */
printk ( KERN_INFO " %s: firmware signature= \" %s \" \n " , modname ,
cfm - > signature ) ;
/* Verify firmware signature */
if ( strcmp ( cfm - > signature , CFM_SIGNATURE ) ) {
printk ( KERN_ERR " %s:load_cyc2x: not Cyclom-2X firmware! \n " ,
modname ) ;
return - EINVAL ;
}
printk ( KERN_INFO " %s: firmware version=%u \n " , modname , cfm - > version ) ;
/* Verify firmware module format version */
if ( cfm - > version ! = CFM_VERSION ) {
printk ( KERN_ERR " %s:%s: firmware format %u rejected! "
" Expecting %u. \n " ,
2008-08-21 03:52:04 +04:00
modname , __func__ , cfm - > version , CFM_VERSION ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
/* Verify firmware module length and checksum */
cksum = checksum ( ( u8 * ) & cfm - > info , sizeof ( struct cycx_fw_info ) +
cfm - > info . codesize ) ;
/*
FIXME cfm - > info . codesize is off by 2
if ( ( ( len - sizeof ( struct cycx_firmware ) - 1 ) ! = cfm - > info . codesize ) | |
*/
if ( cksum ! = cfm - > checksum ) {
printk ( KERN_ERR " %s:%s: firmware corrupted! \n " ,
2008-08-21 03:52:04 +04:00
modname , __func__ ) ;
2005-04-17 02:20:36 +04:00
printk ( KERN_ERR " cdsize = 0x%x (expected 0x%lx) \n " ,
len - ( int ) sizeof ( struct cycx_firmware ) - 1 ,
cfm - > info . codesize ) ;
printk ( KERN_ERR " chksum = 0x%x (expected 0x%x) \n " ,
cksum , cfm - > checksum ) ;
return - EINVAL ;
}
/* If everything is ok, set reset, data and code pointers */
img_hdr = ( struct cycx_fw_header * ) & cfm - > image ;
# ifdef FIRMWARE_DEBUG
2008-08-21 03:52:04 +04:00
printk ( KERN_INFO " %s:%s: image sizes \n " , __func__ , modname ) ;
2005-04-17 02:20:36 +04:00
printk ( KERN_INFO " reset=%lu \n " , img_hdr - > reset_size ) ;
printk ( KERN_INFO " data=%lu \n " , img_hdr - > data_size ) ;
printk ( KERN_INFO " code=%lu \n " , img_hdr - > code_size ) ;
# endif
reset_image = ( ( u8 * ) img_hdr ) + sizeof ( struct cycx_fw_header ) ;
data_image = reset_image + img_hdr - > reset_size ;
code_image = data_image + img_hdr - > data_size ;
/*---- Start load ----*/
/* Announce */
printk ( KERN_INFO " %s: loading firmware %s (ID=%u)... \n " , modname ,
cfm - > descr [ 0 ] ? cfm - > descr : " unknown firmware " ,
cfm - > info . codeid ) ;
for ( i = 0 ; i < 5 ; i + + ) {
/* Reset Cyclom hardware */
if ( ! reset_cyc2x ( hw - > dpmbase ) ) {
printk ( KERN_ERR " %s: dpm problem or board not found \n " ,
modname ) ;
return - EINVAL ;
}
/* Load reset.bin */
cycx_reset_boot ( hw - > dpmbase , reset_image , img_hdr - > reset_size ) ;
/* reset is waiting for boot */
writew ( GEN_POWER_ON , pt_cycld ) ;
2005-06-27 20:20:04 +04:00
msleep_interruptible ( 1 * 1000 ) ;
2005-04-17 02:20:36 +04:00
for ( j = 0 ; j < 3 ; j + + )
if ( ! readw ( pt_cycld ) )
goto reset_loaded ;
else
2005-06-27 20:20:04 +04:00
msleep_interruptible ( 1 * 1000 ) ;
2005-04-17 02:20:36 +04:00
}
printk ( KERN_ERR " %s: reset not started. \n " , modname ) ;
return - EINVAL ;
reset_loaded :
/* Load data.bin */
if ( cycx_data_boot ( hw - > dpmbase , data_image , img_hdr - > data_size ) ) {
printk ( KERN_ERR " %s: cannot load data file. \n " , modname ) ;
return - EINVAL ;
}
/* Load code.bin */
if ( cycx_code_boot ( hw - > dpmbase , code_image , img_hdr - > code_size ) ) {
printk ( KERN_ERR " %s: cannot load code file. \n " , modname ) ;
return - EINVAL ;
}
/* Prepare boot-time configuration data */
cycx_bootcfg ( hw ) ;
/* kick-off CPU */
cycx_start ( hw - > dpmbase ) ;
/* Arthur Ganzert's tip: wait a while after the firmware loading...
seg abr 26 17 : 17 : 12 EST 1999 - acme */
2005-06-27 20:20:04 +04:00
msleep_interruptible ( 7 * 1000 ) ;
2005-04-17 02:20:36 +04:00
printk ( KERN_INFO " %s: firmware loaded! \n " , modname ) ;
/* enable interrupts */
cycx_inten ( hw ) ;
return 0 ;
}
/* Prepare boot-time firmware configuration data.
* o initialize configuration data area
From async . doc - V_3 .4 .0 - 07 / 18 / 1994
- As of now , only static buffers are available to the user .
So , the bit VD_RXDIRC must be set in ' valid ' . That means that user
wants to use the static transmission and reception buffers . */
static void cycx_bootcfg ( struct cycx_hw * hw )
{
/* use fixed buffers */
writeb ( FIXED_BUFFERS , hw - > dpmbase + CONF_OFFSET ) ;
}
/* Detect Cyclom 2x adapter.
* Following tests are used to detect Cyclom 2 x adapter :
* to be completed based on the tests done below
* Return 1 if detected o . k . or 0 if failed .
* Note : This test is destructive ! Adapter will be left in shutdown
* state after the test . */
static int detect_cyc2x ( void __iomem * addr )
{
reset_cyc2x ( addr ) ;
return memory_exists ( addr ) ;
}
/* Miscellaneous */
/* Get option's index into the options list.
* Return option ' s index ( 1 . . N ) or zero if option is invalid . */
static int get_option_index ( long * optlist , long optval )
{
int i = 1 ;
for ( ; i < = optlist [ 0 ] ; + + i )
if ( optlist [ i ] = = optval )
return i ;
return 0 ;
}
/* Reset adapter's CPU. */
static int reset_cyc2x ( void __iomem * addr )
{
writeb ( 0 , addr + RST_ENABLE ) ;
2005-06-27 20:20:04 +04:00
msleep_interruptible ( 2 * 1000 ) ;
2005-04-17 02:20:36 +04:00
writeb ( 0 , addr + RST_DISABLE ) ;
2005-06-27 20:20:04 +04:00
msleep_interruptible ( 2 * 1000 ) ;
2005-04-17 02:20:36 +04:00
return memory_exists ( addr ) ;
}
/* Calculate 16-bit CRC using CCITT polynomial. */
static u16 checksum ( u8 * buf , u32 len )
{
u16 crc = 0 ;
u16 mask , flag ;
for ( ; len ; - - len , + + buf )
for ( mask = 0x80 ; mask ; mask > > = 1 ) {
flag = ( crc & 0x8000 ) ;
crc < < = 1 ;
crc | = ( ( * buf & mask ) ? 1 : 0 ) ;
if ( flag )
crc ^ = 0x1021 ;
}
return crc ;
}
module_init ( cycx_drv_init ) ;
module_exit ( cycx_drv_cleanup ) ;
/* End */