blob: ec052f1ca92c6c1eb7526705b774a1759588ff05 [file] [log] [blame]
/* ----------------------------------------------------------------------------
* SAM Software Package License
* ----------------------------------------------------------------------------
* Copyright (c) 2015, Atmel Corporation
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the disclaimer below.
*
* Atmel's name may not be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ----------------------------------------------------------------------------
*/
#ifdef CONFIG_HAVE_FLEXCOM
#include "peripherals/flexcom.h"
#endif
#include "peripherals/pmc.h"
#include "peripherals/twid.h"
#include "peripherals/twi.h"
#include "peripherals/xdmad.h"
#include "peripherals/l2cc.h"
#include "cortex-a/cp15.h"
#include "trace.h"
#include "io.h"
#include "timer.h"
#include <assert.h>
#include <string.h>
#define TWID_DMA_THRESHOLD 16
#define TWID_TIMEOUT 100
static uint32_t _twid_wait_twi_transfer(struct _twi_desc* desc)
{
struct _timeout timeout;
timer_start_timeout(&timeout, TWID_TIMEOUT);
while(!twi_is_transfer_complete(desc->addr)){
if (timer_timeout_reached(&timeout)) {
trace_error("twid: Unable to complete transfert!\r\n");
twid_configure(desc);
return TWID_ERROR_TRANSFER;
}
}
return TWID_SUCCESS;
}
static void _twid_xdmad_callback_wrapper(struct _xdmad_channel* channel,
void* args)
{
trace_debug("TWID DMA Transfert Finished\r\n");
struct _twi_desc* twid = (struct _twi_desc*) args;
xdmad_free_channel(channel);
if (twid->region_start && twid->region_end) {
l2cc_invalidate_region(twid->region_start, twid->region_end);
}
if (twid && twid->callback)
twid->callback(twid, twid->cb_args);
}
static void _twid_init_dma_read_channel(const struct _twi_desc* desc,
struct _xdmad_channel** channel,
struct _xdmad_cfg* cfg)
{
assert(cfg);
assert(channel);
uint32_t id = get_twi_id_from_addr(desc->addr);
assert(id < ID_PERIPH_COUNT);
memset(cfg, 0x0, sizeof(*cfg));
*channel =
xdmad_allocate_channel(id, XDMAD_PERIPH_MEMORY);
assert(*channel);
xdmad_prepare_channel(*channel);
cfg->cfg.uint32_value = XDMAC_CC_TYPE_PER_TRAN
| XDMAC_CC_DSYNC_PER2MEM
| XDMAC_CC_MEMSET_NORMAL_MODE
| XDMAC_CC_CSIZE_CHK_1
| XDMAC_CC_DWIDTH_BYTE
| XDMAC_CC_DIF_AHB_IF0
| XDMAC_CC_SIF_AHB_IF1
| XDMAC_CC_SAM_FIXED_AM;
cfg->src_addr = (void*)&desc->addr->TWI_RHR;
}
static void _twid_dma_read(const struct _twi_desc* desc,
struct _buffer* buffer)
{
struct _xdmad_channel* channel = NULL;
struct _xdmad_cfg cfg;
_twid_init_dma_read_channel(desc, &channel, &cfg);
cfg.cfg.bitfield.dam = XDMAC_CC_DAM_INCREMENTED_AM
>> XDMAC_CC_DAM_Pos;
cfg.dest_addr = buffer->data;
cfg.ublock_size = buffer->size;
cfg.block_size = 0;
xdmad_configure_transfer(channel, &cfg, 0, 0);
xdmad_set_callback(channel, _twid_xdmad_callback_wrapper,
(void*)desc);
l2cc_clean_region(desc->region_start, desc->region_end);
xdmad_start_transfer(channel);
}
static void _twid_init_dma_write_channel(struct _twi_desc* desc,
struct _xdmad_channel** channel,
struct _xdmad_cfg* cfg)
{
assert(cfg);
assert(channel);
uint32_t id = get_twi_id_from_addr(desc->addr);
assert(id < ID_PERIPH_COUNT);
memset(cfg, 0x0, sizeof(*cfg));
*channel =
xdmad_allocate_channel(XDMAD_PERIPH_MEMORY, id);
assert(*channel);
xdmad_prepare_channel(*channel);
cfg->cfg.uint32_value = XDMAC_CC_TYPE_PER_TRAN
| XDMAC_CC_DSYNC_MEM2PER
| XDMAC_CC_MEMSET_NORMAL_MODE
| XDMAC_CC_CSIZE_CHK_1
| XDMAC_CC_DWIDTH_BYTE
| XDMAC_CC_DIF_AHB_IF1
| XDMAC_CC_SIF_AHB_IF0
| XDMAC_CC_DAM_FIXED_AM;
cfg->dest_addr = (void*)&desc->addr->TWI_THR;
}
static void _twid_dma_write(struct _twi_desc* desc,
struct _buffer* buffer)
{
struct _xdmad_channel* channel = NULL;
struct _xdmad_cfg cfg;
_twid_init_dma_write_channel(desc, &channel, &cfg);
cfg.cfg.bitfield.sam = XDMAC_CC_SAM_INCREMENTED_AM
>> XDMAC_CC_SAM_Pos;
cfg.src_addr = buffer->data;
cfg.ublock_size = buffer->size;
cfg.block_size = 0;
xdmad_configure_transfer(channel, &cfg, 0, 0);
xdmad_set_callback(channel, _twid_xdmad_callback_wrapper,
(void*)desc);
l2cc_clean_region(desc->region_start, desc->region_end);
xdmad_start_transfer(channel);
}
void twid_configure(struct _twi_desc* desc)
{
uint32_t id = get_twi_id_from_addr(desc->addr);
assert(id < ID_PERIPH_COUNT);
#ifdef CONFIG_HAVE_FLEXCOM
Flexcom* flexcom = get_flexcom_addr_from_id(get_twi_id_from_addr(desc->addr));
if (flexcom) {
flexcom_select(flexcom, FLEX_MR_OPMODE_TWI);
}
#endif
pmc_enable_peripheral(id);
twi_configure_master(desc->addr, desc->freq);
#ifdef CONFIG_HAVE_TWI_FIFO
if (desc->transfert_mode == TWID_MODE_FIFO) {
uint32_t fifo_depth = get_peripheral_fifo_depth(desc->addr);
twi_fifo_configure(desc->addr, fifo_depth/2, fifo_depth/2,
TWI_FMR_RXRDYM_ONE_DATA | TWI_FMR_TXRDYM_ONE_DATA);
}
#endif
}
static uint32_t _twid_poll_write(struct _twi_desc* desc, struct _buffer* buffer)
{
int i = 0;
struct _timeout timeout;
twi_init_write_transfert(desc->addr,
desc->slave_addr,
desc->iaddr,
desc->isize,
buffer->size);
if (twi_get_status(desc->addr) & TWI_SR_NACK) {
trace_error("twid: command NACK!\r\n");
return TWID_ERROR_ACK;
}
for (i = 0; i < buffer->size; ++i) {
timer_start_timeout(&timeout, TWID_TIMEOUT);
while(!twi_byte_sent(desc->addr)) {
if (timer_timeout_reached(&timeout)) {
trace_error("twid: Device doesn't answer, "
"(TX TIMEOUT)\r\n");
break;
}
}
twi_write_byte(desc->addr, buffer->data[i]);
if(twi_get_status(desc->addr) & TWI_SR_NACK) {
trace_error("twid: command NACK!\r\n");
return TWID_ERROR_ACK;
}
}
/* wait transfert to be finished */
return _twid_wait_twi_transfer(desc);
}
static uint32_t _twid_poll_read(struct _twi_desc* desc, struct _buffer* buffer)
{
int i = 0;
struct _timeout timeout;
twi_init_read_transfert(desc->addr,
desc->slave_addr,
desc->iaddr,
desc->isize,
buffer->size);
if (twi_get_status(desc->addr) & TWI_SR_NACK) {
trace_error("twid: command NACK!\r\n");
return TWID_ERROR_ACK;
}
for (i = 0; i < buffer->size; ++i) {
timer_start_timeout(&timeout, TWID_TIMEOUT);
while(!twi_is_byte_received(desc->addr)) {
if (timer_timeout_reached(&timeout)) {
trace_error("twid: Device doesn't answer, "
"(RX TIMEOUT)\r\n");
break;
}
}
buffer->data[i] = twi_read_byte(desc->addr);
if(twi_get_status(desc->addr) & TWI_SR_NACK) {
trace_error("twid: command NACK\r\n");
return TWID_ERROR_ACK;
}
}
/* wait transfert to be finished */
return _twid_wait_twi_transfer(desc);
}
uint32_t twid_transfert(struct _twi_desc* desc, struct _buffer* rx,
struct _buffer* tx, twid_callback_t cb,
void* user_args)
{
uint32_t status = TWID_SUCCESS;
desc->callback = cb;
desc->cb_args = user_args;
if (mutex_try_lock(&desc->mutex)) {
return TWID_ERROR_LOCK;
}
switch (desc->transfert_mode) {
case TWID_MODE_POLLING:
if (tx) {
status = _twid_poll_write(desc, tx);
if (status) break;
}
if (rx) {
status = _twid_poll_read(desc, rx);
if (status) break;
}
if (cb)
cb(desc, user_args);
mutex_free(&desc->mutex);
break;
case TWID_MODE_DMA:
if (!(rx || tx)) {
status = TWID_ERROR_DUPLEX;
break;
}
if (tx) {
if (tx->size < TWID_DMA_THRESHOLD) {
status = _twid_poll_write(desc, tx);
if (status) break;
if (cb)
cb(desc, user_args);
mutex_free(&desc->mutex);
} else {
twi_init_write_transfert(desc->addr,
desc->slave_addr,
desc->iaddr,
desc->isize,
tx->size);
desc->region_start = (uint32_t)tx->data;
desc->region_end = desc->region_start
+ tx->size;
_twid_dma_write(desc, tx);
}
}
if (rx) {
if (rx->size < TWID_DMA_THRESHOLD) {
status = _twid_poll_read(desc, rx);
if (status) break;
if (cb)
cb(desc, user_args);
mutex_free(&desc->mutex);
} else {
twi_init_read_transfert(desc->addr,
desc->slave_addr,
desc->iaddr,
desc->isize,
rx->size);
desc->region_start = (uint32_t)rx->data;
desc->region_end = desc->region_start
+ rx->size;
if(twi_get_status(desc->addr) & TWI_SR_NACK) {
trace_error("twid: Acknolegment "
"Error\r\n");
status = TWID_ERROR_ACK;
break;
}
_twid_dma_read(desc, rx);
}
}
break;
#ifdef CONFIG_HAVE_TWI_FIFO
case TWID_MODE_FIFO:
if (tx) {
status = twi_write_stream(desc->addr, desc->slave_addr,
desc->iaddr, desc->isize,
tx->data, tx->size);
status = status ? TWID_SUCCESS : TWID_ERROR_ACK;
if (status)
break;
status = _twid_wait_twi_transfer(desc);
if (status)
break;
}
if (rx) {
status = twi_read_stream(desc->addr, desc->slave_addr,
desc->iaddr, desc->isize,
rx->data, rx->size);
status = status ? TWID_SUCCESS : TWID_ERROR_ACK;
if (status)
break;
status = _twid_wait_twi_transfer(desc);
if (status)
break;
}
if (cb)
cb(desc, user_args);
mutex_free(&desc->mutex);
break;
#endif
default:
trace_debug("Unkown mode");
}
if (status)
mutex_free(&desc->mutex);
return status;
}
void twid_finish_transfert_callback(struct _twi_desc* desc, void* user_args)
{
(void)user_args;
twid_finish_transfert(desc);
}
void twid_finish_transfert(struct _twi_desc* desc)
{
mutex_free(&desc->mutex);
}
uint32_t twid_is_busy(const struct _twi_desc* desc)
{
return mutex_is_locked(&desc->mutex);
}
void twid_wait_transfert(const struct _twi_desc* desc)
{
while (mutex_is_locked(&desc->mutex));
}