diff options
Diffstat (limited to 'sys/arm/ti/ti_sdma.c')
-rw-r--r-- | sys/arm/ti/ti_sdma.c | 1244 |
1 files changed, 1244 insertions, 0 deletions
diff --git a/sys/arm/ti/ti_sdma.c b/sys/arm/ti/ti_sdma.c new file mode 100644 index 000000000000..e89a5c7b4f3c --- /dev/null +++ b/sys/arm/ti/ti_sdma.c @@ -0,0 +1,1244 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2011 + * Ben Gray <ben.r.gray@gmail.com>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS 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. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/interrupt.h> +#include <sys/module.h> +#include <sys/malloc.h> +#include <sys/mutex.h> +#include <sys/rman.h> +#include <sys/queue.h> +#include <sys/taskqueue.h> +#include <sys/timetc.h> +#include <machine/bus.h> +#include <machine/intr.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <arm/ti/ti_cpuid.h> +#include <arm/ti/ti_sysc.h> +#include <arm/ti/ti_sdma.h> +#include <arm/ti/ti_sdmareg.h> + +/** + * Kernel functions for using the DMA controller + * + * + * DMA TRANSFERS: + * A DMA transfer block consists of a number of frames (FN). Each frame + * consists of a number of elements, and each element can have a size of 8, 16, + * or 32 bits. + * + * OMAP44xx and newer chips support linked list (aka scatter gather) transfers, + * where a linked list of source/destination pairs can be placed in memory + * for the H/W to process. Earlier chips only allowed you to chain multiple + * channels together. However currently this linked list feature is not + * supported by the driver. + * + */ + +/** + * Data structure per DMA channel. + * + * + */ +struct ti_sdma_channel { + /* + * The configuration registers for the given channel, these are modified + * by the set functions and only written to the actual registers when a + * transaction is started. + */ + uint32_t reg_csdp; + uint32_t reg_ccr; + uint32_t reg_cicr; + + /* Set when one of the configuration registers above change */ + uint32_t need_reg_write; + + /* Callback function used when an interrupt is tripped on the given channel */ + void (*callback)(unsigned int ch, uint32_t ch_status, void *data); + + /* Callback data passed in the callback ... duh */ + void* callback_data; + +}; + +/** + * DMA driver context, allocated and stored globally, this driver is not + * intetned to ever be unloaded (see ti_sdma_sc). + * + */ +struct ti_sdma_softc { + device_t sc_dev; + struct resource* sc_irq_res; + struct resource* sc_mem_res; + + /* + * I guess in theory we should have a mutex per DMA channel for register + * modifications. But since we know we are never going to be run on a SMP + * system, we can use just the single lock for all channels. + */ + struct mtx sc_mtx; + + /* Stores the H/W revision read from the registers */ + uint32_t sc_hw_rev; + + /* + * Bits in the sc_active_channels data field indicate if the channel has + * been activated. + */ + uint32_t sc_active_channels; + + struct ti_sdma_channel sc_channel[NUM_DMA_CHANNELS]; + +}; + +static struct ti_sdma_softc *ti_sdma_sc = NULL; + +/** + * Macros for driver mutex locking + */ +#define TI_SDMA_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) +#define TI_SDMA_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) +#define TI_SDMA_LOCK_INIT(_sc) \ + mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->sc_dev), \ + "ti_sdma", MTX_SPIN) +#define TI_SDMA_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); +#define TI_SDMA_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED); +#define TI_SDMA_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED); + +/** + * Function prototypes + * + */ +static void ti_sdma_intr(void *); + +/** + * ti_sdma_read_4 - reads a 32-bit value from one of the DMA registers + * @sc: DMA device context + * @off: The offset of a register from the DMA register address range + * + * + * RETURNS: + * 32-bit value read from the register. + */ +static inline uint32_t +ti_sdma_read_4(struct ti_sdma_softc *sc, bus_size_t off) +{ + return bus_read_4(sc->sc_mem_res, off); +} + +/** + * ti_sdma_write_4 - writes a 32-bit value to one of the DMA registers + * @sc: DMA device context + * @off: The offset of a register from the DMA register address range + * + * + * RETURNS: + * 32-bit value read from the register. + */ +static inline void +ti_sdma_write_4(struct ti_sdma_softc *sc, bus_size_t off, uint32_t val) +{ + bus_write_4(sc->sc_mem_res, off, val); +} + +/** + * ti_sdma_is_omap3_rev - returns true if H/W is from OMAP3 series + * @sc: DMA device context + * + */ +static inline int +ti_sdma_is_omap3_rev(struct ti_sdma_softc *sc) +{ + return (sc->sc_hw_rev == DMA4_OMAP3_REV); +} + +/** + * ti_sdma_is_omap4_rev - returns true if H/W is from OMAP4 series + * @sc: DMA device context + * + */ +static inline int +ti_sdma_is_omap4_rev(struct ti_sdma_softc *sc) +{ + return (sc->sc_hw_rev == DMA4_OMAP4_REV); +} + +/** + * ti_sdma_intr - interrupt handler for all 4 DMA IRQs + * @arg: ignored + * + * Called when any of the four DMA IRQs are triggered. + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * nothing + */ +static void +ti_sdma_intr(void *arg) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + uint32_t intr; + uint32_t csr; + unsigned int ch, j; + struct ti_sdma_channel* channel; + + TI_SDMA_LOCK(sc); + + for (j = 0; j < NUM_DMA_IRQS; j++) { + /* Get the flag interrupts (enabled) */ + intr = ti_sdma_read_4(sc, DMA4_IRQSTATUS_L(j)); + intr &= ti_sdma_read_4(sc, DMA4_IRQENABLE_L(j)); + if (intr == 0x00000000) + continue; + + /* Loop through checking the status bits */ + for (ch = 0; ch < NUM_DMA_CHANNELS; ch++) { + if (intr & (1 << ch)) { + channel = &sc->sc_channel[ch]; + + /* Read the CSR regsiter and verify we don't have a spurious IRQ */ + csr = ti_sdma_read_4(sc, DMA4_CSR(ch)); + if (csr == 0) { + device_printf(sc->sc_dev, "Spurious DMA IRQ for channel " + "%d\n", ch); + continue; + } + + /* Sanity check this channel is active */ + if ((sc->sc_active_channels & (1 << ch)) == 0) { + device_printf(sc->sc_dev, "IRQ %d for a non-activated " + "channel %d\n", j, ch); + continue; + } + + /* Check the status error codes */ + if (csr & DMA4_CSR_DROP) + device_printf(sc->sc_dev, "Synchronization event drop " + "occurred during the transfer on channel %u\n", + ch); + if (csr & DMA4_CSR_SECURE_ERR) + device_printf(sc->sc_dev, "Secure transaction error event " + "on channel %u\n", ch); + if (csr & DMA4_CSR_MISALIGNED_ADRS_ERR) + device_printf(sc->sc_dev, "Misaligned address error event " + "on channel %u\n", ch); + if (csr & DMA4_CSR_TRANS_ERR) { + device_printf(sc->sc_dev, "Transaction error event on " + "channel %u\n", ch); + /* + * Apparently according to linux code, there is an errata + * that says the channel is not disabled upon this error. + * They explicitly disable the channel here .. since I + * haven't seen the errata, I'm going to ignore for now. + */ + } + + /* Clear the status flags for the IRQ */ + ti_sdma_write_4(sc, DMA4_CSR(ch), DMA4_CSR_CLEAR_MASK); + ti_sdma_write_4(sc, DMA4_IRQSTATUS_L(j), (1 << ch)); + + /* Call the callback for the given channel */ + if (channel->callback) + channel->callback(ch, csr, channel->callback_data); + } + } + } + + TI_SDMA_UNLOCK(sc); + + return; +} + +/** + * ti_sdma_activate_channel - activates a DMA channel + * @ch: upon return contains the channel allocated + * @callback: a callback function to associate with the channel + * @data: optional data supplied when the callback is called + * + * Simply activates a channel be enabling and writing default values to the + * channel's register set. It doesn't start a transaction, just populates the + * internal data structures and sets defaults. + * + * Note this function doesn't enable interrupts, for that you need to call + * ti_sdma_enable_channel_irq(). If not using IRQ to detect the end of the + * transfer, you can use ti_sdma_status_poll() to detect a change in the + * status. + * + * A channel must be activated before any of the other DMA functions can be + * called on it. + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * 0 on success, otherwise an error code + */ +int +ti_sdma_activate_channel(unsigned int *ch, + void (*callback)(unsigned int ch, uint32_t status, void *data), + void *data) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + struct ti_sdma_channel *channel = NULL; + uint32_t addr; + unsigned int i; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + if (ch == NULL) + return (EINVAL); + + TI_SDMA_LOCK(sc); + + /* Check to see if all channels are in use */ + if (sc->sc_active_channels == 0xffffffff) { + TI_SDMA_UNLOCK(sc); + return (ENOMEM); + } + + /* Find the first non-active channel */ + for (i = 0; i < NUM_DMA_CHANNELS; i++) { + if (!(sc->sc_active_channels & (0x1 << i))) { + sc->sc_active_channels |= (0x1 << i); + *ch = i; + break; + } + } + + /* Get the channel struct and populate the fields */ + channel = &sc->sc_channel[*ch]; + + channel->callback = callback; + channel->callback_data = data; + + channel->need_reg_write = 1; + + /* Set the default configuration for the DMA channel */ + channel->reg_csdp = DMA4_CSDP_DATA_TYPE(0x2) + | DMA4_CSDP_SRC_BURST_MODE(0) + | DMA4_CSDP_DST_BURST_MODE(0) + | DMA4_CSDP_SRC_ENDIANISM(0) + | DMA4_CSDP_DST_ENDIANISM(0) + | DMA4_CSDP_WRITE_MODE(0) + | DMA4_CSDP_SRC_PACKED(0) + | DMA4_CSDP_DST_PACKED(0); + + channel->reg_ccr = DMA4_CCR_DST_ADDRESS_MODE(1) + | DMA4_CCR_SRC_ADDRESS_MODE(1) + | DMA4_CCR_READ_PRIORITY(0) + | DMA4_CCR_WRITE_PRIORITY(0) + | DMA4_CCR_SYNC_TRIGGER(0) + | DMA4_CCR_FRAME_SYNC(0) + | DMA4_CCR_BLOCK_SYNC(0); + + channel->reg_cicr = DMA4_CICR_TRANS_ERR_IE + | DMA4_CICR_SECURE_ERR_IE + | DMA4_CICR_SUPERVISOR_ERR_IE + | DMA4_CICR_MISALIGNED_ADRS_ERR_IE; + + /* Clear all the channel registers, this should abort any transaction */ + for (addr = DMA4_CCR(*ch); addr <= DMA4_COLOR(*ch); addr += 4) + ti_sdma_write_4(sc, addr, 0x00000000); + + TI_SDMA_UNLOCK(sc); + + return 0; +} + +/** + * ti_sdma_deactivate_channel - deactivates a channel + * @ch: the channel to deactivate + * + * + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +int +ti_sdma_deactivate_channel(unsigned int ch) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + unsigned int j; + unsigned int addr; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + TI_SDMA_LOCK(sc); + + /* First check if the channel is currently active */ + if ((sc->sc_active_channels & (1 << ch)) == 0) { + TI_SDMA_UNLOCK(sc); + return (EBUSY); + } + + /* Mark the channel as inactive */ + sc->sc_active_channels &= ~(1 << ch); + + /* Disable all DMA interrupts for the channel. */ + ti_sdma_write_4(sc, DMA4_CICR(ch), 0); + + /* Make sure the DMA transfer is stopped. */ + ti_sdma_write_4(sc, DMA4_CCR(ch), 0); + + /* Clear the CSR register and IRQ status register */ + ti_sdma_write_4(sc, DMA4_CSR(ch), DMA4_CSR_CLEAR_MASK); + for (j = 0; j < NUM_DMA_IRQS; j++) { + ti_sdma_write_4(sc, DMA4_IRQSTATUS_L(j), (1 << ch)); + } + + /* Clear all the channel registers, this should abort any transaction */ + for (addr = DMA4_CCR(ch); addr <= DMA4_COLOR(ch); addr += 4) + ti_sdma_write_4(sc, addr, 0x00000000); + + TI_SDMA_UNLOCK(sc); + + return 0; +} + +/** + * ti_sdma_disable_channel_irq - disables IRQ's on the given channel + * @ch: the channel to disable IRQ's on + * + * Disable interrupt generation for the given channel. + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +int +ti_sdma_disable_channel_irq(unsigned int ch) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + uint32_t irq_enable; + unsigned int j; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + TI_SDMA_LOCK(sc); + + if ((sc->sc_active_channels & (1 << ch)) == 0) { + TI_SDMA_UNLOCK(sc); + return (EINVAL); + } + + /* Disable all the individual error conditions */ + sc->sc_channel[ch].reg_cicr = 0x0000; + ti_sdma_write_4(sc, DMA4_CICR(ch), 0x0000); + + /* Disable the channel interrupt enable */ + for (j = 0; j < NUM_DMA_IRQS; j++) { + irq_enable = ti_sdma_read_4(sc, DMA4_IRQENABLE_L(j)); + irq_enable &= ~(1 << ch); + + ti_sdma_write_4(sc, DMA4_IRQENABLE_L(j), irq_enable); + } + + /* Indicate the registers need to be rewritten on the next transaction */ + sc->sc_channel[ch].need_reg_write = 1; + + TI_SDMA_UNLOCK(sc); + + return (0); +} + +/** + * ti_sdma_disable_channel_irq - enables IRQ's on the given channel + * @ch: the channel to enable IRQ's on + * @flags: bitmask of interrupt types to enable + * + * Flags can be a bitmask of the following options: + * DMA_IRQ_FLAG_DROP + * DMA_IRQ_FLAG_HALF_FRAME_COMPL + * DMA_IRQ_FLAG_FRAME_COMPL + * DMA_IRQ_FLAG_START_LAST_FRAME + * DMA_IRQ_FLAG_BLOCK_COMPL + * DMA_IRQ_FLAG_ENDOF_PKT + * DMA_IRQ_FLAG_DRAIN + * + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +int +ti_sdma_enable_channel_irq(unsigned int ch, uint32_t flags) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + uint32_t irq_enable; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + TI_SDMA_LOCK(sc); + + if ((sc->sc_active_channels & (1 << ch)) == 0) { + TI_SDMA_UNLOCK(sc); + return (EINVAL); + } + + /* Always enable the error interrupts if we have interrupts enabled */ + flags |= DMA4_CICR_TRANS_ERR_IE | DMA4_CICR_SECURE_ERR_IE | + DMA4_CICR_SUPERVISOR_ERR_IE | DMA4_CICR_MISALIGNED_ADRS_ERR_IE; + + sc->sc_channel[ch].reg_cicr = flags; + + /* Write the values to the register */ + ti_sdma_write_4(sc, DMA4_CICR(ch), flags); + + /* Enable the channel interrupt enable */ + irq_enable = ti_sdma_read_4(sc, DMA4_IRQENABLE_L(0)); + irq_enable |= (1 << ch); + + ti_sdma_write_4(sc, DMA4_IRQENABLE_L(0), irq_enable); + + /* Indicate the registers need to be rewritten on the next transaction */ + sc->sc_channel[ch].need_reg_write = 1; + + TI_SDMA_UNLOCK(sc); + + return (0); +} + +/** + * ti_sdma_get_channel_status - returns the status of a given channel + * @ch: the channel number to get the status of + * @status: upon return will contain the status bitmask, see below for possible + * values. + * + * DMA_STATUS_DROP + * DMA_STATUS_HALF + * DMA_STATUS_FRAME + * DMA_STATUS_LAST + * DMA_STATUS_BLOCK + * DMA_STATUS_SYNC + * DMA_STATUS_PKT + * DMA_STATUS_TRANS_ERR + * DMA_STATUS_SECURE_ERR + * DMA_STATUS_SUPERVISOR_ERR + * DMA_STATUS_MISALIGNED_ADRS_ERR + * DMA_STATUS_DRAIN_END + * + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +int +ti_sdma_get_channel_status(unsigned int ch, uint32_t *status) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + uint32_t csr; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + TI_SDMA_LOCK(sc); + + if ((sc->sc_active_channels & (1 << ch)) == 0) { + TI_SDMA_UNLOCK(sc); + return (EINVAL); + } + + TI_SDMA_UNLOCK(sc); + + csr = ti_sdma_read_4(sc, DMA4_CSR(ch)); + + if (status != NULL) + *status = csr; + + return (0); +} + +/** + * ti_sdma_start_xfer - starts a DMA transfer + * @ch: the channel number to set the endianness of + * @src_paddr: the source phsyical address + * @dst_paddr: the destination phsyical address + * @frmcnt: the number of frames per block + * @elmcnt: the number of elements in a frame, an element is either an 8, 16 + * or 32-bit value as defined by ti_sdma_set_xfer_burst() + * + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +int +ti_sdma_start_xfer(unsigned int ch, unsigned int src_paddr, + unsigned long dst_paddr, + unsigned int frmcnt, unsigned int elmcnt) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + struct ti_sdma_channel *channel; + uint32_t ccr; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + TI_SDMA_LOCK(sc); + + if ((sc->sc_active_channels & (1 << ch)) == 0) { + TI_SDMA_UNLOCK(sc); + return (EINVAL); + } + + channel = &sc->sc_channel[ch]; + + /* a) Write the CSDP register */ + ti_sdma_write_4(sc, DMA4_CSDP(ch), + channel->reg_csdp | DMA4_CSDP_WRITE_MODE(1)); + + /* b) Set the number of element per frame CEN[23:0] */ + ti_sdma_write_4(sc, DMA4_CEN(ch), elmcnt); + + /* c) Set the number of frame per block CFN[15:0] */ + ti_sdma_write_4(sc, DMA4_CFN(ch), frmcnt); + + /* d) Set the Source/dest start address index CSSA[31:0]/CDSA[31:0] */ + ti_sdma_write_4(sc, DMA4_CSSA(ch), src_paddr); + ti_sdma_write_4(sc, DMA4_CDSA(ch), dst_paddr); + + /* e) Write the CCR register */ + ti_sdma_write_4(sc, DMA4_CCR(ch), channel->reg_ccr); + + /* f) - Set the source element index increment CSEI[15:0] */ + ti_sdma_write_4(sc, DMA4_CSE(ch), 0x0001); + + /* - Set the source frame index increment CSFI[15:0] */ + ti_sdma_write_4(sc, DMA4_CSF(ch), 0x0001); + + /* - Set the destination element index increment CDEI[15:0]*/ + ti_sdma_write_4(sc, DMA4_CDE(ch), 0x0001); + + /* - Set the destination frame index increment CDFI[31:0] */ + ti_sdma_write_4(sc, DMA4_CDF(ch), 0x0001); + + /* Clear the status register */ + ti_sdma_write_4(sc, DMA4_CSR(ch), 0x1FFE); + + /* Write the start-bit and away we go */ + ccr = ti_sdma_read_4(sc, DMA4_CCR(ch)); + ccr |= (1 << 7); + ti_sdma_write_4(sc, DMA4_CCR(ch), ccr); + + /* Clear the reg write flag */ + channel->need_reg_write = 0; + + TI_SDMA_UNLOCK(sc); + + return (0); +} + +/** + * ti_sdma_start_xfer_packet - starts a packet DMA transfer + * @ch: the channel number to use for the transfer + * @src_paddr: the source physical address + * @dst_paddr: the destination physical address + * @frmcnt: the number of frames to transfer + * @elmcnt: the number of elements in a frame, an element is either an 8, 16 + * or 32-bit value as defined by ti_sdma_set_xfer_burst() + * @pktsize: the number of elements in each transfer packet + * + * The @frmcnt and @elmcnt define the overall number of bytes to transfer, + * typically @frmcnt is 1 and @elmcnt contains the total number of elements. + * @pktsize is the size of each individual packet, there might be multiple + * packets per transfer. i.e. for the following with element size of 32-bits + * + * frmcnt = 1, elmcnt = 512, pktsize = 128 + * + * Total transfer bytes = 1 * 512 = 512 elements or 2048 bytes + * Packets transferred = 128 / 512 = 4 + * + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +int +ti_sdma_start_xfer_packet(unsigned int ch, unsigned int src_paddr, + unsigned long dst_paddr, unsigned int frmcnt, + unsigned int elmcnt, unsigned int pktsize) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + struct ti_sdma_channel *channel; + uint32_t ccr; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + TI_SDMA_LOCK(sc); + + if ((sc->sc_active_channels & (1 << ch)) == 0) { + TI_SDMA_UNLOCK(sc); + return (EINVAL); + } + + channel = &sc->sc_channel[ch]; + + /* a) Write the CSDP register */ + if (channel->need_reg_write) + ti_sdma_write_4(sc, DMA4_CSDP(ch), + channel->reg_csdp | DMA4_CSDP_WRITE_MODE(1)); + + /* b) Set the number of elements to transfer CEN[23:0] */ + ti_sdma_write_4(sc, DMA4_CEN(ch), elmcnt); + + /* c) Set the number of frames to transfer CFN[15:0] */ + ti_sdma_write_4(sc, DMA4_CFN(ch), frmcnt); + + /* d) Set the Source/dest start address index CSSA[31:0]/CDSA[31:0] */ + ti_sdma_write_4(sc, DMA4_CSSA(ch), src_paddr); + ti_sdma_write_4(sc, DMA4_CDSA(ch), dst_paddr); + + /* e) Write the CCR register */ + ti_sdma_write_4(sc, DMA4_CCR(ch), + channel->reg_ccr | DMA4_CCR_PACKET_TRANS); + + /* f) - Set the source element index increment CSEI[15:0] */ + ti_sdma_write_4(sc, DMA4_CSE(ch), 0x0001); + + /* - Set the packet size, this is dependent on the sync source */ + if (channel->reg_ccr & DMA4_CCR_SEL_SRC_DST_SYNC(1)) + ti_sdma_write_4(sc, DMA4_CSF(ch), pktsize); + else + ti_sdma_write_4(sc, DMA4_CDF(ch), pktsize); + + /* - Set the destination frame index increment CDFI[31:0] */ + ti_sdma_write_4(sc, DMA4_CDE(ch), 0x0001); + + /* Clear the status register */ + ti_sdma_write_4(sc, DMA4_CSR(ch), 0x1FFE); + + /* Write the start-bit and away we go */ + ccr = ti_sdma_read_4(sc, DMA4_CCR(ch)); + ccr |= (1 << 7); + ti_sdma_write_4(sc, DMA4_CCR(ch), ccr); + + /* Clear the reg write flag */ + channel->need_reg_write = 0; + + TI_SDMA_UNLOCK(sc); + + return (0); +} + +/** + * ti_sdma_stop_xfer - stops any currently active transfers + * @ch: the channel number to set the endianness of + * + * This function call is effectively a NOP if no transaction is in progress. + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +int +ti_sdma_stop_xfer(unsigned int ch) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + unsigned int j; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + TI_SDMA_LOCK(sc); + + if ((sc->sc_active_channels & (1 << ch)) == 0) { + TI_SDMA_UNLOCK(sc); + return (EINVAL); + } + + /* Disable all DMA interrupts for the channel. */ + ti_sdma_write_4(sc, DMA4_CICR(ch), 0); + + /* Make sure the DMA transfer is stopped. */ + ti_sdma_write_4(sc, DMA4_CCR(ch), 0); + + /* Clear the CSR register and IRQ status register */ + ti_sdma_write_4(sc, DMA4_CSR(ch), DMA4_CSR_CLEAR_MASK); + for (j = 0; j < NUM_DMA_IRQS; j++) { + ti_sdma_write_4(sc, DMA4_IRQSTATUS_L(j), (1 << ch)); + } + + /* Configuration registers need to be re-written on the next xfer */ + sc->sc_channel[ch].need_reg_write = 1; + + TI_SDMA_UNLOCK(sc); + + return (0); +} + +/** + * ti_sdma_set_xfer_endianess - sets the endianness of subsequent transfers + * @ch: the channel number to set the endianness of + * @src: the source endianness (either DMA_ENDIAN_LITTLE or DMA_ENDIAN_BIG) + * @dst: the destination endianness (either DMA_ENDIAN_LITTLE or DMA_ENDIAN_BIG) + * + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +int +ti_sdma_set_xfer_endianess(unsigned int ch, unsigned int src, unsigned int dst) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + TI_SDMA_LOCK(sc); + + if ((sc->sc_active_channels & (1 << ch)) == 0) { + TI_SDMA_UNLOCK(sc); + return (EINVAL); + } + + sc->sc_channel[ch].reg_csdp &= ~DMA4_CSDP_SRC_ENDIANISM(1); + sc->sc_channel[ch].reg_csdp |= DMA4_CSDP_SRC_ENDIANISM(src); + + sc->sc_channel[ch].reg_csdp &= ~DMA4_CSDP_DST_ENDIANISM(1); + sc->sc_channel[ch].reg_csdp |= DMA4_CSDP_DST_ENDIANISM(dst); + + sc->sc_channel[ch].need_reg_write = 1; + + TI_SDMA_UNLOCK(sc); + + return 0; +} + +/** + * ti_sdma_set_xfer_burst - sets the source and destination element size + * @ch: the channel number to set the burst settings of + * @src: the source endianness (either DMA_BURST_NONE, DMA_BURST_16, DMA_BURST_32 + * or DMA_BURST_64) + * @dst: the destination endianness (either DMA_BURST_NONE, DMA_BURST_16, + * DMA_BURST_32 or DMA_BURST_64) + * + * This function sets the size of the elements for all subsequent transfers. + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +int +ti_sdma_set_xfer_burst(unsigned int ch, unsigned int src, unsigned int dst) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + TI_SDMA_LOCK(sc); + + if ((sc->sc_active_channels & (1 << ch)) == 0) { + TI_SDMA_UNLOCK(sc); + return (EINVAL); + } + + sc->sc_channel[ch].reg_csdp &= ~DMA4_CSDP_SRC_BURST_MODE(0x3); + sc->sc_channel[ch].reg_csdp |= DMA4_CSDP_SRC_BURST_MODE(src); + + sc->sc_channel[ch].reg_csdp &= ~DMA4_CSDP_DST_BURST_MODE(0x3); + sc->sc_channel[ch].reg_csdp |= DMA4_CSDP_DST_BURST_MODE(dst); + + sc->sc_channel[ch].need_reg_write = 1; + + TI_SDMA_UNLOCK(sc); + + return 0; +} + +/** + * ti_sdma_set_xfer_data_type - driver attach function + * @ch: the channel number to set the endianness of + * @type: the xfer data type (either DMA_DATA_8BITS_SCALAR, DMA_DATA_16BITS_SCALAR + * or DMA_DATA_32BITS_SCALAR) + * + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +int +ti_sdma_set_xfer_data_type(unsigned int ch, unsigned int type) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + TI_SDMA_LOCK(sc); + + if ((sc->sc_active_channels & (1 << ch)) == 0) { + TI_SDMA_UNLOCK(sc); + return (EINVAL); + } + + sc->sc_channel[ch].reg_csdp &= ~DMA4_CSDP_DATA_TYPE(0x3); + sc->sc_channel[ch].reg_csdp |= DMA4_CSDP_DATA_TYPE(type); + + sc->sc_channel[ch].need_reg_write = 1; + + TI_SDMA_UNLOCK(sc); + + return 0; +} + +/** + * ti_sdma_set_callback - driver attach function + * @dev: dma device handle + * + * + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +int +ti_sdma_set_callback(unsigned int ch, + void (*callback)(unsigned int ch, uint32_t status, void *data), + void *data) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + TI_SDMA_LOCK(sc); + + if ((sc->sc_active_channels & (1 << ch)) == 0) { + TI_SDMA_UNLOCK(sc); + return (EINVAL); + } + + sc->sc_channel[ch].callback = callback; + sc->sc_channel[ch].callback_data = data; + + sc->sc_channel[ch].need_reg_write = 1; + + TI_SDMA_UNLOCK(sc); + + return 0; +} + +/** + * ti_sdma_sync_params - sets channel sync settings + * @ch: the channel number to set the sync on + * @trigger: the number of the sync trigger, this depends on what other H/W + * module is triggering/receiving the DMA transactions + * @mode: flags describing the sync mode to use, it may have one or more of + * the following bits set; TI_SDMA_SYNC_FRAME, + * TI_SDMA_SYNC_BLOCK, TI_SDMA_SYNC_TRIG_ON_SRC. + * + * + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +int +ti_sdma_sync_params(unsigned int ch, unsigned int trigger, unsigned int mode) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + uint32_t ccr; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + TI_SDMA_LOCK(sc); + + if ((sc->sc_active_channels & (1 << ch)) == 0) { + TI_SDMA_UNLOCK(sc); + return (EINVAL); + } + + ccr = sc->sc_channel[ch].reg_ccr; + + ccr &= ~DMA4_CCR_SYNC_TRIGGER(0x7F); + ccr |= DMA4_CCR_SYNC_TRIGGER(trigger + 1); + + if (mode & TI_SDMA_SYNC_FRAME) + ccr |= DMA4_CCR_FRAME_SYNC(1); + else + ccr &= ~DMA4_CCR_FRAME_SYNC(1); + + if (mode & TI_SDMA_SYNC_BLOCK) + ccr |= DMA4_CCR_BLOCK_SYNC(1); + else + ccr &= ~DMA4_CCR_BLOCK_SYNC(1); + + if (mode & TI_SDMA_SYNC_TRIG_ON_SRC) + ccr |= DMA4_CCR_SEL_SRC_DST_SYNC(1); + else + ccr &= ~DMA4_CCR_SEL_SRC_DST_SYNC(1); + + sc->sc_channel[ch].reg_ccr = ccr; + + sc->sc_channel[ch].need_reg_write = 1; + + TI_SDMA_UNLOCK(sc); + + return 0; +} + +/** + * ti_sdma_set_addr_mode - driver attach function + * @ch: the channel number to set the endianness of + * @rd_mode: the xfer source addressing mode (either DMA_ADDR_CONSTANT, + * DMA_ADDR_POST_INCREMENT, DMA_ADDR_SINGLE_INDEX or + * DMA_ADDR_DOUBLE_INDEX) + * @wr_mode: the xfer destination addressing mode (either DMA_ADDR_CONSTANT, + * DMA_ADDR_POST_INCREMENT, DMA_ADDR_SINGLE_INDEX or + * DMA_ADDR_DOUBLE_INDEX) + * + * + * LOCKING: + * DMA registers protected by internal mutex + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +int +ti_sdma_set_addr_mode(unsigned int ch, unsigned int src_mode, + unsigned int dst_mode) +{ + struct ti_sdma_softc *sc = ti_sdma_sc; + uint32_t ccr; + + /* Sanity check */ + if (sc == NULL) + return (ENOMEM); + + TI_SDMA_LOCK(sc); + + if ((sc->sc_active_channels & (1 << ch)) == 0) { + TI_SDMA_UNLOCK(sc); + return (EINVAL); + } + + ccr = sc->sc_channel[ch].reg_ccr; + + ccr &= ~DMA4_CCR_SRC_ADDRESS_MODE(0x3); + ccr |= DMA4_CCR_SRC_ADDRESS_MODE(src_mode); + + ccr &= ~DMA4_CCR_DST_ADDRESS_MODE(0x3); + ccr |= DMA4_CCR_DST_ADDRESS_MODE(dst_mode); + + sc->sc_channel[ch].reg_ccr = ccr; + + sc->sc_channel[ch].need_reg_write = 1; + + TI_SDMA_UNLOCK(sc); + + return 0; +} + +/** + * ti_sdma_probe - driver probe function + * @dev: dma device handle + * + * + * + * RETURNS: + * Always returns 0. + */ +static int +ti_sdma_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_is_compatible(dev, "ti,omap4430-sdma")) + return (ENXIO); + + device_set_desc(dev, "TI sDMA Controller"); + return (0); +} + +/** + * ti_sdma_attach - driver attach function + * @dev: dma device handle + * + * Initialises memory mapping/pointers to the DMA register set and requests + * IRQs. This is effectively the setup function for the driver. + * + * RETURNS: + * 0 on success or a negative error code failure. + */ +static int +ti_sdma_attach(device_t dev) +{ + struct ti_sdma_softc *sc = device_get_softc(dev); + unsigned int timeout; + unsigned int i; + int rid; + void *ihl; + int err; + + /* Setup the basics */ + sc->sc_dev = dev; + + /* No channels active at the moment */ + sc->sc_active_channels = 0x00000000; + + /* Mutex to protect the shared data structures */ + TI_SDMA_LOCK_INIT(sc); + + /* Get the memory resource for the register mapping */ + rid = 0; + sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); + if (sc->sc_mem_res == NULL) + panic("%s: Cannot map registers", device_get_name(dev)); + + /* Enable the interface and functional clocks */ + ti_sysc_clock_enable(device_get_parent(dev)); + + /* Read the sDMA revision register and sanity check it's known */ + sc->sc_hw_rev = ti_sdma_read_4(sc, + ti_sysc_get_rev_address_offset_host(device_get_parent(dev))); + device_printf(dev, "sDMA revision %08x\n", sc->sc_hw_rev); + + if (!ti_sdma_is_omap4_rev(sc) && !ti_sdma_is_omap3_rev(sc)) { + device_printf(sc->sc_dev, "error - unknown sDMA H/W revision\n"); + return (EINVAL); + } + + /* Disable all interrupts */ + for (i = 0; i < NUM_DMA_IRQS; i++) { + ti_sdma_write_4(sc, DMA4_IRQENABLE_L(i), 0x00000000); + } + + /* Soft-reset is only supported on pre-OMAP44xx devices */ + if (ti_sdma_is_omap3_rev(sc)) { + /* Soft-reset */ + ti_sdma_write_4(sc, DMA4_OCP_SYSCONFIG, 0x0002); + + /* Set the timeout to 100ms*/ + timeout = (hz < 10) ? 1 : ((100 * hz) / 1000); + + /* Wait for DMA reset to complete */ + while ((ti_sdma_read_4(sc, DMA4_SYSSTATUS) & 0x1) == 0x0) { + /* Sleep for a tick */ + pause("DMARESET", 1); + + if (timeout-- == 0) { + device_printf(sc->sc_dev, "sDMA reset operation timed out\n"); + return (EINVAL); + } + } + } + + /* + * Install interrupt handlers for the for possible interrupts. Any channel + * can trip one of the four IRQs + */ + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_ACTIVE | RF_SHAREABLE); + if (sc->sc_irq_res == NULL) + panic("Unable to setup the dma irq handler.\n"); + + err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, + NULL, ti_sdma_intr, NULL, &ihl); + if (err) + panic("%s: Cannot register IRQ", device_get_name(dev)); + + /* Store the DMA structure globally ... this driver should never be unloaded */ + ti_sdma_sc = sc; + + return (0); +} + +static device_method_t ti_sdma_methods[] = { + DEVMETHOD(device_probe, ti_sdma_probe), + DEVMETHOD(device_attach, ti_sdma_attach), + {0, 0}, +}; + +static driver_t ti_sdma_driver = { + "ti_sdma", + ti_sdma_methods, + sizeof(struct ti_sdma_softc), +}; + +DRIVER_MODULE(ti_sdma, simplebus, ti_sdma_driver, 0, 0); +MODULE_DEPEND(ti_sdma, ti_sysc, 1, 1, 1); |