blob: 94fd0698ba51a11ce987080dec3be26c2d7618aa [file] [log] [blame]
/* Copyright 2017 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "common.h"
#include "console.h"
#include "dma.h"
#include "hooks.h"
#include "registers.h"
#include "task.h"
#include "tfdp_chip.h"
#include "timer.h"
#include "util.h"
/* Console output macros */
#define CPUTS(outstr) cputs(CC_DMA, outstr)
#define CPRINTS(format, args...) cprints(CC_DMA, format, ##args)
dma_chan_t *dma_get_channel(enum dma_channel channel)
{
dma_chan_t *pd = NULL;
if (channel < MCHP_DMAC_COUNT) {
pd = (dma_chan_t *)(MCHP_DMA_BASE + MCHP_DMA_CH_OFS +
(channel << MCHP_DMA_CH_OFS_BITPOS));
}
return pd;
}
void dma_disable(enum dma_channel channel)
{
if (channel < MCHP_DMAC_COUNT) {
if (MCHP_DMA_CH_CTRL(channel) & MCHP_DMA_RUN)
MCHP_DMA_CH_CTRL(channel) &= ~(MCHP_DMA_RUN);
if (MCHP_DMA_CH_ACT(channel) & MCHP_DMA_ACT_EN)
MCHP_DMA_CH_ACT(channel) = 0;
}
}
void dma_disable_all(void)
{
uint16_t ch;
__maybe_unused uint32_t unused = 0;
for (ch = 0; ch < MCHP_DMAC_COUNT; ch++) {
/* Abort any current transfer. */
MCHP_DMA_CH_CTRL(ch) |= MCHP_DMA_ABORT;
/* Disable the channel. */
MCHP_DMA_CH_CTRL(ch) &= ~(MCHP_DMA_RUN);
MCHP_DMA_CH_ACT(ch) = 0;
}
/* Soft-reset the block. */
MCHP_DMA_MAIN_CTRL = MCHP_DMA_MAIN_CTRL_SRST;
unused += MCHP_DMA_MAIN_CTRL;
MCHP_DMA_MAIN_CTRL = MCHP_DMA_MAIN_CTRL_ACT;
}
/**
* Prepare a channel for use and start it
*
* @param chan Channel to read
* @param count Number of bytes to transfer
* @param periph Pointer to peripheral data register
* @param memory Pointer to memory address for receive/transmit
* @param flags DMA flags for the control register, normally:
* MCHP_DMA_INC_MEM | MCHP_DMA_TO_DEV for tx
* MCHP_DMA_INC_MEM for rx
* Plus transfer unit length(1, 2, or 4) in bits[22:20]
* @note MCHP DMA does not require address aliasing. Because count
* is the number of bytes to transfer memory start - memory end = count.
*/
static void prepare_channel(enum dma_channel ch, unsigned int count,
void *periph, void *memory, unsigned int flags)
{
if (ch < MCHP_DMAC_COUNT) {
MCHP_DMA_CH_CTRL(ch) = 0;
MCHP_DMA_CH_MEM_START(ch) = (uint32_t)memory;
MCHP_DMA_CH_MEM_END(ch) = (uint32_t)memory + count;
MCHP_DMA_CH_DEV_ADDR(ch) = (uint32_t)periph;
MCHP_DMA_CH_CTRL(ch) = flags;
MCHP_DMA_CH_ACT(ch) = MCHP_DMA_ACT_EN;
}
}
void dma_go(dma_chan_t *chan)
{
/* Flush data in write buffer so that DMA can get the
* latest data.
*/
asm volatile("dsb;");
if (chan != NULL)
chan->ctrl |= MCHP_DMA_RUN;
}
void dma_go_chan(enum dma_channel ch)
{
asm volatile("dsb;");
if (ch < MCHP_DMAC_COUNT)
MCHP_DMA_CH_CTRL(ch) |= MCHP_DMA_RUN;
}
void dma_prepare_tx(const struct dma_option *option, unsigned int count,
const void *memory)
{
if (option != NULL)
/*
* Cast away const for memory pointer; this is ok because
* we know we're preparing the channel for transmit.
*/
prepare_channel(
option->channel, count, option->periph, (void *)memory,
MCHP_DMA_INC_MEM | MCHP_DMA_TO_DEV |
MCHP_DMA_DEV(option->channel) | option->flags);
}
void dma_xfr_prepare_tx(const struct dma_option *option, uint32_t count,
const void *memory, uint32_t dma_xfr_units)
{
uint32_t nflags;
if (option != NULL) {
nflags = option->flags & ~(MCHP_DMA_XFER_SIZE_MASK);
nflags |= MCHP_DMA_XFER_SIZE(dma_xfr_units & 0x07);
/*
* Cast away const for memory pointer; this is ok because
* we know we're preparing the channel for transmit.
*/
prepare_channel(option->channel, count, option->periph,
(void *)memory,
MCHP_DMA_INC_MEM | MCHP_DMA_TO_DEV |
MCHP_DMA_DEV(option->channel) | nflags);
}
}
void dma_start_rx(const struct dma_option *option, unsigned int count,
void *memory)
{
if (option != NULL) {
prepare_channel(option->channel, count, option->periph, memory,
MCHP_DMA_INC_MEM |
MCHP_DMA_DEV(option->channel) |
option->flags);
dma_go_chan(option->channel);
}
}
/*
* Configure and start DMA channel for read from device and write to
* memory. Allow caller to override DMA transfer unit length.
*/
void dma_xfr_start_rx(const struct dma_option *option, uint32_t dma_xfr_ulen,
uint32_t count, void *memory)
{
uint32_t ch, ctrl;
if (option != NULL) {
ch = option->channel;
if (ch < MCHP_DMAC_COUNT) {
MCHP_DMA_CH_CTRL(ch) = 0;
MCHP_DMA_CH_MEM_START(ch) = (uint32_t)memory;
MCHP_DMA_CH_MEM_END(ch) = (uint32_t)memory + count;
MCHP_DMA_CH_DEV_ADDR(ch) = (uint32_t)option->periph;
ctrl = option->flags & ~(MCHP_DMA_XFER_SIZE_MASK);
ctrl |= MCHP_DMA_INC_MEM;
ctrl |= MCHP_DMA_XFER_SIZE(dma_xfr_ulen);
ctrl |= MCHP_DMA_DEV(option->channel);
MCHP_DMA_CH_CTRL(ch) = ctrl;
MCHP_DMA_CH_ACT(ch) = MCHP_DMA_ACT_EN;
}
dma_go_chan(option->channel);
}
}
/*
* Return the number of bytes transferred.
* The number of bytes transferred can be easily determined
* from the difference in DMA memory start address register
* and memory end address register. No need to look at DMA
* transfer size field because the hardware increments memory
* start address by unit size on each unit transferred.
* Why is a signed integer being used for a count value?
*/
int dma_bytes_done(dma_chan_t *chan, int orig_count)
{
int bcnt;
if (chan == NULL)
return 0;
bcnt = (int)chan->mem_end;
bcnt -= (int)chan->mem_start;
bcnt = orig_count - bcnt;
return bcnt;
}
bool dma_is_enabled(dma_chan_t *chan)
{
return (chan->ctrl & MCHP_DMA_RUN);
}
int dma_bytes_done_chan(enum dma_channel ch, uint32_t orig_count)
{
uint32_t cnt;
cnt = 0;
if (ch < MCHP_DMAC_COUNT)
if (MCHP_DMA_CH_CTRL(ch) & MCHP_DMA_RUN)
cnt = (uint32_t)orig_count -
(MCHP_DMA_CH_MEM_END(ch) -
MCHP_DMA_CH_MEM_START(ch));
return (int)cnt;
}
/*
* Initialize DMA block.
* Clear PCR DMA sleep enable.
* Soft-Reset block should clear after one clock but read-back to
* be safe.
* Set block activate bit after reset.
*/
void dma_init(void)
{
MCHP_PCR_SLP_DIS_DEV(MCHP_PCR_DMA);
MCHP_DMA_MAIN_CTRL = MCHP_DMA_MAIN_CTRL_SRST;
MCHP_DMA_MAIN_CTRL;
MCHP_DMA_MAIN_CTRL = MCHP_DMA_MAIN_CTRL_ACT;
}
int dma_wait(enum dma_channel channel)
{
timestamp_t deadline;
if (channel < MCHP_DMAC_COUNT) {
if (MCHP_DMA_CH_ACT(channel) == 0)
return EC_SUCCESS;
deadline.val = get_time().val + DMA_TRANSFER_TIMEOUT_US;
while (!(MCHP_DMA_CH_ISTS(channel) & MCHP_DMA_STS_DONE)) {
if (deadline.val <= get_time().val)
return EC_ERROR_TIMEOUT;
udelay(DMA_POLLING_INTERVAL_US);
}
return EC_SUCCESS;
}
return EC_ERROR_INVAL;
}
/*
* Clear all interrupt status in specified DMA channel
*/
void dma_clear_isr(enum dma_channel channel)
{
if (channel < MCHP_DMAC_COUNT)
MCHP_DMA_CH_ISTS(channel) = 0x0f;
}
void dma_cfg_buffers(enum dma_channel ch, const void *membuf, uint32_t nb,
const void *pdev)
{
if (ch < MCHP_DMAC_COUNT) {
MCHP_DMA_CH_MEM_START(ch) = (uint32_t)membuf;
MCHP_DMA_CH_MEM_END(ch) = (uint32_t)membuf + nb;
MCHP_DMA_CH_DEV_ADDR(ch) = (uint32_t)pdev;
}
}
/*
* ch = zero based DMA channel number
* unit_len = DMA unit size 1, 2 or 4 bytes
* flags
* b[0] = direction, 0=device_to_memory, 1=memory_to_device
* b[1] = 1 increment memory address
* b[2] = 1 increment device address
* b[3] = disable HW flow control
*/
void dma_cfg_xfr(enum dma_channel ch, uint8_t unit_len, uint8_t dev_id,
uint8_t flags)
{
uint32_t ctrl;
if (ch < MCHP_DMAC_COUNT) {
ctrl = MCHP_DMA_XFER_SIZE(unit_len & 0x07);
ctrl += MCHP_DMA_DEV(dev_id & MCHP_DMA_DEV_MASK0);
if (flags & 0x01)
ctrl |= MCHP_DMA_TO_DEV;
if (flags & 0x02)
ctrl |= MCHP_DMA_INC_MEM;
if (flags & 0x04)
ctrl |= MCHP_DMA_INC_DEV;
if (flags & 0x08)
ctrl |= MCHP_DMA_DIS_HW_FLOW;
MCHP_DMA_CH_CTRL(ch) = ctrl;
}
}
void dma_clr_chan(enum dma_channel ch)
{
if (ch < MCHP_DMAC_COUNT) {
MCHP_DMA_CH_ACT(ch) = 0;
MCHP_DMA_CH_CTRL(ch) = 0;
MCHP_DMA_CH_IEN(ch) = 0;
MCHP_DMA_CH_ISTS(ch) = 0xff;
MCHP_DMA_CH_FSM_RO(ch) = MCHP_DMA_CH_ISTS(ch);
MCHP_DMA_CH_ACT(ch) = 1;
}
}
void dma_run(enum dma_channel ch)
{
if (ch < MCHP_DMAC_COUNT) {
if (MCHP_DMA_CH_CTRL(ch) & MCHP_DMA_DIS_HW_FLOW)
MCHP_DMA_CH_CTRL(ch) |= MCHP_DMA_SW_GO;
else
MCHP_DMA_CH_CTRL(ch) |= MCHP_DMA_RUN;
}
}
/*
* Check if DMA channel is done or stopped on error
* Returns 0 not done or stopped on error
* Returns non-zero if done or stopped.
* Caller should check bit pattern for specific bit,
* done, flow control error, and bus error.
*/
uint32_t dma_is_done_chan(enum dma_channel ch)
{
if (ch < MCHP_DMAC_COUNT)
return (uint32_t)(MCHP_DMA_CH_ISTS(ch) & 0x07);
return 0;
}
/*
* Use DMA Channel 0 CRC32 ALU to compute CRC32 of data.
* Hardware implements IEEE 802.3 CRC32.
* IEEE 802.3 CRC32 initial value = 0xffffffff.
* Data must be aligned >= 4-bytes and number of bytes must
* be a multiple of 4.
*/
int dma_crc32_start(const uint8_t *mstart, const uint32_t nbytes, int ien)
{
if ((mstart == NULL) || (nbytes == 0))
return EC_ERROR_INVAL;
if ((((uint32_t)mstart | nbytes) & 0x03) != 0)
return EC_ERROR_INVAL;
MCHP_DMA_CH_ACT(0) = 0;
MCHP_DMA_CH_CTRL(0) = 0;
MCHP_DMA_CH_IEN(0) = 0;
MCHP_DMA_CH_ISTS(0) = 0xff;
MCHP_DMA_CH0_CRC32_EN = 1;
MCHP_DMA_CH0_CRC32_DATA = 0xfffffffful;
/* program device address to point to read-only register */
MCHP_DMA_CH_DEV_ADDR(0) = (uint32_t)(MCHP_DMA_CH_BASE + 0x1c);
MCHP_DMA_CH_MEM_START(0) = (uint32_t)mstart;
MCHP_DMA_CH_MEM_END(0) = (uint32_t)mstart + nbytes;
if (ien != 0)
MCHP_DMA_CH_IEN(0) = 0x07;
MCHP_DMA_CH_ACT(0) = 1;
MCHP_DMA_CH_CTRL(0) = MCHP_DMA_TO_DEV + MCHP_DMA_INC_MEM +
MCHP_DMA_DIS_HW_FLOW + MCHP_DMA_XFER_SIZE(4);
MCHP_DMA_CH_CTRL(0) |= MCHP_DMA_SW_GO;
return EC_SUCCESS;
}