FIX: - if a sdo transfer timeout occurres, reset the sdo line even if the callback function does not.
/*
This file is part of CanFestival, a library implementing CanOpen Stack.
Copyright (C): Cosateq GmbH & Co.KG
http://www.cosateq.com/
http://www.scale-rt.com/
See COPYING file for copyrights details.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
COMEDI interface for CO-PCICAN card.
*/
#include <linux/module.h>
#include <linux/comedi.h>
#include <linux/comedilib.h>
#define NEED_PRINT_MESSAGE
#include "can_driver.h"
#include "def.h"
#include "co_pcicanops.h"
MODULE_LICENSE("GPL");
/* at the moment not threadsafe :-( */
static unsigned char selectedChannelRx = 0, selectedChannelTx = 0;
static int rt_strcmp( const char *src, const char *dst )
{
int ret = 0;
while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
{
src++;
dst++;
}
if( ret < 0 )
{
ret = -1 ;
}
else
{
if( ret > 0 )
ret = 1 ;
}
return ret;
}
static int TranslateBaudRate( char* optarg )
{
/* values see documentation of CO-PCICAN */
if( !rt_strcmp( optarg, "1M" ) ) return 0;
if( !rt_strcmp( optarg, "800K" ) ) return 1;
if( !rt_strcmp( optarg, "500K" ) ) return 2;
if( !rt_strcmp( optarg, "250K" ) ) return 3;
if( !rt_strcmp( optarg, "125K" ) ) return 4;
if( !rt_strcmp( optarg, "100K" ) ) return 5;
if( !rt_strcmp( optarg, "83.3K" ) ) return 6;
if( !rt_strcmp( optarg, "10K" ) ) return 7;
return -1;
}
/*********CO-PCICAN specific functions to communicate with the board**********/
typedef struct
{
char used;
CAN_HANDLE fd;
void* receiveTask;
void* d;
} CANPort; /* taken from drivers/unix.c */
comedi_t* get_dev_of_port( CAN_PORT port )
{
CANPort *thisPort = (CANPort*)port;
CAN_HANDLE thisHandle;
comedi_t *dev;
if( thisPort == NULL )
{
MSG("can_copcican_comedi: get_dev_of_port(): thisPort is NULL\n");
return NULL;
}
thisHandle = thisPort->fd;
if( thisHandle == NULL )
{
MSG("can_copcican_comedi: get_dev_of_port(): thisHandle is NULL\n");
return NULL;
}
dev = (comedi_t*)thisHandle;
/*MSG("can_copcican_comedi: get_dev_of_port(): handle is 0x%08x\n", (unsigned int)dev);*/
return dev;
}
int co_pcican_enter_run_mode( comedi_t *dev )
{
unsigned int opcmd[15];
unsigned int opcode = CMDQ_OPC_ENTER_RUN_MODE;
comedi_insn insn;
if( dev == NULL )
{
MSG("can_copcican_comedi: co_pcican_enter_run_mode(): dev is NULL\n");
return 1;
}
memset( opcmd, 0x00, sizeof(opcmd) );
opcmd[0] = 0;
memset( &insn, 0x00, sizeof(insn) );
insn.insn = INSN_CONFIG;
insn.n = 1;
insn.data = (void*)opcmd;
insn.subdev = 0;
insn.unused[0] = opcode; /* in use for CO-PCICAN */
comedi_do_insn( dev, &insn );
opcmd[0] = 2;
insn.subdev = 2;
insn.unused[0] = opcode; /* in use for CO-PCICAN */
return comedi_do_insn( dev, &insn );
}
int co_pcican_enter_config_mode( comedi_t *dev )
{
unsigned int opcmd[15];
unsigned int opcode = CMDQ_OPC_ENTER_CONFIG_MODE;
comedi_insn insn;
if( dev == NULL )
{
MSG("can_copcican_comedi: co_pcican_enter_config_mode(): dev is NULL\n");
return 1;
}
memset( opcmd, 0x00, sizeof(opcmd) );
opcmd[0] = 0;
memset( &insn, 0x00, sizeof(insn) );
insn.insn = INSN_CONFIG;
insn.n = 1;
insn.data = (void*)opcmd;
insn.subdev = 0;
insn.unused[0] = opcode; /* in use for CO-PCICAN */
comedi_do_insn( dev, &insn );
opcmd[0] = 2;
insn.subdev = 2;
insn.unused[0] = opcode; /* in use for CO-PCICAN */
return comedi_do_insn( dev, &insn );
}
int co_pcican_select_channel( const unsigned char channel, const unsigned int direction )
{
if( channel >= NUM_CAN_CHANNELS )
{
MSG("can_copcican_comedi: co_pcican_select_channel(): invalid channel\n");
return -1;
}
/* at the moment not threadsafe :-( */
switch( direction )
{
case RX: selectedChannelRx = channel;
break;
case TX: selectedChannelTx = channel;
break;
default: return -1;
}
return 0;
}
int co_pcican_configure_selected_channel( comedi_t *dev, s_BOARD *board, const unsigned int direction )
{
unsigned int opcmd[15];
unsigned int opcode = CMDQ_OPC_SET_CONFIG_CHANNEL;
unsigned int selectedChannel;
comedi_insn insn;
if( dev == NULL )
{
MSG("can_copcican_comedi: co_pcican_configure_selected_channel(): dev is NULL\n");
return -1;
}
if( board == NULL )
{
MSG("can_copcican_comedi: co_pcican_configure_selected_channel(): board is NULL\n");
return -1;
}
if( board->baudrate == NULL )
{
MSG("can_copcican_comedi: co_pcican_configure_selected_channel(): baudrate is NULL\n");
return -1;
}
switch( direction )
{
case RX: selectedChannel = selectedChannelRx;
break;
case TX: selectedChannel = selectedChannelTx;
break;
default: selectedChannel = 0xff;
}
if( selectedChannel >= NUM_CAN_CHANNELS )
{
MSG("can_copcican_comedi: co_pcican_configure_selected_channel(): invalid channel selected\n");
return -1;
}
memset( opcmd, 0x00, sizeof(opcmd) );
opcmd[0] = selectedChannel;
opcmd[1] = TranslateBaudRate( board->baudrate );
memset( &insn, 0x00, sizeof(insn) );
insn.insn = INSN_CONFIG;
insn.n = 1;
insn.data = (void*)opcmd;
if( selectedChannel < 2 )
insn.subdev = 0;
else
insn.subdev = 2;
insn.unused[0] = opcode; /* in use for CO-PCICAN */
return comedi_do_insn( dev, &insn );
}
/*********functions which permit to communicate with the board****************/
UNS8 canReceive_driver( CAN_HANDLE fd0, Message *m )
{
MESSAGE_T canmsg;
comedi_insn insn;
UNS8 ret = 0;
comedi_t *dev = (comedi_t*)fd0;
if( dev == NULL )
{
MSG("can_copcican_comedi: canReceive_driver(): dev is NULL\n");
return 1;
}
if( selectedChannelRx >= NUM_CAN_CHANNELS )
{
MSG("can_copcican_comedi: canReceive_driver(): invalid channel selected\n");
return 1;
}
if( m == NULL )
{
MSG("can_copcican_comedi: canReceive_driver(): message is NULL\n");
return 1;
}
memset( &canmsg, 0x00, sizeof(MESSAGE_T) );
memset( &insn, 0x00, sizeof(insn) );
insn.insn = INSN_READ;
insn.n = 1;
insn.data = (void*)&canmsg;
if( selectedChannelRx < 2 )
insn.subdev = comedi_find_subdevice_by_type(dev, COMEDI_SUBD_DI, 0);
else
insn.subdev = comedi_find_subdevice_by_type(dev, COMEDI_SUBD_DI, 2);
insn.unused[1] = selectedChannelRx; /* in use for CO-PCICAN */
insn.unused[2] = sizeof(MESSAGE_T); /* in use for CO-PCICAN */
comedi_do_insn( dev, &insn );
if( canmsg.timestamp_lo == 0 )
{
memset( m, 0x00, sizeof(Message) );
m->cob_id = 0xffff; /* set to invalid so nothing happens */
}
else
{
m->len = canmsg.size;
m->cob_id = canmsg.id;
m->rtr = canmsg.type & MSG_RTR;
if( !m->rtr )
{
/* this is for safety */
if( m->len > 8 )
m->len = 8;
memcpy( m->data, canmsg.data, m->len);
}
}
return ret;
}
/***************************************************************************/
UNS8 canSend_driver( CAN_HANDLE fd0, Message *m )
{
MESSAGE_T canmsg;
comedi_insn insn;
UNS8 ret = 0;
comedi_t *dev = (comedi_t*)fd0;
if( dev == NULL )
{
MSG("can_copcican_comedi: canSend_driver(): dev is NULL\n");
return 1;
}
if( selectedChannelTx >= NUM_CAN_CHANNELS )
{
MSG("can_copcican_comedi: canSend_driver(): invalid channel selected\n");
return 1;
}
if( m == NULL )
{
MSG("can_copcican_comedi: canSend_driver(): message is NULL\n");
return 1;
}
memset( &canmsg, 0x00, sizeof(MESSAGE_T) );
canmsg.size = m->len;
canmsg.id = m->cob_id;
if( canmsg.id >= 0x800 )
canmsg.type |= MSG_EXT;
if( m->rtr )
{
canmsg.type |= MSG_RTR;
}
else
{
/* this is for safety */
if( canmsg.size > 8 )
canmsg.size = 8;
memcpy( canmsg.data, m->data, canmsg.size);
}
memset( &insn, 0x00, sizeof(insn) );
insn.insn = INSN_WRITE;
insn.n = 1;
insn.data = (void*)&canmsg;
if( selectedChannelTx < 2 )
insn.subdev = comedi_find_subdevice_by_type(dev, COMEDI_SUBD_DO, 0);
else
insn.subdev = comedi_find_subdevice_by_type(dev, COMEDI_SUBD_DO, 2);
insn.unused[1] = selectedChannelTx; /* in use for CO-PCICAN */
insn.unused[2] = sizeof(MESSAGE_T) - sizeof(unsigned long); /* in use for CO-PCICAN, no timestamp for tx */
if( comedi_do_insn( dev, &insn ) < 0 )
ret = 1;
return ret;
}
/***************************************************************************/
CAN_HANDLE canOpen_driver( s_BOARD *board )
{
comedi_t *dev;
if( board == NULL )
{
MSG("can_copcican_comedi: canOpen_driver(): board is NULL\n");
return NULL;
}
if( board->busname == NULL )
{
MSG("can_copcican_comedi: canOpen_driver(): busname is NULL\n");
return NULL;
}
dev = comedi_open( board->busname );
/*MSG("can_copcican_comedi: canOpen_driver(): handle is 0x%08x\n", (unsigned int)dev);*/
return dev;
}
/***************************************************************************/
int canClose_driver( CAN_HANDLE fd0 )
{
comedi_t *dev = (comedi_t*)fd0;
if( dev == NULL )
{
MSG("can_copcican_comedi: canClose_driver(): dev is NULL\n");
return 1;
}
comedi_close( dev );
selectedChannelRx = 0;
selectedChannelTx = 0;
return 0;
}
/***************************************************************************/
UNS8 canChangeBaudRate_driver( CAN_HANDLE fd0, char* baud )
{
s_BOARD board;
UNS8 ret = 0;
comedi_t *dev = (comedi_t*)fd0;
if( dev == NULL )
{
MSG("can_copcican_comedi: canChangeBaudRate_driver(): dev is NULL\n");
return 1;
}
if( baud == NULL )
{
MSG("can_copcican_comedi: canChangeBaudRate_driver(): baud is NULL\n");
return 1;
}
memset( &board, 0x00, sizeof(s_BOARD) );
board.baudrate = baud;
if( co_pcican_configure_selected_channel( dev, &board, RX ) < 0 )
ret = 1;
if( co_pcican_configure_selected_channel( dev, &board, TX ) < 0 )
ret = 1;
return ret;
}
static int init(void)
{
printk("can_copcican_comedi for CanFestival loaded\n");
return 0;
}
static void exit(void)
{
printk("can_copcican_comedi unloaded\n");
}
module_init(init);
module_exit(exit);
EXPORT_SYMBOL(get_dev_of_port);
EXPORT_SYMBOL(co_pcican_enter_run_mode);
EXPORT_SYMBOL(co_pcican_enter_config_mode);
EXPORT_SYMBOL(co_pcican_select_channel);
EXPORT_SYMBOL(co_pcican_configure_selected_channel);
EXPORT_SYMBOL(canOpen_driver);
EXPORT_SYMBOL(canClose_driver);
EXPORT_SYMBOL(canSend_driver);
EXPORT_SYMBOL(canReceive_driver);
EXPORT_SYMBOL(canChangeBaudRate_driver);