// ----------------------------------------------------------------------------------------
//
//  avr/can/mcp2515.cpp
//    
//  Bob Cook Development, Robotics Library
//  http://www.bobcookdev.com/rl/
//    
//  This file implements CAN support using the Microchip MCP2515 via SPI.
//
//  Copyright (C) 2007 Bob Cook
//
//      This program is free software; you can redistribute it and/or modify it
//      under the terms of the GNU General Public License as published by the
//      Free Software Foundation; either version 2 of the License, or (at your
//      option) any later version.
//    
//      This program is distributed in the hope that it will be useful, but 
//      WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
//      or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
//      for more details.
//    
//      You should have received a copy of the GNU General Public License along
//      with this program; if not, write to the Free Software Foundation, Inc., 
//      51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
// ----------------------------------------------------------------------------------------


#include "mcp2515.h"

#include <avr/io.h>

#include "packages/avr/device/int_helpers.h"
#include "packages/avr/device/spi.h"
#include "packages/avr/device/spinwait.h"

#include "packages/common/util/cbuffer.h"


// ----------------------------------------------------------------------------------------

#include "project_defs.h"

#if defined( PRJ_MCP2515_ENABLE_DEBUG )

#include <stdio.h>
#include <avr/pgmspace.h>

#endif


#if defined( PRJ_MCP2515_ENABLE )


// ----------------------------------------------------------------------------------------

#if !defined( PRJ_MCP2515_SELECT_DDR )
#error PRJ_MCP2515_SELECT_DDR is required!
#endif

#if !defined( PRJ_MCP2515_SELECT_PORT )
#error PRJ_MCP2515_SELECT_PORT is required!
#endif

#if !defined( PRJ_MCP2515_SELECT_PIN )
#error PRJ_MCP2515_SELECT_PIN is required!
#endif

#if defined( PRJ_MCP2515_USE_HW_RESET )

#if !defined( PRJ_MCP2515_RESET_DDR )
#error PRJ_MCP2515_RESET_DDR is required!
#endif

#if !defined( PRJ_MCP2515_RESET_PORT )
#error PRJ_MCP2515_RESET_PORT is required!
#endif

#if !defined( PRJ_MCP2515_RESET_PIN )
#error PRJ_MCP2515_RESET_PIN is required!
#endif

#endif  // PRJ_MCP2515_USE_HW_RESET

#if defined( PRJ_MCP2515_USE_INTERRUPT_RXTX )

#if !defined( PRJ_MCP2515_RX_BUFFER_SIZE )
#define PRJ_MCP2515_RX_BUFFER_SIZE 4
#endif

#if !defined( PRJ_MCP2515_TX_BUFFER_SIZE )
#define PRJ_MCP2515_TX_BUFFER_SIZE 6
#endif

#endif  // PRJ_MCP2515_USE_INTERRUPT_RXTX


// ----------------------------------------------------------------------------------------
// MCP2515 command definitions

const uint8_t MCP_COMMAND_WRITE  = 0x02;
const uint8_t MCP_COMMAND_READ   = 0x03;
const uint8_t MCP_COMMAND_BITMOD = 0x05;

const uint8_t MCP_COMMAND_LOAD_TX0 = 0x40;
const uint8_t MCP_COMMAND_LOAD_TX1 = 0x42;
const uint8_t MCP_COMMAND_LOAD_TX2 = 0x44;

const uint8_t MCP_COMMAND_RTS_TX0 = 0x81;
const uint8_t MCP_COMMAND_RTS_TX1 = 0x82;
const uint8_t MCP_COMMAND_RTS_TX2 = 0x84;
const uint8_t MCP_COMMAND_RTS_ALL = 0x87;

const uint8_t MCP_COMMAND_READ_RX0 = 0x90;
const uint8_t MCP_COMMAND_READ_RX1 = 0x94;

const uint8_t MCP_COMMAND_READ_STATUS = 0xa0;
const uint8_t MCP_COMMAND_RX_STATUS   = 0xb0;

const uint8_t MCP_COMMAND_RESET = 0xc0;


// ----------------------------------------------------------------------------------------
// MCP2515 mode definitions

const uint8_t MCP_MODE_NORMAL     = 0x00;
const uint8_t MCP_MODE_SLEEP      = 0x20;
const uint8_t MCP_MODE_LOOPBACK   = 0x40;
const uint8_t MCP_MODE_LISTENONLY = 0x60;
const uint8_t MCP_MODE_CONFIG     = 0x80;
const uint8_t MCP_MODE_POWERUP    = 0xE0;


// ----------------------------------------------------------------------------------------
// MCP2515 interrupt enable & flag definitions

const uint8_t MCP_INTERRUPT_MERR = ( 1 << 7 );
const uint8_t MCP_INTERRUPT_WAKI = ( 1 << 6 );
const uint8_t MCP_INTERRUPT_ERRI = ( 1 << 5 );
const uint8_t MCP_INTERRUPT_TX2I = ( 1 << 4 );
const uint8_t MCP_INTERRUPT_TX1I = ( 1 << 3 );
const uint8_t MCP_INTERRUPT_TX0I = ( 1 << 2 );
const uint8_t MCP_INTERRUPT_RX1I = ( 1 << 1 );
const uint8_t MCP_INTERRUPT_RX0I = ( 1 << 0 );


// ----------------------------------------------------------------------------------------
// MCP2515 register definitions

const uint8_t MCP_REGISTER_RXF0SIDH = 0x00;
const uint8_t MCP_REGISTER_RXF0SIDL = 0x01;
const uint8_t MCP_REGISTER_RXF0EID8 = 0x02;
const uint8_t MCP_REGISTER_RXF0EID0 = 0x03;

const uint8_t MCP_REGISTER_RXF1SIDH = 0x04;
const uint8_t MCP_REGISTER_RXF1SIDL = 0x05;
const uint8_t MCP_REGISTER_RXF1EID8 = 0x06;
const uint8_t MCP_REGISTER_RXF1EID0 = 0x07;

const uint8_t MCP_REGISTER_RXF2SIDH = 0x08;
const uint8_t MCP_REGISTER_RXF2SIDL = 0x09;
const uint8_t MCP_REGISTER_RXF2EID8 = 0x0a;
const uint8_t MCP_REGISTER_RXF2EID0 = 0x0b;

const uint8_t MCP_REGISTER_RXF3SIDH = 0x10;
const uint8_t MCP_REGISTER_RXF3SIDL = 0x11;
const uint8_t MCP_REGISTER_RXF3EID8 = 0x12;
const uint8_t MCP_REGISTER_RXF3EID0 = 0x13;

const uint8_t MCP_REGISTER_RXF4SIDH = 0x14;
const uint8_t MCP_REGISTER_RXF4SIDL = 0x15;
const uint8_t MCP_REGISTER_RXF4EID8 = 0x16;
const uint8_t MCP_REGISTER_RXF4EID0 = 0x17;

const uint8_t MCP_REGISTER_RXF5SIDH = 0x18;
const uint8_t MCP_REGISTER_RXF5SIDL = 0x19;
const uint8_t MCP_REGISTER_RXF5EID8 = 0x1a;
const uint8_t MCP_REGISTER_RXF5EID0 = 0x1b;

const uint8_t MCP_REGISTER_TEC = 0x1c;
const uint8_t MCP_REGISTER_REC = 0x1d;

const uint8_t MCP_REGISTER_CNF1 = 0x2a;
const uint8_t MCP_REGISTER_CNF2 = 0x29;
const uint8_t MCP_REGISTER_CNF3 = 0x28;

const uint8_t MCP_REGISTER_CANINTE = 0x2B;
const uint8_t MCP_REGISTER_CANINTF = 0x2C;

const uint8_t MCP_REGISTER_TXB0CTRL = 0x30;
const uint8_t MCP_REGISTER_TXB0SIDH = 0x31;
const uint8_t MCP_REGISTER_TXB0SIDL = 0x32;
const uint8_t MCP_REGISTER_TXB0EID8 = 0x33;
const uint8_t MCP_REGISTER_TXB0EID0 = 0x34;
const uint8_t MCP_REGISTER_TXB0DLC  = 0x35;
const uint8_t MCP_REGISTER_TXB0DATA = 0x36;

const uint8_t MCP_REGISTER_TXB1CTRL = 0x40;
const uint8_t MCP_REGISTER_TXB1SIDH = 0x41;
const uint8_t MCP_REGISTER_TXB1SIDL = 0x42;
const uint8_t MCP_REGISTER_TXB1EID8 = 0x43;
const uint8_t MCP_REGISTER_TXB1EID0 = 0x44;
const uint8_t MCP_REGISTER_TXB1DLC  = 0x45;
const uint8_t MCP_REGISTER_TXB1DATA = 0x46;

const uint8_t MCP_REGISTER_TXB2CTRL = 0x50;
const uint8_t MCP_REGISTER_TXB2SIDH = 0x51;
const uint8_t MCP_REGISTER_TXB2SIDL = 0x52;
const uint8_t MCP_REGISTER_TXB2EID8 = 0x53;
const uint8_t MCP_REGISTER_TXB2EID0 = 0x54;
const uint8_t MCP_REGISTER_TXB2DLC  = 0x55;
const uint8_t MCP_REGISTER_TXB2DATA = 0x56;

const uint8_t MCP_REGISTER_RXB0CTRL = 0x60;
const uint8_t MCP_REGISTER_RXB0SIDH = 0x61;
const uint8_t MCP_REGISTER_RXB0SIDL = 0x62;
const uint8_t MCP_REGISTER_RXB0EID8 = 0x63;
const uint8_t MCP_REGISTER_RXB0EID0 = 0x64;
const uint8_t MCP_REGISTER_RXB0DLC  = 0x65;
const uint8_t MCP_REGISTER_RXB0DATA = 0x66;

const uint8_t MCP_REGISTER_RXB1CTRL = 0x70;
const uint8_t MCP_REGISTER_RXB1SIDH = 0x71;
const uint8_t MCP_REGISTER_RXB1SIDL = 0x72;
const uint8_t MCP_REGISTER_RXB1EID8 = 0x73;
const uint8_t MCP_REGISTER_RXB1EID0 = 0x74;
const uint8_t MCP_REGISTER_RXB1DLC  = 0x75;
const uint8_t MCP_REGISTER_RXB1DATA = 0x76;

const uint8_t MCP_REGISTER_TXRTSCTRL = 0x0d;
const uint8_t MCP_REGISTER_CANSTAT   = 0x0e;
const uint8_t MCP_REGISTER_CANCTRL   = 0x0f;


// ----------------------------------------------------------------------------------------

#if defined( PRJ_MCP2515_USE_INTERRUPT_RXTX )

typedef struct
{
    uint32_t id;
    uint8_t  priority;
    uint8_t  length;
    uint8_t  data[ 8 ];

}   mcp2515_data_t;

static cbuffer< mcp2515_data_t, uint8_t, PRJ_MCP2515_RX_BUFFER_SIZE > g_rx_buffer;
static cbuffer< mcp2515_data_t, uint8_t, PRJ_MCP2515_TX_BUFFER_SIZE > g_tx_buffer;

#endif  // PRJ_MCP2515_USE_INTERRUPT_RXTX


// ----------------------------------------------------------------------------------------

inline void device_select()
{
    PRJ_MCP2515_SELECT_PORT &= ~( 1 << PRJ_MCP2515_SELECT_PIN );
}

inline void device_unselect()
{
    PRJ_MCP2515_SELECT_PORT |= ( 1 << PRJ_MCP2515_SELECT_PIN );
}


// ----------------------------------------------------------------------------------------

static uint8_t read_register( uint8_t address )
{
    uint8_t result;

    device_select();

    spi_write( MCP_COMMAND_READ );
    spi_write( address );
    result = spi_read();

    device_unselect();

    return result;
}


// ----------------------------------------------------------------------------------------

static void read_register( uint8_t address, uint8_t* values, uint8_t count )
{
    device_select();

    spi_write( MCP_COMMAND_READ );
    spi_write( address );

    while ( count > 0 ) 
    {
        *values++ = spi_read();
        --count;
    }

    device_unselect();
}


// ----------------------------------------------------------------------------------------

static void write_register( uint8_t address, uint8_t value )
{
    device_select();
    
    spi_write( MCP_COMMAND_WRITE );
    spi_write( address );
    spi_write( value );

    device_unselect();
}


// ----------------------------------------------------------------------------------------

static void write_register( uint8_t address, const uint8_t* values, uint8_t count )
{
    device_select();

    spi_write( MCP_COMMAND_WRITE );
    spi_write( address );

    while ( count > 0 )
    {
        spi_write( *values++ );
        --count;
    }

    device_unselect();
}


// ----------------------------------------------------------------------------------------

static void modify_register( uint8_t address, uint8_t mask, const uint8_t value )
{
    device_select();
    
    spi_write( MCP_COMMAND_BITMOD );
    spi_write( address );
    spi_write( mask );
    spi_write( value );

    device_unselect();
}


// ----------------------------------------------------------------------------------------
//  mapping the four register bytes to the uint32_t CAN identifier: 
//
//  + the first eleven bits contain the standard ID
//  + the next eighteen bits contain the extended ID
//
//      00000000000000000000000000000000  uint32_t
//
//      3   2   2   1   1   1   7   3  0
//      1   7   3   9   5   1
//
//                           +--+------+  std
//         +-+------++------+             ext
//
//                           76543210     SIDH
//         10                        765  SIDL
//           76543210                     EID8
//                   76543210             EID0

static uint32_t get_can_id( uint8_t sidh_register )
{
    uint8_t address_data[ 4 ];  // SIDH, SIDL, EID8, EID0

    read_register( sidh_register, address_data, 4 );

    uint32_t result;
     
    result  = static_cast< uint32_t >( address_data[ 0 ] ) << 3;
    result |= static_cast< uint32_t >( address_data[ 1 ] ) >> 5;
    result |= static_cast< uint32_t >( address_data[ 1 ] & 0x3 ) << 27;
    result |= static_cast< uint32_t >( address_data[ 3 ] ) << 11;
    result |= static_cast< uint32_t >( address_data[ 2 ] ) << 19;

    return result;
}


// ----------------------------------------------------------------------------------------
//  uses the same format as for get_can_id()

static void set_can_id( uint8_t sidh_register, uint32_t id )
{
    uint8_t address_data[ 4 ];  // SIDH, SIDL, EID8, EID0

    address_data[ 0 ] = static_cast< uint8_t >( id >> 3 );
    address_data[ 1 ] = static_cast< uint8_t >( id ) << 5;
    id >>= 11;
    address_data[ 3 ] = static_cast< uint8_t >( id );
    id >>= 8;
    address_data[ 2 ] = static_cast< uint8_t >( id );
    id >>= 8;
    address_data[ 1 ] |= static_cast< uint8_t >( id ) & 0x3;
    address_data[ 1 ] |= ( 1 << 3 ); // EXIDE

    write_register( sidh_register, address_data, 4 );
}


// ----------------------------------------------------------------------------------------

#if defined( PRJ_MCP2515_USE_INTERRUPT_RXTX )

static void read_into_rx_buffer( uint8_t rx_ctrl_register )    
{
    mcp2515_data_t  msg;

    //--    Copy out the message identifier.

    msg.id = get_can_id( rx_ctrl_register + 1 /* SIDH */ );

    //--    Retrieve the request flag or data.

    uint8_t dlc = read_register( rx_ctrl_register + 5 /* DLC */ );

    if ( dlc & ( 1 << 6 ) ) // RTR bit
    {
        msg.length = 0xff;  // signifies this is a request rather than a message
    }
    else
    {
        msg.length = dlc;
        read_register( rx_ctrl_register + 6, msg.data, dlc );
    }
    
    //--    Add it to the queue.

    g_rx_buffer.push( msg );
}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_MCP2515_USE_INTERRUPT_RXTX )

static void send_from_tx_buffer()
{
    mcp2515_data_t  msg = g_tx_buffer.pop();

    //--    Load the destination address.

    set_can_id( MCP_REGISTER_TXB0CTRL + 1 /* SIDH */, msg.id );

    //--    Request or message? Set up the registers appropriately.

    if ( msg.length == 0xff )
    {
        write_register( MCP_REGISTER_TXB0CTRL + 5 /* DLC */, ( 1 << 6 ) );
    }
    else
    {
        //--    Load the message contents.

        write_register( MCP_REGISTER_TXB0CTRL + 5 /* DLC */, msg.length );

        write_register( MCP_REGISTER_TXB0CTRL + 6 /* DATA */,
                        msg.data, 
                        msg.length );
    }

    //--    Fire off the transmit.

    modify_register( MCP_REGISTER_TXB0CTRL, 0x0b, ( 0x08 | msg.priority ) );
}

#endif


// ----------------------------------------------------------------------------------------

#if !defined( PRJ_MCP2515_USE_INTERRUPT_RXTX )

static bool find_empty_transmit_buffer( uint8_t* txbufctrl )
{
    if ( ( read_register( MCP_REGISTER_TXB0CTRL ) & ( 1 << 3 ) ) == 0 )
    {
        *txbufctrl = MCP_REGISTER_TXB0CTRL;
        return true;
    }
    else if ( ( read_register( MCP_REGISTER_TXB1CTRL ) & ( 1 << 3 ) ) == 0 )
    {
        *txbufctrl = MCP_REGISTER_TXB1CTRL;
        return true;
    }
    else if ( ( read_register( MCP_REGISTER_TXB2CTRL ) & ( 1 << 3 ) ) == 0 )
    {
        *txbufctrl = MCP_REGISTER_TXB2CTRL;
        return true;
    }

    return false;
}
    
#endif


// ----------------------------------------------------------------------------------------
    
bool mcp2515_init()
{
    //--    Configure the SPI hardware; the caller might have done it but it doesn't
    //      hurt to do it again.

    spi_init();

    //--    Configure our select line.

    PRJ_MCP2515_SELECT_DDR |= ( 1 << PRJ_MCP2515_SELECT_PIN );
    device_unselect();

    //--    Configure our reset line (if supplied); it should be pulled high normally.

#if defined( PRJ_MCP2515_USE_HW_RESET )

    PRJ_MCP2515_RESET_DDR  |= ( 1 << PRJ_MCP2515_RESET_PIN );
    PRJ_MCP2515_RESET_PORT |= ( 1 << PRJ_MCP2515_RESET_PIN );

#endif
    
    //--    Reset the MCP2515 chip.

    mcp2515_reset();

    //--    Set mode to configuration operation.
    
    modify_register( MCP_REGISTER_CANCTRL, 0xe0, 0x80 );
    
    if ( ( read_register( MCP_REGISTER_CANCTRL ) & 0xe0 ) != 0x80 )
    {
        return false;
    }

    //--    Set up the bus speed via the bit timing registers; this depends on the input
    //      clock frequency for the chip.

#if defined( PRJ_MCP2515_CANBUS_20_KHZ ) && defined( PRJ_MCP2515_FREQUENCY_4_MHZ )

    //--    SWJ = 0 (1 x TQ), BRP = 4

    write_register( MCP_REGISTER_CNF1, ( ( 0 << 6 ) | ( 4 ) ) );

    //--    BLTMODE = 1, SAM = 0, PHSEG1 = 7 (8 x TQ), PRSEG = 2 (3 x TQ)

    write_register( MCP_REGISTER_CNF2, ( ( 1 << 7 ) | ( 0 << 6 ) | ( 7 << 3 ) | ( 2 ) ) );
        
    //--    SOF = 0, WAKFIL = 0, PHSEG2 = 7 (8 x TQ)

    write_register( MCP_REGISTER_CNF3, ( ( 0 << 7 ) | ( 0 << 6 ) | ( 7 ) ) );


#elif defined( PRJ_MCP2515_CANBUS_125_KHZ ) && defined( PRJ_MCP2515_FREQUENCY_4_MHZ )

    //--    SWJ = 0 (1 x TQ), BRP = 0

    write_register( MCP_REGISTER_CNF1, ( ( 0 << 6 ) | ( 0 ) ) );

    //--    BLTMODE = 1, SAM = 0, PHSEG1 = 5 (6 x TQ), PRSEG = 2 (3 x TQ)

    write_register( MCP_REGISTER_CNF2, ( ( 1 << 7 ) | ( 0 << 6 ) | ( 5 << 3 ) | ( 2 ) ) );
        
    //--    SOF = 0, WAKFIL = 0, PHSEG2 = 5 (6 x TQ)

    write_register( MCP_REGISTER_CNF3, ( ( 0 << 7 ) | ( 0 << 6 ) | ( 5 ) ) );


#elif defined( PRJ_MCP2515_CANBUS_20_KHZ ) && defined( PRJ_MCP2515_FREQUENCY_16_MHZ )

    //--    SWJ = 0 (1 x TQ), BRP = 19

    write_register( MCP_REGISTER_CNF1, ( ( 0 << 6 ) | ( 19 ) ) );

    //--    BLTMODE = 1, SAM = 0, PHSEG1 = 7 (8 x TQ), PRSEG = 2 (3 x TQ)

    write_register( MCP_REGISTER_CNF2, ( ( 1 << 7 ) | ( 0 << 6 ) | ( 7 << 3 ) | ( 2 ) ) );
        
    //--    SOF = 0, WAKFIL = 0, PHSEG2 = 7 (8 x TQ)

    write_register( MCP_REGISTER_CNF3, ( ( 0 << 7 ) | ( 0 << 6 ) | ( 7 ) ) );


#elif defined( PRJ_MCP2515_CANBUS_125_KHZ ) && defined( PRJ_MCP2515_FREQUENCY_16_MHZ )

    //--    SWJ = 0 (1 x TQ), BRP = 3

    write_register( MCP_REGISTER_CNF1, ( ( 0 << 6 ) | ( 3 ) ) );

    //--    BLTMODE = 1, SAM = 0, PHSEG1 = 5 (6 x TQ), PRSEG = 2 (3 x TQ)

    write_register( MCP_REGISTER_CNF2, ( ( 1 << 7 ) | ( 0 << 6 ) | ( 5 << 3 ) | ( 2 ) ) );
        
    //--    SOF = 0, WAKFIL = 0, PHSEG2 = 5 (6 x TQ)

    write_register( MCP_REGISTER_CNF3, ( ( 0 << 7 ) | ( 0 << 6 ) | ( 5 ) ) );


#else
#error Unsupported CAN bus speed selection!
#endif

    //--    Reset the three transmit buffers.

    write_register( MCP_REGISTER_TXB0CTRL, 0x00 );
    write_register( MCP_REGISTER_TXB1CTRL, 0x00 );
    write_register( MCP_REGISTER_TXB2CTRL, 0x00 );

    //--    Reset the two receive buffers.

    write_register( MCP_REGISTER_RXB0CTRL, 0x00 );
    write_register( MCP_REGISTER_RXB1CTRL, 0x00 );

    //--    Clear all receive filters.

    set_can_id( MCP_REGISTER_RXF0SIDH, 0x0000 );
    set_can_id( MCP_REGISTER_RXF1SIDH, 0x0000 );
    set_can_id( MCP_REGISTER_RXF2SIDH, 0x0000 );
    set_can_id( MCP_REGISTER_RXF3SIDH, 0x0000 );
    set_can_id( MCP_REGISTER_RXF4SIDH, 0x0000 );
    set_can_id( MCP_REGISTER_RXF5SIDH, 0x0000 );

    //--    Enable both receive-buffers to receive messages with standard and extended ids;
    //      enable message receive rollover.

    write_register( MCP_REGISTER_RXB0CTRL,
                    ( ( 1 << 6 )          // RMX1
                    | ( 1 << 5 )          // RMX0
                    | ( 0 << 3 )          // RXRTR
                    | ( 1 << 2 )          // BUKT
                    | ( 0 << 1 )          // BUKT1
                    | ( 0 << 0 ) ) );     // FILHIT0

    write_register( MCP_REGISTER_RXB1CTRL,
                    ( ( 1 << 6 )          // RMX1
                    | ( 1 << 5 )          // RMX0
                    | ( 0 << 3 )          // RXRTR
                    | ( 0 << 2 )          // BUKT
                    | ( 0 << 1 )          // BUKT1
                    | ( 0 << 0 ) ) );     // FILHIT0

    //--    Set up the interrupt-driven RX and TX, if required.

#if defined( PRJ_MCP2515_USE_INTERRUPT_RXTX )

    modify_register( MCP_REGISTER_CANINTF, MCP_INTERRUPT_TX0I, 0xff );
    modify_register( MCP_REGISTER_CANINTE, MCP_INTERRUPT_RX0I, 0xff );

#endif

    //--    Set mode to normal operation.

    modify_register( MCP_REGISTER_CANCTRL, 0xe0, 0x00 );

    if ( ( read_register( MCP_REGISTER_CANCTRL ) & 0xe0 ) != 0x00 )
    {
        return false;
    }

    return true;
}


// ----------------------------------------------------------------------------------------
    
void mcp2515_reset()
{
    //--    Do a hardware reset if we have hardware support; otherwise do it via software.

#if defined( PRJ_MCP2515_USE_HW_RESET )

    PRJ_MCP2515_RESET_PORT &= ~( 1 << PRJ_MCP2515_RESET_PIN );
    spinwait_delay_ms( 10 );
    PRJ_MCP2515_RESET_PORT |= ( 1 << PRJ_MCP2515_RESET_PIN );
    spinwait_delay_ms( 10 );

#else

    //--    Wait for the hardware to stabilize; this isn't required on resets other than
    //      the first, but it doesn't hurt.

    spinwait_delay_ms( 10 );

    device_select();
    spi_write( MCP_COMMAND_RESET );
    device_unselect();
    spinwait_delay_ms( 10 );    // approximately long enough

#endif

    //--    Reset sets up configuration mode, switch back to normal operation.

    modify_register( MCP_REGISTER_CANCTRL, 0xe0, 0x00 );
}


// ----------------------------------------------------------------------------------------

#if defined( PRJ_MCP2515_USE_INTERRUPT_RXTX ) || defined( PRJ_MCP2515_USE_INTERRUPT_ERR )

void mcp2515_process_interrupt()
{
    //--    The exact nature of the interrupt is contained in the CANINTF register.

    uint8_t canintf = read_register( MCP_REGISTER_CANINTF );

    if ( canintf & MCP_INTERRUPT_RX1I )
    {
        read_into_rx_buffer( MCP_REGISTER_RXB1CTRL );
        modify_register( MCP_REGISTER_CANINTF, MCP_INTERRUPT_RX1I, 0x00 );
    }

    if ( canintf & MCP_INTERRUPT_RX0I )
    {
        read_into_rx_buffer( MCP_REGISTER_RXB0CTRL );
        modify_register( MCP_REGISTER_CANINTF, MCP_INTERRUPT_RX0I, 0x00 );
    }
    
    if ( canintf & MCP_INTERRUPT_TX0I )
    {
        if ( g_tx_buffer.is_empty() )
        {
            modify_register( MCP_REGISTER_CANINTE, MCP_INTERRUPT_TX0I, 0x00 );
        }
        else
        {
            modify_register( MCP_REGISTER_CANINTF, MCP_INTERRUPT_TX0I, 0x00 );
            send_from_tx_buffer();
        }
    }
}

#endif


// ----------------------------------------------------------------------------------------

bool mcp2515_read    
    (
        uint32_t* can_id,
        bool*     is_request,
        uint8_t*  data_ptr,
        uint8_t*  length
    )
{
    
#if defined( PRJ_MCP2515_USE_INTERRUPT_RXTX )

    //--    Pending message?

    if ( g_rx_buffer.is_empty() )
    {
        return false;
    }

    //--    Fetch it from the queue and copy it out.

    mcp2515_data_t  msg = g_rx_buffer.pop();

    *can_id = msg.id;
    
    if ( msg.length == 0xff )
    {
        *is_request = true;
    }
    else
    {
        *is_request = false;
        *length     = msg.length;

        for ( uint8_t i = 0; i < msg.length; i++ )
        {
            *data_ptr++ = msg.data[ i ];
        }
    }

    return true;

#else

    //--    Look for a pending message.

    uint8_t read_status = read_register( MCP_REGISTER_CANINTF );
    uint8_t rx_ctrl_register;

    if ( ( read_status & 0x01 ) != 0 ) // RX0IF
    {
        rx_ctrl_register = MCP_REGISTER_RXB0CTRL;
    }
    else if ( ( read_status & 0x02 ) != 0 ) // RX1IF
    {
        rx_ctrl_register = MCP_REGISTER_RXB1CTRL;
    }
    else
    {
        return false;
    }

    //--    Copy out the message identifier.

    *can_id = get_can_id( rx_ctrl_register + 1 ); // SIDH

    //--    Retrieve the request flag or data.

    uint8_t dlc = read_register( rx_ctrl_register + 5 ); // DLC

    if ( dlc & ( 1 << 6 ) ) // RTR bit
    {
        *is_request = true;
    }
    else
    {
        *is_request = false;
        *length = dlc;
        read_register( rx_ctrl_register + 6, data_ptr, dlc );
    }

    //--    Clear the message pending flag.

    if ( rx_ctrl_register == MCP_REGISTER_RXB0CTRL )
    {
        modify_register( MCP_REGISTER_CANINTF, 0x01, 0x00 ); // RX0IF
    }
    else if ( rx_ctrl_register == MCP_REGISTER_RXB1CTRL )
    {
        modify_register( MCP_REGISTER_CANINTF, 0x02, 0x00 ); // RX1IF
    }

    return true;

#endif  // PRJ_MCP2515_USE_INTERRUPT_RXTX

}


// ----------------------------------------------------------------------------------------

bool mcp2515_send
    (
        const uint32_t& can_id,
        const uint8_t*  data_ptr,
        uint8_t         length,
        uint8_t         priority
    )
{
    
#if defined( PRJ_MCP2515_USE_INTERRUPT_RXTX )

    //--    Got space in the buffer?

    if ( g_tx_buffer.is_full() )
    {
        return false;
    }

    //--    Add the message to the buffer, then enable the interrupt to trigger a
    //      transmit. Note this must be done with interrupts disabled to avoid a
    //      race condition that hangs here.

    mcp2515_data_t  msg;
    
    msg.id       = can_id;
    msg.priority = priority;
    msg.length   = length;

    for ( uint8_t i = 0; i < length; i++ )
    {
        msg.data[ i ] = *data_ptr++;
    }
    
    g_tx_buffer.push( msg );

    {
        guard_disable_interrupts no_ints;

        modify_register( MCP_REGISTER_CANINTE, MCP_INTERRUPT_TX0I, 0xff );
    }

    return true;

#else

    //--    Find an empty buffer.

    uint8_t tx_ctrl_register;

    if ( ! find_empty_transmit_buffer( &tx_ctrl_register ) )
    {
        return false;
    }

    //--    Load the destination address.

    set_can_id( tx_ctrl_register + 1 /* SIDH */, can_id );

    //--    Load the message contents.

    write_register( tx_ctrl_register + 5, // DLC 
                    length );

    write_register( tx_ctrl_register + 6, // DATA
                    data_ptr, 
                    length );

    //--    Fire off the transmit.

    modify_register( tx_ctrl_register, 0x0b, ( 0x08 | priority ) );

    return true;

#endif  // PRJ_MCP2515_USE_INTERRUPT_RXTX

}


// ----------------------------------------------------------------------------------------
    
bool mcp2515_request( const uint32_t& can_id, uint8_t priority )
{
    
#if defined( PRJ_MCP2515_USE_INTERRUPT_RXTX )

    //--    Got space in the buffer?

    if ( g_tx_buffer.is_full() )
    {
        return false;
    }

    //--    Add the message to the buffer, then enable the interrupt to trigger a
    //      transmit. Note this must be done with interrupts disabled to avoid a
    //      race condition that hangs here.

    mcp2515_data_t  msg;
    
    msg.id       = can_id;
    msg.priority = priority;
    msg.length   = 0xff; // request, not an actual message
    
    g_tx_buffer.push( msg );

    {
        guard_disable_interrupts no_ints;

        modify_register( MCP_REGISTER_CANINTE, MCP_INTERRUPT_TX0I, 0xff );
    }

    return true;

#else

    //--    Find an empty buffer.

    uint8_t tx_ctrl_register;

    if ( ! find_empty_transmit_buffer( &tx_ctrl_register ) )
    {
        return false;
    }

    //--    Load the destination address.

    set_can_id( tx_ctrl_register + 1 /* SIDH */, can_id );

    //--    Set the RTR bit in the DLC register.

    write_register( tx_ctrl_register + 5 /* DLC */, ( 1 << 6 ) );

    //--    Fire off the transmit.

    modify_register( tx_ctrl_register, 0x0b, ( 0x08 | priority ) );

    return true;

#endif  // PRJ_MCP2515_USE_INTERRUPT_RXTX

}


// ----------------------------------------------------------------------------------------

#if defined( PRJ_MCP2515_ENABLE_DEBUG )
    
static void print_register( const prog_char* label, uint8_t address )
{
    uint8_t value = read_register( address );

    printf_P( label );
    
    for ( uint8_t i = 0; i < 8; i++ )
    {
        if ( value & 0x80 )
        {
            putchar( '1' );
        }
        else
        {
            putchar( '0' );
        }
        value = value << 1;
    }

    putchar( '\n' );
}

void mcp2515_debug_print_registers()
{
    printf_P( PSTR("MCP2515 registers:\n") );

    print_register( PSTR("    TXRTSCTRL: "), MCP_REGISTER_TXRTSCTRL );
    print_register( PSTR("    CANSTAT  : "), MCP_REGISTER_CANSTAT );
    print_register( PSTR("    CANCTRL  : "), MCP_REGISTER_CANCTRL );
    putchar( '\n' );

    print_register( PSTR("    TXB0CTRL : "), MCP_REGISTER_TXB0CTRL );
    print_register( PSTR("    TXB0SIDH : "), MCP_REGISTER_TXB0SIDH );
    print_register( PSTR("    TXB0SIDL : "), MCP_REGISTER_TXB0SIDL );
    print_register( PSTR("    TXB0EID8 : "), MCP_REGISTER_TXB0EID8 );
    print_register( PSTR("    TXB0EID0 : "), MCP_REGISTER_TXB0EID0 );
    printf_P( PSTR("    TXB0DLC  : %u"), read_register( MCP_REGISTER_TXB0DLC ) );
    putchar( '\n' );
    
    print_register( PSTR("    TXB1CTRL : "), MCP_REGISTER_TXB1CTRL );
    print_register( PSTR("    TXB1SIDH : "), MCP_REGISTER_TXB1SIDH );
    print_register( PSTR("    TXB1SIDL : "), MCP_REGISTER_TXB1SIDL );
    print_register( PSTR("    TXB1EID8 : "), MCP_REGISTER_TXB1EID8 );
    print_register( PSTR("    TXB1EID0 : "), MCP_REGISTER_TXB1EID0 );
    printf_P( PSTR("    TXB1DLC  : %u"), read_register( MCP_REGISTER_TXB1DLC ) );
    putchar( '\n' );
    
    print_register( PSTR("    TXB2CTRL : "), MCP_REGISTER_TXB2CTRL );
    print_register( PSTR("    TXB2SIDH : "), MCP_REGISTER_TXB2SIDH );
    print_register( PSTR("    TXB2SIDL : "), MCP_REGISTER_TXB2SIDL );
    print_register( PSTR("    TXB2EID8 : "), MCP_REGISTER_TXB2EID8 );
    print_register( PSTR("    TXB2EID0 : "), MCP_REGISTER_TXB2EID0 );
    printf_P( PSTR("    TXB2DLC  : %u"), read_register( MCP_REGISTER_TXB2DLC ) );
    putchar( '\n' );
    
    print_register( PSTR("    RXB0CTRL : "), MCP_REGISTER_RXB0CTRL );
    print_register( PSTR("    RXB0SIDH : "), MCP_REGISTER_RXB0SIDH );
    print_register( PSTR("    RXB0SIDL : "), MCP_REGISTER_RXB0SIDL );
    print_register( PSTR("    RXB0EID8 : "), MCP_REGISTER_RXB0EID8 );
    print_register( PSTR("    RXB0EID0 : "), MCP_REGISTER_RXB0EID0 );
    printf_P( PSTR("    RXB0DLC  : %u"), read_register( MCP_REGISTER_RXB0DLC ) );
    putchar( '\n' );
    
    print_register( PSTR("    RXB1CTRL : "), MCP_REGISTER_RXB1CTRL );
    print_register( PSTR("    RXB1SIDH : "), MCP_REGISTER_RXB1SIDH );
    print_register( PSTR("    RXB1SIDL : "), MCP_REGISTER_RXB1SIDL );
    print_register( PSTR("    RXB1EID8 : "), MCP_REGISTER_RXB1EID8 );
    print_register( PSTR("    RXB1EID0 : "), MCP_REGISTER_RXB1EID0 );
    printf_P( PSTR("    RXB0DLC  : %u"), read_register( MCP_REGISTER_RXB1DLC ) );
    putchar( '\n' );
    
    for ( uint8_t i = 0; i < 0x80; i++ )
    {
        if ( ( ( i % 0x10 ) == 0 ) && ( i != 0 ) )
        {
            putchar( '\n' );
            printf_P( PSTR("0x%X: "), i );
        }
        else if ( i == 0 )
        {
            printf_P( PSTR("0x00: ") );
        }
    
        uint8_t reg = read_register( i );
    
        printf_P( PSTR("0x") );
        if ( reg < 0x10 )
        {
            putchar( '0' );
        }
        printf_P( PSTR("%X "), reg );
    }

    putchar( '\n' );
}

#endif  // PRJ_MCP2515_ENABLE_DEBUG


// ----------------------------------------------------------------------------------------

#endif  // PRJ_MCP2515_ENABLE


// ----------------------------------------------------------------------------------------
//  end of mcp2515.cpp
// ----------------------------------------------------------------------------------------

