diff options
Diffstat (limited to 'bsps/sparc/shared/can/grcan.c')
-rw-r--r-- | bsps/sparc/shared/can/grcan.c | 2001 |
1 files changed, 2001 insertions, 0 deletions
diff --git a/bsps/sparc/shared/can/grcan.c b/bsps/sparc/shared/can/grcan.c new file mode 100644 index 0000000000..da236ef7cc --- /dev/null +++ b/bsps/sparc/shared/can/grcan.c @@ -0,0 +1,2001 @@ +/* + * GRCAN driver + * + * COPYRIGHT (c) 2007. + * Cobham Gaisler AB. + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#include <bsp.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <ctype.h> +#include <rtems/bspIo.h> + +#include <bsp/grcan.h> +#include <drvmgr/drvmgr.h> +#include <drvmgr/ambapp_bus.h> +#include <ambapp.h> + +#if (((__RTEMS_MAJOR__ << 16) | (__RTEMS_MINOR__ << 8) | __RTEMS_REVISION__) >= 0x040b63) + +/* Spin locks mapped via rtems_interrupt_lock_* API: */ +#define SPIN_DECLARE(lock) RTEMS_INTERRUPT_LOCK_MEMBER(lock) +#define SPIN_INIT(lock, name) rtems_interrupt_lock_initialize(lock, name) +#define SPIN_LOCK(lock, level) rtems_interrupt_lock_acquire_isr(lock, &level) +#define SPIN_LOCK_IRQ(lock, level) rtems_interrupt_lock_acquire(lock, &level) +#define SPIN_UNLOCK(lock, level) rtems_interrupt_lock_release_isr(lock, &level) +#define SPIN_UNLOCK_IRQ(lock, level) rtems_interrupt_lock_release(lock, &level) +#define SPIN_IRQFLAGS(k) rtems_interrupt_lock_context k +#define SPIN_ISR_IRQFLAGS(k) SPIN_IRQFLAGS(k) + +#else + +/* maintain compatibility with older versions of RTEMS: */ +#define SPIN_DECLARE(name) +#define SPIN_INIT(lock, name) +#define SPIN_LOCK(lock, level) +#define SPIN_LOCK_IRQ(lock, level) rtems_interrupt_disable(level) +#define SPIN_UNLOCK(lock, level) +#define SPIN_UNLOCK_IRQ(lock, level) rtems_interrupt_enable(level) +#define SPIN_IRQFLAGS(k) rtems_interrupt_level k +#define SPIN_ISR_IRQFLAGS(k) + +#endif + +/* Maximum number of GRCAN devices supported by driver */ +#define GRCAN_COUNT_MAX 8 + +#define WRAP_AROUND_TX_MSGS 1 +#define WRAP_AROUND_RX_MSGS 2 +#define GRCAN_MSG_SIZE sizeof(struct grcan_msg) +#define BLOCK_SIZE (16*4) + +/* grcan needs to have it buffers aligned to 1k boundaries */ +#define BUFFER_ALIGNMENT_NEEDS 1024 + +/* Default Maximium buffer size for statically allocated buffers */ +#ifndef TX_BUF_SIZE + #define TX_BUF_SIZE (BLOCK_SIZE*16) +#endif + +/* Make receiver buffers bigger than transmitt */ +#ifndef RX_BUF_SIZE + #define RX_BUF_SIZE ((3*BLOCK_SIZE)*16) +#endif + +#ifndef IRQ_CLEAR_PENDING + #define IRQ_CLEAR_PENDING(irqno) +#endif + +#ifndef IRQ_UNMASK + #define IRQ_UNMASK(irqno) +#endif + +#ifndef IRQ_MASK + #define IRQ_MASK(irqno) +#endif + +#ifndef GRCAN_DEFAULT_BAUD + /* default to 500kbits/s */ + #define GRCAN_DEFAULT_BAUD 500000 +#endif + +#ifndef GRCAN_SAMPLING_POINT + #define GRCAN_SAMPLING_POINT 80 +#endif + +/* Uncomment for debug output */ +/****************** DEBUG Definitions ********************/ +#define DBG_TX 2 +#define DBG_RX 4 +#define DBG_STATE 8 + +#define DEBUG_FLAGS (DBG_STATE | DBG_RX | DBG_TX ) +/* +#define DEBUG +#define DEBUGFUNCS +*/ +#include <bsp/debug_defs.h> + +/*********************************************************/ + +int state2err[4] = { + /* STATE_STOPPED */ GRCAN_RET_NOTSTARTED, + /* STATE_STARTED */ GRCAN_RET_OK, + /* STATE_BUSOFF */ GRCAN_RET_BUSOFF, + /* STATE_AHBERR */ GRCAN_RET_AHBERR +}; + +struct grcan_msg { + unsigned int head[2]; + unsigned char data[8]; +}; + +struct grcan_config { + struct grcan_timing timing; + struct grcan_selection selection; + int abort; + int silent; +}; + +struct grcan_priv { + struct drvmgr_dev *dev; /* Driver manager device */ + char devName[32]; /* Device Name */ + unsigned int baseaddr, ram_base; + struct grcan_regs *regs; + int irq; + int minor; + int open; + int started; + unsigned int channel; + int flushing; + unsigned int corefreq_hz; + + /* Circular DMA buffers */ + void *_rx, *_rx_hw; + void *_tx, *_tx_hw; + void *txbuf_adr; + void *rxbuf_adr; + struct grcan_msg *rx; + struct grcan_msg *tx; + unsigned int rxbuf_size; /* requested RX buf size in bytes */ + unsigned int txbuf_size; /* requested TX buf size in bytes */ + + int txblock, rxblock; + int txcomplete, rxcomplete; + + struct grcan_filter sfilter; + struct grcan_filter afilter; + int config_changed; /* 0=no changes, 1=changes ==> a Core reset is needed */ + struct grcan_config config; + struct grcan_stats stats; + + rtems_id rx_sem, tx_sem, txempty_sem, dev_sem; + SPIN_DECLARE(devlock); +}; + +static void __inline__ grcan_hw_reset(struct grcan_regs *regs); + +static int grcan_hw_read_try( + struct grcan_priv *pDev, + struct grcan_regs *regs, + CANMsg *buffer, + int max); + +static int grcan_hw_write_try( + struct grcan_priv *pDev, + struct grcan_regs *regs, + CANMsg *buffer, + int count); + +static void grcan_hw_config( + struct grcan_regs *regs, + struct grcan_config *conf); + +static void grcan_hw_accept( + struct grcan_regs *regs, + struct grcan_filter *afilter); + +static void grcan_hw_sync( + struct grcan_regs *regs, + struct grcan_filter *sfilter); + +static void grcan_interrupt(void *arg); + +#ifdef GRCAN_REG_BYPASS_CACHE +#define READ_REG(address) _grcan_read_nocache((unsigned int)(address)) +#else +#define READ_REG(address) (*(volatile unsigned int *)(address)) +#endif + +#ifdef GRCAN_DMA_BYPASS_CACHE +#define READ_DMA_WORD(address) _grcan_read_nocache((unsigned int)(address)) +#define READ_DMA_BYTE(address) _grcan_read_nocache_byte((unsigned int)(address)) +static unsigned char __inline__ _grcan_read_nocache_byte(unsigned int address) +{ + unsigned char tmp; + __asm__ (" lduba [%1]1, %0 " + : "=r"(tmp) + : "r"(address) + ); + return tmp; +} +#else +#define READ_DMA_WORD(address) (*(volatile unsigned int *)(address)) +#define READ_DMA_BYTE(address) (*(volatile unsigned char *)(address)) +#endif + +#if defined(GRCAN_REG_BYPASS_CACHE) || defined(GRCAN_DMA_BYPASS_CACHE) +static unsigned int __inline__ _grcan_read_nocache(unsigned int address) +{ + unsigned int tmp; + __asm__ (" lda [%1]1, %0 " + : "=r"(tmp) + : "r"(address) + ); + return tmp; +} +#endif + +#define NELEM(a) ((int) (sizeof (a) / sizeof (a[0]))) + +static int grcan_count = 0; +static struct grcan_priv *priv_tab[GRCAN_COUNT_MAX]; + +/******************* Driver manager interface ***********************/ + +/* Driver prototypes */ +int grcan_device_init(struct grcan_priv *pDev); + +int grcan_init2(struct drvmgr_dev *dev); +int grcan_init3(struct drvmgr_dev *dev); + +struct drvmgr_drv_ops grcan_ops = +{ + .init = {NULL, grcan_init2, grcan_init3, NULL}, + .remove = NULL, + .info = NULL +}; + +struct amba_dev_id grcan_ids[] = +{ + {VENDOR_GAISLER, GAISLER_GRCAN}, + {VENDOR_GAISLER, GAISLER_GRHCAN}, + {0, 0} /* Mark end of table */ +}; + +struct amba_drv_info grcan_drv_info = +{ + { + DRVMGR_OBJ_DRV, /* Driver */ + NULL, /* Next driver */ + NULL, /* Device list */ + DRIVER_AMBAPP_GAISLER_GRCAN_ID, /* Driver ID */ + "GRCAN_DRV", /* Driver Name */ + DRVMGR_BUS_TYPE_AMBAPP, /* Bus Type */ + &grcan_ops, + NULL, /* Funcs */ + 0, /* No devices yet */ + 0, + }, + &grcan_ids[0] +}; + +void grcan_register_drv (void) +{ + DBG("Registering GRCAN driver\n"); + drvmgr_drv_register(&grcan_drv_info.general); +} + +int grcan_init2(struct drvmgr_dev *dev) +{ + struct grcan_priv *priv; + + DBG("GRCAN[%d] on bus %s\n", dev->minor_drv, dev->parent->dev->name); + if (GRCAN_COUNT_MAX <= grcan_count) + return DRVMGR_ENORES; + priv = dev->priv = malloc(sizeof(struct grcan_priv)); + if ( !priv ) + return DRVMGR_NOMEM; + memset(priv, 0, sizeof(*priv)); + priv->dev = dev; + + /* This core will not find other cores, so we wait for init2() */ + + return DRVMGR_OK; +} + +int grcan_init3(struct drvmgr_dev *dev) +{ + struct grcan_priv *priv; + char prefix[32]; + + priv = dev->priv; + + /* + * Now we take care of device initialization. + */ + + if ( grcan_device_init(priv) ) { + return DRVMGR_FAIL; + } + + priv_tab[grcan_count] = priv; + grcan_count++; + + /* Get Filesystem name prefix */ + prefix[0] = '\0'; + if ( drvmgr_get_dev_prefix(dev, prefix) ) { + /* Failed to get prefix, make sure of a unique FS name + * by using the driver minor. + */ + sprintf(priv->devName, "grcan%d", dev->minor_drv); + } else { + /* Got special prefix, this means we have a bus prefix + * And we should use our "bus minor" + */ + sprintf(priv->devName, "%sgrcan%d", prefix, dev->minor_bus); + } + + return DRVMGR_OK; +} + +int grcan_device_init(struct grcan_priv *pDev) +{ + struct amba_dev_info *ambadev; + struct ambapp_core *pnpinfo; + + /* Get device information from AMBA PnP information */ + ambadev = (struct amba_dev_info *)pDev->dev->businfo; + if ( ambadev == NULL ) { + return -1; + } + pnpinfo = &ambadev->info; + pDev->irq = pnpinfo->irq; + pDev->regs = (struct grcan_regs *)pnpinfo->apb_slv->start; + pDev->minor = pDev->dev->minor_drv; + + /* Get frequency in Hz */ + if ( drvmgr_freq_get(pDev->dev, DEV_APB_SLV, &pDev->corefreq_hz) ) { + return -1; + } + + DBG("GRCAN frequency: %d Hz\n", pDev->corefreq_hz); + + /* Reset Hardware before attaching IRQ handler */ + grcan_hw_reset(pDev->regs); + + /* RX Semaphore created with count = 0 */ + if ( rtems_semaphore_create(rtems_build_name('G', 'C', 'R', '0' + pDev->minor), + 0, + RTEMS_FIFO|RTEMS_SIMPLE_BINARY_SEMAPHORE|RTEMS_NO_INHERIT_PRIORITY|\ + RTEMS_LOCAL|RTEMS_NO_PRIORITY_CEILING, + 0, + &pDev->rx_sem) != RTEMS_SUCCESSFUL ) { + return RTEMS_INTERNAL_ERROR; + } + + /* TX Semaphore created with count = 0 */ + if ( rtems_semaphore_create(rtems_build_name('G', 'C', 'T', '0' + pDev->minor), + 0, + RTEMS_FIFO|RTEMS_SIMPLE_BINARY_SEMAPHORE|RTEMS_NO_INHERIT_PRIORITY|\ + RTEMS_LOCAL|RTEMS_NO_PRIORITY_CEILING, + 0, + &pDev->tx_sem) != RTEMS_SUCCESSFUL ) { + return RTEMS_INTERNAL_ERROR; + } + + /* TX Empty Semaphore created with count = 0 */ + if ( rtems_semaphore_create(rtems_build_name('G', 'C', 'E', '0' + pDev->minor), + 0, + RTEMS_FIFO|RTEMS_SIMPLE_BINARY_SEMAPHORE|RTEMS_NO_INHERIT_PRIORITY|\ + RTEMS_LOCAL|RTEMS_NO_PRIORITY_CEILING, + 0, + &pDev->txempty_sem) != RTEMS_SUCCESSFUL ) { + return RTEMS_INTERNAL_ERROR; + } + + /* Device Semaphore created with count = 1 */ + if ( rtems_semaphore_create(rtems_build_name('G', 'C', 'A', '0' + pDev->minor), + 1, + RTEMS_FIFO|RTEMS_SIMPLE_BINARY_SEMAPHORE|RTEMS_NO_INHERIT_PRIORITY|\ + RTEMS_LOCAL|RTEMS_NO_PRIORITY_CEILING, + 0, + &pDev->dev_sem) != RTEMS_SUCCESSFUL ) { + return RTEMS_INTERNAL_ERROR; + } + + return 0; +} + +static void __inline__ grcan_hw_reset(struct grcan_regs *regs) +{ + regs->ctrl = GRCAN_CTRL_RESET; +} + +static rtems_device_driver grcan_hw_start(struct grcan_priv *pDev) +{ + /* + * tmp is set but never used. GCC gives a warning for this + * and we need to tell GCC not to complain. + */ + unsigned int tmp RTEMS_UNUSED; + + SPIN_IRQFLAGS(oldLevel); + + FUNCDBG(); + + /* Check that memory has been allocated successfully */ + if (!pDev->tx || !pDev->rx) + return RTEMS_NO_MEMORY; + + /* Configure FIFO configuration register + * and Setup timing + */ + if (pDev->config_changed) { + grcan_hw_config(pDev->regs, &pDev->config); + pDev->config_changed = 0; + } + + /* Setup receiver */ + pDev->regs->rx0addr = (unsigned int)pDev->_rx_hw; + pDev->regs->rx0size = pDev->rxbuf_size; + + /* Setup Transmitter */ + pDev->regs->tx0addr = (unsigned int)pDev->_tx_hw; + pDev->regs->tx0size = pDev->txbuf_size; + + /* Setup acceptance filters */ + grcan_hw_accept(pDev->regs, &pDev->afilter); + + /* Sync filters */ + grcan_hw_sync(pDev->regs, &pDev->sfilter); + + /* Clear status bits */ + tmp = READ_REG(&pDev->regs->stat); + pDev->regs->stat = 0; + + /* Setup IRQ handling */ + + /* Clear all IRQs */ + tmp = READ_REG(&pDev->regs->pir); + pDev->regs->picr = 0x1ffff; + + /* unmask TxLoss|TxErrCntr|RxErrCntr|TxAHBErr|RxAHBErr|OR|OFF|PASS */ + pDev->regs->imr = 0x1601f; + + /* Enable routing of the IRQs */ + SPIN_LOCK_IRQ(&pDev->devlock, oldLevel); + IRQ_UNMASK(pDev->irq + GRCAN_IRQ_TXSYNC); + IRQ_UNMASK(pDev->irq + GRCAN_IRQ_RXSYNC); + IRQ_UNMASK(pDev->irq + GRCAN_IRQ_IRQ); + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + + /* Enable receiver/transmitter */ + pDev->regs->rx0ctrl = GRCAN_RXCTRL_ENABLE; + pDev->regs->tx0ctrl = GRCAN_TXCTRL_ENABLE; + + /* Enable HurriCANe core */ + pDev->regs->ctrl = GRCAN_CTRL_ENABLE; + + /* Leave transmitter disabled, it is enabled when + * trying to send something. + */ + return RTEMS_SUCCESSFUL; +} + +static void grcan_hw_stop(struct grcan_priv *pDev) +{ + FUNCDBG(); + + /* Mask all IRQs */ + pDev->regs->imr = 0; + IRQ_MASK(pDev->irq + GRCAN_IRQ_TXSYNC); + IRQ_MASK(pDev->irq + GRCAN_IRQ_RXSYNC); + IRQ_MASK(pDev->irq + GRCAN_IRQ_IRQ); + + /* Disable receiver & transmitter */ + pDev->regs->rx0ctrl = 0; + pDev->regs->tx0ctrl = 0; +} + +static void grcan_sw_stop(struct grcan_priv *pDev) +{ + /* + * Release semaphores to wake all threads waiting for an IRQ. + * The threads that + * get woken up must check started state in + * order to determine that they should return to + * user space with error status. + * + * Entering into started mode again will reset the + * semaphore count. + */ + rtems_semaphore_release(pDev->rx_sem); + rtems_semaphore_release(pDev->tx_sem); + rtems_semaphore_release(pDev->txempty_sem); +} + +static void grcan_hw_config(struct grcan_regs *regs, struct grcan_config *conf) +{ + unsigned int config = 0; + + /* Reset HurriCANe Core */ + regs->ctrl = 0; + + if (conf->silent) + config |= GRCAN_CFG_SILENT; + + if (conf->abort) + config |= GRCAN_CFG_ABORT; + + if (conf->selection.selection) + config |= GRCAN_CFG_SELECTION; + + if (conf->selection.enable0) + config |= GRCAN_CFG_ENABLE0; + + if (conf->selection.enable1) + config |= GRCAN_CFG_ENABLE1; + + /* Timing */ + config |= (conf->timing.bpr << GRCAN_CFG_BPR_BIT) & GRCAN_CFG_BPR; + config |= (conf->timing.rsj << GRCAN_CFG_RSJ_BIT) & GRCAN_CFG_RSJ; + config |= (conf->timing.ps1 << GRCAN_CFG_PS1_BIT) & GRCAN_CFG_PS1; + config |= (conf->timing.ps2 << GRCAN_CFG_PS2_BIT) & GRCAN_CFG_PS2; + config |= + (conf->timing.scaler << GRCAN_CFG_SCALER_BIT) & GRCAN_CFG_SCALER; + + /* Write configuration */ + regs->conf = config; + + /* Enable HurriCANe Core */ + regs->ctrl = GRCAN_CTRL_ENABLE; +} + +static void grcan_hw_accept( + struct grcan_regs *regs, + struct grcan_filter *afilter +) +{ + /* Disable Sync mask totaly (if we change scode or smask + * in an unfortunate way we may trigger a sync match) + */ + regs->rx0mask = 0xffffffff; + + /* Set Sync Filter in a controlled way */ + regs->rx0code = afilter->code; + regs->rx0mask = afilter->mask; +} + +static void grcan_hw_sync(struct grcan_regs *regs, struct grcan_filter *sfilter) +{ + /* Disable Sync mask totaly (if we change scode or smask + * in an unfortunate way we may trigger a sync match) + */ + regs->smask = 0xffffffff; + + /* Set Sync Filter in a controlled way */ + regs->scode = sfilter->code; + regs->smask = sfilter->mask; +} + +static unsigned int grcan_hw_rxavail( + unsigned int rp, + unsigned int wp, unsigned int size +) +{ + if (rp == wp) { + /* read pointer and write pointer is equal only + * when RX buffer is empty. + */ + return 0; + } + + if (wp > rp) { + return (wp - rp) / GRCAN_MSG_SIZE; + } else { + return (size - (rp - wp)) / GRCAN_MSG_SIZE; + } +} + +static unsigned int grcan_hw_txspace( + unsigned int rp, + unsigned int wp, + unsigned int size +) +{ + unsigned int left; + + if (rp == wp) { + /* read pointer and write pointer is equal only + * when TX buffer is empty. + */ + return size / GRCAN_MSG_SIZE - WRAP_AROUND_TX_MSGS; + } + + /* size - 4 - abs(read-write) */ + if (wp > rp) { + left = size - (wp - rp); + } else { + left = rp - wp; + } + + return left / GRCAN_MSG_SIZE - WRAP_AROUND_TX_MSGS; +} + +#define MIN_TSEG1 1 +#define MIN_TSEG2 2 +#define MAX_TSEG1 14 +#define MAX_TSEG2 8 + +static int grcan_calc_timing( + unsigned int baud, /* The requested BAUD to calculate timing for */ + unsigned int core_hz, /* Frequency in Hz of GRCAN Core */ + unsigned int sampl_pt, + struct grcan_timing *timing /* result is placed here */ +) +{ + int best_error = 1000000000; + int error; + int best_tseg = 0, best_brp = 0, brp = 0; + int tseg = 0, tseg1 = 0, tseg2 = 0; + int sjw = 1; + + /* Default to 90% */ + if ((sampl_pt < 50) || (sampl_pt > 99)) { + sampl_pt = GRCAN_SAMPLING_POINT; + } + + if ((baud < 5000) || (baud > 1000000)) { + /* invalid speed mode */ + return -1; + } + + /* find best match, return -2 if no good reg + * combination is available for this frequency + */ + + /* some heuristic specials */ + if (baud > ((1000000 + 500000) / 2)) + sampl_pt = 75; + + if (baud < ((12500 + 10000) / 2)) + sampl_pt = 75; + + /* tseg even = round down, odd = round up */ + for ( + tseg = (MIN_TSEG1 + MIN_TSEG2 + 2) * 2; + tseg <= (MAX_TSEG2 + MAX_TSEG1 + 2) * 2 + 1; + tseg++ + ) { + brp = core_hz / ((1 + tseg / 2) * baud) + tseg % 2; + if ( + (brp <= 0) || + ((brp > 256 * 1) && (brp <= 256 * 2) && (brp & 0x1)) || + ((brp > 256 * 2) && (brp <= 256 * 4) && (brp & 0x3)) || + ((brp > 256 * 4) && (brp <= 256 * 8) && (brp & 0x7)) || + (brp > 256 * 8) + ) + continue; + + error = baud - core_hz / (brp * (1 + tseg / 2)); + if (error < 0) { + error = -error; + } + + if (error <= best_error) { + best_error = error; + best_tseg = tseg / 2; + best_brp = brp - 1; + } + } + + if (best_error && (baud / best_error < 10)) { + return -2; + } else if (!timing) + return 0; /* nothing to store result in, but a valid bitrate can be calculated */ + + tseg2 = best_tseg - (sampl_pt * (best_tseg + 1)) / 100; + + if (tseg2 < MIN_TSEG2) { + tseg2 = MIN_TSEG2; + } + + if (tseg2 > MAX_TSEG2) { + tseg2 = MAX_TSEG2; + } + + tseg1 = best_tseg - tseg2 - 2; + + if (tseg1 > MAX_TSEG1) { + tseg1 = MAX_TSEG1; + tseg2 = best_tseg - tseg1 - 2; + } + + /* Get scaler and BRP from pseudo BRP */ + if (best_brp <= 256) { + timing->scaler = best_brp; + timing->bpr = 0; + } else if (best_brp <= 256 * 2) { + timing->scaler = ((best_brp + 1) >> 1) - 1; + timing->bpr = 1; + } else if (best_brp <= 256 * 4) { + timing->scaler = ((best_brp + 1) >> 2) - 1; + timing->bpr = 2; + } else { + timing->scaler = ((best_brp + 1) >> 3) - 1; + timing->bpr = 3; + } + + timing->ps1 = tseg1 + 1; + timing->ps2 = tseg2; + timing->rsj = sjw; + + return 0; +} + +static int grcan_hw_read_try( + struct grcan_priv *pDev, + struct grcan_regs *regs, + CANMsg * buffer, + int max +) +{ + int i, j; + CANMsg *dest; + struct grcan_msg *source, tmp; + unsigned int wp, rp, size, rxmax, addr; + int trunk_msg_cnt; + + FUNCDBG(); + + wp = READ_REG(®s->rx0wr); + rp = READ_REG(®s->rx0rd); + + /* + * Due to hardware wrap around simplification write pointer will + * never reach the read pointer, at least a gap of 8 bytes. + * The only time they are equal is when the read pointer has + * reached the write pointer (empty buffer) + * + */ + if (wp != rp) { + /* Not empty, we have received chars... + * Read as much as possible from DMA buffer + */ + size = READ_REG(®s->rx0size); + + /* Get number of bytes available in RX buffer */ + trunk_msg_cnt = grcan_hw_rxavail(rp, wp, size); + + /* truncate size if user space buffer hasn't room for + * all received chars. + */ + if (trunk_msg_cnt > max) + trunk_msg_cnt = max; + + /* Read until i is 0 */ + i = trunk_msg_cnt; + + addr = (unsigned int)pDev->rx; + source = (struct grcan_msg *)(addr + rp); + dest = buffer; + rxmax = addr + (size - GRCAN_MSG_SIZE); + + /* Read as many can messages as possible */ + while (i > 0) { + /* Read CAN message from DMA buffer */ + tmp.head[0] = READ_DMA_WORD(&source->head[0]); + tmp.head[1] = READ_DMA_WORD(&source->head[1]); + if (tmp.head[1] & 0x4) { + DBGC(DBG_RX, "overrun\n"); + } + if (tmp.head[1] & 0x2) { + DBGC(DBG_RX, "bus-off mode\n"); + } + if (tmp.head[1] & 0x1) { + DBGC(DBG_RX, "error-passive mode\n"); + } + /* Convert one grcan CAN message to one "software" CAN message */ + dest->extended = tmp.head[0] >> 31; + dest->rtr = (tmp.head[0] >> 30) & 0x1; + if (dest->extended) { + dest->id = tmp.head[0] & 0x3fffffff; + } else { + dest->id = (tmp.head[0] >> 18) & 0xfff; + } + dest->len = tmp.head[1] >> 28; + for (j = 0; j < dest->len; j++) + dest->data[j] = READ_DMA_BYTE(&source->data[j]); + + /* wrap around if neccessary */ + source = + ((unsigned int)source >= rxmax) ? + (struct grcan_msg *)addr : source + 1; + dest++; /* straight user buffer */ + i--; + } + { + /* A bus off interrupt may have occured after checking pDev->started */ + SPIN_IRQFLAGS(oldLevel); + + SPIN_LOCK_IRQ(&pDev->devlock, oldLevel); + if (pDev->started == STATE_STARTED) { + regs->rx0rd = (unsigned int) source - addr; + regs->rx0ctrl = GRCAN_RXCTRL_ENABLE; + } else { + DBGC(DBG_STATE, "cancelled due to a BUS OFF error\n"); + trunk_msg_cnt = state2err[pDev->started]; + } + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + } + return trunk_msg_cnt; + } + return 0; +} + +static int grcan_hw_write_try( + struct grcan_priv *pDev, + struct grcan_regs *regs, + CANMsg * buffer, + int count +) +{ + unsigned int rp, wp, size, txmax, addr; + int ret; + struct grcan_msg *dest; + CANMsg *source; + int space_left; + unsigned int tmp; + int i; + + DBGC(DBG_TX, "\n"); + /*FUNCDBG(); */ + + rp = READ_REG(®s->tx0rd); + wp = READ_REG(®s->tx0wr); + size = READ_REG(®s->tx0size); + + space_left = grcan_hw_txspace(rp, wp, size); + + /* is circular fifo full? */ + if (space_left < 1) + return 0; + + /* Truncate size */ + if (space_left > count) + space_left = count; + ret = space_left; + + addr = (unsigned int)pDev->tx; + + dest = (struct grcan_msg *)(addr + wp); + source = (CANMsg *) buffer; + txmax = addr + (size - GRCAN_MSG_SIZE); + + while (space_left > 0) { + /* Convert and write CAN message to DMA buffer */ + if (source->extended) { + tmp = (1 << 31) | (source->id & 0x3fffffff); + } else { + tmp = (source->id & 0xfff) << 18; + } + if (source->rtr) + tmp |= (1 << 30); + dest->head[0] = tmp; + dest->head[1] = source->len << 28; + for (i = 0; i < source->len; i++) + dest->data[i] = source->data[i]; + source++; /* straight user buffer */ + dest = + ((unsigned int)dest >= txmax) ? + (struct grcan_msg *)addr : dest + 1; + space_left--; + } + + { + /* A bus off interrupt may have occured after checking pDev->started */ + SPIN_IRQFLAGS(oldLevel); + + SPIN_LOCK_IRQ(&pDev->devlock, oldLevel); + if (pDev->started == STATE_STARTED) { + regs->tx0wr = (unsigned int) dest - addr; + regs->tx0ctrl = GRCAN_TXCTRL_ENABLE; + } else { + DBGC(DBG_STATE, "cancelled due to a BUS OFF error\n"); + ret = state2err[pDev->started]; + } + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + } + return ret; +} + +static int grcan_wait_rxdata(struct grcan_priv *pDev, int min) +{ + unsigned int wp, rp, size, irq; + unsigned int irq_trunk, dataavail; + int wait, state; + SPIN_IRQFLAGS(oldLevel); + + FUNCDBG(); + + /*** block until receive IRQ received + * Set up a valid IRQ point so that an IRQ is received + * when one or more messages are received + */ + SPIN_LOCK_IRQ(&pDev->devlock, oldLevel); + state = pDev->started; + + /* A bus off interrupt may have occured after checking pDev->started */ + if (state != STATE_STARTED) { + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + if (state == STATE_BUSOFF) { + DBGC(DBG_STATE, "cancelled due to a BUS OFF error\n"); + } else if (state == STATE_AHBERR) { + DBGC(DBG_STATE, "cancelled due to a AHB error\n"); + } else { + DBGC(DBG_STATE, "cancelled due to STOP (unexpected) \n"); + } + return state2err[state]; + } + + size = READ_REG(&pDev->regs->rx0size); + rp = READ_REG(&pDev->regs->rx0rd); + wp = READ_REG(&pDev->regs->rx0wr); + + /**** Calculate IRQ Pointer ****/ + irq = wp + min * GRCAN_MSG_SIZE; + /* wrap irq around */ + if (irq >= size) { + irq_trunk = irq - size; + } else + irq_trunk = irq; + + /* init IRQ HW */ + pDev->regs->rx0irq = irq_trunk; + + /* Clear pending Rx IRQ */ + pDev->regs->picr = GRCAN_RXIRQ_IRQ; + + wp = READ_REG(&pDev->regs->rx0wr); + + /* Calculate messages available */ + dataavail = grcan_hw_rxavail(rp, wp, size); + + if (dataavail < min) { + /* Still empty, proceed with sleep - Turn on IRQ (unmask irq) */ + pDev->regs->imr = READ_REG(&pDev->regs->imr) | GRCAN_RXIRQ_IRQ; + wait = 1; + } else { + /* enough message has been received, abort sleep - don't unmask interrupt */ + wait = 0; + } + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + + /* Wait for IRQ to fire only if has been triggered */ + if (wait) { + rtems_semaphore_obtain(pDev->rx_sem, RTEMS_WAIT, RTEMS_NO_TIMEOUT); + /* + * The semaphore is released either due to the expected IRQ + * condition or by BUSOFF, AHBERROR or another thread calling + * grcan_stop(). In either case, state2err[] has the correnct + * return value. + */ + return state2err[pDev->started]; + } + + return 0; +} + +/* Wait for TX circular buffer to have room for min CAN messagges. TXIRQ is used to pin + * point the location of the CAN message corresponding to min. + * + * min must be at least WRAP_AROUND_TX_MSGS less than max buffer capacity + * (pDev->txbuf_size/GRCAN_MSG_SIZE) for this algo to work. + */ +static int grcan_wait_txspace(struct grcan_priv *pDev, int min) +{ + int wait, state; + unsigned int irq, rp, wp, size, space_left; + unsigned int irq_trunk; + SPIN_IRQFLAGS(oldLevel); + + DBGC(DBG_TX, "\n"); + /*FUNCDBG(); */ + + SPIN_LOCK_IRQ(&pDev->devlock, oldLevel); + state = pDev->started; + /* A bus off interrupt may have occured after checking pDev->started */ + if (state != STATE_STARTED) { + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + if (state == STATE_BUSOFF) { + DBGC(DBG_STATE, "cancelled due to a BUS OFF error\n"); + } else if (state == STATE_AHBERR) { + DBGC(DBG_STATE, "cancelled due to a AHB error\n"); + } else { + DBGC(DBG_STATE, "cancelled due to STOP (unexpected)\n"); + } + return state2err[state]; + } + + pDev->regs->tx0ctrl = GRCAN_TXCTRL_ENABLE; + + size = READ_REG(&pDev->regs->tx0size); + wp = READ_REG(&pDev->regs->tx0wr); + + rp = READ_REG(&pDev->regs->tx0rd); + + /**** Calculate IRQ Pointer ****/ + irq = rp + min * GRCAN_MSG_SIZE; + /* wrap irq around */ + if (irq >= size) { + irq_trunk = irq - size; + } else + irq_trunk = irq; + + /* trigger HW to do a IRQ when enough room in buffer */ + pDev->regs->tx0irq = irq_trunk; + + /* Clear pending Tx IRQ */ + pDev->regs->picr = GRCAN_TXIRQ_IRQ; + + /* One problem, if HW already gone past IRQ place the IRQ will + * never be received resulting in a thread hang. We check if so + * before proceeding. + * + * has the HW already gone past the IRQ generation place? + * == does min fit info tx buffer? + */ + rp = READ_REG(&pDev->regs->tx0rd); + + space_left = grcan_hw_txspace(rp, wp, size); + + if (space_left < min) { + /* Still too full, proceed with sleep - Turn on IRQ (unmask irq) */ + pDev->regs->imr = READ_REG(&pDev->regs->imr) | GRCAN_TXIRQ_IRQ; + wait = 1; + } else { + /* There are enough room in buffer, abort wait - don't unmask interrupt */ + wait = 0; + } + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + + /* Wait for IRQ to fire only if it has been triggered */ + if (wait) { + rtems_semaphore_obtain(pDev->tx_sem, RTEMS_WAIT, RTEMS_NO_TIMEOUT); + return state2err[pDev->started]; + } + + /* At this point the TxIRQ has been masked, we ned not to mask it */ + return 0; +} + +static int grcan_tx_flush(struct grcan_priv *pDev) +{ + int wait, state; + unsigned int rp, wp; + SPIN_IRQFLAGS(oldLevel); + FUNCDBG(); + + /* loop until all data in circular buffer has been read by hw. + * (write pointer != read pointer ) + * + * Hardware doesn't update write pointer - we do + */ + while ( + (wp = READ_REG(&pDev->regs->tx0wr)) != + (rp = READ_REG(&pDev->regs->tx0rd)) + ) { + /* Wait for TX empty IRQ */ + SPIN_LOCK_IRQ(&pDev->devlock, oldLevel); + state = pDev->started; + + /* A bus off interrupt may have occured after checking pDev->started */ + if (state != STATE_STARTED) { + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + if (state == STATE_BUSOFF) { + DBGC(DBG_STATE, "cancelled due to a BUS OFF error\n"); + } else if (state == STATE_AHBERR) { + DBGC(DBG_STATE, "cancelled due to a AHB error\n"); + } else { + DBGC(DBG_STATE, "cancelled due to STOP (unexpected)\n"); + } + return state2err[state]; + } + + /* Clear pending TXEmpty IRQ */ + pDev->regs->picr = GRCAN_TXEMPTY_IRQ; + + if (wp != READ_REG(&pDev->regs->tx0rd)) { + /* Still not empty, proceed with sleep - Turn on IRQ (unmask irq) */ + pDev->regs->imr = + READ_REG(&pDev->regs->imr) | GRCAN_TXEMPTY_IRQ; + wait = 1; + } else { + /* TX fifo is empty */ + wait = 0; + } + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + if (!wait) + break; + + /* Wait for IRQ to wake us */ + rtems_semaphore_obtain(pDev->txempty_sem, RTEMS_WAIT, RTEMS_NO_TIMEOUT); + state = pDev->started; + if (state != STATE_STARTED) { + return state2err[state]; + } + } + return 0; +} + +static int grcan_alloc_buffers(struct grcan_priv *pDev, int rx, int tx) +{ + unsigned int adr; + FUNCDBG(); + + if ( tx ) { + adr = (unsigned int)pDev->txbuf_adr; + if (adr & 0x1) { + /* User defined "remote" address. Translate it into + * a CPU accessible address + */ + pDev->_tx_hw = (void *)(adr & ~0x1); + drvmgr_translate_check( + pDev->dev, + DMAMEM_TO_CPU, + (void *)pDev->_tx_hw, + (void **)&pDev->_tx, + pDev->txbuf_size); + pDev->tx = (struct grcan_msg *)pDev->_tx; + } else { + if (adr == 0) { + pDev->_tx = malloc(pDev->txbuf_size + + BUFFER_ALIGNMENT_NEEDS); + if (!pDev->_tx) + return -1; + } else { + /* User defined "cou-local" address. Translate + * it into a CPU accessible address + */ + pDev->_tx = (void *)adr; + } + /* Align TX buffer */ + pDev->tx = (struct grcan_msg *) + (((unsigned int)pDev->_tx + + (BUFFER_ALIGNMENT_NEEDS-1)) & + ~(BUFFER_ALIGNMENT_NEEDS-1)); + + /* Translate address into an hardware accessible + * address + */ + drvmgr_translate_check( + pDev->dev, + CPUMEM_TO_DMA, + (void *)pDev->tx, + (void **)&pDev->_tx_hw, + pDev->txbuf_size); + } + } + + if ( rx ) { + adr = (unsigned int)pDev->rxbuf_adr; + if (adr & 0x1) { + /* User defined "remote" address. Translate it into + * a CPU accessible address + */ + pDev->_rx_hw = (void *)(adr & ~0x1); + drvmgr_translate_check( + pDev->dev, + DMAMEM_TO_CPU, + (void *)pDev->_rx_hw, + (void **)&pDev->_rx, + pDev->rxbuf_size); + pDev->rx = (struct grcan_msg *)pDev->_rx; + } else { + if (adr == 0) { + pDev->_rx = malloc(pDev->rxbuf_size + + BUFFER_ALIGNMENT_NEEDS); + if (!pDev->_rx) + return -1; + } else { + /* User defined "cou-local" address. Translate + * it into a CPU accessible address + */ + pDev->_rx = (void *)adr; + } + /* Align RX buffer */ + pDev->rx = (struct grcan_msg *) + (((unsigned int)pDev->_rx + + (BUFFER_ALIGNMENT_NEEDS-1)) & + ~(BUFFER_ALIGNMENT_NEEDS-1)); + + /* Translate address into an hardware accessible + * address + */ + drvmgr_translate_check( + pDev->dev, + CPUMEM_TO_DMA, + (void *)pDev->rx, + (void **)&pDev->_rx_hw, + pDev->rxbuf_size); + } + } + return 0; +} + +static void grcan_free_buffers(struct grcan_priv *pDev, int rx, int tx) +{ + FUNCDBG(); + + if (tx && pDev->_tx) { + free(pDev->_tx); + pDev->_tx = NULL; + pDev->tx = NULL; + } + + if (rx && pDev->_rx) { + free(pDev->_rx); + pDev->_rx = NULL; + pDev->rx = NULL; + } +} + +int grcan_dev_count(void) +{ + return grcan_count; +} + +void *grcan_open_by_name(char *name, int *dev_no) +{ + int i; + for (i = 0; i < grcan_count; i++){ + struct grcan_priv *pDev; + + pDev = priv_tab[i]; + if (NULL == pDev) { + continue; + } + if (strncmp(pDev->devName, name, NELEM(pDev->devName)) == 0) { + if (dev_no) + *dev_no = i; + return grcan_open(i); + } + } + return NULL; +} + +void *grcan_open(int dev_no) +{ + struct grcan_priv *pDev; + void *ret; + union drvmgr_key_value *value; + + FUNCDBG(); + + if (grcan_count == 0 || (grcan_count <= dev_no)) { + return NULL; + } + + pDev = priv_tab[dev_no]; + + /* Wait until we get semaphore */ + if (rtems_semaphore_obtain(pDev->dev_sem, RTEMS_WAIT, RTEMS_NO_TIMEOUT) + != RTEMS_SUCCESSFUL) { + return NULL; + } + + /* is device busy/taken? */ + if ( pDev->open ) { + ret = NULL; + goto out; + } + + SPIN_INIT(&pDev->devlock, pDev->devName); + + /* Mark device taken */ + pDev->open = 1; + + pDev->txblock = pDev->rxblock = 1; + pDev->txcomplete = pDev->rxcomplete = 0; + pDev->started = STATE_STOPPED; + pDev->config_changed = 1; + pDev->config.silent = 0; + pDev->config.abort = 0; + pDev->config.selection.selection = 0; + pDev->config.selection.enable0 = 0; + pDev->config.selection.enable1 = 1; + pDev->flushing = 0; + pDev->rx = pDev->_rx = NULL; + pDev->tx = pDev->_tx = NULL; + pDev->txbuf_adr = 0; + pDev->rxbuf_adr = 0; + pDev->txbuf_size = TX_BUF_SIZE; + pDev->rxbuf_size = RX_BUF_SIZE; + + /* Override default buffer sizes if available from bus resource */ + value = drvmgr_dev_key_get(pDev->dev, "txBufSize", DRVMGR_KT_INT); + if ( value ) + pDev->txbuf_size = value->i; + + value = drvmgr_dev_key_get(pDev->dev, "rxBufSize", DRVMGR_KT_INT); + if ( value ) + pDev->rxbuf_size = value->i; + + value = drvmgr_dev_key_get(pDev->dev, "txBufAdr", DRVMGR_KT_POINTER); + if ( value ) + pDev->txbuf_adr = value->ptr; + + value = drvmgr_dev_key_get(pDev->dev, "rxBufAdr", DRVMGR_KT_POINTER); + if ( value ) + pDev->rxbuf_adr = value->ptr; + + DBG("Defaulting to rxbufsize: %d, txbufsize: %d\n",RX_BUF_SIZE,TX_BUF_SIZE); + + /* Default to accept all messages */ + pDev->afilter.mask = 0x00000000; + pDev->afilter.code = 0x00000000; + + /* Default to disable sync messages (only trigger when id is set to all ones) */ + pDev->sfilter.mask = 0xffffffff; + pDev->sfilter.code = 0x00000000; + + /* Calculate default timing register values */ + grcan_calc_timing(GRCAN_DEFAULT_BAUD,pDev->corefreq_hz,GRCAN_SAMPLING_POINT,&pDev->config.timing); + + if ( grcan_alloc_buffers(pDev,1,1) ) { + ret = NULL; + goto out; + } + + /* Clear statistics */ + memset(&pDev->stats,0,sizeof(struct grcan_stats)); + + ret = pDev; +out: + rtems_semaphore_release(pDev->dev_sem); + return ret; +} + +int grcan_close(void *d) +{ + struct grcan_priv *pDev = d; + + FUNCDBG(); + + grcan_stop(d); + + grcan_hw_reset(pDev->regs); + + grcan_free_buffers(pDev,1,1); + + /* Mark Device as closed */ + pDev->open = 0; + + return 0; +} + +int grcan_read(void *d, CANMsg *msg, size_t ucount) +{ + struct grcan_priv *pDev = d; + CANMsg *dest; + unsigned int count, left; + int nread; + int req_cnt; + + FUNCDBG(); + + dest = msg; + req_cnt = ucount; + + if ( (!dest) || (req_cnt<1) ) + return GRCAN_RET_INVARG; + + if (pDev->started != STATE_STARTED) { + return GRCAN_RET_NOTSTARTED; + } + + DBGC(DBG_RX, "grcan_read [%p]: buf: %p len: %u\n", d, msg, (unsigned int) ucount); + + nread = grcan_hw_read_try(pDev,pDev->regs,dest,req_cnt); + if (nread < 0) { + return nread; + } + count = nread; + if ( !( pDev->rxblock && pDev->rxcomplete && (count!=req_cnt) ) ){ + if ( count > 0 ) { + /* Successfully received messages (at least one) */ + return count; + } + + /* nothing read, shall we block? */ + if ( !pDev->rxblock ) { + /* non-blocking mode */ + return GRCAN_RET_TIMEOUT; + } + } + + while (count == 0 || (pDev->rxcomplete && (count!=req_cnt))) { + if (!pDev->rxcomplete) { + left = 1; /* return as soon as there is one message available */ + } else { + left = req_cnt - count; /* return as soon as all data are available */ + + /* never wait for more than the half the maximum size of the receive buffer + * Why? We need some time to copy buffer before to catch up with hw, + * otherwise we would have to copy everything when the data has been + * received. + */ + if (left > ((pDev->rxbuf_size/GRCAN_MSG_SIZE) / 2)){ + left = (pDev->rxbuf_size/GRCAN_MSG_SIZE) / 2; + } + } + + nread = grcan_wait_rxdata(pDev, left); + if (nread) { + /* The wait has been aborted, probably due to + * the device driver has been closed by another + * thread or a bus-off. Return error code. + */ + return nread; + } + + /* Try read bytes from circular buffer */ + nread = grcan_hw_read_try( + pDev, + pDev->regs, + dest+count, + req_cnt-count); + + if (nread < 0) { + /* The read was aborted by bus-off. */ + return nread; + } + count += nread; + } + /* no need to unmask IRQ as IRQ Handler do that for us. */ + return count; +} + +int grcan_write(void *d, CANMsg *msg, size_t ucount) +{ + struct grcan_priv *pDev = d; + CANMsg *source; + unsigned int count, left; + int nwritten; + int req_cnt; + + DBGC(DBG_TX,"\n"); + + if ((pDev->started != STATE_STARTED) || pDev->config.silent || pDev->flushing) + return GRCAN_RET_NOTSTARTED; + + req_cnt = ucount; + source = (CANMsg *) msg; + + /* check proper length and buffer pointer */ + if (( req_cnt < 1) || (source == NULL) ){ + return GRCAN_RET_INVARG; + } + + nwritten = grcan_hw_write_try(pDev,pDev->regs,source,req_cnt); + if (nwritten < 0) { + return nwritten; + } + count = nwritten; + if ( !(pDev->txblock && pDev->txcomplete && (count!=req_cnt)) ) { + if ( count > 0 ) { + /* Successfully transmitted chars (at least one char) */ + return count; + } + + /* nothing written, shall we block? */ + if ( !pDev->txblock ) { + /* non-blocking mode */ + return GRCAN_RET_TIMEOUT; + } + } + + /* if in txcomplete mode we need to transmit all chars */ + while((count == 0) || (pDev->txcomplete && (count!=req_cnt)) ){ + /*** block until room to fit all or as much of transmit buffer as possible + * IRQ comes. Set up a valid IRQ point so that an IRQ is received + * when we can put a chunk of data into transmit fifo + */ + if ( !pDev->txcomplete ){ + left = 1; /* wait for anything to fit buffer */ + }else{ + left = req_cnt - count; /* wait for all data to fit in buffer */ + + /* never wait for more than the half the maximum size of the transmit + * buffer + * Why? We need some time to fill buffer before hw catches up. + */ + if ( left > ((pDev->txbuf_size/GRCAN_MSG_SIZE)/2) ){ + left = (pDev->txbuf_size/GRCAN_MSG_SIZE)/2; + } + } + + nwritten = grcan_wait_txspace(pDev,left); + /* Wait until more room in transmit buffer */ + if ( nwritten ) { + /* The wait has been aborted, probably due to + * the device driver has been closed by another + * thread. To avoid deadlock we return directly + * with error status. + */ + return nwritten; + } + + /* Try read bytes from circular buffer */ + nwritten = grcan_hw_write_try( + pDev, + pDev->regs, + source+count, + req_cnt-count); + + if (nwritten < 0) { + /* Write was aborted by bus-off. */ + return nwritten; + } + count += nwritten; + } + /* no need to unmask IRQ as IRQ Handler do that for us. */ + + return count; +} + +int grcan_start(void *d) +{ + struct grcan_priv *pDev = d; + + FUNCDBG(); + + if (grcan_get_state(d) == STATE_STARTED) { + return -1; + } + + if ( (grcan_hw_start(pDev)) != RTEMS_SUCCESSFUL ){ + return -2; + } + + /* Clear semaphore state. This is to avoid effects from previous + * bus-off/stop where semahpores where flushed() but the count remained. + */ + rtems_semaphore_obtain(pDev->rx_sem, RTEMS_NO_WAIT, 0); + rtems_semaphore_obtain(pDev->tx_sem, RTEMS_NO_WAIT, 0); + rtems_semaphore_obtain(pDev->txempty_sem, RTEMS_NO_WAIT, 0); + + /* Read and write are now open... */ + pDev->started = STATE_STARTED; + DBGC(DBG_STATE, "STOPPED|BUSOFF|AHBERR->STARTED\n"); + + /* Register interrupt routine and enable IRQ at IRQ ctrl */ + drvmgr_interrupt_register(pDev->dev, 0, pDev->devName, + grcan_interrupt, pDev); + + return 0; +} + +int grcan_stop(void *d) +{ + struct grcan_priv *pDev = d; + SPIN_IRQFLAGS(oldLevel); + int do_sw_stop; + + FUNCDBG(); + + if (pDev->started == STATE_STOPPED) + return -1; + + SPIN_LOCK_IRQ(&pDev->devlock, oldLevel); + if (pDev->started == STATE_STARTED) { + grcan_hw_stop(pDev); + do_sw_stop = 1; + DBGC(DBG_STATE, "STARTED->STOPPED\n"); + } else { + /* + * started == STATE_[STOPPED|BUSOFF|AHBERR] so grcan_hw_stop() + * might already been called from ISR. + */ + DBGC(DBG_STATE, "[STOPPED|BUSOFF|AHBERR]->STOPPED\n"); + do_sw_stop = 0; + } + pDev->started = STATE_STOPPED; + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + + if (do_sw_stop) + grcan_sw_stop(pDev); + + /* Disable interrupts */ + drvmgr_interrupt_unregister(pDev->dev, 0, grcan_interrupt, pDev); + + return 0; +} + +int grcan_get_state(void *d) +{ + struct grcan_priv *pDev = d; + + FUNCDBG(); + + return pDev->started; +} + +int grcan_flush(void *d) +{ + struct grcan_priv *pDev = d; + int tmp; + + FUNCDBG(); + + if ((pDev->started != STATE_STARTED) || pDev->flushing || pDev->config.silent) + return -1; + + pDev->flushing = 1; + tmp = grcan_tx_flush(pDev); + pDev->flushing = 0; + if ( tmp ) { + /* The wait has been aborted, probably due to + * the device driver has been closed by another + * thread. + */ + return -1; + } + + return 0; +} + +int grcan_set_silent(void* d, int silent) +{ + struct grcan_priv *pDev = d; + + FUNCDBG(); + + if (pDev->started == STATE_STARTED) + return -1; + + pDev->config.silent = silent; + pDev->config_changed = 1; + + return 0; +} + +int grcan_set_abort(void* d, int abort) +{ + struct grcan_priv *pDev = d; + + FUNCDBG(); + + if (pDev->started == STATE_STARTED) + return -1; + + pDev->config.abort = abort; + /* This Configuration parameter doesn't need HurriCANe reset + * ==> no pDev->config_changed = 1; + */ + + return 0; +} + +int grcan_set_selection(void *d, const struct grcan_selection *selection) +{ + struct grcan_priv *pDev = d; + + FUNCDBG(); + + if (pDev->started == STATE_STARTED) + return -1; + + if ( !selection ) + return -2; + + pDev->config.selection = *selection; + pDev->config_changed = 1; + + return 0; +} + +int grcan_set_rxblock(void *d, int block) +{ + struct grcan_priv *pDev = d; + + FUNCDBG(); + + pDev->rxblock = block; + + return 0; +} + +int grcan_set_txblock(void *d, int block) +{ + struct grcan_priv *pDev = d; + + FUNCDBG(); + + pDev->txblock = block; + + return 0; +} + +int grcan_set_txcomplete(void *d, int complete) +{ + struct grcan_priv *pDev = d; + + FUNCDBG(); + + pDev->txcomplete = complete; + + return 0; +} + +int grcan_set_rxcomplete(void *d, int complete) +{ + struct grcan_priv *pDev = d; + + FUNCDBG(); + + pDev->rxcomplete = complete; + + return 0; +} + +int grcan_get_stats(void *d, struct grcan_stats *stats) +{ + struct grcan_priv *pDev = d; + SPIN_IRQFLAGS(oldLevel); + + FUNCDBG(); + + if ( !stats ) + return -1; + + SPIN_LOCK_IRQ(&pDev->devlock, oldLevel); + *stats = pDev->stats; + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + + return 0; +} + +int grcan_clr_stats(void *d) +{ + struct grcan_priv *pDev = d; + SPIN_IRQFLAGS(oldLevel); + + FUNCDBG(); + + SPIN_LOCK_IRQ(&pDev->devlock, oldLevel); + memset(&pDev->stats,0,sizeof(struct grcan_stats)); + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + + return 0; +} + +int grcan_set_speed(void *d, unsigned int speed) +{ + struct grcan_priv *pDev = d; + struct grcan_timing timing; + int ret; + + FUNCDBG(); + + /* cannot change speed during run mode */ + if (pDev->started == STATE_STARTED) + return -1; + + /* get speed rate from argument */ + ret = grcan_calc_timing(speed, pDev->corefreq_hz, GRCAN_SAMPLING_POINT, &timing); + if ( ret ) + return -2; + + /* save timing/speed */ + pDev->config.timing = timing; + pDev->config_changed = 1; + + return 0; +} + +int grcan_set_btrs(void *d, const struct grcan_timing *timing) +{ + struct grcan_priv *pDev = d; + + FUNCDBG(); + + /* Set BTR registers manually + * Read GRCAN/HurriCANe Manual. + */ + if (pDev->started == STATE_STARTED) + return -1; + + if ( !timing ) + return -2; + + pDev->config.timing = *timing; + pDev->config_changed = 1; + + return 0; +} + +int grcan_set_afilter(void *d, const struct grcan_filter *filter) +{ + struct grcan_priv *pDev = d; + + FUNCDBG(); + + if ( !filter ){ + /* Disable filtering - let all messages pass */ + pDev->afilter.mask = 0x0; + pDev->afilter.code = 0x0; + }else{ + /* Save filter */ + pDev->afilter = *filter; + } + /* Set hardware acceptance filter */ + grcan_hw_accept(pDev->regs,&pDev->afilter); + + return 0; +} + +int grcan_set_sfilter(void *d, const struct grcan_filter *filter) +{ + struct grcan_priv *pDev = d; + SPIN_IRQFLAGS(oldLevel); + + FUNCDBG(); + + if ( !filter ){ + /* disable TX/RX SYNC filtering */ + pDev->sfilter.mask = 0xffffffff; + pDev->sfilter.mask = 0; + + /* disable Sync interrupt */ + SPIN_LOCK_IRQ(&pDev->devlock, oldLevel); + pDev->regs->imr = READ_REG(&pDev->regs->imr) & ~(GRCAN_RXSYNC_IRQ|GRCAN_TXSYNC_IRQ); + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + }else{ + /* Save filter */ + pDev->sfilter = *filter; + + /* Enable Sync interrupt */ + SPIN_LOCK_IRQ(&pDev->devlock, oldLevel); + pDev->regs->imr = READ_REG(&pDev->regs->imr) | (GRCAN_RXSYNC_IRQ|GRCAN_TXSYNC_IRQ); + SPIN_UNLOCK_IRQ(&pDev->devlock, oldLevel); + } + /* Set Sync RX/TX filter */ + grcan_hw_sync(pDev->regs,&pDev->sfilter); + + return 0; +} + +int grcan_get_status(void* d, unsigned int *data) +{ + struct grcan_priv *pDev = d; + + FUNCDBG(); + + if ( !data ) + return -1; + + /* Read out the statsu register from the GRCAN core */ + data[0] = READ_REG(&pDev->regs->stat); + + return 0; +} + +/* Error indicators */ +#define GRCAN_IRQ_ERRORS \ + (GRCAN_RXAHBERR_IRQ | GRCAN_TXAHBERR_IRQ | GRCAN_OFF_IRQ) +#define GRCAN_STAT_ERRORS (GRCAN_STAT_AHBERR | GRCAN_STAT_OFF) +/* Warning & RX/TX sync indicators */ +#define GRCAN_IRQ_WARNS \ + (GRCAN_ERR_IRQ | GRCAN_OR_IRQ | GRCAN_TXLOSS_IRQ | \ + GRCAN_RXSYNC_IRQ | GRCAN_TXSYNC_IRQ) +#define GRCAN_STAT_WARNS (GRCAN_STAT_OR | GRCAN_STAT_PASS) + +/* Handle the IRQ */ +static void grcan_interrupt(void *arg) +{ + struct grcan_priv *pDev = arg; + unsigned int status = READ_REG(&pDev->regs->pimsr); + unsigned int canstat = READ_REG(&pDev->regs->stat); + unsigned int imr_clear; + SPIN_ISR_IRQFLAGS(irqflags); + + /* Spurious IRQ call? */ + if ( !status && !canstat ) + return; + + if (pDev->started != STATE_STARTED) { + DBGC(DBG_STATE, "not STARTED (unexpected interrupt)\n"); + pDev->regs->picr = status; + return; + } + + FUNCDBG(); + + if ( (status & GRCAN_IRQ_ERRORS) || (canstat & GRCAN_STAT_ERRORS) ) { + /* Bus-off condition interrupt + * The link is brought down by hardware, we wake all threads + * that is blocked in read/write calls and stop futher calls + * to read/write until user has called ioctl(fd,START,0). + */ + SPIN_LOCK(&pDev->devlock, irqflags); + DBGC(DBG_STATE, "STARTED->BUSOFF|AHBERR\n"); + pDev->stats.ints++; + if ((status & GRCAN_OFF_IRQ) || (canstat & GRCAN_STAT_OFF)) { + /* CAN Bus-off interrupt */ + DBGC(DBG_STATE, "BUSOFF: status: 0x%x, canstat: 0x%x\n", + status, canstat); + pDev->started = STATE_BUSOFF; + pDev->stats.busoff_cnt++; + } else { + /* RX or Tx AHB Error interrupt */ + printk("AHBERROR: status: 0x%x, canstat: 0x%x\n", + status, canstat); + pDev->started = STATE_AHBERR; + pDev->stats.ahberr_cnt++; + } + grcan_hw_stop(pDev); /* this mask all IRQ sources */ + pDev->regs->picr = 0x1ffff; /* clear all interrupts */ + /* + * Prevent driver from affecting bus. Driver can be started + * again with grcan_start(). + */ + SPIN_UNLOCK(&pDev->devlock, irqflags); + + /* Release semaphores to wake blocked threads. */ + grcan_sw_stop(pDev); + + /* + * NOTE: Another interrupt may be pending now so ISR could be + * executed one more time aftert this (first) return. + */ + return; + } + + /* Mask interrupts in one place under spin-lock. */ + imr_clear = status & (GRCAN_RXIRQ_IRQ | GRCAN_TXIRQ_IRQ | GRCAN_TXEMPTY_IRQ); + + SPIN_LOCK(&pDev->devlock, irqflags); + + /* Increment number of interrupts counter */ + pDev->stats.ints++; + if ((status & GRCAN_IRQ_WARNS) || (canstat & GRCAN_STAT_WARNS)) { + + if ( (status & GRCAN_ERR_IRQ) || (canstat & GRCAN_STAT_PASS) ) { + /* Error-Passive interrupt */ + pDev->stats.passive_cnt++; + } + + if ( (status & GRCAN_OR_IRQ) || (canstat & GRCAN_STAT_OR) ) { + /* Over-run during reception interrupt */ + pDev->stats.overrun_cnt++; + } + + if ( status & GRCAN_TXLOSS_IRQ ) { + pDev->stats.txloss_cnt++; + } + + if ( status & GRCAN_TXSYNC_IRQ ) { + /* TxSync message transmitted interrupt */ + pDev->stats.txsync_cnt++; + } + + if ( status & GRCAN_RXSYNC_IRQ ) { + /* RxSync message received interrupt */ + pDev->stats.rxsync_cnt++; + } + } + + if (imr_clear) { + pDev->regs->imr = READ_REG(&pDev->regs->imr) & ~imr_clear; + + SPIN_UNLOCK(&pDev->devlock, irqflags); + + if ( status & GRCAN_RXIRQ_IRQ ) { + /* RX IRQ pointer interrupt */ + rtems_semaphore_release(pDev->rx_sem); + } + + if ( status & GRCAN_TXIRQ_IRQ ) { + /* TX IRQ pointer interrupt */ + rtems_semaphore_release(pDev->tx_sem); + } + + if (status & GRCAN_TXEMPTY_IRQ ) { + rtems_semaphore_release(pDev->txempty_sem); + } + } else { + SPIN_UNLOCK(&pDev->devlock, irqflags); + } + + /* Clear IRQs */ + pDev->regs->picr = status; +} |