/* 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; 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; }