[PATCH 4/8] ARM i.MX: add SDMA driver
Uwe Kleine-König
u.kleine-koenig at pengutronix.de
Tue Aug 10 15:08:54 EDT 2010
On Mon, Aug 09, 2010 at 11:05:39AM +0200, Sascha Hauer wrote:
> This patch adds DMA support for i.MX25/31/35/51 based SoCs.
> The SDMA engine is a scatter/gather DMA engine which is implemented
> as a seperate coprocessor. SDMA needs its own firmware which is
> requested using the standard request_firmware mechanism. The firmware
> has different entry points for each peripheral type, so drivers
> have to pass the peripheral type to the DMA engine which in turn
> picks the correct firmware entry point from a table contained in
> the firmware image itself.
> The original Freescale code also supports support for transfering
> data to the internal SRAM which needs different entry points to
> the firmware. Support for this is currently not implemented. Also,
> support for the ASRC (asymmetric sample rate converter) is skipped.
>
> This code has been tested with sound on i.MX31/35 and with SD/MMC on
> i.MX31. It should work on i.MX25/51, but this is currently untested.
>
> Signed-off-by: Sascha Hauer <s.hauer at pengutronix.de>
> ---
> arch/arm/mach-mx3/Kconfig | 2 +
> arch/arm/plat-mxc/Kconfig | 10 +
> arch/arm/plat-mxc/Makefile | 1 +
> arch/arm/plat-mxc/include/mach/sdma.h | 8 +
> arch/arm/plat-mxc/sdma.c | 1181 +++++++++++++++++++++++++++++++++
> 5 files changed, 1202 insertions(+), 0 deletions(-)
> create mode 100644 arch/arm/plat-mxc/include/mach/sdma.h
> create mode 100644 arch/arm/plat-mxc/sdma.c
>
> diff --git a/arch/arm/mach-mx3/Kconfig b/arch/arm/mach-mx3/Kconfig
> index 85beece..301375c 100644
> --- a/arch/arm/mach-mx3/Kconfig
> +++ b/arch/arm/mach-mx3/Kconfig
> @@ -3,12 +3,14 @@ if ARCH_MX3
> config ARCH_MX31
> select ARCH_HAS_RNGA
> select ARCH_MXC_AUDMUX_V2
> + select IMX_HAVE_SDMA
> bool
>
> config ARCH_MX35
> bool
> select ARCH_MXC_IOMUX_V3
> select ARCH_MXC_AUDMUX_V2
> + select IMX_HAVE_SDMA
>
> comment "MX3 platforms:"
>
> diff --git a/arch/arm/plat-mxc/Kconfig b/arch/arm/plat-mxc/Kconfig
> index 0527e65..6741625 100644
> --- a/arch/arm/plat-mxc/Kconfig
> +++ b/arch/arm/plat-mxc/Kconfig
> @@ -109,4 +109,14 @@ config ARCH_MXC_AUDMUX_V1
> config ARCH_MXC_AUDMUX_V2
> bool
>
> +config IMX_HAVE_SDMA
> + bool
> +
> +config IMX_SDMA
> + depends on IMX_HAVE_SDMA
> + tristate "Enable SDMA support"
> + help
> + Include support for the SDMA engine. The SDMA engine needs additional
> + firmware support. SDMA can be compiled as a module to support loading
> + the firmware when a rootfs is present.
> endif
> diff --git a/arch/arm/plat-mxc/Makefile b/arch/arm/plat-mxc/Makefile
> index ff9880c..378f8ca 100644
> --- a/arch/arm/plat-mxc/Makefile
> +++ b/arch/arm/plat-mxc/Makefile
> @@ -16,6 +16,7 @@ obj-$(CONFIG_MXC_ULPI) += ulpi.o
> obj-$(CONFIG_ARCH_MXC_AUDMUX_V1) += audmux-v1.o
> obj-$(CONFIG_ARCH_MXC_AUDMUX_V2) += audmux-v2.o
> obj-$(CONFIG_MXC_DEBUG_BOARD) += 3ds_debugboard.o
> +obj-$(CONFIG_IMX_SDMA) += sdma.o
> ifdef CONFIG_SND_IMX_SOC
> obj-y += ssi-fiq.o
> obj-y += ssi-fiq-ksym.o
> diff --git a/arch/arm/plat-mxc/include/mach/sdma.h b/arch/arm/plat-mxc/include/mach/sdma.h
> new file mode 100644
> index 0000000..5d542b8
> --- /dev/null
> +++ b/arch/arm/plat-mxc/include/mach/sdma.h
> @@ -0,0 +1,8 @@
> +#ifndef __MACH_MXC_SDMA_H__
> +#define __MACH_MXC_SDMA_H__
__MACH_SDMA_H__ please
> +
> +struct sdma_platform_data {
> + int sdma_version;
> +};
> +
> +#endif /* __MACH_MXC_SDMA_H__ */
> diff --git a/arch/arm/plat-mxc/sdma.c b/arch/arm/plat-mxc/sdma.c
> new file mode 100644
> index 0000000..3fbc8d8
> --- /dev/null
> +++ b/arch/arm/plat-mxc/sdma.c
> @@ -0,0 +1,1181 @@
> +/*
> + * arch/arm/plat-mxc/sdma.c
> + *
> + * This file contains a driver for the Freescale Smart DMA engine
> + *
> + * Copyright 2010 Sascha Hauer, Pengutronix <s.hauer at pengutronix.de>
> + *
> + * Based on code from Freescale:
> + *
> + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved.
> + *
> + * The code contained herein is licensed under the GNU General Public
> + * License. You may obtain a copy of the GNU General Public License
> + * Version 2 or later at the following locations:
> + *
> + * http://www.opensource.org/licenses/gpl-license.html
> + * http://www.gnu.org/copyleft/gpl.html
> + */
> +
> +#include <linux/init.h>
> +#include <linux/types.h>
> +#include <linux/mm.h>
> +#include <linux/interrupt.h>
> +#include <linux/clk.h>
> +#include <linux/semaphore.h>
> +#include <linux/spinlock.h>
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/firmware.h>
> +#include <linux/slab.h>
> +#include <linux/platform_device.h>
> +
> +#include <asm/irq.h>
> +#include <mach/sdma.h>
> +#include <mach/dma.h>
> +#include <mach/hardware.h>
> +
> +/* SDMA registers */
> +#define SDMA_H_C0PTR (sdma_base + 0x000)
> +#define SDMA_H_INTR (sdma_base + 0x004)
> +#define SDMA_H_STATSTOP (sdma_base + 0x008)
> +#define SDMA_H_START (sdma_base + 0x00c)
> +#define SDMA_H_EVTOVR (sdma_base + 0x010)
> +#define SDMA_H_DSPOVR (sdma_base + 0x014)
> +#define SDMA_H_HOSTOVR (sdma_base + 0x018)
> +#define SDMA_H_EVTPEND (sdma_base + 0x01c)
> +#define SDMA_H_DSPENBL (sdma_base + 0x020)
> +#define SDMA_H_RESET (sdma_base + 0x024)
> +#define SDMA_H_EVTERR (sdma_base + 0x028)
> +#define SDMA_H_INTRMSK (sdma_base + 0x02c)
> +#define SDMA_H_PSW (sdma_base + 0x030)
> +#define SDMA_H_EVTERRDBG (sdma_base + 0x034)
> +#define SDMA_H_CONFIG (sdma_base + 0x038)
> +#define SDMA_ONCE_ENB (sdma_base + 0x040)
> +#define SDMA_ONCE_DATA (sdma_base + 0x044)
> +#define SDMA_ONCE_INSTR (sdma_base + 0x048)
> +#define SDMA_ONCE_STAT (sdma_base + 0x04c)
> +#define SDMA_ONCE_CMD (sdma_base + 0x050)
> +#define SDMA_EVT_MIRROR (sdma_base + 0x054)
> +#define SDMA_ILLINSTADDR (sdma_base + 0x058)
> +#define SDMA_CHN0ADDR (sdma_base + 0x05c)
> +#define SDMA_ONCE_RTB (sdma_base + 0x060)
> +#define SDMA_XTRIG_CONF1 (sdma_base + 0x070)
> +#define SDMA_XTRIG_CONF2 (sdma_base + 0x074)
> +#define SDMA_CHNENBL_0 (sdma_base + (sdma_version == 2 ? 0x200 : 0x80))
> +#define SDMA_CHNPRI_0 (sdma_base + 0x100)
> +
> +/*
> + * Buffer descriptor status values.
> + */
> +#define BD_DONE 0x01
> +#define BD_WRAP 0x02
> +#define BD_CONT 0x04
> +#define BD_INTR 0x08
> +#define BD_RROR 0x10
> +#define BD_LAST 0x20
> +#define BD_EXTD 0x80
> +
> +/*
> + * Data Node descriptor status values.
> + */
> +#define DND_END_OF_FRAME 0x80
> +#define DND_END_OF_XFER 0x40
> +#define DND_DONE 0x20
> +#define DND_UNUSED 0x01
> +
> +/*
> + * IPCV2 descriptor status values.
> + */
> +#define BD_IPCV2_END_OF_FRAME 0x40
> +
> +#define IPCV2_MAX_NODES 50
> +/*
> + * Error bit set in the CCB status field by the SDMA,
> + * in setbd routine, in case of a transfer error
> + */
> +#define DATA_ERROR 0x10000000
> +
> +/*
> + * Buffer descriptor commands.
> + */
> +#define C0_ADDR 0x01
> +#define C0_LOAD 0x02
> +#define C0_DUMP 0x03
> +#define C0_SETCTX 0x07
> +#define C0_GETCTX 0x03
> +#define C0_SETDM 0x01
> +#define C0_SETPM 0x04
> +#define C0_GETDM 0x02
> +#define C0_GETPM 0x08
> +/*
> + * Change endianness indicator in the BD command field
> + */
> +#define CHANGE_ENDIANNESS 0x80
> +
> +/*
> + * Mode/Count of data node descriptors - IPCv2
> + */
> +#ifdef __BIG_ENDIAN
> +struct sdma_mode_count {
> + u32 command : 8; /* command mostlky used for channel 0 */
> + u32 status : 8; /* E,R,I,C,W,D status bits stored here */
> + u32 count : 16; /* size of the buffer pointed by this BD */
> +};
> +#else
> +struct sdma_mode_count {
> + u32 count : 16; /* size of the buffer pointed by this BD */
> + u32 status : 8; /* E,R,I,C,W,D status bits stored here */
> + u32 command : 8; /* command mostlky used for channel 0 */
> +};
> +#endif
> +
> +/*
> + * Buffer descriptor
> + */
> +struct sdma_buffer_descriptor {
> + struct sdma_mode_count mode;
> + void *buffer_addr; /* address of the buffer described */
> + void *ext_buffer_addr; /* extended buffer address */
> +};
> +
> +/*
> + * Channel control Block
> + */
> +struct sdma_channel_control {
> + struct sdma_buffer_descriptor *currentBDptr; /* current buffer descriptor processed */
> + struct sdma_buffer_descriptor *baseBDptr; /* first element of buffer descriptor array */
> + void *unused;
> + void *unused1;
> +};
> +
> +/**
> + * Context structure.
> + */
> +#ifdef __BIG_ENDIAN
> +struct sdma_state_registers {
> + u32 sf : 1; /* source falut while loading data */
> + u32 unused0: 1;
> + u32 rpc :14; /* return program counter */
> + u32 t : 1; /* test bit:status of arithmetic & test instruction*/
> + u32 unused1: 1;
> + u32 pc :14; /* program counter */
> + u32 lm : 2; /* loop mode */
> + u32 epc :14; /* loop end program counter */
> + u32 df : 1; /* destiantion falut while storing data */
> + u32 unused2: 1;
> + u32 spc :14; /* loop start program counter */
> +};
> +#else
> +struct sdma_state_registers {
> + u32 pc :14; /* program counter */
> + u32 unused1: 1;
> + u32 t : 1; /* test bit: status of arithmetic & test instruction*/
> + u32 rpc :14; /* return program counter */
> + u32 unused0: 1;
> + u32 sf : 1; /* source falut while loading data */
> + u32 spc :14; /* loop start program counter */
> + u32 unused2: 1;
> + u32 df : 1; /* destiantion falut while storing data */
> + u32 epc :14; /* loop end program counter */
> + u32 lm : 2; /* loop mode */
> +};
> +#endif
> +
> +struct sdma_context_data {
> + struct sdma_state_registers channel_state; /* channel state bits */
> + u32 gReg[8]; /* general registers */
> + u32 mda; /* burst dma destination address register */
> + u32 msa; /* burst dma source address register */
> + u32 ms; /* burst dma status register */
> + u32 md; /* burst dma data register */
> + u32 pda; /* peripheral dma destination address register */
> + u32 psa; /* peripheral dma source address register */
> + u32 ps; /* peripheral dma status register */
> + u32 pd; /* peripheral dma data register */
> + u32 ca; /* CRC polynomial register */
> + u32 cs; /* CRC accumulator register */
> + u32 dda; /* dedicated core destination address register */
> + u32 dsa; /* dedicated core source address register */
> + u32 ds; /* dedicated core status register */
> + u32 dd; /* dedicated core data register */
> + u32 scratch0;
> + u32 scratch1;
> + u32 scratch2;
> + u32 scratch3;
> + u32 scratch4;
> + u32 scratch5;
> + u32 scratch6;
> + u32 scratch7;
> +};
> +
> +struct sdma_channel {
> + /* Channel number */
> + int channel;
> + /* Channel usage name */
> + int in_use;
> + /* Transfer type. Needed for setting SDMA script */
> + int dmamode;
> + /* Peripheral type. Needed for setting SDMA script */
> + sdma_peripheral_type peripheral_type;
> + /* Peripheral event id */
> + int event_id;
> + /* Peripheral event id2 (for channels that use 2 events) */
> + int event_id2;
> + /* Running status */
> + int running;
> + /* SDMA data access word size */
> + unsigned long word_size;
> +
> + /* ID of the buffer that was processed */
> + unsigned int buf_tail;
> +
> + void (*callback)(int channel, void *arg, int error);
> + void *callback_arg;
> +
> + wait_queue_head_t waitq; /* channel completion waitqeue */
> +
> + int num_bd;
> +
> + struct sdma_buffer_descriptor *bd;
> + dma_addr_t bd_phys;
> +
> + int pc_from_device, pc_to_device;
> +
> + unsigned long flags;
> + dma_addr_t per_address;
> +
> + uint32_t event_mask1, event_mask2;
> + uint32_t watermark_level;
> + uint32_t shp_addr, per_addr;
> +};
> +
> +#define MAX_DMA_CHANNELS 32
> +#define MXC_SDMA_DEFAULT_PRIORITY 1
> +#define MXC_SDMA_MIN_PRIORITY 1
> +#define MXC_SDMA_MAX_PRIORITY 7
> +
> +/*
> + * This enumerates transfer types
> + */
> +typedef enum {
> + emi_2_per = 0, /* EMI memory to peripheral */
> + emi_2_int, /* EMI memory to internal RAM */
> + emi_2_emi, /* EMI memory to EMI memory */
> + emi_2_dsp, /* EMI memory to DSP memory */
> + per_2_int, /* Peripheral to internal RAM */
> + per_2_emi, /* Peripheral to internal EMI memory */
> + per_2_dsp, /* Peripheral to DSP memory */
> + per_2_per, /* Peripheral to Peripheral */
> + int_2_per, /* Internal RAM to peripheral */
> + int_2_int, /* Internal RAM to Internal RAM */
> + int_2_emi, /* Internal RAM to EMI memory */
> + int_2_dsp, /* Internal RAM to DSP memory */
> + dsp_2_per, /* DSP memory to peripheral */
> + dsp_2_int, /* DSP memory to internal RAM */
> + dsp_2_emi, /* DSP memory to EMI memory */
> + dsp_2_dsp, /* DSP memory to DSP memory */
> + emi_2_dsp_loop, /* EMI memory to DSP memory loopback */
> + dsp_2_emi_loop, /* DSP memory to EMI memory loopback */
> + dvfs_pll, /* DVFS script with PLL change */
> + dvfs_pdr /* DVFS script without PLL change */
> +} sdma_transfer_type;
> +
> +/*
> + * Structure containing sdma request parameters.
s/ / /
> + */
> +struct sdma_script_start_addrs {
> + int ap_2_ap_addr;
> + int ap_2_bp_addr;
> + int ap_2_ap_fixed_addr;
> + int bp_2_ap_addr;
> + int loopback_on_dsp_side_addr;
> + int mcu_interrupt_only_addr;
> +
> + int firi_2_per_addr;
> + int firi_2_mcu_addr;
> + int per_2_firi_addr;
> + int mcu_2_firi_addr;
> +
> + int uart_2_per_addr;
> + int uart_2_mcu_addr;
> + int per_2_app_addr;
> + int mcu_2_app_addr;
> + int per_2_per_addr;
> +
> + int uartsh_2_per_addr;
> + int uartsh_2_mcu_addr;
> + int per_2_shp_addr;
> + int mcu_2_shp_addr;
> +
> + int ata_2_mcu_addr;
> + int mcu_2_ata_addr;
> +
> + int app_2_per_addr;
> + int app_2_mcu_addr;
> + int shp_2_per_addr;
> + int shp_2_mcu_addr;
> +
> + int mshc_2_mcu_addr;
> + int mcu_2_mshc_addr;
> +
> + int spdif_2_mcu_addr;
> + int mcu_2_spdif_addr;
> +
> + int asrc_2_mcu_addr;
> +
> + int ext_mem_2_ipu_addr;
> +
> + int descrambler_addr;
> +
> + int dptc_dvfs_addr;
> +
> + int utra_addr;
> +
> + int ram_code_start_addr;
> +};
> +
> +#define SDMA_FIRMWARE_MAGIC 0x414d4453
> +
> +struct sdma_firmware_header {
> + uint32_t magic; /* "SDMA" */
> + uint32_t version_major; /* increased whenever layout of struct sdma_script_start_addrs changes */
> + uint32_t version_minor; /* firmware version */
> + uint32_t script_addrs_start; /* offset of struct sdma_script_start_addrs in this image */
> + uint32_t num_script_addrs; /* Number of script addresses in this image */
> + uint32_t ram_code_start; /* offset of SDMA ram image in this firmware image */
> + uint32_t ram_code_size; /* size of SDMA ram image */
> +};
I wonder if it's worth the effort to have symbolic names for the script
addresses. i.e.
struct sdma_firmware_header {
uint32_t magic;
uint32_t version_major;
uint32_t version_minor;
uint32_t script_addrs_start; /* offset of struct scriptaddr[] in this image */
uint32_t num_script_addrs; /* number of script addresses or
alternatively just NULL-terminate the list? */
....
};
struct scriptaddr {
int startaddr;
char name[0];
};
It's a bit more flexible and doesn't necessarily introduce the need for
bumping the version_major when a new address is added. Maybe require
alphabetic ordering then to make lookup a bit faster?
> +
> +static struct sdma_channel sdma_data[MAX_DMA_CHANNELS];
> +static struct sdma_channel_control *channel_control;
> +static void __iomem *sdma_base;
> +static int sdma_version;
> +static int sdma_num_events;
> +static struct sdma_context_data *sdma_context;
> +dma_addr_t sdma_context_phys;
> +
> +#define SDMA_H_CONFIG_DSPDMA (1 << 12) /* indicates if the DSPDMA is used */
> +#define SDMA_H_CONFIG_RTD_PINS (1 << 11) /* indicates if Real-Time Debug pins are enabled */
> +#define SDMA_H_CONFIG_ACR (1 << 4) /* indicates if AHB freq /core freq = 2 or 1 */
> +#define SDMA_H_CONFIG_CSM (3) /* indicates which context switch mode is selected*/
> +
> +static int sdma_config_ownership(int channel, int event_override,
> + int mcu_verride, int dsp_override)
> +{
> + u32 evt, mcu, dsp;
> +
> + if (event_override && mcu_verride && dsp_override)
> + return -EINVAL;
> +
> + evt = readl(SDMA_H_EVTOVR);
> + mcu = readl(SDMA_H_HOSTOVR);
> + dsp = readl(SDMA_H_DSPOVR);
readl performs little endian access, so I suggest to either use
__raw_readl or run unifdef -U__BIG_ENDIAN on this patch.
Best regards
Uwe
--
Pengutronix e.K. | Uwe Kleine-König |
Industrial Linux Solutions | http://www.pengutronix.de/ |
More information about the linux-arm-kernel
mailing list