blob: c0c2e3a43c1c8c50e70bab589bdce79bd530dabf [file] [log] [blame]
/*******************************************************************************
* (c) Copyright 2012 Microsemi SoC Products Group. All rights reserved.
*
* SmartFusion2 COMBLK access functions.
*
* SVN $Revision: 5615 $
* SVN $Date: 2013-04-05 14:48:10 +0100 (Fri, 05 Apr 2013) $
*/
#include "mss_comblk.h"
#include "../../CMSIS/mss_assert.h"
/*==============================================================================
*
*/
/*------------------------------------------------------------------------------
* Control register bit masks.
*/
#define CR_FLUSHOUT_MASK 0x01u
#define CR_SIZETX_MASK 0x04u
#define CR_ENABLE_MASK 0x10u
#define CR_LOOPBACK_MASK 0x20u
/*------------------------------------------------------------------------------
* Status and interrupt enable registers bit masks.
*/
#define TXTOKAY_MASK 0x01u
#define RCVOKAY_MASK 0x02u
/*------------------------------------------------------------------------------
* DATA8 register bit masks.
*/
#define DATA8_COMMAND_MASK 0x8000u
/*------------------------------------------------------------------------------
* COMBLK driver states.
*/
#define COMBLK_IDLE 0u
#define COMBLK_TX_CMD 1u
#define COMBLK_TX_DATA 2u
#define COMBLK_WAIT_RESPONSE 3u
#define COMBLK_RX_RESPONSE 4u
#define COMBLK_TX_PAGED_DATA 5u
/*==============================================================================
* COMBLK interrupt servcie routine.
*/
void ComBlk_IRQHandler(void);
/*==============================================================================
* Local functions.
*/
static void abort_current_cmd(void);
static void send_cmd_opcode(uint8_t opcode);
static uint32_t fill_tx_fifo(const uint8_t * p_cmd, uint32_t cmd_size);
static void handle_tx_okay_irq(void);
static void handle_rx_okay_irq(void);
static void complete_request(uint16_t response_length);
static void process_sys_ctrl_command(uint8_t cmd_opcode);
/*==============================================================================
* Global variables:
*/
static volatile uint8_t g_comblk_cmd_opcode = 0u;
static const uint8_t * g_comblk_p_cmd = 0u;
static volatile uint16_t g_comblk_cmd_size = 0u;
static const uint8_t * g_comblk_p_data = 0u;
static volatile uint32_t g_comblk_data_size = 0u;
static uint8_t * g_comblk_p_response = 0u;
static uint16_t g_comblk_response_size = 0u;
static volatile uint16_t g_comblk_response_idx = 0u;
static comblk_completion_handler_t g_comblk_completion_handler = 0;
/*typedef uint32_t (*comblk_page_handler_t)(uint8_t const ** pp_next_page);
*/
static uint32_t (*g_comblk_page_handler)(uint8_t const ** pp_next_page) = 0;
static uint8_t g_request_in_progress = 0u;
static uint8_t g_comblk_state = COMBLK_IDLE;
static comblk_async_event_handler_t g_async_event_handler = 0;
/*==============================================================================
*
*/
void MSS_COMBLK_init(comblk_async_event_handler_t async_event_handler)
{
/*
* Disable and clear previous interrupts.
*/
NVIC_DisableIRQ(ComBlk_IRQn);
COMBLK->INT_ENABLE = 0u;
NVIC_ClearPendingIRQ(ComBlk_IRQn);
g_async_event_handler = async_event_handler;
/*
* Initialialize COMBLK driver state variables:
*/
g_request_in_progress = 0u;
g_comblk_cmd_opcode = 0u;
g_comblk_p_cmd = 0u;
g_comblk_cmd_size = 0u;
g_comblk_p_data = 0u;
g_comblk_data_size = 0u;
g_comblk_p_response = 0u;
g_comblk_response_size = 0u;
g_comblk_response_idx = 0u;
g_comblk_completion_handler = 0;
g_comblk_state = COMBLK_IDLE;
COMBLK->CONTROL |= CR_ENABLE_MASK;
COMBLK->CONTROL &= ~CR_LOOPBACK_MASK;
/*--------------------------------------------------------------------------
* Enable receive interrupt to receive asynchronous events from the system
* controller.
*/
COMBLK->INT_ENABLE &= ~TXTOKAY_MASK;
COMBLK->INT_ENABLE |= RCVOKAY_MASK;
NVIC_EnableIRQ(ComBlk_IRQn);
}
/*==============================================================================
*
*/
void MSS_COMBLK_send_cmd_with_ptr
(
uint8_t cmd_opcode,
uint32_t cmd_params_ptr,
uint8_t * p_response,
uint16_t response_size,
comblk_completion_handler_t completion_handler
)
{
uint32_t tx_okay;
/*--------------------------------------------------------------------------
* Disable and clear previous interrupts.
*/
NVIC_DisableIRQ(ComBlk_IRQn);
COMBLK->INT_ENABLE = 0u;
NVIC_ClearPendingIRQ(ComBlk_IRQn);
/*--------------------------------------------------------------------------
* Abort current command if any.
*/
abort_current_cmd();
/*--------------------------------------------------------------------------
* Initialialize COMBLK driver state variables:
*/
g_request_in_progress = 1u;
g_comblk_cmd_opcode = cmd_opcode;
g_comblk_p_cmd = 0u;
g_comblk_cmd_size = 0u;
g_comblk_p_data = 0u;
g_comblk_data_size = 0u;
g_comblk_p_response = p_response;
g_comblk_response_size = response_size;
g_comblk_response_idx = 0u;
g_comblk_page_handler = 0u;
g_comblk_completion_handler = completion_handler;
/*--------------------------------------------------------------------------
* Send command opcode as a single byte write to the Tx FIFO.
*/
send_cmd_opcode(g_comblk_cmd_opcode);
/*--------------------------------------------------------------------------
* Send the command parameters pointer to the Tx FIFO as a single 4 bytes
* write to the Tx FIFO.
*/
COMBLK->CONTROL |= CR_SIZETX_MASK;
/* Wait for space to become available in Tx FIFO. */
do {
tx_okay = COMBLK->STATUS & TXTOKAY_MASK;
} while(0u == tx_okay);
/* Send command opcode. */
COMBLK->DATA32 = cmd_params_ptr;
COMBLK->CONTROL &= ~CR_SIZETX_MASK;
g_comblk_state = COMBLK_WAIT_RESPONSE;
/*--------------------------------------------------------------------------
* Enable interrupt.
*/
COMBLK->INT_ENABLE |= RCVOKAY_MASK;
NVIC_EnableIRQ(ComBlk_IRQn);
}
/*==============================================================================
*
*/
void MSS_COMBLK_send_cmd
(
const uint8_t * p_cmd,
uint16_t cmd_size,
const uint8_t * p_data,
uint32_t data_size,
uint8_t * p_response,
uint16_t response_size,
comblk_completion_handler_t completion_handler
)
{
uint32_t size_sent;
ASSERT(cmd_size > 0);
/*
* Disable and clear previous interrupts.
*/
NVIC_DisableIRQ(ComBlk_IRQn);
COMBLK->INT_ENABLE = 0u;
NVIC_ClearPendingIRQ(ComBlk_IRQn);
/*
* Abort current command if any.
*/
abort_current_cmd();
/*
* Initialialize COMBLK driver state variables:
*/
g_request_in_progress = 1u;
g_comblk_cmd_opcode = p_cmd[0];
g_comblk_p_cmd = p_cmd;
g_comblk_cmd_size = cmd_size;
g_comblk_p_data = p_data;
g_comblk_data_size = data_size;
g_comblk_p_response = p_response;
g_comblk_response_size = response_size;
g_comblk_response_idx = 0u;
g_comblk_page_handler = 0u;
g_comblk_completion_handler = completion_handler;
COMBLK->INT_ENABLE |= RCVOKAY_MASK;
/*
* Fill FIFO with command.
*/
send_cmd_opcode(g_comblk_cmd_opcode);
size_sent = fill_tx_fifo(&p_cmd[1], cmd_size - 1u);
++size_sent; /* Adjust for opcode byte sent. */
if(size_sent < cmd_size)
{
g_comblk_cmd_size = g_comblk_cmd_size - (uint16_t)size_sent;
g_comblk_p_cmd = &g_comblk_p_cmd[size_sent];
g_comblk_state = COMBLK_TX_CMD;
}
else
{
g_comblk_cmd_size = 0u;
if(g_comblk_data_size > 0u)
{
g_comblk_state = COMBLK_TX_DATA;
}
else
{
g_comblk_state = COMBLK_WAIT_RESPONSE;
}
}
/*
* Enable interrupt.
*/
NVIC_EnableIRQ(ComBlk_IRQn);
}
/*==============================================================================
*
*/
void MSS_COMBLK_send_paged_cmd
(
const uint8_t * p_cmd,
uint16_t cmd_size,
uint8_t * p_response,
uint16_t response_size,
uint32_t (*page_read_handler)(uint8_t const **),
void (*completion_handler)(uint8_t *, uint16_t)
)
{
uint32_t size_sent;
uint8_t irq_enable = 0u;
ASSERT(cmd_size > 0u);
/*
* Disable and clear previous interrupts.
*/
NVIC_DisableIRQ(ComBlk_IRQn);
COMBLK->INT_ENABLE = 0u;
NVIC_ClearPendingIRQ(ComBlk_IRQn);
/*
* Abort current command if any.
*/
abort_current_cmd();
/*
* Initialialize COMBLK driver state variables:
*/
g_request_in_progress = 1u;
g_comblk_cmd_opcode = p_cmd[0];
g_comblk_p_cmd = p_cmd;
g_comblk_cmd_size = cmd_size;
g_comblk_p_data = 0;
g_comblk_data_size = 0u;
g_comblk_p_response = p_response;
g_comblk_response_size = response_size;
g_comblk_response_idx = 0u;
g_comblk_page_handler = page_read_handler;
g_comblk_completion_handler = completion_handler;
/*
* Fill FIFO with command.
*/
send_cmd_opcode(g_comblk_cmd_opcode);
size_sent = fill_tx_fifo(&p_cmd[1], cmd_size - 1u);
++size_sent; /* Adjust for opcode byte sent. */
if(size_sent < cmd_size)
{
g_comblk_cmd_size = g_comblk_cmd_size - (uint16_t)size_sent;
g_comblk_p_cmd = &g_comblk_p_cmd[size_sent];
g_comblk_state = COMBLK_TX_CMD;
irq_enable = TXTOKAY_MASK | RCVOKAY_MASK;
}
else
{
g_comblk_cmd_size = 0u;
g_comblk_state = COMBLK_TX_PAGED_DATA;
irq_enable = TXTOKAY_MASK | RCVOKAY_MASK;
}
/*
* Enable interrupt.
*/
COMBLK->INT_ENABLE |= irq_enable;
NVIC_EnableIRQ(ComBlk_IRQn);
}
/*==============================================================================
* COMBLK interrupt handler.
*/
void ComBlk_IRQHandler(void)
{
uint8_t status;
uint8_t tx_okay;
uint8_t rcv_okay;
status = (uint8_t)COMBLK->STATUS;
/* Mask off interrupt that are not enabled.*/
status &= COMBLK->INT_ENABLE;
rcv_okay = status & RCVOKAY_MASK;
if(rcv_okay)
{
handle_rx_okay_irq();
}
tx_okay = status & TXTOKAY_MASK;
if(tx_okay)
{
handle_tx_okay_irq();
}
}
/*==============================================================================
*
*/
static void handle_tx_okay_irq(void)
{
switch(g_comblk_state)
{
/*----------------------------------------------------------------------
* The TX_OKAY interrupt should only be enabled for states COMBLK_TX_CMD
* and COMBLK_TX_DATA.
*/
case COMBLK_TX_CMD:
if(g_comblk_cmd_size > 0u)
{
uint32_t size_sent;
size_sent = fill_tx_fifo(g_comblk_p_cmd, g_comblk_cmd_size);
if(size_sent < g_comblk_cmd_size)
{
g_comblk_cmd_size = g_comblk_cmd_size - (uint16_t)size_sent;
g_comblk_p_cmd = &g_comblk_p_cmd[size_sent];
}
else
{
g_comblk_cmd_size = 0u;
if(g_comblk_data_size > 0u)
{
g_comblk_state = COMBLK_TX_DATA;
}
else
{
g_comblk_state = COMBLK_WAIT_RESPONSE;
}
}
}
else
{
/*
* This is an invalid situation indicating a bug in the driver
* or corrupted memory.
*/
ASSERT(0);
abort_current_cmd();
}
break;
case COMBLK_TX_DATA:
if(g_comblk_data_size > 0u)
{
uint32_t size_sent;
size_sent = fill_tx_fifo(g_comblk_p_data, g_comblk_data_size);
if(size_sent < g_comblk_data_size)
{
g_comblk_data_size = g_comblk_data_size - size_sent;
g_comblk_p_data = &g_comblk_p_data[size_sent];
}
else
{
COMBLK->INT_ENABLE &= ~TXTOKAY_MASK;
g_comblk_state = COMBLK_WAIT_RESPONSE;
}
}
else
{
/*
* This is an invalid situation indicating a bug in the driver
* or corrupted memory.
*/
ASSERT(0);
abort_current_cmd();
}
break;
case COMBLK_TX_PAGED_DATA:
/*
* Read a page of data if required.
*/
if(0u == g_comblk_data_size)
{
if(g_comblk_page_handler != 0)
{
g_comblk_data_size = g_comblk_page_handler(&g_comblk_p_data);
if(0u == g_comblk_data_size)
{
COMBLK->INT_ENABLE &= ~TXTOKAY_MASK;
g_comblk_state = COMBLK_WAIT_RESPONSE;
}
}
else
{
ASSERT(0);
abort_current_cmd();
}
}
/*
* Transmit the page data or move to COMBLK_WAIT_RESPONSE state if
* no further page data could be obtained by the call to the page
* handler above.
*/
if(0u == g_comblk_data_size)
{
COMBLK->INT_ENABLE &= ~TXTOKAY_MASK;
g_comblk_state = COMBLK_WAIT_RESPONSE;
}
else
{
uint32_t size_sent;
size_sent = fill_tx_fifo(g_comblk_p_data, g_comblk_data_size);
g_comblk_data_size = g_comblk_data_size - size_sent;
g_comblk_p_data = &g_comblk_p_data[size_sent];
}
break;
/*----------------------------------------------------------------------
* The TX_OKAY interrupt should NOT be enabled for states COMBLK_IDLE,
* COMBLK_WAIT_RESPONSE and COMBLK_RX_RESPONSE.
*/
case COMBLK_IDLE:
/* Fall through */
case COMBLK_WAIT_RESPONSE:
/* Fall through */
case COMBLK_RX_RESPONSE:
/* Fall through */
default:
COMBLK->INT_ENABLE &= ~TXTOKAY_MASK;
complete_request(0u);
g_comblk_state = COMBLK_IDLE;
break;
}
}
/*==============================================================================
*
*/
static void handle_rx_okay_irq(void)
{
uint16_t data16;
uint16_t is_command;
data16 = (uint16_t)COMBLK->DATA8;
is_command = data16 & DATA8_COMMAND_MASK;
switch(g_comblk_state)
{
/*----------------------------------------------------------------------
* The RCV_OKAY interrupt should only be enabled for states
* COMBLK_WAIT_RESPONSE and COMBLK_RX_RESPONSE.
*/
case COMBLK_WAIT_RESPONSE:
if(is_command)
{
uint8_t rxed_opcode;
rxed_opcode = (uint8_t)data16;
if(rxed_opcode == g_comblk_cmd_opcode)
{
g_comblk_response_idx = 0u;
g_comblk_p_response[g_comblk_response_idx] = rxed_opcode;
++g_comblk_response_idx;
g_comblk_state = COMBLK_RX_RESPONSE;
}
else
{
process_sys_ctrl_command(rxed_opcode);
}
}
break;
case COMBLK_RX_RESPONSE:
if(is_command)
{
uint8_t rxed_opcode;
rxed_opcode = (uint8_t)data16;
process_sys_ctrl_command(rxed_opcode);
}
else
{
if(g_comblk_response_idx < g_comblk_response_size)
{
uint8_t rxed_data;
rxed_data = (uint8_t)data16;
g_comblk_p_response[g_comblk_response_idx] = rxed_data;
++g_comblk_response_idx;
}
if(g_comblk_response_idx == g_comblk_response_size)
{
complete_request(g_comblk_response_idx);
g_comblk_state = COMBLK_IDLE;
}
}
break;
/*----------------------------------------------------------------------
* The RCV_OKAY interrupt should NOT be enabled for states
* COMBLK_IDLE, COMBLK_TX_CMD and COMBLK_TX_DATA.
*/
case COMBLK_TX_PAGED_DATA:
/* This is needed because when there is an error, we need to terminate loading the data */
if(!is_command)
{
g_comblk_p_response[1] = (uint8_t)data16;
complete_request(2u);
g_comblk_state = COMBLK_IDLE;
}
break;
case COMBLK_IDLE:
/* Fall through */
case COMBLK_TX_CMD:
/* Fall through */
case COMBLK_TX_DATA:
/* Fall through */
if(is_command)
{
uint8_t rxed_opcode;
rxed_opcode = (uint8_t)data16;
process_sys_ctrl_command(rxed_opcode);
}
break;
default:
complete_request(0u);
g_comblk_state = COMBLK_IDLE;
break;
}
}
/*==============================================================================
*
*/
static void complete_request
(
uint16_t response_length
)
{
if(g_comblk_completion_handler != 0)
{
g_comblk_completion_handler(g_comblk_p_response, response_length);
g_comblk_completion_handler = 0;
g_request_in_progress = 0u;
}
}
/*==============================================================================
*
*/
static void abort_current_cmd(void)
{
if(g_request_in_progress)
{
uint32_t flush_in_progress;
/*
* Call completion handler just in case we are in a multi threaded system
* to avoid a task lockup.
*/
complete_request(g_comblk_response_idx);
/*
* Flush the FIFOs
*/
COMBLK->CONTROL |= CR_FLUSHOUT_MASK;
do {
flush_in_progress = COMBLK->CONTROL & CR_FLUSHOUT_MASK;
} while(flush_in_progress);
}
}
/*==============================================================================
*
*/
static void send_cmd_opcode
(
uint8_t opcode
)
{
uint32_t tx_okay;
/* Set transmit FIFO to transfer bytes. */
COMBLK->CONTROL &= ~CR_SIZETX_MASK;
/* Wait for space to become available in Tx FIFO. */
do {
tx_okay = COMBLK->STATUS & TXTOKAY_MASK;
} while(0u == tx_okay);
/* Send command opcode. */
COMBLK->FRAME_START8 = opcode;
}
/*==============================================================================
*
*/
static uint32_t fill_tx_fifo
(
const uint8_t * p_cmd,
uint32_t cmd_size
)
{
volatile uint32_t tx_okay;
uint32_t size_sent;
/* Set transmit FIFO to transfer bytes. */
COMBLK->CONTROL &= ~CR_SIZETX_MASK;
size_sent = 0u;
tx_okay = COMBLK->STATUS & TXTOKAY_MASK;
while((tx_okay != 0u) && (size_sent < cmd_size))
{
COMBLK->DATA8 = p_cmd[size_sent];
++size_sent;
tx_okay = COMBLK->STATUS & TXTOKAY_MASK;
}
return size_sent;
}
/*==============================================================================
*
*/
static void process_sys_ctrl_command(uint8_t cmd_opcode)
{
if(g_async_event_handler != 0)
{
g_async_event_handler(cmd_opcode);
}
}