Commit e3e1288e authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/djbw/async_tx

* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/djbw/async_tx: (48 commits)
  DMAENGINE: move COH901318 to arch_initcall
  dma: imx-dma: fix signedness bug
  dma/timberdale: simplify conditional
  ste_dma40: remove channel_type
  ste_dma40: remove enum for endianess
  ste_dma40: remove TIM_FOR_LINK option
  ste_dma40: move mode_opt to separate config
  ste_dma40: move channel mode to a separate field
  ste_dma40: move priority to separate field
  ste_dma40: add variable to indicate valid dma_cfg
  async_tx: make async_tx channel switching opt-in
  move async raid6 test to lib/Kconfig.debug
  dmaengine: Add Freescale i.MX1/21/27 DMA driver
  intel_mid_dma: change the slave interface
  intel_mid_dma: fix the WARN_ONs
  intel_mid_dma: Add sg list support to DMA driver
  intel_mid_dma: Allow DMAC2 to share interrupt
  intel_mid_dma: Allow IRQ sharing
  intel_mid_dma: Add runtime PM support
  DMAENGINE: define a dummy filter function for ste_dma40
  ...
parents 9ae6d039 964dc256
...@@ -27,6 +27,8 @@ ...@@ -27,6 +27,8 @@
#define imx_has_dma_v1() (cpu_is_mx1() || cpu_is_mx21() || cpu_is_mx27()) #define imx_has_dma_v1() (cpu_is_mx1() || cpu_is_mx21() || cpu_is_mx27())
#include <mach/dma.h>
#define IMX_DMA_CHANNELS 16 #define IMX_DMA_CHANNELS 16
#define DMA_MODE_READ 0 #define DMA_MODE_READ 0
...@@ -96,12 +98,6 @@ int imx_dma_request(int channel, const char *name); ...@@ -96,12 +98,6 @@ int imx_dma_request(int channel, const char *name);
void imx_dma_free(int channel); void imx_dma_free(int channel);
enum imx_dma_prio {
DMA_PRIO_HIGH = 0,
DMA_PRIO_MEDIUM = 1,
DMA_PRIO_LOW = 2
};
int imx_dma_request_by_prio(const char *name, enum imx_dma_prio prio); int imx_dma_request_by_prio(const char *name, enum imx_dma_prio prio);
#endif /* __MACH_DMA_V1_H__ */ #endif /* __MACH_DMA_V1_H__ */
...@@ -208,35 +208,25 @@ static struct resource dma40_resources[] = { ...@@ -208,35 +208,25 @@ static struct resource dma40_resources[] = {
/* Default configuration for physcial memcpy */ /* Default configuration for physcial memcpy */
struct stedma40_chan_cfg dma40_memcpy_conf_phy = { struct stedma40_chan_cfg dma40_memcpy_conf_phy = {
.channel_type = (STEDMA40_CHANNEL_IN_PHY_MODE | .mode = STEDMA40_MODE_PHYSICAL,
STEDMA40_LOW_PRIORITY_CHANNEL |
STEDMA40_PCHAN_BASIC_MODE),
.dir = STEDMA40_MEM_TO_MEM, .dir = STEDMA40_MEM_TO_MEM,
.src_info.endianess = STEDMA40_LITTLE_ENDIAN,
.src_info.data_width = STEDMA40_BYTE_WIDTH, .src_info.data_width = STEDMA40_BYTE_WIDTH,
.src_info.psize = STEDMA40_PSIZE_PHY_1, .src_info.psize = STEDMA40_PSIZE_PHY_1,
.src_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL, .src_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL,
.dst_info.endianess = STEDMA40_LITTLE_ENDIAN,
.dst_info.data_width = STEDMA40_BYTE_WIDTH, .dst_info.data_width = STEDMA40_BYTE_WIDTH,
.dst_info.psize = STEDMA40_PSIZE_PHY_1, .dst_info.psize = STEDMA40_PSIZE_PHY_1,
.dst_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL, .dst_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL,
}; };
/* Default configuration for logical memcpy */ /* Default configuration for logical memcpy */
struct stedma40_chan_cfg dma40_memcpy_conf_log = { struct stedma40_chan_cfg dma40_memcpy_conf_log = {
.channel_type = (STEDMA40_CHANNEL_IN_LOG_MODE |
STEDMA40_LOW_PRIORITY_CHANNEL |
STEDMA40_LCHAN_SRC_LOG_DST_LOG |
STEDMA40_NO_TIM_FOR_LINK),
.dir = STEDMA40_MEM_TO_MEM, .dir = STEDMA40_MEM_TO_MEM,
.src_info.endianess = STEDMA40_LITTLE_ENDIAN,
.src_info.data_width = STEDMA40_BYTE_WIDTH, .src_info.data_width = STEDMA40_BYTE_WIDTH,
.src_info.psize = STEDMA40_PSIZE_LOG_1, .src_info.psize = STEDMA40_PSIZE_LOG_1,
.src_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL, .src_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL,
.dst_info.endianess = STEDMA40_LITTLE_ENDIAN,
.dst_info.data_width = STEDMA40_BYTE_WIDTH, .dst_info.data_width = STEDMA40_BYTE_WIDTH,
.dst_info.psize = STEDMA40_PSIZE_LOG_1, .dst_info.psize = STEDMA40_PSIZE_LOG_1,
.dst_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL, .dst_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL,
...@@ -269,7 +259,6 @@ static struct stedma40_platform_data dma40_plat_data = { ...@@ -269,7 +259,6 @@ static struct stedma40_platform_data dma40_plat_data = {
.memcpy_len = ARRAY_SIZE(dma40_memcpy_event), .memcpy_len = ARRAY_SIZE(dma40_memcpy_event),
.memcpy_conf_phy = &dma40_memcpy_conf_phy, .memcpy_conf_phy = &dma40_memcpy_conf_phy,
.memcpy_conf_log = &dma40_memcpy_conf_log, .memcpy_conf_log = &dma40_memcpy_conf_log,
.llis_per_log = 8,
.disabled_channels = {-1}, .disabled_channels = {-1},
}; };
......
/*
* Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __ASM_ARCH_MXC_DMA_H__
#define __ASM_ARCH_MXC_DMA_H__
#include <linux/scatterlist.h>
#include <linux/device.h>
#include <linux/dmaengine.h>
/*
* This enumerates peripheral types. Used for SDMA.
*/
enum sdma_peripheral_type {
IMX_DMATYPE_SSI, /* MCU domain SSI */
IMX_DMATYPE_SSI_SP, /* Shared SSI */
IMX_DMATYPE_MMC, /* MMC */
IMX_DMATYPE_SDHC, /* SDHC */
IMX_DMATYPE_UART, /* MCU domain UART */
IMX_DMATYPE_UART_SP, /* Shared UART */
IMX_DMATYPE_FIRI, /* FIRI */
IMX_DMATYPE_CSPI, /* MCU domain CSPI */
IMX_DMATYPE_CSPI_SP, /* Shared CSPI */
IMX_DMATYPE_SIM, /* SIM */
IMX_DMATYPE_ATA, /* ATA */
IMX_DMATYPE_CCM, /* CCM */
IMX_DMATYPE_EXT, /* External peripheral */
IMX_DMATYPE_MSHC, /* Memory Stick Host Controller */
IMX_DMATYPE_MSHC_SP, /* Shared Memory Stick Host Controller */
IMX_DMATYPE_DSP, /* DSP */
IMX_DMATYPE_MEMORY, /* Memory */
IMX_DMATYPE_FIFO_MEMORY,/* FIFO type Memory */
IMX_DMATYPE_SPDIF, /* SPDIF */
IMX_DMATYPE_IPU_MEMORY, /* IPU Memory */
IMX_DMATYPE_ASRC, /* ASRC */
IMX_DMATYPE_ESAI, /* ESAI */
};
enum imx_dma_prio {
DMA_PRIO_HIGH = 0,
DMA_PRIO_MEDIUM = 1,
DMA_PRIO_LOW = 2
};
struct imx_dma_data {
int dma_request; /* DMA request line */
enum sdma_peripheral_type peripheral_type;
int priority;
};
static inline int imx_dma_is_ipu(struct dma_chan *chan)
{
return !strcmp(dev_name(chan->device->dev), "ipu-core");
}
static inline int imx_dma_is_general_purpose(struct dma_chan *chan)
{
return !strcmp(dev_name(chan->device->dev), "imx-sdma") ||
!strcmp(dev_name(chan->device->dev), "imx-dma");
}
#endif
#ifndef __MACH_MXC_SDMA_H__
#define __MACH_MXC_SDMA_H__
/**
* struct sdma_platform_data - platform specific data for SDMA engine
*
* @sdma_version The version of this SDMA engine
* @cpu_name used to generate the firmware name
* @to_version CPU Tape out version
*/
struct sdma_platform_data {
int sdma_version;
char *cpu_name;
int to_version;
};
#endif /* __MACH_MXC_SDMA_H__ */
/* /*
* arch/arm/plat-nomadik/include/plat/ste_dma40.h * Copyright (C) ST-Ericsson SA 2007-2010
* * Author: Per Forlin <per.forlin@stericsson.com> for ST-Ericsson
* Copyright (C) ST-Ericsson 2007-2010 * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson
* License terms: GNU General Public License (GPL) version 2 * License terms: GNU General Public License (GPL) version 2
* Author: Per Friden <per.friden@stericsson.com>
* Author: Jonas Aaberg <jonas.aberg@stericsson.com>
*/ */
...@@ -19,37 +17,20 @@ ...@@ -19,37 +17,20 @@
#define STEDMA40_DEV_DST_MEMORY (-1) #define STEDMA40_DEV_DST_MEMORY (-1)
#define STEDMA40_DEV_SRC_MEMORY (-1) #define STEDMA40_DEV_SRC_MEMORY (-1)
/* enum stedma40_mode {
* Description of bitfields of channel_type variable is available in STEDMA40_MODE_LOGICAL = 0,
* the info structure. STEDMA40_MODE_PHYSICAL,
*/ STEDMA40_MODE_OPERATION,
};
/* Priority */ enum stedma40_mode_opt {
#define STEDMA40_INFO_PRIO_TYPE_POS 2 STEDMA40_PCHAN_BASIC_MODE = 0,
#define STEDMA40_HIGH_PRIORITY_CHANNEL (0x1 << STEDMA40_INFO_PRIO_TYPE_POS) STEDMA40_LCHAN_SRC_LOG_DST_LOG = 0,
#define STEDMA40_LOW_PRIORITY_CHANNEL (0x2 << STEDMA40_INFO_PRIO_TYPE_POS) STEDMA40_PCHAN_MODULO_MODE,
STEDMA40_PCHAN_DOUBLE_DST_MODE,
/* Mode */ STEDMA40_LCHAN_SRC_PHY_DST_LOG,
#define STEDMA40_INFO_CH_MODE_TYPE_POS 6 STEDMA40_LCHAN_SRC_LOG_DST_PHY,
#define STEDMA40_CHANNEL_IN_PHY_MODE (0x1 << STEDMA40_INFO_CH_MODE_TYPE_POS) };
#define STEDMA40_CHANNEL_IN_LOG_MODE (0x2 << STEDMA40_INFO_CH_MODE_TYPE_POS)
#define STEDMA40_CHANNEL_IN_OPER_MODE (0x3 << STEDMA40_INFO_CH_MODE_TYPE_POS)
/* Mode options */
#define STEDMA40_INFO_CH_MODE_OPT_POS 8
#define STEDMA40_PCHAN_BASIC_MODE (0x1 << STEDMA40_INFO_CH_MODE_OPT_POS)
#define STEDMA40_PCHAN_MODULO_MODE (0x2 << STEDMA40_INFO_CH_MODE_OPT_POS)
#define STEDMA40_PCHAN_DOUBLE_DST_MODE (0x3 << STEDMA40_INFO_CH_MODE_OPT_POS)
#define STEDMA40_LCHAN_SRC_PHY_DST_LOG (0x1 << STEDMA40_INFO_CH_MODE_OPT_POS)
#define STEDMA40_LCHAN_SRC_LOG_DST_PHS (0x2 << STEDMA40_INFO_CH_MODE_OPT_POS)
#define STEDMA40_LCHAN_SRC_LOG_DST_LOG (0x3 << STEDMA40_INFO_CH_MODE_OPT_POS)
/* Interrupt */
#define STEDMA40_INFO_TIM_POS 10
#define STEDMA40_NO_TIM_FOR_LINK (0x0 << STEDMA40_INFO_TIM_POS)
#define STEDMA40_TIM_FOR_LINK (0x1 << STEDMA40_INFO_TIM_POS)
/* End of channel_type configuration */
#define STEDMA40_ESIZE_8_BIT 0x0 #define STEDMA40_ESIZE_8_BIT 0x0
#define STEDMA40_ESIZE_16_BIT 0x1 #define STEDMA40_ESIZE_16_BIT 0x1
...@@ -72,16 +53,14 @@ ...@@ -72,16 +53,14 @@
#define STEDMA40_PSIZE_LOG_8 STEDMA40_PSIZE_PHY_8 #define STEDMA40_PSIZE_LOG_8 STEDMA40_PSIZE_PHY_8
#define STEDMA40_PSIZE_LOG_16 STEDMA40_PSIZE_PHY_16 #define STEDMA40_PSIZE_LOG_16 STEDMA40_PSIZE_PHY_16
/* Maximum number of possible physical channels */
#define STEDMA40_MAX_PHYS 32
enum stedma40_flow_ctrl { enum stedma40_flow_ctrl {
STEDMA40_NO_FLOW_CTRL, STEDMA40_NO_FLOW_CTRL,
STEDMA40_FLOW_CTRL, STEDMA40_FLOW_CTRL,
}; };
enum stedma40_endianess {
STEDMA40_LITTLE_ENDIAN,
STEDMA40_BIG_ENDIAN
};
enum stedma40_periph_data_width { enum stedma40_periph_data_width {
STEDMA40_BYTE_WIDTH = STEDMA40_ESIZE_8_BIT, STEDMA40_BYTE_WIDTH = STEDMA40_ESIZE_8_BIT,
STEDMA40_HALFWORD_WIDTH = STEDMA40_ESIZE_16_BIT, STEDMA40_HALFWORD_WIDTH = STEDMA40_ESIZE_16_BIT,
...@@ -89,34 +68,40 @@ enum stedma40_periph_data_width { ...@@ -89,34 +68,40 @@ enum stedma40_periph_data_width {
STEDMA40_DOUBLEWORD_WIDTH = STEDMA40_ESIZE_64_BIT STEDMA40_DOUBLEWORD_WIDTH = STEDMA40_ESIZE_64_BIT
}; };
struct stedma40_half_channel_info {
enum stedma40_endianess endianess;
enum stedma40_periph_data_width data_width;
int psize;
enum stedma40_flow_ctrl flow_ctrl;
};
enum stedma40_xfer_dir { enum stedma40_xfer_dir {
STEDMA40_MEM_TO_MEM, STEDMA40_MEM_TO_MEM = 1,
STEDMA40_MEM_TO_PERIPH, STEDMA40_MEM_TO_PERIPH,
STEDMA40_PERIPH_TO_MEM, STEDMA40_PERIPH_TO_MEM,
STEDMA40_PERIPH_TO_PERIPH STEDMA40_PERIPH_TO_PERIPH
}; };
/**
* struct stedma40_chan_cfg - dst/src channel configuration
*
* @big_endian: true if the src/dst should be read as big endian
* @data_width: Data width of the src/dst hardware
* @p_size: Burst size
* @flow_ctrl: Flow control on/off.
*/
struct stedma40_half_channel_info {
bool big_endian;
enum stedma40_periph_data_width data_width;
int psize;
enum stedma40_flow_ctrl flow_ctrl;
};
/** /**
* struct stedma40_chan_cfg - Structure to be filled by client drivers. * struct stedma40_chan_cfg - Structure to be filled by client drivers.
* *
* @dir: MEM 2 MEM, PERIPH 2 MEM , MEM 2 PERIPH, PERIPH 2 PERIPH * @dir: MEM 2 MEM, PERIPH 2 MEM , MEM 2 PERIPH, PERIPH 2 PERIPH
* @channel_type: priority, mode, mode options and interrupt configuration. * @high_priority: true if high-priority
* @mode: channel mode: physical, logical, or operation
* @mode_opt: options for the chosen channel mode
* @src_dev_type: Src device type * @src_dev_type: Src device type
* @dst_dev_type: Dst device type * @dst_dev_type: Dst device type
* @src_info: Parameters for dst half channel * @src_info: Parameters for dst half channel
* @dst_info: Parameters for dst half channel * @dst_info: Parameters for dst half channel
* @pre_transfer_data: Data to be passed on to the pre_transfer() function.
* @pre_transfer: Callback used if needed before preparation of transfer.
* Only called if device is set. size of bytes to transfer
* (in case of multiple element transfer size is size of the first element).
* *
* *
* This structure has to be filled by the client drivers. * This structure has to be filled by the client drivers.
...@@ -125,15 +110,13 @@ enum stedma40_xfer_dir { ...@@ -125,15 +110,13 @@ enum stedma40_xfer_dir {
*/ */
struct stedma40_chan_cfg { struct stedma40_chan_cfg {
enum stedma40_xfer_dir dir; enum stedma40_xfer_dir dir;
unsigned int channel_type; bool high_priority;
enum stedma40_mode mode;
enum stedma40_mode_opt mode_opt;
int src_dev_type; int src_dev_type;
int dst_dev_type; int dst_dev_type;
struct stedma40_half_channel_info src_info; struct stedma40_half_channel_info src_info;
struct stedma40_half_channel_info dst_info; struct stedma40_half_channel_info dst_info;
void *pre_transfer_data;
int (*pre_transfer) (struct dma_chan *chan,
void *data,
int size);
}; };
/** /**
...@@ -146,7 +129,6 @@ struct stedma40_chan_cfg { ...@@ -146,7 +129,6 @@ struct stedma40_chan_cfg {
* @memcpy_len: length of memcpy * @memcpy_len: length of memcpy
* @memcpy_conf_phy: default configuration of physical channel memcpy * @memcpy_conf_phy: default configuration of physical channel memcpy
* @memcpy_conf_log: default configuration of logical channel memcpy * @memcpy_conf_log: default configuration of logical channel memcpy
* @llis_per_log: number of max linked list items per logical channel
* @disabled_channels: A vector, ending with -1, that marks physical channels * @disabled_channels: A vector, ending with -1, that marks physical channels
* that are for different reasons not available for the driver. * that are for different reasons not available for the driver.
*/ */
...@@ -158,23 +140,10 @@ struct stedma40_platform_data { ...@@ -158,23 +140,10 @@ struct stedma40_platform_data {
u32 memcpy_len; u32 memcpy_len;
struct stedma40_chan_cfg *memcpy_conf_phy; struct stedma40_chan_cfg *memcpy_conf_phy;
struct stedma40_chan_cfg *memcpy_conf_log; struct stedma40_chan_cfg *memcpy_conf_log;
unsigned int llis_per_log; int disabled_channels[STEDMA40_MAX_PHYS];
int disabled_channels[8];
}; };
/** #ifdef CONFIG_STE_DMA40
* setdma40_set_psize() - Used for changing the package size of an
* already configured dma channel.
*
* @chan: dmaengine handle
* @src_psize: new package side for src. (STEDMA40_PSIZE*)
* @src_psize: new package side for dst. (STEDMA40_PSIZE*)
*
* returns 0 on ok, otherwise negative error number.
*/
int stedma40_set_psize(struct dma_chan *chan,
int src_psize,
int dst_psize);
/** /**
* stedma40_filter() - Provides stedma40_chan_cfg to the * stedma40_filter() - Provides stedma40_chan_cfg to the
...@@ -237,4 +206,21 @@ dma_async_tx_descriptor *stedma40_slave_mem(struct dma_chan *chan, ...@@ -237,4 +206,21 @@ dma_async_tx_descriptor *stedma40_slave_mem(struct dma_chan *chan,
direction, flags); direction, flags);
} }
#else
static inline bool stedma40_filter(struct dma_chan *chan, void *data)
{
return false;
}
static inline struct
dma_async_tx_descriptor *stedma40_slave_mem(struct dma_chan *chan,
dma_addr_t addr,
unsigned int size,
enum dma_data_direction direction,
unsigned long flags)
{
return NULL;
}
#endif
#endif #endif
/*
* Freescale MPC83XX / MPC85XX DMA Controller
*
* Copyright (c) 2009 Ira W. Snyder <iws@ovro.caltech.edu>
*
* This file is licensed under the terms of the GNU General Public License
* version 2. This program is licensed "as is" without any warranty of any
* kind, whether express or implied.
*/
#ifndef __ARCH_POWERPC_ASM_FSLDMA_H__
#define __ARCH_POWERPC_ASM_FSLDMA_H__
#include <linux/slab.h>
#include <linux/dmaengine.h>
/*
* Definitions for the Freescale DMA controller's DMA_SLAVE implemention
*
* The Freescale DMA_SLAVE implementation was designed to handle many-to-many
* transfers. An example usage would be an accelerated copy between two
* scatterlists. Another example use would be an accelerated copy from
* multiple non-contiguous device buffers into a single scatterlist.
*
* A DMA_SLAVE transaction is defined by a struct fsl_dma_slave. This
* structure contains a list of hardware addresses that should be copied
* to/from the scatterlist passed into device_prep_slave_sg(). The structure
* also has some fields to enable hardware-specific features.
*/
/**
* struct fsl_dma_hw_addr
* @entry: linked list entry
* @address: the hardware address
* @length: length to transfer
*
* Holds a single physical hardware address / length pair for use
* with the DMAEngine DMA_SLAVE API.
*/
struct fsl_dma_hw_addr {
struct list_head entry;
dma_addr_t address;
size_t length;
};
/**
* struct fsl_dma_slave
* @addresses: a linked list of struct fsl_dma_hw_addr structures
* @request_count: value for DMA request count
* @src_loop_size: setup and enable constant source-address DMA transfers
* @dst_loop_size: setup and enable constant destination address DMA transfers
* @external_start: enable externally started DMA transfers
* @external_pause: enable externally paused DMA transfers
*
* Holds a list of address / length pairs for use with the DMAEngine
* DMA_SLAVE API implementation for the Freescale DMA controller.
*/
struct fsl_dma_slave {
/* List of hardware address/length pairs */
struct list_head addresses;
/* Support for extra controller features */
unsigned int request_count;
unsigned int src_loop_size;
unsigned int dst_loop_size;
bool external_start;
bool external_pause;
};
/**
* fsl_dma_slave_append - add an address/length pair to a struct fsl_dma_slave
* @slave: the &struct fsl_dma_slave to add to
* @address: the hardware address to add
* @length: the length of bytes to transfer from @address
*
* Add a hardware address/length pair to a struct fsl_dma_slave. Returns 0 on
* success, -ERRNO otherwise.
*/
static inline int fsl_dma_slave_append(struct fsl_dma_slave *slave,
dma_addr_t address, size_t length)
{
struct fsl_dma_hw_addr *addr;
addr = kzalloc(sizeof(*addr), GFP_ATOMIC);
if (!addr)
return -ENOMEM;
INIT_LIST_HEAD(&addr->entry);
addr->address = address;
addr->length = length;
list_add_tail(&addr->entry, &slave->addresses);
return 0;
}
/**
* fsl_dma_slave_free - free a struct fsl_dma_slave
* @slave: the struct fsl_dma_slave to free
*
* Free a struct fsl_dma_slave and all associated address/length pairs
*/
static inline void fsl_dma_slave_free(struct fsl_dma_slave *slave)
{
struct fsl_dma_hw_addr *addr, *tmp;
if (slave) {
list_for_each_entry_safe(addr, tmp, &slave->addresses, entry) {
list_del(&addr->entry);
kfree(addr);
}
kfree(slave);
}
}
/**
* fsl_dma_slave_alloc - allocate a struct fsl_dma_slave
* @gfp: the flags to pass to kmalloc when allocating this structure
*
* Allocate a struct fsl_dma_slave for use by the DMA_SLAVE API. Returns a new
* struct fsl_dma_slave on success, or NULL on failure.
*/
static inline struct fsl_dma_slave *fsl_dma_slave_alloc(gfp_t gfp)
{
struct fsl_dma_slave *slave;
slave = kzalloc(sizeof(*slave), gfp);
if (!slave)
return NULL;
INIT_LIST_HEAD(&slave->addresses);
return slave;
}
#endif /* __ARCH_POWERPC_ASM_FSLDMA_H__ */
...@@ -24,19 +24,6 @@ config ASYNC_RAID6_RECOV ...@@ -24,19 +24,6 @@ config ASYNC_RAID6_RECOV
select ASYNC_PQ select ASYNC_PQ
select ASYNC_XOR select ASYNC_XOR
config ASYNC_RAID6_TEST
tristate "Self test for hardware accelerated raid6 recovery"
depends on ASYNC_RAID6_RECOV
select ASYNC_MEMCPY
---help---
This is a one-shot self test that permutes through the
recovery of all the possible two disk failure scenarios for a
N-disk array. Recovery is performed with the asynchronous
raid6 recovery routines, and will optionally use an offload
engine if one is available.
If unsure, say N.
config ASYNC_TX_DISABLE_PQ_VAL_DMA config ASYNC_TX_DISABLE_PQ_VAL_DMA
bool bool
......
...@@ -46,15 +46,22 @@ config INTEL_MID_DMAC ...@@ -46,15 +46,22 @@ config INTEL_MID_DMAC
If unsure, say N. If unsure, say N.
config ASYNC_TX_DISABLE_CHANNEL_SWITCH config ASYNC_TX_ENABLE_CHANNEL_SWITCH
bool bool
config AMBA_PL08X
bool "ARM PrimeCell PL080 or PL081 support"
depends on ARM_AMBA && EXPERIMENTAL
select DMA_ENGINE
help
Platform has a PL08x DMAC device
which can provide DMA engine support
config INTEL_IOATDMA config INTEL_IOATDMA
tristate "Intel I/OAT DMA support" tristate "Intel I/OAT DMA support"
depends on PCI && X86 depends on PCI && X86
select DMA_ENGINE select DMA_ENGINE
select DCA select DCA
select ASYNC_TX_DISABLE_CHANNEL_SWITCH
select ASYNC_TX_DISABLE_PQ_VAL_DMA select ASYNC_TX_DISABLE_PQ_VAL_DMA
select ASYNC_TX_DISABLE_XOR_VAL_DMA select ASYNC_TX_DISABLE_XOR_VAL_DMA
help help
...@@ -69,6 +76,7 @@ config INTEL_IOP_ADMA ...@@ -69,6 +76,7 @@ config INTEL_IOP_ADMA
tristate "Intel IOP ADMA support" tristate "Intel IOP ADMA support"
depends on ARCH_IOP32X || ARCH_IOP33X || ARCH_IOP13XX depends on ARCH_IOP32X || ARCH_IOP33X || ARCH_IOP13XX
select DMA_ENGINE select DMA_ENGINE
select ASYNC_TX_ENABLE_CHANNEL_SWITCH
help help
Enable support for the Intel(R) IOP Series RAID engines. Enable support for the Intel(R) IOP Series RAID engines.
...@@ -93,6 +101,7 @@ config FSL_DMA ...@@ -93,6 +101,7 @@ config FSL_DMA
tristate "Freescale Elo and Elo Plus DMA support" tristate "Freescale Elo and Elo Plus DMA support"
depends on FSL_SOC depends on FSL_SOC
select DMA_ENGINE select DMA_ENGINE
select ASYNC_TX_ENABLE_CHANNEL_SWITCH
---help--- ---help---
Enable support for the Freescale Elo and Elo Plus DMA controllers. Enable support for the Freescale Elo and Elo Plus DMA controllers.
The Elo is the DMA controller on some 82xx and 83xx parts, and the The Elo is the DMA controller on some 82xx and 83xx parts, and the
...@@ -109,6 +118,7 @@ config MV_XOR ...@@ -109,6 +118,7 @@ config MV_XOR
bool "Marvell XOR engine support" bool "Marvell XOR engine support"
depends on PLAT_ORION depends on PLAT_ORION
select DMA_ENGINE select DMA_ENGINE
select ASYNC_TX_ENABLE_CHANNEL_SWITCH
---help--- ---help---
Enable support for the Marvell XOR engine. Enable support for the Marvell XOR engine.
...@@ -166,6 +176,7 @@ config AMCC_PPC440SPE_ADMA ...@@ -166,6 +176,7 @@ config AMCC_PPC440SPE_ADMA
depends on 440SPe || 440SP depends on 440SPe || 440SP
select DMA_ENGINE select DMA_ENGINE
select ARCH_HAS_ASYNC_TX_FIND_CHANNEL select ARCH_HAS_ASYNC_TX_FIND_CHANNEL
select ASYNC_TX_ENABLE_CHANNEL_SWITCH
help help
Enable support for the AMCC PPC440SPe RAID engines. Enable support for the AMCC PPC440SPe RAID engines.
...@@ -195,6 +206,22 @@ config PCH_DMA ...@@ -195,6 +206,22 @@ config PCH_DMA
help help
Enable support for the Topcliff PCH DMA engine. Enable support for the Topcliff PCH DMA engine.
config IMX_SDMA
tristate "i.MX SDMA support"
depends on ARCH_MX25 || ARCH_MX3 || ARCH_MX5
select DMA_ENGINE
help
Support the i.MX SDMA engine. This engine is integrated into
Freescale i.MX25/31/35/51 chips.
config IMX_DMA
tristate "i.MX DMA support"
depends on ARCH_MX1 || ARCH_MX21 || MACH_MX27
select DMA_ENGINE
help
Support the i.MX DMA engine. This engine is integrated into
Freescale i.MX1/21/27 chips.
config DMA_ENGINE config DMA_ENGINE
bool bool
......
...@@ -21,7 +21,10 @@ obj-$(CONFIG_TXX9_DMAC) += txx9dmac.o ...@@ -21,7 +21,10 @@ obj-$(CONFIG_TXX9_DMAC) += txx9dmac.o
obj-$(CONFIG_SH_DMAE) += shdma.o obj-$(CONFIG_SH_DMAE) += shdma.o
obj-$(CONFIG_COH901318) += coh901318.o coh901318_lli.o obj-$(CONFIG_COH901318) += coh901318.o coh901318_lli.o
obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/ obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/
obj-$(CONFIG_IMX_SDMA) += imx-sdma.o
obj-$(CONFIG_IMX_DMA) += imx-dma.o
obj-$(CONFIG_TIMB_DMA) += timb_dma.o obj-$(CONFIG_TIMB_DMA) += timb_dma.o
obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o
obj-$(CONFIG_PL330_DMA) += pl330.o obj-$(CONFIG_PL330_DMA) += pl330.o
obj-$(CONFIG_PCH_DMA) += pch_dma.o obj-$(CONFIG_PCH_DMA) += pch_dma.o
obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o
/*
* Copyright (c) 2006 ARM Ltd.
* Copyright (c) 2010 ST-Ericsson SA
*
* Author: Peter Pearse <peter.pearse@arm.com>
* Author: Linus Walleij <linus.walleij@stericsson.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* The full GNU General Public License is iin this distribution in the
* file called COPYING.
*
* Documentation: ARM DDI 0196G == PL080
* Documentation: ARM DDI 0218E == PL081
*
* PL080 & PL081 both have 16 sets of DMA signals that can be routed to
* any channel.
*
* The PL080 has 8 channels available for simultaneous use, and the PL081
* has only two channels. So on these DMA controllers the number of channels
* and the number of incoming DMA signals are two totally different things.
* It is usually not possible to theoretically handle all physical signals,
* so a multiplexing scheme with possible denial of use is necessary.
*
* The PL080 has a dual bus master, PL081 has a single master.
*
* Memory to peripheral transfer may be visualized as
* Get data from memory to DMAC
* Until no data left
* On burst request from peripheral
* Destination burst from DMAC to peripheral
* Clear burst request
* Raise terminal count interrupt
*
* For peripherals with a FIFO:
* Source burst size == half the depth of the peripheral FIFO
* Destination burst size == the depth of the peripheral FIFO
*
* (Bursts are irrelevant for mem to mem transfers - there are no burst
* signals, the DMA controller will simply facilitate its AHB master.)
*
* ASSUMES default (little) endianness for DMA transfers
*
* Only DMAC flow control is implemented
*
* Global TODO:
* - Break out common code from arch/arm/mach-s3c64xx and share
*/
#include <linux/device.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/dmapool.h>
#include <linux/amba/bus.h>
#include <linux/dmaengine.h>
#include <linux/amba/pl08x.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <asm/hardware/pl080.h>
#include <asm/dma.h>
#include <asm/mach/dma.h>
#include <asm/atomic.h>
#include <asm/processor.h>
#include <asm/cacheflush.h>
#define DRIVER_NAME "pl08xdmac"
/**
* struct vendor_data - vendor-specific config parameters
* for PL08x derivates
* @name: the name of this specific variant
* @channels: the number of channels available in this variant
* @dualmaster: whether this version supports dual AHB masters
* or not.
*/
struct vendor_data {
char *name;
u8 channels;
bool dualmaster;
};
/*
* PL08X private data structures
* An LLI struct - see pl08x TRM
* Note that next uses bit[0] as a bus bit,
* start & end do not - their bus bit info
* is in cctl
*/
struct lli {
dma_addr_t src;
dma_addr_t dst;
dma_addr_t next;
u32 cctl;
};
/**
* struct pl08x_driver_data - the local state holder for the PL08x
* @slave: slave engine for this instance
* @memcpy: memcpy engine for this instance
* @base: virtual memory base (remapped) for the PL08x
* @adev: the corresponding AMBA (PrimeCell) bus entry
* @vd: vendor data for this PL08x variant
* @pd: platform data passed in from the platform/machine
* @phy_chans: array of data for the physical channels
* @pool: a pool for the LLI descriptors
* @pool_ctr: counter of LLIs in the pool
* @lock: a spinlock for this struct
*/
struct pl08x_driver_data {
struct dma_device slave;
struct dma_device memcpy;
void __iomem *base;
struct amba_device *adev;
struct vendor_data *vd;
struct pl08x_platform_data *pd;
struct pl08x_phy_chan *phy_chans;
struct dma_pool *pool;
int pool_ctr;
spinlock_t lock;
};
/*
* PL08X specific defines
*/
/*
* Memory boundaries: the manual for PL08x says that the controller
* cannot read past a 1KiB boundary, so these defines are used to
* create transfer LLIs that do not cross such boundaries.
*/
#define PL08X_BOUNDARY_SHIFT (10) /* 1KB 0x400 */
#define PL08X_BOUNDARY_SIZE (1 << PL08X_BOUNDARY_SHIFT)
/* Minimum period between work queue runs */
#define PL08X_WQ_PERIODMIN 20
/* Size (bytes) of each LLI buffer allocated for one transfer */
# define PL08X_LLI_TSFR_SIZE 0x2000
/* Maximimum times we call dma_pool_alloc on this pool without freeing */
#define PL08X_MAX_ALLOCS 0x40
#define MAX_NUM_TSFR_LLIS (PL08X_LLI_TSFR_SIZE/sizeof(struct lli))
#define PL08X_ALIGN 8
static inline struct pl08x_dma_chan *to_pl08x_chan(struct dma_chan *chan)
{
return container_of(chan, struct pl08x_dma_chan, chan);
}
/*
* Physical channel handling
*/
/* Whether a certain channel is busy or not */
static int pl08x_phy_channel_busy(struct pl08x_phy_chan *ch)
{
unsigned int val;
val = readl(ch->base + PL080_CH_CONFIG);
return val & PL080_CONFIG_ACTIVE;
}
/*
* Set the initial DMA register values i.e. those for the first LLI
* The next lli pointer and the configuration interrupt bit have
* been set when the LLIs were constructed
*/
static void pl08x_set_cregs(struct pl08x_driver_data *pl08x,
struct pl08x_phy_chan *ch)
{
/* Wait for channel inactive */
while (pl08x_phy_channel_busy(ch))
;
dev_vdbg(&pl08x->adev->dev,
"WRITE channel %d: csrc=%08x, cdst=%08x, "
"cctl=%08x, clli=%08x, ccfg=%08x\n",
ch->id,
ch->csrc,
ch->cdst,
ch->cctl,
ch->clli,
ch->ccfg);
writel(ch->csrc, ch->base + PL080_CH_SRC_ADDR);
writel(ch->cdst, ch->base + PL080_CH_DST_ADDR);
writel(ch->clli, ch->base + PL080_CH_LLI);
writel(ch->cctl, ch->base + PL080_CH_CONTROL);
writel(ch->ccfg, ch->base + PL080_CH_CONFIG);
}
static inline void pl08x_config_phychan_for_txd(struct pl08x_dma_chan *plchan)
{
struct pl08x_channel_data *cd = plchan->cd;
struct pl08x_phy_chan *phychan = plchan->phychan;
struct pl08x_txd *txd = plchan->at;
/* Copy the basic control register calculated at transfer config */
phychan->csrc = txd->csrc;
phychan->cdst = txd->cdst;
phychan->clli = txd->clli;
phychan->cctl = txd->cctl;
/* Assign the signal to the proper control registers */
phychan->ccfg = cd->ccfg;
phychan->ccfg &= ~PL080_CONFIG_SRC_SEL_MASK;
phychan->ccfg &= ~PL080_CONFIG_DST_SEL_MASK;
/* If it wasn't set from AMBA, ignore it */
if (txd->direction == DMA_TO_DEVICE)
/* Select signal as destination */
phychan->ccfg |=
(phychan->signal << PL080_CONFIG_DST_SEL_SHIFT);
else if (txd->direction == DMA_FROM_DEVICE)
/* Select signal as source */
phychan->ccfg |=
(phychan->signal << PL080_CONFIG_SRC_SEL_SHIFT);
/* Always enable error interrupts */
phychan->ccfg |= PL080_CONFIG_ERR_IRQ_MASK;
/* Always enable terminal interrupts */
phychan->ccfg |= PL080_CONFIG_TC_IRQ_MASK;
}
/*
* Enable the DMA channel
* Assumes all other configuration bits have been set
* as desired before this code is called
*/
static void pl08x_enable_phy_chan(struct pl08x_driver_data *pl08x,
struct pl08x_phy_chan *ch)
{
u32 val;
/*
* Do not access config register until channel shows as disabled
*/
while (readl(pl08x->base + PL080_EN_CHAN) & (1 << ch->id))
;
/*
* Do not access config register until channel shows as inactive
*/
val = readl(ch->base + PL080_CH_CONFIG);
while ((val & PL080_CONFIG_ACTIVE) || (val & PL080_CONFIG_ENABLE))
val = readl(ch->base + PL080_CH_CONFIG);
writel(val | PL080_CONFIG_ENABLE, ch->base + PL080_CH_CONFIG);
}
/*
* Overall DMAC remains enabled always.
*
* Disabling individual channels could lose data.
*
* Disable the peripheral DMA after disabling the DMAC
* in order to allow the DMAC FIFO to drain, and
* hence allow the channel to show inactive
*
*/
static void pl08x_pause_phy_chan(struct pl08x_phy_chan *ch)
{
u32 val;
/* Set the HALT bit and wait for the FIFO to drain */
val = readl(ch->base + PL080_CH_CONFIG);
val |= PL080_CONFIG_HALT;
writel(val, ch->base + PL080_CH_CONFIG);
/* Wait for channel inactive */
while (pl08x_phy_channel_busy(ch))
;
}
static void pl08x_resume_phy_chan(struct pl08x_phy_chan *ch)
{
u32 val;
/* Clear the HALT bit */
val = readl(ch->base + PL080_CH_CONFIG);
val &= ~PL080_CONFIG_HALT;
writel(val, ch->base + PL080_CH_CONFIG);
}
/* Stops the channel */
static void pl08x_stop_phy_chan(struct pl08x_phy_chan *ch)
{
u32 val;
pl08x_pause_phy_chan(ch);
/* Disable channel */
val = readl(ch->base + PL080_CH_CONFIG);
val &= ~PL080_CONFIG_ENABLE;
val &= ~PL080_CONFIG_ERR_IRQ_MASK;
val &= ~PL080_CONFIG_TC_IRQ_MASK;
writel(val, ch->base + PL080_CH_CONFIG);
}
static inline u32 get_bytes_in_cctl(u32 cctl)
{
/* The source width defines the number of bytes */
u32 bytes = cctl & PL080_CONTROL_TRANSFER_SIZE_MASK;
switch (cctl >> PL080_CONTROL_SWIDTH_SHIFT) {
case PL080_WIDTH_8BIT:
break;
case PL080_WIDTH_16BIT:
bytes *= 2;
break;
case PL080_WIDTH_32BIT:
bytes *= 4;
break;
}
return bytes;
}
/* The channel should be paused when calling this */
static u32 pl08x_getbytes_chan(struct pl08x_dma_chan *plchan)
{
struct pl08x_phy_chan *ch;
struct pl08x_txd *txdi = NULL;
struct pl08x_txd *txd;
unsigned long flags;
u32 bytes = 0;
spin_lock_irqsave(&plchan->lock, flags);
ch = plchan->phychan;
txd = plchan->at;
/*
* Next follow the LLIs to get the number of pending bytes in the
* currently active transaction.
*/
if (ch && txd) {
struct lli *llis_va = txd->llis_va;
struct lli *llis_bus = (struct lli *) txd->llis_bus;
u32 clli = readl(ch->base + PL080_CH_LLI);
/* First get the bytes in the current active LLI */
bytes = get_bytes_in_cctl(readl(ch->base + PL080_CH_CONTROL));
if (clli) {
int i = 0;
/* Forward to the LLI pointed to by clli */
while ((clli != (u32) &(llis_bus[i])) &&
(i < MAX_NUM_TSFR_LLIS))
i++;
while (clli) {
bytes += get_bytes_in_cctl(llis_va[i].cctl);
/*
* A clli of 0x00000000 will terminate the
* LLI list
*/
clli = llis_va[i].next;
i++;
}
}
}
/* Sum up all queued transactions */
if (!list_empty(&plchan->desc_list)) {
list_for_each_entry(txdi, &plchan->desc_list, node) {
bytes += txdi->len;
}
}
spin_unlock_irqrestore(&plchan->lock, flags);
return bytes;
}
/*
* Allocate a physical channel for a virtual channel
*/
static struct pl08x_phy_chan *
pl08x_get_phy_channel(struct pl08x_driver_data *pl08x,
struct pl08x_dma_chan *virt_chan)
{
struct pl08x_phy_chan *ch = NULL;
unsigned long flags;
int i;
/*
* Try to locate a physical channel to be used for
* this transfer. If all are taken return NULL and
* the requester will have to cope by using some fallback
* PIO mode or retrying later.
*/
for (i = 0; i < pl08x->vd->channels; i++) {
ch = &pl08x->phy_chans[i];
spin_lock_irqsave(&ch->lock, flags);
if (!ch->serving) {
ch->serving = virt_chan;
ch->signal = -1;
spin_unlock_irqrestore(&ch->lock, flags);
break;
}
spin_unlock_irqrestore(&ch->lock, flags);
}
if (i == pl08x->vd->channels) {
/* No physical channel available, cope with it */
return NULL;
}
return ch;
}
static inline void pl08x_put_phy_channel(struct pl08x_driver_data *pl08x,
struct pl08x_phy_chan *ch)
{
unsigned long flags;
/* Stop the channel and clear its interrupts */
pl08x_stop_phy_chan(ch);
writel((1 << ch->id), pl08x->base + PL080_ERR_CLEAR);
writel((1 << ch->id), pl08x->base + PL080_TC_CLEAR);
/* Mark it as free */
spin_lock_irqsave(&ch->lock, flags);
ch->serving = NULL;
spin_unlock_irqrestore(&ch->lock, flags);
}
/*
* LLI handling
*/
static inline unsigned int pl08x_get_bytes_for_cctl(unsigned int coded)
{
switch (coded) {
case PL080_WIDTH_8BIT:
return 1;
case PL080_WIDTH_16BIT:
return 2;
case PL080_WIDTH_32BIT:
return 4;
default:
break;
}
BUG();
return 0;
}
static inline u32 pl08x_cctl_bits(u32 cctl, u8 srcwidth, u8 dstwidth,
u32 tsize)
{
u32 retbits = cctl;
/* Remove all src, dst and transfersize bits */
retbits &= ~PL080_CONTROL_DWIDTH_MASK;
retbits &= ~PL080_CONTROL_SWIDTH_MASK;
retbits &= ~PL080_CONTROL_TRANSFER_SIZE_MASK;
/* Then set the bits according to the parameters */
switch (srcwidth) {
case 1:
retbits |= PL080_WIDTH_8BIT << PL080_CONTROL_SWIDTH_SHIFT;
break;
case 2:
retbits |= PL080_WIDTH_16BIT << PL080_CONTROL_SWIDTH_SHIFT;
break;
case 4:
retbits |= PL080_WIDTH_32BIT << PL080_CONTROL_SWIDTH_SHIFT;
break;
default:
BUG();
break;
}
switch (dstwidth) {
case 1:
retbits |= PL080_WIDTH_8BIT << PL080_CONTROL_DWIDTH_SHIFT;
break;
case 2:
retbits |= PL080_WIDTH_16BIT << PL080_CONTROL_DWIDTH_SHIFT;
break;
case 4:
retbits |= PL080_WIDTH_32BIT << PL080_CONTROL_DWIDTH_SHIFT;
break;
default:
BUG();
break;
}
retbits |= tsize << PL080_CONTROL_TRANSFER_SIZE_SHIFT;
return retbits;
}
/*
* Autoselect a master bus to use for the transfer
* this prefers the destination bus if both available
* if fixed address on one bus the other will be chosen
*/
void pl08x_choose_master_bus(struct pl08x_bus_data *src_bus,
struct pl08x_bus_data *dst_bus, struct pl08x_bus_data **mbus,
struct pl08x_bus_data **sbus, u32 cctl)
{
if (!(cctl & PL080_CONTROL_DST_INCR)) {
*mbus = src_bus;
*sbus = dst_bus;
} else if (!(cctl & PL080_CONTROL_SRC_INCR)) {
*mbus = dst_bus;
*sbus = src_bus;
} else {
if (dst_bus->buswidth == 4) {
*mbus = dst_bus;
*sbus = src_bus;
} else if (src_bus->buswidth == 4) {
*mbus = src_bus;
*sbus = dst_bus;
} else if (dst_bus->buswidth == 2) {
*mbus = dst_bus;
*sbus = src_bus;
} else if (src_bus->buswidth == 2) {
*mbus = src_bus;
*sbus = dst_bus;
} else {
/* src_bus->buswidth == 1 */
*mbus = dst_bus;
*sbus = src_bus;
}
}
}
/*
* Fills in one LLI for a certain transfer descriptor
* and advance the counter
*/
int pl08x_fill_lli_for_desc(struct pl08x_driver_data *pl08x,
struct pl08x_txd *txd, int num_llis, int len,
u32 cctl, u32 *remainder)
{
struct lli *llis_va = txd->llis_va;
struct lli *llis_bus = (struct lli *) txd->llis_bus;
BUG_ON(num_llis >= MAX_NUM_TSFR_LLIS);
llis_va[num_llis].cctl = cctl;
llis_va[num_llis].src = txd->srcbus.addr;
llis_va[num_llis].dst = txd->dstbus.addr;
/*
* On versions with dual masters, you can optionally AND on
* PL080_LLI_LM_AHB2 to the LLI to tell the hardware to read
* in new LLIs with that controller, but we always try to
* choose AHB1 to point into memory. The idea is to have AHB2
* fixed on the peripheral and AHB1 messing around in the
* memory. So we don't manipulate this bit currently.
*/
llis_va[num_llis].next =
(dma_addr_t)((u32) &(llis_bus[num_llis + 1]));
if (cctl & PL080_CONTROL_SRC_INCR)
txd->srcbus.addr += len;
if (cctl & PL080_CONTROL_DST_INCR)
txd->dstbus.addr += len;
*remainder -= len;
return num_llis + 1;
}
/*
* Return number of bytes to fill to boundary, or len
*/
static inline u32 pl08x_pre_boundary(u32 addr, u32 len)
{
u32 boundary;
boundary = ((addr >> PL08X_BOUNDARY_SHIFT) + 1)
<< PL08X_BOUNDARY_SHIFT;
if (boundary < addr + len)
return boundary - addr;
else
return len;
}
/*
* This fills in the table of LLIs for the transfer descriptor
* Note that we assume we never have to change the burst sizes
* Return 0 for error
*/
static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
struct pl08x_txd *txd)
{
struct pl08x_channel_data *cd = txd->cd;
struct pl08x_bus_data *mbus, *sbus;
u32 remainder;
int num_llis = 0;
u32 cctl;
int max_bytes_per_lli;
int total_bytes = 0;
struct lli *llis_va;
struct lli *llis_bus;
if (!txd) {
dev_err(&pl08x->adev->dev, "%s no descriptor\n", __func__);
return 0;
}
txd->llis_va = dma_pool_alloc(pl08x->pool, GFP_NOWAIT,
&txd->llis_bus);
if (!txd->llis_va) {
dev_err(&pl08x->adev->dev, "%s no memory for llis\n", __func__);
return 0;
}
pl08x->pool_ctr++;
/*
* Initialize bus values for this transfer
* from the passed optimal values
*/
if (!cd) {
dev_err(&pl08x->adev->dev, "%s no channel data\n", __func__);
return 0;
}
/* Get the default CCTL from the platform data */
cctl = cd->cctl;
/*
* On the PL080 we have two bus masters and we
* should select one for source and one for
* destination. We try to use AHB2 for the
* bus which does not increment (typically the
* peripheral) else we just choose something.
*/
cctl &= ~(PL080_CONTROL_DST_AHB2 | PL080_CONTROL_SRC_AHB2);
if (pl08x->vd->dualmaster) {
if (cctl & PL080_CONTROL_SRC_INCR)
/* Source increments, use AHB2 for destination */
cctl |= PL080_CONTROL_DST_AHB2;
else if (cctl & PL080_CONTROL_DST_INCR)
/* Destination increments, use AHB2 for source */
cctl |= PL080_CONTROL_SRC_AHB2;
else
/* Just pick something, source AHB1 dest AHB2 */
cctl |= PL080_CONTROL_DST_AHB2;
}
/* Find maximum width of the source bus */
txd->srcbus.maxwidth =
pl08x_get_bytes_for_cctl((cctl & PL080_CONTROL_SWIDTH_MASK) >>
PL080_CONTROL_SWIDTH_SHIFT);
/* Find maximum width of the destination bus */
txd->dstbus.maxwidth =
pl08x_get_bytes_for_cctl((cctl & PL080_CONTROL_DWIDTH_MASK) >>
PL080_CONTROL_DWIDTH_SHIFT);
/* Set up the bus widths to the maximum */
txd->srcbus.buswidth = txd->srcbus.maxwidth;
txd->dstbus.buswidth = txd->dstbus.maxwidth;
dev_vdbg(&pl08x->adev->dev,
"%s source bus is %d bytes wide, dest bus is %d bytes wide\n",
__func__, txd->srcbus.buswidth, txd->dstbus.buswidth);
/*
* Bytes transferred == tsize * MIN(buswidths), not max(buswidths)
*/
max_bytes_per_lli = min(txd->srcbus.buswidth, txd->dstbus.buswidth) *
PL080_CONTROL_TRANSFER_SIZE_MASK;
dev_vdbg(&pl08x->adev->dev,
"%s max bytes per lli = %d\n",
__func__, max_bytes_per_lli);
/* We need to count this down to zero */
remainder = txd->len;
dev_vdbg(&pl08x->adev->dev,
"%s remainder = %d\n",
__func__, remainder);
/*
* Choose bus to align to
* - prefers destination bus if both available
* - if fixed address on one bus chooses other
* - modifies cctl to choose an apropriate master
*/
pl08x_choose_master_bus(&txd->srcbus, &txd->dstbus,
&mbus, &sbus, cctl);
/*
* The lowest bit of the LLI register
* is also used to indicate which master to
* use for reading the LLIs.
*/
if (txd->len < mbus->buswidth) {
/*
* Less than a bus width available
* - send as single bytes
*/
while (remainder) {
dev_vdbg(&pl08x->adev->dev,
"%s single byte LLIs for a transfer of "
"less than a bus width (remain %08x)\n",
__func__, remainder);
cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
num_llis =
pl08x_fill_lli_for_desc(pl08x, txd, num_llis, 1,
cctl, &remainder);
total_bytes++;
}
} else {
/*
* Make one byte LLIs until master bus is aligned
* - slave will then be aligned also
*/
while ((mbus->addr) % (mbus->buswidth)) {
dev_vdbg(&pl08x->adev->dev,
"%s adjustment lli for less than bus width "
"(remain %08x)\n",
__func__, remainder);
cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
num_llis = pl08x_fill_lli_for_desc
(pl08x, txd, num_llis, 1, cctl, &remainder);
total_bytes++;
}
/*
* Master now aligned
* - if slave is not then we must set its width down
*/
if (sbus->addr % sbus->buswidth) {
dev_dbg(&pl08x->adev->dev,
"%s set down bus width to one byte\n",
__func__);
sbus->buswidth = 1;
}
/*
* Make largest possible LLIs until less than one bus
* width left
*/
while (remainder > (mbus->buswidth - 1)) {
int lli_len, target_len;
int tsize;
int odd_bytes;
/*
* If enough left try to send max possible,
* otherwise try to send the remainder
*/
target_len = remainder;
if (remainder > max_bytes_per_lli)
target_len = max_bytes_per_lli;
/*
* Set bus lengths for incrementing busses
* to number of bytes which fill to next memory
* boundary
*/
if (cctl & PL080_CONTROL_SRC_INCR)
txd->srcbus.fill_bytes =
pl08x_pre_boundary(
txd->srcbus.addr,
remainder);
else
txd->srcbus.fill_bytes =
max_bytes_per_lli;
if (cctl & PL080_CONTROL_DST_INCR)
txd->dstbus.fill_bytes =
pl08x_pre_boundary(
txd->dstbus.addr,
remainder);
else
txd->dstbus.fill_bytes =
max_bytes_per_lli;
/*
* Find the nearest
*/
lli_len = min(txd->srcbus.fill_bytes,
txd->dstbus.fill_bytes);
BUG_ON(lli_len > remainder);
if (lli_len <= 0) {
dev_err(&pl08x->adev->dev,
"%s lli_len is %d, <= 0\n",
__func__, lli_len);
return 0;
}
if (lli_len == target_len) {
/*
* Can send what we wanted
*/
/*
* Maintain alignment
*/
lli_len = (lli_len/mbus->buswidth) *
mbus->buswidth;
odd_bytes = 0;
} else {
/*
* So now we know how many bytes to transfer
* to get to the nearest boundary
* The next lli will past the boundary
* - however we may be working to a boundary
* on the slave bus
* We need to ensure the master stays aligned
*/
odd_bytes = lli_len % mbus->buswidth;
/*
* - and that we are working in multiples
* of the bus widths
*/
lli_len -= odd_bytes;
}
if (lli_len) {
/*
* Check against minimum bus alignment:
* Calculate actual transfer size in relation
* to bus width an get a maximum remainder of
* the smallest bus width - 1
*/
/* FIXME: use round_down()? */
tsize = lli_len / min(mbus->buswidth,
sbus->buswidth);
lli_len = tsize * min(mbus->buswidth,
sbus->buswidth);
if (target_len != lli_len) {
dev_vdbg(&pl08x->adev->dev,
"%s can't send what we want. Desired %08x, lli of %08x bytes in txd of %08x\n",
__func__, target_len, lli_len, txd->len);
}
cctl = pl08x_cctl_bits(cctl,
txd->srcbus.buswidth,
txd->dstbus.buswidth,
tsize);
dev_vdbg(&pl08x->adev->dev,
"%s fill lli with single lli chunk of size %08x (remainder %08x)\n",
__func__, lli_len, remainder);
num_llis = pl08x_fill_lli_for_desc(pl08x, txd,
num_llis, lli_len, cctl,
&remainder);
total_bytes += lli_len;
}
if (odd_bytes) {
/*
* Creep past the boundary,
* maintaining master alignment
*/
int j;
for (j = 0; (j < mbus->buswidth)
&& (remainder); j++) {
cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
dev_vdbg(&pl08x->adev->dev,
"%s align with boundardy, single byte (remain %08x)\n",
__func__, remainder);
num_llis =
pl08x_fill_lli_for_desc(pl08x,
txd, num_llis, 1,
cctl, &remainder);
total_bytes++;
}
}
}
/*
* Send any odd bytes
*/
if (remainder < 0) {
dev_err(&pl08x->adev->dev, "%s remainder not fitted 0x%08x bytes\n",
__func__, remainder);
return 0;
}
while (remainder) {
cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
dev_vdbg(&pl08x->adev->dev,
"%s align with boundardy, single odd byte (remain %d)\n",
__func__, remainder);
num_llis = pl08x_fill_lli_for_desc(pl08x, txd, num_llis,
1, cctl, &remainder);
total_bytes++;
}
}
if (total_bytes != txd->len) {
dev_err(&pl08x->adev->dev,
"%s size of encoded lli:s don't match total txd, transferred 0x%08x from size 0x%08x\n",
__func__, total_bytes, txd->len);
return 0;
}
if (num_llis >= MAX_NUM_TSFR_LLIS) {
dev_err(&pl08x->adev->dev,
"%s need to increase MAX_NUM_TSFR_LLIS from 0x%08x\n",
__func__, (u32) MAX_NUM_TSFR_LLIS);
return 0;
}
/*
* Decide whether this is a loop or a terminated transfer
*/
llis_va = txd->llis_va;
llis_bus = (struct lli *) txd->llis_bus;
if (cd->circular_buffer) {
/*
* Loop the circular buffer so that the next element
* points back to the beginning of the LLI.
*/
llis_va[num_llis - 1].next =
(dma_addr_t)((unsigned int)&(llis_bus[0]));
} else {
/*
* On non-circular buffers, the final LLI terminates
* the LLI.
*/
llis_va[num_llis - 1].next = 0;
/*
* The final LLI element shall also fire an interrupt
*/
llis_va[num_llis - 1].cctl |= PL080_CONTROL_TC_IRQ_EN;
}
/* Now store the channel register values */
txd->csrc = llis_va[0].src;
txd->cdst = llis_va[0].dst;
if (num_llis > 1)
txd->clli = llis_va[0].next;
else
txd->clli = 0;
txd->cctl = llis_va[0].cctl;
/* ccfg will be set at physical channel allocation time */
#ifdef VERBOSE_DEBUG
{
int i;
for (i = 0; i < num_llis; i++) {
dev_vdbg(&pl08x->adev->dev,
"lli %d @%p: csrc=%08x, cdst=%08x, cctl=%08x, clli=%08x\n",
i,
&llis_va[i],
llis_va[i].src,
llis_va[i].dst,
llis_va[i].cctl,
llis_va[i].next
);
}
}
#endif
return num_llis;
}
/* You should call this with the struct pl08x lock held */
static void pl08x_free_txd(struct pl08x_driver_data *pl08x,
struct pl08x_txd *txd)
{
if (!txd)
dev_err(&pl08x->adev->dev,
"%s no descriptor to free\n",
__func__);
/* Free the LLI */
dma_pool_free(pl08x->pool, txd->llis_va,
txd->llis_bus);
pl08x->pool_ctr--;
kfree(txd);
}
static void pl08x_free_txd_list(struct pl08x_driver_data *pl08x,
struct pl08x_dma_chan *plchan)
{
struct pl08x_txd *txdi = NULL;
struct pl08x_txd *next;
if (!list_empty(&plchan->desc_list)) {
list_for_each_entry_safe(txdi,
next, &plchan->desc_list, node) {
list_del(&txdi->node);
pl08x_free_txd(pl08x, txdi);
}
}
}
/*
* The DMA ENGINE API
*/
static int pl08x_alloc_chan_resources(struct dma_chan *chan)
{
return 0;
}
static void pl08x_free_chan_resources(struct dma_chan *chan)
{
}
/*
* This should be called with the channel plchan->lock held
*/
static int prep_phy_channel(struct pl08x_dma_chan *plchan,
struct pl08x_txd *txd)
{
struct pl08x_driver_data *pl08x = plchan->host;
struct pl08x_phy_chan *ch;
int ret;
/* Check if we already have a channel */
if (plchan->phychan)
return 0;
ch = pl08x_get_phy_channel(pl08x, plchan);
if (!ch) {
/* No physical channel available, cope with it */
dev_dbg(&pl08x->adev->dev, "no physical channel available for xfer on %s\n", plchan->name);
return -EBUSY;
}
/*
* OK we have a physical channel: for memcpy() this is all we
* need, but for slaves the physical signals may be muxed!
* Can the platform allow us to use this channel?
*/
if (plchan->slave &&
ch->signal < 0 &&
pl08x->pd->get_signal) {
ret = pl08x->pd->get_signal(plchan);
if (ret < 0) {
dev_dbg(&pl08x->adev->dev,
"unable to use physical channel %d for transfer on %s due to platform restrictions\n",
ch->id, plchan->name);
/* Release physical channel & return */
pl08x_put_phy_channel(pl08x, ch);
return -EBUSY;
}
ch->signal = ret;
}
dev_dbg(&pl08x->adev->dev, "allocated physical channel %d and signal %d for xfer on %s\n",
ch->id,
ch->signal,
plchan->name);
plchan->phychan = ch;
return 0;
}
static dma_cookie_t pl08x_tx_submit(struct dma_async_tx_descriptor *tx)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(tx->chan);
atomic_inc(&plchan->last_issued);
tx->cookie = atomic_read(&plchan->last_issued);
/* This unlock follows the lock in the prep() function */
spin_unlock_irqrestore(&plchan->lock, plchan->lockflags);
return tx->cookie;
}
static struct dma_async_tx_descriptor *pl08x_prep_dma_interrupt(
struct dma_chan *chan, unsigned long flags)
{
struct dma_async_tx_descriptor *retval = NULL;
return retval;
}
/*
* Code accessing dma_async_is_complete() in a tight loop
* may give problems - could schedule where indicated.
* If slaves are relying on interrupts to signal completion this
* function must not be called with interrupts disabled
*/
static enum dma_status
pl08x_dma_tx_status(struct dma_chan *chan,
dma_cookie_t cookie,
struct dma_tx_state *txstate)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
dma_cookie_t last_used;
dma_cookie_t last_complete;
enum dma_status ret;
u32 bytesleft = 0;
last_used = atomic_read(&plchan->last_issued);
last_complete = plchan->lc;
ret = dma_async_is_complete(cookie, last_complete, last_used);
if (ret == DMA_SUCCESS) {
dma_set_tx_state(txstate, last_complete, last_used, 0);
return ret;
}
/*
* schedule(); could be inserted here
*/
/*
* This cookie not complete yet
*/
last_used = atomic_read(&plchan->last_issued);
last_complete = plchan->lc;
/* Get number of bytes left in the active transactions and queue */
bytesleft = pl08x_getbytes_chan(plchan);
dma_set_tx_state(txstate, last_complete, last_used,
bytesleft);
if (plchan->state == PL08X_CHAN_PAUSED)
return DMA_PAUSED;
/* Whether waiting or running, we're in progress */
return DMA_IN_PROGRESS;
}
/* PrimeCell DMA extension */
struct burst_table {
int burstwords;
u32 reg;
};
static const struct burst_table burst_sizes[] = {
{
.burstwords = 256,
.reg = (PL080_BSIZE_256 << PL080_CONTROL_SB_SIZE_SHIFT) |
(PL080_BSIZE_256 << PL080_CONTROL_DB_SIZE_SHIFT),
},
{
.burstwords = 128,
.reg = (PL080_BSIZE_128 << PL080_CONTROL_SB_SIZE_SHIFT) |
(PL080_BSIZE_128 << PL080_CONTROL_DB_SIZE_SHIFT),
},
{
.burstwords = 64,
.reg = (PL080_BSIZE_64 << PL080_CONTROL_SB_SIZE_SHIFT) |
(PL080_BSIZE_64 << PL080_CONTROL_DB_SIZE_SHIFT),
},
{
.burstwords = 32,
.reg = (PL080_BSIZE_32 << PL080_CONTROL_SB_SIZE_SHIFT) |
(PL080_BSIZE_32 << PL080_CONTROL_DB_SIZE_SHIFT),
},
{
.burstwords = 16,
.reg = (PL080_BSIZE_16 << PL080_CONTROL_SB_SIZE_SHIFT) |
(PL080_BSIZE_16 << PL080_CONTROL_DB_SIZE_SHIFT),
},
{
.burstwords = 8,
.reg = (PL080_BSIZE_8 << PL080_CONTROL_SB_SIZE_SHIFT) |
(PL080_BSIZE_8 << PL080_CONTROL_DB_SIZE_SHIFT),
},
{
.burstwords = 4,
.reg = (PL080_BSIZE_4 << PL080_CONTROL_SB_SIZE_SHIFT) |
(PL080_BSIZE_4 << PL080_CONTROL_DB_SIZE_SHIFT),
},
{
.burstwords = 1,
.reg = (PL080_BSIZE_1 << PL080_CONTROL_SB_SIZE_SHIFT) |
(PL080_BSIZE_1 << PL080_CONTROL_DB_SIZE_SHIFT),
},
};
static void dma_set_runtime_config(struct dma_chan *chan,
struct dma_slave_config *config)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
struct pl08x_driver_data *pl08x = plchan->host;
struct pl08x_channel_data *cd = plchan->cd;
enum dma_slave_buswidth addr_width;
u32 maxburst;
u32 cctl = 0;
/* Mask out all except src and dst channel */
u32 ccfg = cd->ccfg & 0x000003DEU;
int i = 0;
/* Transfer direction */
plchan->runtime_direction = config->direction;
if (config->direction == DMA_TO_DEVICE) {
plchan->runtime_addr = config->dst_addr;
cctl |= PL080_CONTROL_SRC_INCR;
ccfg |= PL080_FLOW_MEM2PER << PL080_CONFIG_FLOW_CONTROL_SHIFT;
addr_width = config->dst_addr_width;
maxburst = config->dst_maxburst;
} else if (config->direction == DMA_FROM_DEVICE) {
plchan->runtime_addr = config->src_addr;
cctl |= PL080_CONTROL_DST_INCR;
ccfg |= PL080_FLOW_PER2MEM << PL080_CONFIG_FLOW_CONTROL_SHIFT;
addr_width = config->src_addr_width;
maxburst = config->src_maxburst;
} else {
dev_err(&pl08x->adev->dev,
"bad runtime_config: alien transfer direction\n");
return;
}
switch (addr_width) {
case DMA_SLAVE_BUSWIDTH_1_BYTE:
cctl |= (PL080_WIDTH_8BIT << PL080_CONTROL_SWIDTH_SHIFT) |
(PL080_WIDTH_8BIT << PL080_CONTROL_DWIDTH_SHIFT);
break;
case DMA_SLAVE_BUSWIDTH_2_BYTES:
cctl |= (PL080_WIDTH_16BIT << PL080_CONTROL_SWIDTH_SHIFT) |
(PL080_WIDTH_16BIT << PL080_CONTROL_DWIDTH_SHIFT);
break;
case DMA_SLAVE_BUSWIDTH_4_BYTES:
cctl |= (PL080_WIDTH_32BIT << PL080_CONTROL_SWIDTH_SHIFT) |
(PL080_WIDTH_32BIT << PL080_CONTROL_DWIDTH_SHIFT);
break;
default:
dev_err(&pl08x->adev->dev,
"bad runtime_config: alien address width\n");
return;
}
/*
* Now decide on a maxburst:
* If this channel will only request single transfers, set
* this down to ONE element.
*/
if (plchan->cd->single) {
cctl |= (PL080_BSIZE_1 << PL080_CONTROL_SB_SIZE_SHIFT) |
(PL080_BSIZE_1 << PL080_CONTROL_DB_SIZE_SHIFT);
} else {
while (i < ARRAY_SIZE(burst_sizes)) {
if (burst_sizes[i].burstwords <= maxburst)
break;
i++;
}
cctl |= burst_sizes[i].reg;
}
/* Access the cell in privileged mode, non-bufferable, non-cacheable */
cctl &= ~PL080_CONTROL_PROT_MASK;
cctl |= PL080_CONTROL_PROT_SYS;
/* Modify the default channel data to fit PrimeCell request */
cd->cctl = cctl;
cd->ccfg = ccfg;
dev_dbg(&pl08x->adev->dev,
"configured channel %s (%s) for %s, data width %d, "
"maxburst %d words, LE, CCTL=%08x, CCFG=%08x\n",
dma_chan_name(chan), plchan->name,
(config->direction == DMA_FROM_DEVICE) ? "RX" : "TX",
addr_width,
maxburst,
cctl, ccfg);
}
/*
* Slave transactions callback to the slave device to allow
* synchronization of slave DMA signals with the DMAC enable
*/
static void pl08x_issue_pending(struct dma_chan *chan)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
struct pl08x_driver_data *pl08x = plchan->host;
unsigned long flags;
spin_lock_irqsave(&plchan->lock, flags);
/* Something is already active */
if (plchan->at) {
spin_unlock_irqrestore(&plchan->lock, flags);
return;
}
/* Didn't get a physical channel so waiting for it ... */
if (plchan->state == PL08X_CHAN_WAITING)
return;
/* Take the first element in the queue and execute it */
if (!list_empty(&plchan->desc_list)) {
struct pl08x_txd *next;
next = list_first_entry(&plchan->desc_list,
struct pl08x_txd,
node);
list_del(&next->node);
plchan->at = next;
plchan->state = PL08X_CHAN_RUNNING;
/* Configure the physical channel for the active txd */
pl08x_config_phychan_for_txd(plchan);
pl08x_set_cregs(pl08x, plchan->phychan);
pl08x_enable_phy_chan(pl08x, plchan->phychan);
}
spin_unlock_irqrestore(&plchan->lock, flags);
}
static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan,
struct pl08x_txd *txd)
{
int num_llis;
struct pl08x_driver_data *pl08x = plchan->host;
int ret;
num_llis = pl08x_fill_llis_for_desc(pl08x, txd);
if (!num_llis)
return -EINVAL;
spin_lock_irqsave(&plchan->lock, plchan->lockflags);
/*
* If this device is not using a circular buffer then
* queue this new descriptor for transfer.
* The descriptor for a circular buffer continues
* to be used until the channel is freed.
*/
if (txd->cd->circular_buffer)
dev_err(&pl08x->adev->dev,
"%s attempting to queue a circular buffer\n",
__func__);
else
list_add_tail(&txd->node,
&plchan->desc_list);
/*
* See if we already have a physical channel allocated,
* else this is the time to try to get one.
*/
ret = prep_phy_channel(plchan, txd);
if (ret) {
/*
* No physical channel available, we will
* stack up the memcpy channels until there is a channel
* available to handle it whereas slave transfers may
* have been denied due to platform channel muxing restrictions
* and since there is no guarantee that this will ever be
* resolved, and since the signal must be aquired AFTER
* aquiring the physical channel, we will let them be NACK:ed
* with -EBUSY here. The drivers can alway retry the prep()
* call if they are eager on doing this using DMA.
*/
if (plchan->slave) {
pl08x_free_txd_list(pl08x, plchan);
spin_unlock_irqrestore(&plchan->lock, plchan->lockflags);
return -EBUSY;
}
/* Do this memcpy whenever there is a channel ready */
plchan->state = PL08X_CHAN_WAITING;
plchan->waiting = txd;
} else
/*
* Else we're all set, paused and ready to roll,
* status will switch to PL08X_CHAN_RUNNING when
* we call issue_pending(). If there is something
* running on the channel already we don't change
* its state.
*/
if (plchan->state == PL08X_CHAN_IDLE)
plchan->state = PL08X_CHAN_PAUSED;
/*
* Notice that we leave plchan->lock locked on purpose:
* it will be unlocked in the subsequent tx_submit()
* call. This is a consequence of the current API.
*/
return 0;
}
/*
* Initialize a descriptor to be used by memcpy submit
*/
static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy(
struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
size_t len, unsigned long flags)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
struct pl08x_driver_data *pl08x = plchan->host;
struct pl08x_txd *txd;
int ret;
txd = kzalloc(sizeof(struct pl08x_txd), GFP_NOWAIT);
if (!txd) {
dev_err(&pl08x->adev->dev,
"%s no memory for descriptor\n", __func__);
return NULL;
}
dma_async_tx_descriptor_init(&txd->tx, chan);
txd->direction = DMA_NONE;
txd->srcbus.addr = src;
txd->dstbus.addr = dest;
/* Set platform data for m2m */
txd->cd = &pl08x->pd->memcpy_channel;
/* Both to be incremented or the code will break */
txd->cd->cctl |= PL080_CONTROL_SRC_INCR | PL080_CONTROL_DST_INCR;
txd->tx.tx_submit = pl08x_tx_submit;
txd->tx.callback = NULL;
txd->tx.callback_param = NULL;
txd->len = len;
INIT_LIST_HEAD(&txd->node);
ret = pl08x_prep_channel_resources(plchan, txd);
if (ret)
return NULL;
/*
* NB: the channel lock is held at this point so tx_submit()
* must be called in direct succession.
*/
return &txd->tx;
}
struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_data_direction direction,
unsigned long flags)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
struct pl08x_driver_data *pl08x = plchan->host;
struct pl08x_txd *txd;
int ret;
/*
* Current implementation ASSUMES only one sg
*/
if (sg_len != 1) {
dev_err(&pl08x->adev->dev, "%s prepared too long sglist\n",
__func__);
BUG();
}
dev_dbg(&pl08x->adev->dev, "%s prepare transaction of %d bytes from %s\n",
__func__, sgl->length, plchan->name);
txd = kzalloc(sizeof(struct pl08x_txd), GFP_NOWAIT);
if (!txd) {
dev_err(&pl08x->adev->dev, "%s no txd\n", __func__);
return NULL;
}
dma_async_tx_descriptor_init(&txd->tx, chan);
if (direction != plchan->runtime_direction)
dev_err(&pl08x->adev->dev, "%s DMA setup does not match "
"the direction configured for the PrimeCell\n",
__func__);
/*
* Set up addresses, the PrimeCell configured address
* will take precedence since this may configure the
* channel target address dynamically at runtime.
*/
txd->direction = direction;
if (direction == DMA_TO_DEVICE) {
txd->srcbus.addr = sgl->dma_address;
if (plchan->runtime_addr)
txd->dstbus.addr = plchan->runtime_addr;
else
txd->dstbus.addr = plchan->cd->addr;
} else if (direction == DMA_FROM_DEVICE) {
if (plchan->runtime_addr)
txd->srcbus.addr = plchan->runtime_addr;
else
txd->srcbus.addr = plchan->cd->addr;
txd->dstbus.addr = sgl->dma_address;
} else {
dev_err(&pl08x->adev->dev,
"%s direction unsupported\n", __func__);
return NULL;
}
txd->cd = plchan->cd;
txd->tx.tx_submit = pl08x_tx_submit;
txd->tx.callback = NULL;
txd->tx.callback_param = NULL;
txd->len = sgl->length;
INIT_LIST_HEAD(&txd->node);
ret = pl08x_prep_channel_resources(plchan, txd);
if (ret)
return NULL;
/*
* NB: the channel lock is held at this point so tx_submit()
* must be called in direct succession.
*/
return &txd->tx;
}
static int pl08x_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
unsigned long arg)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
struct pl08x_driver_data *pl08x = plchan->host;
unsigned long flags;
int ret = 0;
/* Controls applicable to inactive channels */
if (cmd == DMA_SLAVE_CONFIG) {
dma_set_runtime_config(chan,
(struct dma_slave_config *)
arg);
return 0;
}
/*
* Anything succeeds on channels with no physical allocation and
* no queued transfers.
*/
spin_lock_irqsave(&plchan->lock, flags);
if (!plchan->phychan && !plchan->at) {
spin_unlock_irqrestore(&plchan->lock, flags);
return 0;
}
switch (cmd) {
case DMA_TERMINATE_ALL:
plchan->state = PL08X_CHAN_IDLE;
if (plchan->phychan) {
pl08x_stop_phy_chan(plchan->phychan);
/*
* Mark physical channel as free and free any slave
* signal
*/
if ((plchan->phychan->signal >= 0) &&
pl08x->pd->put_signal) {
pl08x->pd->put_signal(plchan);
plchan->phychan->signal = -1;
}
pl08x_put_phy_channel(pl08x, plchan->phychan);
plchan->phychan = NULL;
}
/* Stop any pending tasklet */
tasklet_disable(&plchan->tasklet);
/* Dequeue jobs and free LLIs */
if (plchan->at) {
pl08x_free_txd(pl08x, plchan->at);
plchan->at = NULL;
}
/* Dequeue jobs not yet fired as well */
pl08x_free_txd_list(pl08x, plchan);
break;
case DMA_PAUSE:
pl08x_pause_phy_chan(plchan->phychan);
plchan->state = PL08X_CHAN_PAUSED;
break;
case DMA_RESUME:
pl08x_resume_phy_chan(plchan->phychan);
plchan->state = PL08X_CHAN_RUNNING;
break;
default:
/* Unknown command */
ret = -ENXIO;
break;
}
spin_unlock_irqrestore(&plchan->lock, flags);
return ret;
}
bool pl08x_filter_id(struct dma_chan *chan, void *chan_id)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
char *name = chan_id;
/* Check that the channel is not taken! */
if (!strcmp(plchan->name, name))
return true;
return false;
}
/*
* Just check that the device is there and active
* TODO: turn this bit on/off depending on the number of
* physical channels actually used, if it is zero... well
* shut it off. That will save some power. Cut the clock
* at the same time.
*/
static void pl08x_ensure_on(struct pl08x_driver_data *pl08x)
{
u32 val;
val = readl(pl08x->base + PL080_CONFIG);
val &= ~(PL080_CONFIG_M2_BE | PL080_CONFIG_M1_BE | PL080_CONFIG_ENABLE);
/* We implictly clear bit 1 and that means little-endian mode */
val |= PL080_CONFIG_ENABLE;
writel(val, pl08x->base + PL080_CONFIG);
}
static void pl08x_tasklet(unsigned long data)
{
struct pl08x_dma_chan *plchan = (struct pl08x_dma_chan *) data;
struct pl08x_phy_chan *phychan = plchan->phychan;
struct pl08x_driver_data *pl08x = plchan->host;
if (!plchan)
BUG();
spin_lock(&plchan->lock);
if (plchan->at) {
dma_async_tx_callback callback =
plchan->at->tx.callback;
void *callback_param =
plchan->at->tx.callback_param;
/*
* Update last completed
*/
plchan->lc =
(plchan->at->tx.cookie);
/*
* Callback to signal completion
*/
if (callback)
callback(callback_param);
/*
* Device callbacks should NOT clear
* the current transaction on the channel
* Linus: sometimes they should?
*/
if (!plchan->at)
BUG();
/*
* Free the descriptor if it's not for a device
* using a circular buffer
*/
if (!plchan->at->cd->circular_buffer) {
pl08x_free_txd(pl08x, plchan->at);
plchan->at = NULL;
}
/*
* else descriptor for circular
* buffers only freed when
* client has disabled dma
*/
}
/*
* If a new descriptor is queued, set it up
* plchan->at is NULL here
*/
if (!list_empty(&plchan->desc_list)) {
struct pl08x_txd *next;
next = list_first_entry(&plchan->desc_list,
struct pl08x_txd,
node);
list_del(&next->node);
plchan->at = next;
/* Configure the physical channel for the next txd */
pl08x_config_phychan_for_txd(plchan);
pl08x_set_cregs(pl08x, plchan->phychan);
pl08x_enable_phy_chan(pl08x, plchan->phychan);
} else {
struct pl08x_dma_chan *waiting = NULL;
/*
* No more jobs, so free up the physical channel
* Free any allocated signal on slave transfers too
*/
if ((phychan->signal >= 0) && pl08x->pd->put_signal) {
pl08x->pd->put_signal(plchan);
phychan->signal = -1;
}
pl08x_put_phy_channel(pl08x, phychan);
plchan->phychan = NULL;
plchan->state = PL08X_CHAN_IDLE;
/*
* And NOW before anyone else can grab that free:d
* up physical channel, see if there is some memcpy
* pending that seriously needs to start because of
* being stacked up while we were choking the
* physical channels with data.
*/
list_for_each_entry(waiting, &pl08x->memcpy.channels,
chan.device_node) {
if (waiting->state == PL08X_CHAN_WAITING &&
waiting->waiting != NULL) {
int ret;
/* This should REALLY not fail now */
ret = prep_phy_channel(waiting,
waiting->waiting);
BUG_ON(ret);
waiting->state = PL08X_CHAN_RUNNING;
waiting->waiting = NULL;
pl08x_issue_pending(&waiting->chan);
break;
}
}
}
spin_unlock(&plchan->lock);
}
static irqreturn_t pl08x_irq(int irq, void *dev)
{
struct pl08x_driver_data *pl08x = dev;
u32 mask = 0;
u32 val;
int i;
val = readl(pl08x->base + PL080_ERR_STATUS);
if (val) {
/*
* An error interrupt (on one or more channels)
*/
dev_err(&pl08x->adev->dev,
"%s error interrupt, register value 0x%08x\n",
__func__, val);
/*
* Simply clear ALL PL08X error interrupts,
* regardless of channel and cause
* FIXME: should be 0x00000003 on PL081 really.
*/
writel(0x000000FF, pl08x->base + PL080_ERR_CLEAR);
}
val = readl(pl08x->base + PL080_INT_STATUS);
for (i = 0; i < pl08x->vd->channels; i++) {
if ((1 << i) & val) {
/* Locate physical channel */
struct pl08x_phy_chan *phychan = &pl08x->phy_chans[i];
struct pl08x_dma_chan *plchan = phychan->serving;
/* Schedule tasklet on this channel */
tasklet_schedule(&plchan->tasklet);
mask |= (1 << i);
}
}
/*
* Clear only the terminal interrupts on channels we processed
*/
writel(mask, pl08x->base + PL080_TC_CLEAR);
return mask ? IRQ_HANDLED : IRQ_NONE;
}
/*
* Initialise the DMAC memcpy/slave channels.
* Make a local wrapper to hold required data
*/
static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x,
struct dma_device *dmadev,
unsigned int channels,
bool slave)
{
struct pl08x_dma_chan *chan;
int i;
INIT_LIST_HEAD(&dmadev->channels);
/*
* Register as many many memcpy as we have physical channels,
* we won't always be able to use all but the code will have
* to cope with that situation.
*/
for (i = 0; i < channels; i++) {
chan = kzalloc(sizeof(struct pl08x_dma_chan), GFP_KERNEL);
if (!chan) {
dev_err(&pl08x->adev->dev,
"%s no memory for channel\n", __func__);
return -ENOMEM;
}
chan->host = pl08x;
chan->state = PL08X_CHAN_IDLE;
if (slave) {
chan->slave = true;
chan->name = pl08x->pd->slave_channels[i].bus_id;
chan->cd = &pl08x->pd->slave_channels[i];
} else {
chan->cd = &pl08x->pd->memcpy_channel;
chan->name = kasprintf(GFP_KERNEL, "memcpy%d", i);
if (!chan->name) {
kfree(chan);
return -ENOMEM;
}
}
dev_info(&pl08x->adev->dev,
"initialize virtual channel \"%s\"\n",
chan->name);
chan->chan.device = dmadev;
atomic_set(&chan->last_issued, 0);
chan->lc = atomic_read(&chan->last_issued);
spin_lock_init(&chan->lock);
INIT_LIST_HEAD(&chan->desc_list);
tasklet_init(&chan->tasklet, pl08x_tasklet,
(unsigned long) chan);
list_add_tail(&chan->chan.device_node, &dmadev->channels);
}
dev_info(&pl08x->adev->dev, "initialized %d virtual %s channels\n",
i, slave ? "slave" : "memcpy");
return i;
}
static void pl08x_free_virtual_channels(struct dma_device *dmadev)
{
struct pl08x_dma_chan *chan = NULL;
struct pl08x_dma_chan *next;
list_for_each_entry_safe(chan,
next, &dmadev->channels, chan.device_node) {
list_del(&chan->chan.device_node);
kfree(chan);
}
}
#ifdef CONFIG_DEBUG_FS
static const char *pl08x_state_str(enum pl08x_dma_chan_state state)
{
switch (state) {
case PL08X_CHAN_IDLE:
return "idle";
case PL08X_CHAN_RUNNING:
return "running";
case PL08X_CHAN_PAUSED:
return "paused";
case PL08X_CHAN_WAITING:
return "waiting";
default:
break;
}
return "UNKNOWN STATE";
}
static int pl08x_debugfs_show(struct seq_file *s, void *data)
{
struct pl08x_driver_data *pl08x = s->private;
struct pl08x_dma_chan *chan;
struct pl08x_phy_chan *ch;
unsigned long flags;
int i;
seq_printf(s, "PL08x physical channels:\n");
seq_printf(s, "CHANNEL:\tUSER:\n");
seq_printf(s, "--------\t-----\n");
for (i = 0; i < pl08x->vd->channels; i++) {
struct pl08x_dma_chan *virt_chan;
ch = &pl08x->phy_chans[i];
spin_lock_irqsave(&ch->lock, flags);
virt_chan = ch->serving;
seq_printf(s, "%d\t\t%s\n",
ch->id, virt_chan ? virt_chan->name : "(none)");
spin_unlock_irqrestore(&ch->lock, flags);
}
seq_printf(s, "\nPL08x virtual memcpy channels:\n");
seq_printf(s, "CHANNEL:\tSTATE:\n");
seq_printf(s, "--------\t------\n");
list_for_each_entry(chan, &pl08x->memcpy.channels, chan.device_node) {
seq_printf(s, "%s\t\t\%s\n", chan->name,
pl08x_state_str(chan->state));
}
seq_printf(s, "\nPL08x virtual slave channels:\n");
seq_printf(s, "CHANNEL:\tSTATE:\n");
seq_printf(s, "--------\t------\n");
list_for_each_entry(chan, &pl08x->slave.channels, chan.device_node) {
seq_printf(s, "%s\t\t\%s\n", chan->name,
pl08x_state_str(chan->state));
}
return 0;
}
static int pl08x_debugfs_open(struct inode *inode, struct file *file)
{
return single_open(file, pl08x_debugfs_show, inode->i_private);
}
static const struct file_operations pl08x_debugfs_operations = {
.open = pl08x_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static void init_pl08x_debugfs(struct pl08x_driver_data *pl08x)
{
/* Expose a simple debugfs interface to view all clocks */
(void) debugfs_create_file(dev_name(&pl08x->adev->dev), S_IFREG | S_IRUGO,
NULL, pl08x,
&pl08x_debugfs_operations);
}
#else
static inline void init_pl08x_debugfs(struct pl08x_driver_data *pl08x)
{
}
#endif
static int pl08x_probe(struct amba_device *adev, struct amba_id *id)
{
struct pl08x_driver_data *pl08x;
struct vendor_data *vd = id->data;
int ret = 0;
int i;
ret = amba_request_regions(adev, NULL);
if (ret)
return ret;
/* Create the driver state holder */
pl08x = kzalloc(sizeof(struct pl08x_driver_data), GFP_KERNEL);
if (!pl08x) {
ret = -ENOMEM;
goto out_no_pl08x;
}
/* Initialize memcpy engine */
dma_cap_set(DMA_MEMCPY, pl08x->memcpy.cap_mask);
pl08x->memcpy.dev = &adev->dev;
pl08x->memcpy.device_alloc_chan_resources = pl08x_alloc_chan_resources;
pl08x->memcpy.device_free_chan_resources = pl08x_free_chan_resources;
pl08x->memcpy.device_prep_dma_memcpy = pl08x_prep_dma_memcpy;
pl08x->memcpy.device_prep_dma_interrupt = pl08x_prep_dma_interrupt;
pl08x->memcpy.device_tx_status = pl08x_dma_tx_status;
pl08x->memcpy.device_issue_pending = pl08x_issue_pending;
pl08x->memcpy.device_control = pl08x_control;
/* Initialize slave engine */
dma_cap_set(DMA_SLAVE, pl08x->slave.cap_mask);
pl08x->slave.dev = &adev->dev;
pl08x->slave.device_alloc_chan_resources = pl08x_alloc_chan_resources;
pl08x->slave.device_free_chan_resources = pl08x_free_chan_resources;
pl08x->slave.device_prep_dma_interrupt = pl08x_prep_dma_interrupt;
pl08x->slave.device_tx_status = pl08x_dma_tx_status;
pl08x->slave.device_issue_pending = pl08x_issue_pending;
pl08x->slave.device_prep_slave_sg = pl08x_prep_slave_sg;
pl08x->slave.device_control = pl08x_control;
/* Get the platform data */
pl08x->pd = dev_get_platdata(&adev->dev);
if (!pl08x->pd) {
dev_err(&adev->dev, "no platform data supplied\n");
goto out_no_platdata;
}
/* Assign useful pointers to the driver state */
pl08x->adev = adev;
pl08x->vd = vd;
/* A DMA memory pool for LLIs, align on 1-byte boundary */
pl08x->pool = dma_pool_create(DRIVER_NAME, &pl08x->adev->dev,
PL08X_LLI_TSFR_SIZE, PL08X_ALIGN, 0);
if (!pl08x->pool) {
ret = -ENOMEM;
goto out_no_lli_pool;
}
spin_lock_init(&pl08x->lock);
pl08x->base = ioremap(adev->res.start, resource_size(&adev->res));
if (!pl08x->base) {
ret = -ENOMEM;
goto out_no_ioremap;
}
/* Turn on the PL08x */
pl08x_ensure_on(pl08x);
/*
* Attach the interrupt handler
*/
writel(0x000000FF, pl08x->base + PL080_ERR_CLEAR);
writel(0x000000FF, pl08x->base + PL080_TC_CLEAR);
ret = request_irq(adev->irq[0], pl08x_irq, IRQF_DISABLED,
vd->name, pl08x);
if (ret) {
dev_err(&adev->dev, "%s failed to request interrupt %d\n",
__func__, adev->irq[0]);
goto out_no_irq;
}
/* Initialize physical channels */
pl08x->phy_chans = kmalloc((vd->channels * sizeof(struct pl08x_phy_chan)),
GFP_KERNEL);
if (!pl08x->phy_chans) {
dev_err(&adev->dev, "%s failed to allocate "
"physical channel holders\n",
__func__);
goto out_no_phychans;
}
for (i = 0; i < vd->channels; i++) {
struct pl08x_phy_chan *ch = &pl08x->phy_chans[i];
ch->id = i;
ch->base = pl08x->base + PL080_Cx_BASE(i);
spin_lock_init(&ch->lock);
ch->serving = NULL;
ch->signal = -1;
dev_info(&adev->dev,
"physical channel %d is %s\n", i,
pl08x_phy_channel_busy(ch) ? "BUSY" : "FREE");
}
/* Register as many memcpy channels as there are physical channels */
ret = pl08x_dma_init_virtual_channels(pl08x, &pl08x->memcpy,
pl08x->vd->channels, false);
if (ret <= 0) {
dev_warn(&pl08x->adev->dev,
"%s failed to enumerate memcpy channels - %d\n",
__func__, ret);
goto out_no_memcpy;
}
pl08x->memcpy.chancnt = ret;
/* Register slave channels */
ret = pl08x_dma_init_virtual_channels(pl08x, &pl08x->slave,
pl08x->pd->num_slave_channels,
true);
if (ret <= 0) {
dev_warn(&pl08x->adev->dev,
"%s failed to enumerate slave channels - %d\n",
__func__, ret);
goto out_no_slave;
}
pl08x->slave.chancnt = ret;
ret = dma_async_device_register(&pl08x->memcpy);
if (ret) {
dev_warn(&pl08x->adev->dev,
"%s failed to register memcpy as an async device - %d\n",
__func__, ret);
goto out_no_memcpy_reg;
}
ret = dma_async_device_register(&pl08x->slave);
if (ret) {
dev_warn(&pl08x->adev->dev,
"%s failed to register slave as an async device - %d\n",
__func__, ret);
goto out_no_slave_reg;
}
amba_set_drvdata(adev, pl08x);
init_pl08x_debugfs(pl08x);
dev_info(&pl08x->adev->dev, "ARM(R) %s DMA block initialized @%08x\n",
vd->name, adev->res.start);
return 0;
out_no_slave_reg:
dma_async_device_unregister(&pl08x->memcpy);
out_no_memcpy_reg:
pl08x_free_virtual_channels(&pl08x->slave);
out_no_slave:
pl08x_free_virtual_channels(&pl08x->memcpy);
out_no_memcpy:
kfree(pl08x->phy_chans);
out_no_phychans:
free_irq(adev->irq[0], pl08x);
out_no_irq:
iounmap(pl08x->base);
out_no_ioremap:
dma_pool_destroy(pl08x->pool);
out_no_lli_pool:
out_no_platdata:
kfree(pl08x);
out_no_pl08x:
amba_release_regions(adev);
return ret;
}
/* PL080 has 8 channels and the PL080 have just 2 */
static struct vendor_data vendor_pl080 = {
.name = "PL080",
.channels = 8,
.dualmaster = true,
};
static struct vendor_data vendor_pl081 = {
.name = "PL081",
.channels = 2,
.dualmaster = false,
};
static struct amba_id pl08x_ids[] = {
/* PL080 */
{
.id = 0x00041080,
.mask = 0x000fffff,
.data = &vendor_pl080,
},
/* PL081 */
{
.id = 0x00041081,
.mask = 0x000fffff,
.data = &vendor_pl081,
},
/* Nomadik 8815 PL080 variant */
{
.id = 0x00280880,
.mask = 0x00ffffff,
.data = &vendor_pl080,
},
{ 0, 0 },
};
static struct amba_driver pl08x_amba_driver = {
.drv.name = DRIVER_NAME,
.id_table = pl08x_ids,
.probe = pl08x_probe,
};
static int __init pl08x_init(void)
{
int retval;
retval = amba_driver_register(&pl08x_amba_driver);
if (retval)
printk(KERN_WARNING DRIVER_NAME
"failed to register as an amba device (%d)\n",
retval);
return retval;
}
subsys_initcall(pl08x_init);
...@@ -1610,7 +1610,7 @@ int __init coh901318_init(void) ...@@ -1610,7 +1610,7 @@ int __init coh901318_init(void)
{ {
return platform_driver_probe(&coh901318_driver, coh901318_probe); return platform_driver_probe(&coh901318_driver, coh901318_probe);
} }
subsys_initcall(coh901318_init); arch_initcall(coh901318_init);
void __exit coh901318_exit(void) void __exit coh901318_exit(void)
{ {
......
...@@ -690,8 +690,12 @@ int dma_async_device_register(struct dma_device *device) ...@@ -690,8 +690,12 @@ int dma_async_device_register(struct dma_device *device)
!device->device_prep_dma_memset); !device->device_prep_dma_memset);
BUG_ON(dma_has_cap(DMA_INTERRUPT, device->cap_mask) && BUG_ON(dma_has_cap(DMA_INTERRUPT, device->cap_mask) &&
!device->device_prep_dma_interrupt); !device->device_prep_dma_interrupt);
BUG_ON(dma_has_cap(DMA_SG, device->cap_mask) &&
!device->device_prep_dma_sg);
BUG_ON(dma_has_cap(DMA_SLAVE, device->cap_mask) && BUG_ON(dma_has_cap(DMA_SLAVE, device->cap_mask) &&
!device->device_prep_slave_sg); !device->device_prep_slave_sg);
BUG_ON(dma_has_cap(DMA_CYCLIC, device->cap_mask) &&
!device->device_prep_dma_cyclic);
BUG_ON(dma_has_cap(DMA_SLAVE, device->cap_mask) && BUG_ON(dma_has_cap(DMA_SLAVE, device->cap_mask) &&
!device->device_control); !device->device_control);
...@@ -702,7 +706,7 @@ int dma_async_device_register(struct dma_device *device) ...@@ -702,7 +706,7 @@ int dma_async_device_register(struct dma_device *device)
BUG_ON(!device->dev); BUG_ON(!device->dev);
/* note: this only matters in the /* note: this only matters in the
* CONFIG_ASYNC_TX_DISABLE_CHANNEL_SWITCH=y case * CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH=n case
*/ */
if (device_has_all_tx_types(device)) if (device_has_all_tx_types(device))
dma_cap_set(DMA_ASYNC_TX, device->cap_mask); dma_cap_set(DMA_ASYNC_TX, device->cap_mask);
...@@ -976,7 +980,7 @@ void dma_async_tx_descriptor_init(struct dma_async_tx_descriptor *tx, ...@@ -976,7 +980,7 @@ void dma_async_tx_descriptor_init(struct dma_async_tx_descriptor *tx,
struct dma_chan *chan) struct dma_chan *chan)
{ {
tx->chan = chan; tx->chan = chan;
#ifndef CONFIG_ASYNC_TX_DISABLE_CHANNEL_SWITCH #ifdef CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH
spin_lock_init(&tx->lock); spin_lock_init(&tx->lock);
#endif #endif
} }
......
...@@ -35,9 +35,10 @@ ...@@ -35,9 +35,10 @@
#include <linux/dmapool.h> #include <linux/dmapool.h>
#include <linux/of_platform.h> #include <linux/of_platform.h>
#include <asm/fsldma.h>
#include "fsldma.h" #include "fsldma.h"
static const char msg_ld_oom[] = "No free memory for link descriptor\n";
static void dma_init(struct fsldma_chan *chan) static void dma_init(struct fsldma_chan *chan)
{ {
/* Reset the channel */ /* Reset the channel */
...@@ -499,7 +500,7 @@ fsl_dma_prep_interrupt(struct dma_chan *dchan, unsigned long flags) ...@@ -499,7 +500,7 @@ fsl_dma_prep_interrupt(struct dma_chan *dchan, unsigned long flags)
new = fsl_dma_alloc_descriptor(chan); new = fsl_dma_alloc_descriptor(chan);
if (!new) { if (!new) {
dev_err(chan->dev, "No free memory for link descriptor\n"); dev_err(chan->dev, msg_ld_oom);
return NULL; return NULL;
} }
...@@ -536,8 +537,7 @@ static struct dma_async_tx_descriptor *fsl_dma_prep_memcpy( ...@@ -536,8 +537,7 @@ static struct dma_async_tx_descriptor *fsl_dma_prep_memcpy(
/* Allocate the link descriptor from DMA pool */ /* Allocate the link descriptor from DMA pool */
new = fsl_dma_alloc_descriptor(chan); new = fsl_dma_alloc_descriptor(chan);
if (!new) { if (!new) {
dev_err(chan->dev, dev_err(chan->dev, msg_ld_oom);
"No free memory for link descriptor\n");
goto fail; goto fail;
} }
#ifdef FSL_DMA_LD_DEBUG #ifdef FSL_DMA_LD_DEBUG
...@@ -583,223 +583,205 @@ static struct dma_async_tx_descriptor *fsl_dma_prep_memcpy( ...@@ -583,223 +583,205 @@ static struct dma_async_tx_descriptor *fsl_dma_prep_memcpy(
return NULL; return NULL;
} }
/** static struct dma_async_tx_descriptor *fsl_dma_prep_sg(struct dma_chan *dchan,
* fsl_dma_prep_slave_sg - prepare descriptors for a DMA_SLAVE transaction struct scatterlist *dst_sg, unsigned int dst_nents,
* @chan: DMA channel struct scatterlist *src_sg, unsigned int src_nents,
* @sgl: scatterlist to transfer to/from unsigned long flags)
* @sg_len: number of entries in @scatterlist
* @direction: DMA direction
* @flags: DMAEngine flags
*
* Prepare a set of descriptors for a DMA_SLAVE transaction. Following the
* DMA_SLAVE API, this gets the device-specific information from the
* chan->private variable.
*/
static struct dma_async_tx_descriptor *fsl_dma_prep_slave_sg(
struct dma_chan *dchan, struct scatterlist *sgl, unsigned int sg_len,
enum dma_data_direction direction, unsigned long flags)
{ {
struct fsldma_chan *chan;
struct fsl_desc_sw *first = NULL, *prev = NULL, *new = NULL; struct fsl_desc_sw *first = NULL, *prev = NULL, *new = NULL;
struct fsl_dma_slave *slave; struct fsldma_chan *chan = to_fsl_chan(dchan);
size_t copy; size_t dst_avail, src_avail;
dma_addr_t dst, src;
int i; size_t len;
struct scatterlist *sg;
size_t sg_used;
size_t hw_used;
struct fsl_dma_hw_addr *hw;
dma_addr_t dma_dst, dma_src;
if (!dchan) /* basic sanity checks */
if (dst_nents == 0 || src_nents == 0)
return NULL; return NULL;
if (!dchan->private) if (dst_sg == NULL || src_sg == NULL)
return NULL; return NULL;
chan = to_fsl_chan(dchan); /*
slave = dchan->private; * TODO: should we check that both scatterlists have the same
* TODO: number of bytes in total? Is that really an error?
*/
if (list_empty(&slave->addresses)) /* get prepared for the loop */
return NULL; dst_avail = sg_dma_len(dst_sg);
src_avail = sg_dma_len(src_sg);
hw = list_first_entry(&slave->addresses, struct fsl_dma_hw_addr, entry); /* run until we are out of scatterlist entries */
hw_used = 0; while (true) {
/* /* create the largest transaction possible */
* Build the hardware transaction to copy from the scatterlist to len = min_t(size_t, src_avail, dst_avail);
* the hardware, or from the hardware to the scatterlist len = min_t(size_t, len, FSL_DMA_BCR_MAX_CNT);
* if (len == 0)
* If you are copying from the hardware to the scatterlist and it goto fetch;
* takes two hardware entries to fill an entire page, then both
* hardware entries will be coalesced into the same page dst = sg_dma_address(dst_sg) + sg_dma_len(dst_sg) - dst_avail;
* src = sg_dma_address(src_sg) + sg_dma_len(src_sg) - src_avail;
* If you are copying from the scatterlist to the hardware and a
* single page can fill two hardware entries, then the data will /* allocate and populate the descriptor */
* be read out of the page into the first hardware entry, and so on new = fsl_dma_alloc_descriptor(chan);
*/ if (!new) {
for_each_sg(sgl, sg, sg_len, i) { dev_err(chan->dev, msg_ld_oom);
sg_used = 0; goto fail;
}
/* Loop until the entire scatterlist entry is used */
while (sg_used < sg_dma_len(sg)) {
/*
* If we've used up the current hardware address/length
* pair, we need to load a new one
*
* This is done in a while loop so that descriptors with
* length == 0 will be skipped
*/
while (hw_used >= hw->length) {
/*
* If the current hardware entry is the last
* entry in the list, we're finished
*/
if (list_is_last(&hw->entry, &slave->addresses))
goto finished;
/* Get the next hardware address/length pair */
hw = list_entry(hw->entry.next,
struct fsl_dma_hw_addr, entry);
hw_used = 0;
}
/* Allocate the link descriptor from DMA pool */
new = fsl_dma_alloc_descriptor(chan);
if (!new) {
dev_err(chan->dev, "No free memory for "
"link descriptor\n");
goto fail;
}
#ifdef FSL_DMA_LD_DEBUG #ifdef FSL_DMA_LD_DEBUG
dev_dbg(chan->dev, "new link desc alloc %p\n", new); dev_dbg(chan->dev, "new link desc alloc %p\n", new);
#endif #endif
/* set_desc_cnt(chan, &new->hw, len);
* Calculate the maximum number of bytes to transfer, set_desc_src(chan, &new->hw, src);
* making sure it is less than the DMA controller limit set_desc_dst(chan, &new->hw, dst);
*/
copy = min_t(size_t, sg_dma_len(sg) - sg_used,
hw->length - hw_used);
copy = min_t(size_t, copy, FSL_DMA_BCR_MAX_CNT);
/*
* DMA_FROM_DEVICE
* from the hardware to the scatterlist
*
* DMA_TO_DEVICE
* from the scatterlist to the hardware
*/
if (direction == DMA_FROM_DEVICE) {
dma_src = hw->address + hw_used;
dma_dst = sg_dma_address(sg) + sg_used;
} else {
dma_src = sg_dma_address(sg) + sg_used;
dma_dst = hw->address + hw_used;
}
/* Fill in the descriptor */
set_desc_cnt(chan, &new->hw, copy);
set_desc_src(chan, &new->hw, dma_src);
set_desc_dst(chan, &new->hw, dma_dst);
/*
* If this is not the first descriptor, chain the
* current descriptor after the previous descriptor
*/
if (!first) {
first = new;
} else {
set_desc_next(chan, &prev->hw,
new->async_tx.phys);
}
new->async_tx.cookie = 0;
async_tx_ack(&new->async_tx);
prev = new;
sg_used += copy;
hw_used += copy;
/* Insert the link descriptor into the LD ring */
list_add_tail(&new->node, &first->tx_list);
}
}
finished: if (!first)
first = new;
else
set_desc_next(chan, &prev->hw, new->async_tx.phys);
/* All of the hardware address/length pairs had length == 0 */ new->async_tx.cookie = 0;
if (!first || !new) async_tx_ack(&new->async_tx);
return NULL; prev = new;
new->async_tx.flags = flags; /* Insert the link descriptor to the LD ring */
new->async_tx.cookie = -EBUSY; list_add_tail(&new->node, &first->tx_list);
/* Set End-of-link to the last link descriptor of new list */ /* update metadata */
set_ld_eol(chan, new); dst_avail -= len;
src_avail -= len;
fetch:
/* fetch the next dst scatterlist entry */
if (dst_avail == 0) {
/* no more entries: we're done */
if (dst_nents == 0)
break;
/* fetch the next entry: if there are no more: done */
dst_sg = sg_next(dst_sg);
if (dst_sg == NULL)
break;
dst_nents--;
dst_avail = sg_dma_len(dst_sg);
}
/* Enable extra controller features */ /* fetch the next src scatterlist entry */
if (chan->set_src_loop_size) if (src_avail == 0) {
chan->set_src_loop_size(chan, slave->src_loop_size);
if (chan->set_dst_loop_size) /* no more entries: we're done */
chan->set_dst_loop_size(chan, slave->dst_loop_size); if (src_nents == 0)
break;
if (chan->toggle_ext_start) /* fetch the next entry: if there are no more: done */
chan->toggle_ext_start(chan, slave->external_start); src_sg = sg_next(src_sg);
if (src_sg == NULL)
break;
if (chan->toggle_ext_pause) src_nents--;
chan->toggle_ext_pause(chan, slave->external_pause); src_avail = sg_dma_len(src_sg);
}
}
if (chan->set_request_count) new->async_tx.flags = flags; /* client is in control of this ack */
chan->set_request_count(chan, slave->request_count); new->async_tx.cookie = -EBUSY;
/* Set End-of-link to the last link descriptor of new list */
set_ld_eol(chan, new);
return &first->async_tx; return &first->async_tx;
fail: fail:
/* If first was not set, then we failed to allocate the very first
* descriptor, and we're done */
if (!first) if (!first)
return NULL; return NULL;
fsldma_free_desc_list_reverse(chan, &first->tx_list);
return NULL;
}
/**
* fsl_dma_prep_slave_sg - prepare descriptors for a DMA_SLAVE transaction
* @chan: DMA channel
* @sgl: scatterlist to transfer to/from
* @sg_len: number of entries in @scatterlist
* @direction: DMA direction
* @flags: DMAEngine flags
*
* Prepare a set of descriptors for a DMA_SLAVE transaction. Following the
* DMA_SLAVE API, this gets the device-specific information from the
* chan->private variable.
*/
static struct dma_async_tx_descriptor *fsl_dma_prep_slave_sg(
struct dma_chan *dchan, struct scatterlist *sgl, unsigned int sg_len,
enum dma_data_direction direction, unsigned long flags)
{
/* /*
* First is set, so all of the descriptors we allocated have been added * This operation is not supported on the Freescale DMA controller
* to first->tx_list, INCLUDING "first" itself. Therefore we
* must traverse the list backwards freeing each descriptor in turn
* *
* We're re-using variables for the loop, oh well * However, we need to provide the function pointer to allow the
* device_control() method to work.
*/ */
fsldma_free_desc_list_reverse(chan, &first->tx_list);
return NULL; return NULL;
} }
static int fsl_dma_device_control(struct dma_chan *dchan, static int fsl_dma_device_control(struct dma_chan *dchan,
enum dma_ctrl_cmd cmd, unsigned long arg) enum dma_ctrl_cmd cmd, unsigned long arg)
{ {
struct dma_slave_config *config;
struct fsldma_chan *chan; struct fsldma_chan *chan;
unsigned long flags; unsigned long flags;
int size;
/* Only supports DMA_TERMINATE_ALL */
if (cmd != DMA_TERMINATE_ALL)
return -ENXIO;
if (!dchan) if (!dchan)
return -EINVAL; return -EINVAL;
chan = to_fsl_chan(dchan); chan = to_fsl_chan(dchan);
/* Halt the DMA engine */ switch (cmd) {
dma_halt(chan); case DMA_TERMINATE_ALL:
/* Halt the DMA engine */
dma_halt(chan);
spin_lock_irqsave(&chan->desc_lock, flags); spin_lock_irqsave(&chan->desc_lock, flags);
/* Remove and free all of the descriptors in the LD queue */ /* Remove and free all of the descriptors in the LD queue */
fsldma_free_desc_list(chan, &chan->ld_pending); fsldma_free_desc_list(chan, &chan->ld_pending);
fsldma_free_desc_list(chan, &chan->ld_running); fsldma_free_desc_list(chan, &chan->ld_running);
spin_unlock_irqrestore(&chan->desc_lock, flags); spin_unlock_irqrestore(&chan->desc_lock, flags);
return 0;
case DMA_SLAVE_CONFIG:
config = (struct dma_slave_config *)arg;
/* make sure the channel supports setting burst size */
if (!chan->set_request_count)
return -ENXIO;
/* we set the controller burst size depending on direction */
if (config->direction == DMA_TO_DEVICE)
size = config->dst_addr_width * config->dst_maxburst;
else
size = config->src_addr_width * config->src_maxburst;
chan->set_request_count(chan, size);
return 0;
case FSLDMA_EXTERNAL_START:
/* make sure the channel supports external start */
if (!chan->toggle_ext_start)
return -ENXIO;
chan->toggle_ext_start(chan, arg);
return 0;
default:
return -ENXIO;
}
return 0; return 0;
} }
...@@ -1327,11 +1309,13 @@ static int __devinit fsldma_of_probe(struct platform_device *op, ...@@ -1327,11 +1309,13 @@ static int __devinit fsldma_of_probe(struct platform_device *op,
dma_cap_set(DMA_MEMCPY, fdev->common.cap_mask); dma_cap_set(DMA_MEMCPY, fdev->common.cap_mask);
dma_cap_set(DMA_INTERRUPT, fdev->common.cap_mask); dma_cap_set(DMA_INTERRUPT, fdev->common.cap_mask);
dma_cap_set(DMA_SG, fdev->common.cap_mask);
dma_cap_set(DMA_SLAVE, fdev->common.cap_mask); dma_cap_set(DMA_SLAVE, fdev->common.cap_mask);
fdev->common.device_alloc_chan_resources = fsl_dma_alloc_chan_resources; fdev->common.device_alloc_chan_resources = fsl_dma_alloc_chan_resources;
fdev->common.device_free_chan_resources = fsl_dma_free_chan_resources; fdev->common.device_free_chan_resources = fsl_dma_free_chan_resources;
fdev->common.device_prep_dma_interrupt = fsl_dma_prep_interrupt; fdev->common.device_prep_dma_interrupt = fsl_dma_prep_interrupt;
fdev->common.device_prep_dma_memcpy = fsl_dma_prep_memcpy; fdev->common.device_prep_dma_memcpy = fsl_dma_prep_memcpy;
fdev->common.device_prep_dma_sg = fsl_dma_prep_sg;
fdev->common.device_tx_status = fsl_tx_status; fdev->common.device_tx_status = fsl_tx_status;
fdev->common.device_issue_pending = fsl_dma_memcpy_issue_pending; fdev->common.device_issue_pending = fsl_dma_memcpy_issue_pending;
fdev->common.device_prep_slave_sg = fsl_dma_prep_slave_sg; fdev->common.device_prep_slave_sg = fsl_dma_prep_slave_sg;
......
/*
* drivers/dma/imx-dma.c
*
* This file contains a driver for the Freescale i.MX DMA engine
* found on i.MX1/21/27
*
* Copyright 2010 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
*
* 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/spinlock.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/dmaengine.h>
#include <asm/irq.h>
#include <mach/dma-v1.h>
#include <mach/hardware.h>
struct imxdma_channel {
struct imxdma_engine *imxdma;
unsigned int channel;
unsigned int imxdma_channel;
enum dma_slave_buswidth word_size;
dma_addr_t per_address;
u32 watermark_level;
struct dma_chan chan;
spinlock_t lock;
struct dma_async_tx_descriptor desc;
dma_cookie_t last_completed;
enum dma_status status;
int dma_request;
struct scatterlist *sg_list;
};
#define MAX_DMA_CHANNELS 8
struct imxdma_engine {
struct device *dev;
struct dma_device dma_device;
struct imxdma_channel channel[MAX_DMA_CHANNELS];
};
static struct imxdma_channel *to_imxdma_chan(struct dma_chan *chan)
{
return container_of(chan, struct imxdma_channel, chan);
}
static void imxdma_handle(struct imxdma_channel *imxdmac)
{
if (imxdmac->desc.callback)
imxdmac->desc.callback(imxdmac->desc.callback_param);
imxdmac->last_completed = imxdmac->desc.cookie;
}
static void imxdma_irq_handler(int channel, void *data)
{
struct imxdma_channel *imxdmac = data;
imxdmac->status = DMA_SUCCESS;
imxdma_handle(imxdmac);
}
static void imxdma_err_handler(int channel, void *data, int error)
{
struct imxdma_channel *imxdmac = data;
imxdmac->status = DMA_ERROR;
imxdma_handle(imxdmac);
}
static void imxdma_progression(int channel, void *data,
struct scatterlist *sg)
{
struct imxdma_channel *imxdmac = data;
imxdmac->status = DMA_SUCCESS;
imxdma_handle(imxdmac);
}
static int imxdma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
unsigned long arg)
{
struct imxdma_channel *imxdmac = to_imxdma_chan(chan);
struct dma_slave_config *dmaengine_cfg = (void *)arg;
int ret;
unsigned int mode = 0;
switch (cmd) {
case DMA_TERMINATE_ALL:
imxdmac->status = DMA_ERROR;
imx_dma_disable(imxdmac->imxdma_channel);
return 0;
case DMA_SLAVE_CONFIG:
if (dmaengine_cfg->direction == DMA_FROM_DEVICE) {
imxdmac->per_address = dmaengine_cfg->src_addr;
imxdmac->watermark_level = dmaengine_cfg->src_maxburst;
imxdmac->word_size = dmaengine_cfg->src_addr_width;
} else {
imxdmac->per_address = dmaengine_cfg->dst_addr;
imxdmac->watermark_level = dmaengine_cfg->dst_maxburst;
imxdmac->word_size = dmaengine_cfg->dst_addr_width;
}
switch (imxdmac->word_size) {
case DMA_SLAVE_BUSWIDTH_1_BYTE:
mode = IMX_DMA_MEMSIZE_8;
break;
case DMA_SLAVE_BUSWIDTH_2_BYTES:
mode = IMX_DMA_MEMSIZE_16;
break;
default:
case DMA_SLAVE_BUSWIDTH_4_BYTES:
mode = IMX_DMA_MEMSIZE_32;
break;
}
ret = imx_dma_config_channel(imxdmac->imxdma_channel,
mode | IMX_DMA_TYPE_FIFO,
IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
imxdmac->dma_request, 1);
if (ret)
return ret;
imx_dma_config_burstlen(imxdmac->imxdma_channel, imxdmac->watermark_level);
return 0;
default:
return -ENOSYS;
}
return -EINVAL;
}
static enum dma_status imxdma_tx_status(struct dma_chan *chan,
dma_cookie_t cookie,
struct dma_tx_state *txstate)
{
struct imxdma_channel *imxdmac = to_imxdma_chan(chan);
dma_cookie_t last_used;
enum dma_status ret;
last_used = chan->cookie;
ret = dma_async_is_complete(cookie, imxdmac->last_completed, last_used);
dma_set_tx_state(txstate, imxdmac->last_completed, last_used, 0);
return ret;
}
static dma_cookie_t imxdma_assign_cookie(struct imxdma_channel *imxdma)
{
dma_cookie_t cookie = imxdma->chan.cookie;
if (++cookie < 0)
cookie = 1;
imxdma->chan.cookie = cookie;
imxdma->desc.cookie = cookie;
return cookie;
}
static dma_cookie_t imxdma_tx_submit(struct dma_async_tx_descriptor *tx)
{
struct imxdma_channel *imxdmac = to_imxdma_chan(tx->chan);
dma_cookie_t cookie;
spin_lock_irq(&imxdmac->lock);
cookie = imxdma_assign_cookie(imxdmac);
imx_dma_enable(imxdmac->imxdma_channel);
spin_unlock_irq(&imxdmac->lock);
return cookie;
}
static int imxdma_alloc_chan_resources(struct dma_chan *chan)
{
struct imxdma_channel *imxdmac = to_imxdma_chan(chan);
struct imx_dma_data *data = chan->private;
imxdmac->dma_request = data->dma_request;
dma_async_tx_descriptor_init(&imxdmac->desc, chan);
imxdmac->desc.tx_submit = imxdma_tx_submit;
/* txd.flags will be overwritten in prep funcs */
imxdmac->desc.flags = DMA_CTRL_ACK;
imxdmac->status = DMA_SUCCESS;
return 0;
}
static void imxdma_free_chan_resources(struct dma_chan *chan)
{
struct imxdma_channel *imxdmac = to_imxdma_chan(chan);
imx_dma_disable(imxdmac->imxdma_channel);
if (imxdmac->sg_list) {
kfree(imxdmac->sg_list);
imxdmac->sg_list = NULL;
}
}
static struct dma_async_tx_descriptor *imxdma_prep_slave_sg(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_data_direction direction,
unsigned long flags)
{
struct imxdma_channel *imxdmac = to_imxdma_chan(chan);
struct scatterlist *sg;
int i, ret, dma_length = 0;
unsigned int dmamode;
if (imxdmac->status == DMA_IN_PROGRESS)
return NULL;
imxdmac->status = DMA_IN_PROGRESS;
for_each_sg(sgl, sg, sg_len, i) {
dma_length += sg->length;
}
if (direction == DMA_FROM_DEVICE)
dmamode = DMA_MODE_READ;
else
dmamode = DMA_MODE_WRITE;
ret = imx_dma_setup_sg(imxdmac->imxdma_channel, sgl, sg_len,
dma_length, imxdmac->per_address, dmamode);
if (ret)
return NULL;
return &imxdmac->desc;
}
static struct dma_async_tx_descriptor *imxdma_prep_dma_cyclic(
struct dma_chan *chan, dma_addr_t dma_addr, size_t buf_len,
size_t period_len, enum dma_data_direction direction)
{
struct imxdma_channel *imxdmac = to_imxdma_chan(chan);
struct imxdma_engine *imxdma = imxdmac->imxdma;
int i, ret;
unsigned int periods = buf_len / period_len;
unsigned int dmamode;
dev_dbg(imxdma->dev, "%s channel: %d buf_len=%d period_len=%d\n",
__func__, imxdmac->channel, buf_len, period_len);
if (imxdmac->status == DMA_IN_PROGRESS)
return NULL;
imxdmac->status = DMA_IN_PROGRESS;
ret = imx_dma_setup_progression_handler(imxdmac->imxdma_channel,
imxdma_progression);
if (ret) {
dev_err(imxdma->dev, "Failed to setup the DMA handler\n");
return NULL;
}
if (imxdmac->sg_list)
kfree(imxdmac->sg_list);
imxdmac->sg_list = kcalloc(periods + 1,
sizeof(struct scatterlist), GFP_KERNEL);
if (!imxdmac->sg_list)
return NULL;
sg_init_table(imxdmac->sg_list, periods);
for (i = 0; i < periods; i++) {
imxdmac->sg_list[i].page_link = 0;
imxdmac->sg_list[i].offset = 0;
imxdmac->sg_list[i].dma_address = dma_addr;
imxdmac->sg_list[i].length = period_len;
dma_addr += period_len;
}
/* close the loop */
imxdmac->sg_list[periods].offset = 0;
imxdmac->sg_list[periods].length = 0;
imxdmac->sg_list[periods].page_link =
((unsigned long)imxdmac->sg_list | 0x01) & ~0x02;
if (direction == DMA_FROM_DEVICE)
dmamode = DMA_MODE_READ;
else
dmamode = DMA_MODE_WRITE;
ret = imx_dma_setup_sg(imxdmac->imxdma_channel, imxdmac->sg_list, periods,
IMX_DMA_LENGTH_LOOP, imxdmac->per_address, dmamode);
if (ret)
return NULL;
return &imxdmac->desc;
}
static void imxdma_issue_pending(struct dma_chan *chan)
{
/*
* Nothing to do. We only have a single descriptor
*/
}
static int __init imxdma_probe(struct platform_device *pdev)
{
struct imxdma_engine *imxdma;
int ret, i;
imxdma = kzalloc(sizeof(*imxdma), GFP_KERNEL);
if (!imxdma)
return -ENOMEM;
INIT_LIST_HEAD(&imxdma->dma_device.channels);
/* Initialize channel parameters */
for (i = 0; i < MAX_DMA_CHANNELS; i++) {
struct imxdma_channel *imxdmac = &imxdma->channel[i];
imxdmac->imxdma_channel = imx_dma_request_by_prio("dmaengine",
DMA_PRIO_MEDIUM);
if ((int)imxdmac->channel < 0) {
ret = -ENODEV;
goto err_init;
}
imx_dma_setup_handlers(imxdmac->imxdma_channel,
imxdma_irq_handler, imxdma_err_handler, imxdmac);
imxdmac->imxdma = imxdma;
spin_lock_init(&imxdmac->lock);
dma_cap_set(DMA_SLAVE, imxdma->dma_device.cap_mask);
dma_cap_set(DMA_CYCLIC, imxdma->dma_device.cap_mask);
imxdmac->chan.device = &imxdma->dma_device;
imxdmac->chan.chan_id = i;
imxdmac->channel = i;
/* Add the channel to the DMAC list */
list_add_tail(&imxdmac->chan.device_node, &imxdma->dma_device.channels);
}
imxdma->dev = &pdev->dev;
imxdma->dma_device.dev = &pdev->dev;
imxdma->dma_device.device_alloc_chan_resources = imxdma_alloc_chan_resources;
imxdma->dma_device.device_free_chan_resources = imxdma_free_chan_resources;
imxdma->dma_device.device_tx_status = imxdma_tx_status;
imxdma->dma_device.device_prep_slave_sg = imxdma_prep_slave_sg;
imxdma->dma_device.device_prep_dma_cyclic = imxdma_prep_dma_cyclic;
imxdma->dma_device.device_control = imxdma_control;
imxdma->dma_device.device_issue_pending = imxdma_issue_pending;
platform_set_drvdata(pdev, imxdma);
ret = dma_async_device_register(&imxdma->dma_device);
if (ret) {
dev_err(&pdev->dev, "unable to register\n");
goto err_init;
}
return 0;
err_init:
while (i-- >= 0) {
struct imxdma_channel *imxdmac = &imxdma->channel[i];
imx_dma_free(imxdmac->imxdma_channel);
}
kfree(imxdma);
return ret;
}
static int __exit imxdma_remove(struct platform_device *pdev)
{
struct imxdma_engine *imxdma = platform_get_drvdata(pdev);
int i;
dma_async_device_unregister(&imxdma->dma_device);
for (i = 0; i < MAX_DMA_CHANNELS; i++) {
struct imxdma_channel *imxdmac = &imxdma->channel[i];
imx_dma_free(imxdmac->imxdma_channel);
}
kfree(imxdma);
return 0;
}
static struct platform_driver imxdma_driver = {
.driver = {
.name = "imx-dma",
},
.remove = __exit_p(imxdma_remove),
};
static int __init imxdma_module_init(void)
{
return platform_driver_probe(&imxdma_driver, imxdma_probe);
}
subsys_initcall(imxdma_module_init);
MODULE_AUTHOR("Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>");
MODULE_DESCRIPTION("i.MX dma driver");
MODULE_LICENSE("GPL");
/*
* drivers/dma/imx-sdma.c
*
* This file contains a driver for the Freescale Smart DMA engine
*
* Copyright 2010 Sascha Hauer, Pengutronix <s.hauer@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/wait.h>
#include <linux/sched.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 <linux/dmaengine.h>
#include <asm/irq.h>
#include <mach/sdma.h>
#include <mach/dma.h>
#include <mach/hardware.h>
/* SDMA registers */
#define SDMA_H_C0PTR 0x000
#define SDMA_H_INTR 0x004
#define SDMA_H_STATSTOP 0x008
#define SDMA_H_START 0x00c
#define SDMA_H_EVTOVR 0x010
#define SDMA_H_DSPOVR 0x014
#define SDMA_H_HOSTOVR 0x018
#define SDMA_H_EVTPEND 0x01c
#define SDMA_H_DSPENBL 0x020
#define SDMA_H_RESET 0x024
#define SDMA_H_EVTERR 0x028
#define SDMA_H_INTRMSK 0x02c
#define SDMA_H_PSW 0x030
#define SDMA_H_EVTERRDBG 0x034
#define SDMA_H_CONFIG 0x038
#define SDMA_ONCE_ENB 0x040
#define SDMA_ONCE_DATA 0x044
#define SDMA_ONCE_INSTR 0x048
#define SDMA_ONCE_STAT 0x04c
#define SDMA_ONCE_CMD 0x050
#define SDMA_EVT_MIRROR 0x054
#define SDMA_ILLINSTADDR 0x058
#define SDMA_CHN0ADDR 0x05c
#define SDMA_ONCE_RTB 0x060
#define SDMA_XTRIG_CONF1 0x070
#define SDMA_XTRIG_CONF2 0x074
#define SDMA_CHNENBL0_V2 0x200
#define SDMA_CHNENBL0_V1 0x080
#define SDMA_CHNPRI_0 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
*/
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 */
};
/*
* Buffer descriptor
*/
struct sdma_buffer_descriptor {
struct sdma_mode_count mode;
u32 buffer_addr; /* address of the buffer described */
u32 ext_buffer_addr; /* extended buffer address */
} __attribute__ ((packed));
/**
* struct sdma_channel_control - Channel control Block
*
* @current_bd_ptr current buffer descriptor processed
* @base_bd_ptr first element of buffer descriptor array
* @unused padding. The SDMA engine expects an array of 128 byte
* control blocks
*/
struct sdma_channel_control {
u32 current_bd_ptr;
u32 base_bd_ptr;
u32 unused[2];
} __attribute__ ((packed));
/**
* struct sdma_state_registers - SDMA context for a channel
*
* @pc: program counter
* @t: test bit: status of arithmetic & test instruction
* @rpc: return program counter
* @sf: source fault while loading data
* @spc: loop start program counter
* @df: destination fault while storing data
* @epc: loop end program counter
* @lm: loop mode
*/
struct sdma_state_registers {
u32 pc :14;
u32 unused1: 1;
u32 t : 1;
u32 rpc :14;
u32 unused0: 1;
u32 sf : 1;
u32 spc :14;
u32 unused2: 1;
u32 df : 1;
u32 epc :14;
u32 lm : 2;
} __attribute__ ((packed));
/**
* struct sdma_context_data - sdma context specific to a channel
*
* @channel_state: channel state bits
* @gReg: general registers
* @mda: burst dma destination address register
* @msa: burst dma source address register
* @ms: burst dma status register
* @md: burst dma data register
* @pda: peripheral dma destination address register
* @psa: peripheral dma source address register
* @ps: peripheral dma status register
* @pd: peripheral dma data register
* @ca: CRC polynomial register
* @cs: CRC accumulator register
* @dda: dedicated core destination address register
* @dsa: dedicated core source address register
* @ds: dedicated core status register
* @dd: dedicated core data register
*/
struct sdma_context_data {
struct sdma_state_registers channel_state;
u32 gReg[8];
u32 mda;
u32 msa;
u32 ms;
u32 md;
u32 pda;
u32 psa;
u32 ps;
u32 pd;
u32 ca;
u32 cs;
u32 dda;
u32 dsa;
u32 ds;
u32 dd;
u32 scratch0;
u32 scratch1;
u32 scratch2;
u32 scratch3;
u32 scratch4;
u32 scratch5;
u32 scratch6;
u32 scratch7;
} __attribute__ ((packed));
#define NUM_BD (int)(PAGE_SIZE / sizeof(struct sdma_buffer_descriptor))
struct sdma_engine;
/**
* struct sdma_channel - housekeeping for a SDMA channel
*
* @sdma pointer to the SDMA engine for this channel
* @channel the channel number, matches dmaengine chan_id
* @direction transfer type. Needed for setting SDMA script
* @peripheral_type Peripheral type. Needed for setting SDMA script
* @event_id0 aka dma request line
* @event_id1 for channels that use 2 events
* @word_size peripheral access size
* @buf_tail ID of the buffer that was processed
* @done channel completion
* @num_bd max NUM_BD. number of descriptors currently handling
*/
struct sdma_channel {
struct sdma_engine *sdma;
unsigned int channel;
enum dma_data_direction direction;
enum sdma_peripheral_type peripheral_type;
unsigned int event_id0;
unsigned int event_id1;
enum dma_slave_buswidth word_size;
unsigned int buf_tail;
struct completion done;
unsigned int num_bd;
struct sdma_buffer_descriptor *bd;
dma_addr_t bd_phys;
unsigned int pc_from_device, pc_to_device;
unsigned long flags;
dma_addr_t per_address;
u32 event_mask0, event_mask1;
u32 watermark_level;
u32 shp_addr, per_addr;
struct dma_chan chan;
spinlock_t lock;
struct dma_async_tx_descriptor desc;
dma_cookie_t last_completed;
enum dma_status status;
};
#define IMX_DMA_SG_LOOP (1 << 0)
#define MAX_DMA_CHANNELS 32
#define MXC_SDMA_DEFAULT_PRIORITY 1
#define MXC_SDMA_MIN_PRIORITY 1
#define MXC_SDMA_MAX_PRIORITY 7
/**
* struct sdma_script_start_addrs - SDMA script start pointers
*
* start addresses of the different functions in the physical
* address space of the SDMA engine.
*/
struct sdma_script_start_addrs {
u32 ap_2_ap_addr;
u32 ap_2_bp_addr;
u32 ap_2_ap_fixed_addr;
u32 bp_2_ap_addr;
u32 loopback_on_dsp_side_addr;
u32 mcu_interrupt_only_addr;
u32 firi_2_per_addr;
u32 firi_2_mcu_addr;
u32 per_2_firi_addr;
u32 mcu_2_firi_addr;
u32 uart_2_per_addr;
u32 uart_2_mcu_addr;
u32 per_2_app_addr;
u32 mcu_2_app_addr;
u32 per_2_per_addr;
u32 uartsh_2_per_addr;
u32 uartsh_2_mcu_addr;
u32 per_2_shp_addr;
u32 mcu_2_shp_addr;
u32 ata_2_mcu_addr;
u32 mcu_2_ata_addr;
u32 app_2_per_addr;
u32 app_2_mcu_addr;
u32 shp_2_per_addr;
u32 shp_2_mcu_addr;
u32 mshc_2_mcu_addr;
u32 mcu_2_mshc_addr;
u32 spdif_2_mcu_addr;
u32 mcu_2_spdif_addr;
u32 asrc_2_mcu_addr;
u32 ext_mem_2_ipu_addr;
u32 descrambler_addr;
u32 dptc_dvfs_addr;
u32 utra_addr;
u32 ram_code_start_addr;
};
#define SDMA_FIRMWARE_MAGIC 0x414d4453
/**
* struct sdma_firmware_header - Layout of the firmware image
*
* @magic "SDMA"
* @version_major increased whenever layout of struct sdma_script_start_addrs
* changes.
* @version_minor firmware minor version (for binary compatible changes)
* @script_addrs_start offset of struct sdma_script_start_addrs in this image
* @num_script_addrs Number of script addresses in this image
* @ram_code_start offset of SDMA ram image in this firmware image
* @ram_code_size size of SDMA ram image
* @script_addrs Stores the start address of the SDMA scripts
* (in SDMA memory space)
*/
struct sdma_firmware_header {
u32 magic;
u32 version_major;
u32 version_minor;
u32 script_addrs_start;
u32 num_script_addrs;
u32 ram_code_start;
u32 ram_code_size;
};
struct sdma_engine {
struct device *dev;
struct sdma_channel channel[MAX_DMA_CHANNELS];
struct sdma_channel_control *channel_control;
void __iomem *regs;
unsigned int version;
unsigned int num_events;
struct sdma_context_data *context;
dma_addr_t context_phys;
struct dma_device dma_device;
struct clk *clk;
struct sdma_script_start_addrs *script_addrs;
};
#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 inline u32 chnenbl_ofs(struct sdma_engine *sdma, unsigned int event)
{
u32 chnenbl0 = (sdma->version == 2 ? SDMA_CHNENBL0_V2 : SDMA_CHNENBL0_V1);
return chnenbl0 + event * 4;
}
static int sdma_config_ownership(struct sdma_channel *sdmac,
bool event_override, bool mcu_override, bool dsp_override)
{
struct sdma_engine *sdma = sdmac->sdma;
int channel = sdmac->channel;
u32 evt, mcu, dsp;
if (event_override && mcu_override && dsp_override)
return -EINVAL;
evt = __raw_readl(sdma->regs + SDMA_H_EVTOVR);
mcu = __raw_readl(sdma->regs + SDMA_H_HOSTOVR);
dsp = __raw_readl(sdma->regs + SDMA_H_DSPOVR);
if (dsp_override)
dsp &= ~(1 << channel);
else
dsp |= (1 << channel);
if (event_override)
evt &= ~(1 << channel);
else
evt |= (1 << channel);
if (mcu_override)
mcu &= ~(1 << channel);
else
mcu |= (1 << channel);
__raw_writel(evt, sdma->regs + SDMA_H_EVTOVR);
__raw_writel(mcu, sdma->regs + SDMA_H_HOSTOVR);
__raw_writel(dsp, sdma->regs + SDMA_H_DSPOVR);
return 0;
}
/*
* sdma_run_channel - run a channel and wait till it's done
*/
static int sdma_run_channel(struct sdma_channel *sdmac)
{
struct sdma_engine *sdma = sdmac->sdma;
int channel = sdmac->channel;
int ret;
init_completion(&sdmac->done);
__raw_writel(1 << channel, sdma->regs + SDMA_H_START);
ret = wait_for_completion_timeout(&sdmac->done, HZ);
return ret ? 0 : -ETIMEDOUT;
}
static int sdma_load_script(struct sdma_engine *sdma, void *buf, int size,
u32 address)
{
struct sdma_buffer_descriptor *bd0 = sdma->channel[0].bd;
void *buf_virt;
dma_addr_t buf_phys;
int ret;
buf_virt = dma_alloc_coherent(NULL,
size,
&buf_phys, GFP_KERNEL);
if (!buf_virt)
return -ENOMEM;
bd0->mode.command = C0_SETPM;
bd0->mode.status = BD_DONE | BD_INTR | BD_WRAP | BD_EXTD;
bd0->mode.count = size / 2;
bd0->buffer_addr = buf_phys;
bd0->ext_buffer_addr = address;
memcpy(buf_virt, buf, size);
ret = sdma_run_channel(&sdma->channel[0]);
dma_free_coherent(NULL, size, buf_virt, buf_phys);
return ret;
}
static void sdma_event_enable(struct sdma_channel *sdmac, unsigned int event)
{
struct sdma_engine *sdma = sdmac->sdma;
int channel = sdmac->channel;
u32 val;
u32 chnenbl = chnenbl_ofs(sdma, event);
val = __raw_readl(sdma->regs + chnenbl);
val |= (1 << channel);
__raw_writel(val, sdma->regs + chnenbl);
}
static void sdma_event_disable(struct sdma_channel *sdmac, unsigned int event)
{
struct sdma_engine *sdma = sdmac->sdma;
int channel = sdmac->channel;
u32 chnenbl = chnenbl_ofs(sdma, event);
u32 val;
val = __raw_readl(sdma->regs + chnenbl);
val &= ~(1 << channel);
__raw_writel(val, sdma->regs + chnenbl);
}
static void sdma_handle_channel_loop(struct sdma_channel *sdmac)
{
struct sdma_buffer_descriptor *bd;
/*
* loop mode. Iterate over descriptors, re-setup them and
* call callback function.
*/
while (1) {
bd = &sdmac->bd[sdmac->buf_tail];
if (bd->mode.status & BD_DONE)
break;
if (bd->mode.status & BD_RROR)
sdmac->status = DMA_ERROR;
else
sdmac->status = DMA_SUCCESS;
bd->mode.status |= BD_DONE;
sdmac->buf_tail++;
sdmac->buf_tail %= sdmac->num_bd;
if (sdmac->desc.callback)
sdmac->desc.callback(sdmac->desc.callback_param);
}
}
static void mxc_sdma_handle_channel_normal(struct sdma_channel *sdmac)
{
struct sdma_buffer_descriptor *bd;
int i, error = 0;
/*
* non loop mode. Iterate over all descriptors, collect
* errors and call callback function
*/
for (i = 0; i < sdmac->num_bd; i++) {
bd = &sdmac->bd[i];
if (bd->mode.status & (BD_DONE | BD_RROR))
error = -EIO;
}
if (error)
sdmac->status = DMA_ERROR;
else
sdmac->status = DMA_SUCCESS;
if (sdmac->desc.callback)
sdmac->desc.callback(sdmac->desc.callback_param);
sdmac->last_completed = sdmac->desc.cookie;
}
static void mxc_sdma_handle_channel(struct sdma_channel *sdmac)
{
complete(&sdmac->done);
/* not interested in channel 0 interrupts */
if (sdmac->channel == 0)
return;
if (sdmac->flags & IMX_DMA_SG_LOOP)
sdma_handle_channel_loop(sdmac);
else
mxc_sdma_handle_channel_normal(sdmac);
}
static irqreturn_t sdma_int_handler(int irq, void *dev_id)
{
struct sdma_engine *sdma = dev_id;
u32 stat;
stat = __raw_readl(sdma->regs + SDMA_H_INTR);
__raw_writel(stat, sdma->regs + SDMA_H_INTR);
while (stat) {
int channel = fls(stat) - 1;
struct sdma_channel *sdmac = &sdma->channel[channel];
mxc_sdma_handle_channel(sdmac);
stat &= ~(1 << channel);
}
return IRQ_HANDLED;
}
/*
* sets the pc of SDMA script according to the peripheral type
*/
static void sdma_get_pc(struct sdma_channel *sdmac,
enum sdma_peripheral_type peripheral_type)
{
struct sdma_engine *sdma = sdmac->sdma;
int per_2_emi = 0, emi_2_per = 0;
/*
* These are needed once we start to support transfers between
* two peripherals or memory-to-memory transfers
*/
int per_2_per = 0, emi_2_emi = 0;
sdmac->pc_from_device = 0;
sdmac->pc_to_device = 0;
switch (peripheral_type) {
case IMX_DMATYPE_MEMORY:
emi_2_emi = sdma->script_addrs->ap_2_ap_addr;
break;
case IMX_DMATYPE_DSP:
emi_2_per = sdma->script_addrs->bp_2_ap_addr;
per_2_emi = sdma->script_addrs->ap_2_bp_addr;
break;
case IMX_DMATYPE_FIRI:
per_2_emi = sdma->script_addrs->firi_2_mcu_addr;
emi_2_per = sdma->script_addrs->mcu_2_firi_addr;
break;
case IMX_DMATYPE_UART:
per_2_emi = sdma->script_addrs->uart_2_mcu_addr;
emi_2_per = sdma->script_addrs->mcu_2_app_addr;
break;
case IMX_DMATYPE_UART_SP:
per_2_emi = sdma->script_addrs->uartsh_2_mcu_addr;
emi_2_per = sdma->script_addrs->mcu_2_shp_addr;
break;
case IMX_DMATYPE_ATA:
per_2_emi = sdma->script_addrs->ata_2_mcu_addr;
emi_2_per = sdma->script_addrs->mcu_2_ata_addr;
break;
case IMX_DMATYPE_CSPI:
case IMX_DMATYPE_EXT:
case IMX_DMATYPE_SSI:
per_2_emi = sdma->script_addrs->app_2_mcu_addr;
emi_2_per = sdma->script_addrs->mcu_2_app_addr;
break;
case IMX_DMATYPE_SSI_SP:
case IMX_DMATYPE_MMC:
case IMX_DMATYPE_SDHC:
case IMX_DMATYPE_CSPI_SP:
case IMX_DMATYPE_ESAI:
case IMX_DMATYPE_MSHC_SP:
per_2_emi = sdma->script_addrs->shp_2_mcu_addr;
emi_2_per = sdma->script_addrs->mcu_2_shp_addr;
break;
case IMX_DMATYPE_ASRC:
per_2_emi = sdma->script_addrs->asrc_2_mcu_addr;
emi_2_per = sdma->script_addrs->asrc_2_mcu_addr;
per_2_per = sdma->script_addrs->per_2_per_addr;
break;
case IMX_DMATYPE_MSHC:
per_2_emi = sdma->script_addrs->mshc_2_mcu_addr;
emi_2_per = sdma->script_addrs->mcu_2_mshc_addr;
break;
case IMX_DMATYPE_CCM:
per_2_emi = sdma->script_addrs->dptc_dvfs_addr;
break;
case IMX_DMATYPE_SPDIF:
per_2_emi = sdma->script_addrs->spdif_2_mcu_addr;
emi_2_per = sdma->script_addrs->mcu_2_spdif_addr;
break;
case IMX_DMATYPE_IPU_MEMORY:
emi_2_per = sdma->script_addrs->ext_mem_2_ipu_addr;
break;
default:
break;
}
sdmac->pc_from_device = per_2_emi;
sdmac->pc_to_device = emi_2_per;
}
static int sdma_load_context(struct sdma_channel *sdmac)
{
struct sdma_engine *sdma = sdmac->sdma;
int channel = sdmac->channel;
int load_address;
struct sdma_context_data *context = sdma->context;
struct sdma_buffer_descriptor *bd0 = sdma->channel[0].bd;
int ret;
if (sdmac->direction == DMA_FROM_DEVICE) {
load_address = sdmac->pc_from_device;
} else {
load_address = sdmac->pc_to_device;
}
if (load_address < 0)
return load_address;
dev_dbg(sdma->dev, "load_address = %d\n", load_address);
dev_dbg(sdma->dev, "wml = 0x%08x\n", sdmac->watermark_level);
dev_dbg(sdma->dev, "shp_addr = 0x%08x\n", sdmac->shp_addr);
dev_dbg(sdma->dev, "per_addr = 0x%08x\n", sdmac->per_addr);
dev_dbg(sdma->dev, "event_mask0 = 0x%08x\n", sdmac->event_mask0);
dev_dbg(sdma->dev, "event_mask1 = 0x%08x\n", sdmac->event_mask1);
memset(context, 0, sizeof(*context));
context->channel_state.pc = load_address;
/* Send by context the event mask,base address for peripheral
* and watermark level
*/
context->gReg[0] = sdmac->event_mask1;
context->gReg[1] = sdmac->event_mask0;
context->gReg[2] = sdmac->per_addr;
context->gReg[6] = sdmac->shp_addr;
context->gReg[7] = sdmac->watermark_level;
bd0->mode.command = C0_SETDM;
bd0->mode.status = BD_DONE | BD_INTR | BD_WRAP | BD_EXTD;
bd0->mode.count = sizeof(*context) / 4;
bd0->buffer_addr = sdma->context_phys;
bd0->ext_buffer_addr = 2048 + (sizeof(*context) / 4) * channel;
ret = sdma_run_channel(&sdma->channel[0]);
return ret;
}
static void sdma_disable_channel(struct sdma_channel *sdmac)
{
struct sdma_engine *sdma = sdmac->sdma;
int channel = sdmac->channel;
__raw_writel(1 << channel, sdma->regs + SDMA_H_STATSTOP);
sdmac->status = DMA_ERROR;
}
static int sdma_config_channel(struct sdma_channel *sdmac)
{
int ret;
sdma_disable_channel(sdmac);
sdmac->event_mask0 = 0;
sdmac->event_mask1 = 0;
sdmac->shp_addr = 0;
sdmac->per_addr = 0;
if (sdmac->event_id0) {
if (sdmac->event_id0 > 32)
return -EINVAL;
sdma_event_enable(sdmac, sdmac->event_id0);
}
switch (sdmac->peripheral_type) {
case IMX_DMATYPE_DSP:
sdma_config_ownership(sdmac, false, true, true);
break;
case IMX_DMATYPE_MEMORY:
sdma_config_ownership(sdmac, false, true, false);
break;
default:
sdma_config_ownership(sdmac, true, true, false);
break;
}
sdma_get_pc(sdmac, sdmac->peripheral_type);
if ((sdmac->peripheral_type != IMX_DMATYPE_MEMORY) &&
(sdmac->peripheral_type != IMX_DMATYPE_DSP)) {
/* Handle multiple event channels differently */
if (sdmac->event_id1) {
sdmac->event_mask1 = 1 << (sdmac->event_id1 % 32);
if (sdmac->event_id1 > 31)
sdmac->watermark_level |= 1 << 31;
sdmac->event_mask0 = 1 << (sdmac->event_id0 % 32);
if (sdmac->event_id0 > 31)
sdmac->watermark_level |= 1 << 30;
} else {
sdmac->event_mask0 = 1 << sdmac->event_id0;
sdmac->event_mask1 = 1 << (sdmac->event_id0 - 32);
}
/* Watermark Level */
sdmac->watermark_level |= sdmac->watermark_level;
/* Address */
sdmac->shp_addr = sdmac->per_address;
} else {
sdmac->watermark_level = 0; /* FIXME: M3_BASE_ADDRESS */
}
ret = sdma_load_context(sdmac);
return ret;
}
static int sdma_set_channel_priority(struct sdma_channel *sdmac,
unsigned int priority)
{
struct sdma_engine *sdma = sdmac->sdma;
int channel = sdmac->channel;
if (priority < MXC_SDMA_MIN_PRIORITY
|| priority > MXC_SDMA_MAX_PRIORITY) {
return -EINVAL;
}
__raw_writel(priority, sdma->regs + SDMA_CHNPRI_0 + 4 * channel);
return 0;
}
static int sdma_request_channel(struct sdma_channel *sdmac)
{
struct sdma_engine *sdma = sdmac->sdma;
int channel = sdmac->channel;
int ret = -EBUSY;
sdmac->bd = dma_alloc_coherent(NULL, PAGE_SIZE, &sdmac->bd_phys, GFP_KERNEL);
if (!sdmac->bd) {
ret = -ENOMEM;
goto out;
}
memset(sdmac->bd, 0, PAGE_SIZE);
sdma->channel_control[channel].base_bd_ptr = sdmac->bd_phys;
sdma->channel_control[channel].current_bd_ptr = sdmac->bd_phys;
clk_enable(sdma->clk);
sdma_set_channel_priority(sdmac, MXC_SDMA_DEFAULT_PRIORITY);
init_completion(&sdmac->done);
sdmac->buf_tail = 0;
return 0;
out:
return ret;
}
static void sdma_enable_channel(struct sdma_engine *sdma, int channel)
{
__raw_writel(1 << channel, sdma->regs + SDMA_H_START);
}
static dma_cookie_t sdma_assign_cookie(struct sdma_channel *sdma)
{
dma_cookie_t cookie = sdma->chan.cookie;
if (++cookie < 0)
cookie = 1;
sdma->chan.cookie = cookie;
sdma->desc.cookie = cookie;
return cookie;
}
static struct sdma_channel *to_sdma_chan(struct dma_chan *chan)
{
return container_of(chan, struct sdma_channel, chan);
}
static dma_cookie_t sdma_tx_submit(struct dma_async_tx_descriptor *tx)
{
struct sdma_channel *sdmac = to_sdma_chan(tx->chan);
struct sdma_engine *sdma = sdmac->sdma;
dma_cookie_t cookie;
spin_lock_irq(&sdmac->lock);
cookie = sdma_assign_cookie(sdmac);
sdma_enable_channel(sdma, tx->chan->chan_id);
spin_unlock_irq(&sdmac->lock);
return cookie;
}
static int sdma_alloc_chan_resources(struct dma_chan *chan)
{
struct sdma_channel *sdmac = to_sdma_chan(chan);
struct imx_dma_data *data = chan->private;
int prio, ret;
/* No need to execute this for internal channel 0 */
if (chan->chan_id == 0)
return 0;
if (!data)
return -EINVAL;
switch (data->priority) {
case DMA_PRIO_HIGH:
prio = 3;
break;
case DMA_PRIO_MEDIUM:
prio = 2;
break;
case DMA_PRIO_LOW:
default:
prio = 1;
break;
}
sdmac->peripheral_type = data->peripheral_type;
sdmac->event_id0 = data->dma_request;
ret = sdma_set_channel_priority(sdmac, prio);
if (ret)
return ret;
ret = sdma_request_channel(sdmac);
if (ret)
return ret;
dma_async_tx_descriptor_init(&sdmac->desc, chan);
sdmac->desc.tx_submit = sdma_tx_submit;
/* txd.flags will be overwritten in prep funcs */
sdmac->desc.flags = DMA_CTRL_ACK;
return 0;
}
static void sdma_free_chan_resources(struct dma_chan *chan)
{
struct sdma_channel *sdmac = to_sdma_chan(chan);
struct sdma_engine *sdma = sdmac->sdma;
sdma_disable_channel(sdmac);
if (sdmac->event_id0)
sdma_event_disable(sdmac, sdmac->event_id0);
if (sdmac->event_id1)
sdma_event_disable(sdmac, sdmac->event_id1);
sdmac->event_id0 = 0;
sdmac->event_id1 = 0;
sdma_set_channel_priority(sdmac, 0);
dma_free_coherent(NULL, PAGE_SIZE, sdmac->bd, sdmac->bd_phys);
clk_disable(sdma->clk);
}
static struct dma_async_tx_descriptor *sdma_prep_slave_sg(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_data_direction direction,
unsigned long flags)
{
struct sdma_channel *sdmac = to_sdma_chan(chan);
struct sdma_engine *sdma = sdmac->sdma;
int ret, i, count;
int channel = chan->chan_id;
struct scatterlist *sg;
if (sdmac->status == DMA_IN_PROGRESS)
return NULL;
sdmac->status = DMA_IN_PROGRESS;
sdmac->flags = 0;
dev_dbg(sdma->dev, "setting up %d entries for channel %d.\n",
sg_len, channel);
sdmac->direction = direction;
ret = sdma_load_context(sdmac);
if (ret)
goto err_out;
if (sg_len > NUM_BD) {
dev_err(sdma->dev, "SDMA channel %d: maximum number of sg exceeded: %d > %d\n",
channel, sg_len, NUM_BD);
ret = -EINVAL;
goto err_out;
}
for_each_sg(sgl, sg, sg_len, i) {
struct sdma_buffer_descriptor *bd = &sdmac->bd[i];
int param;
bd->buffer_addr = sgl->dma_address;
count = sg->length;
if (count > 0xffff) {
dev_err(sdma->dev, "SDMA channel %d: maximum bytes for sg entry exceeded: %d > %d\n",
channel, count, 0xffff);
ret = -EINVAL;
goto err_out;
}
bd->mode.count = count;
if (sdmac->word_size > DMA_SLAVE_BUSWIDTH_4_BYTES) {
ret = -EINVAL;
goto err_out;
}
if (sdmac->word_size == DMA_SLAVE_BUSWIDTH_4_BYTES)
bd->mode.command = 0;
else
bd->mode.command = sdmac->word_size;
param = BD_DONE | BD_EXTD | BD_CONT;
if (sdmac->flags & IMX_DMA_SG_LOOP) {
param |= BD_INTR;
if (i + 1 == sg_len)
param |= BD_WRAP;
}
if (i + 1 == sg_len)
param |= BD_INTR;
dev_dbg(sdma->dev, "entry %d: count: %d dma: 0x%08x %s%s\n",
i, count, sg->dma_address,
param & BD_WRAP ? "wrap" : "",
param & BD_INTR ? " intr" : "");
bd->mode.status = param;
}
sdmac->num_bd = sg_len;
sdma->channel_control[channel].current_bd_ptr = sdmac->bd_phys;
return &sdmac->desc;
err_out:
return NULL;
}
static struct dma_async_tx_descriptor *sdma_prep_dma_cyclic(
struct dma_chan *chan, dma_addr_t dma_addr, size_t buf_len,
size_t period_len, enum dma_data_direction direction)
{
struct sdma_channel *sdmac = to_sdma_chan(chan);
struct sdma_engine *sdma = sdmac->sdma;
int num_periods = buf_len / period_len;
int channel = chan->chan_id;
int ret, i = 0, buf = 0;
dev_dbg(sdma->dev, "%s channel: %d\n", __func__, channel);
if (sdmac->status == DMA_IN_PROGRESS)
return NULL;
sdmac->status = DMA_IN_PROGRESS;
sdmac->flags |= IMX_DMA_SG_LOOP;
sdmac->direction = direction;
ret = sdma_load_context(sdmac);
if (ret)
goto err_out;
if (num_periods > NUM_BD) {
dev_err(sdma->dev, "SDMA channel %d: maximum number of sg exceeded: %d > %d\n",
channel, num_periods, NUM_BD);
goto err_out;
}
if (period_len > 0xffff) {
dev_err(sdma->dev, "SDMA channel %d: maximum period size exceeded: %d > %d\n",
channel, period_len, 0xffff);
goto err_out;
}
while (buf < buf_len) {
struct sdma_buffer_descriptor *bd = &sdmac->bd[i];
int param;
bd->buffer_addr = dma_addr;
bd->mode.count = period_len;
if (sdmac->word_size > DMA_SLAVE_BUSWIDTH_4_BYTES)
goto err_out;
if (sdmac->word_size == DMA_SLAVE_BUSWIDTH_4_BYTES)
bd->mode.command = 0;
else
bd->mode.command = sdmac->word_size;
param = BD_DONE | BD_EXTD | BD_CONT | BD_INTR;
if (i + 1 == num_periods)
param |= BD_WRAP;
dev_dbg(sdma->dev, "entry %d: count: %d dma: 0x%08x %s%s\n",
i, period_len, dma_addr,
param & BD_WRAP ? "wrap" : "",
param & BD_INTR ? " intr" : "");
bd->mode.status = param;
dma_addr += period_len;
buf += period_len;
i++;
}
sdmac->num_bd = num_periods;
sdma->channel_control[channel].current_bd_ptr = sdmac->bd_phys;
return &sdmac->desc;
err_out:
sdmac->status = DMA_ERROR;
return NULL;
}
static int sdma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
unsigned long arg)
{
struct sdma_channel *sdmac = to_sdma_chan(chan);
struct dma_slave_config *dmaengine_cfg = (void *)arg;
switch (cmd) {
case DMA_TERMINATE_ALL:
sdma_disable_channel(sdmac);
return 0;
case DMA_SLAVE_CONFIG:
if (dmaengine_cfg->direction == DMA_FROM_DEVICE) {
sdmac->per_address = dmaengine_cfg->src_addr;
sdmac->watermark_level = dmaengine_cfg->src_maxburst;
sdmac->word_size = dmaengine_cfg->src_addr_width;
} else {
sdmac->per_address = dmaengine_cfg->dst_addr;
sdmac->watermark_level = dmaengine_cfg->dst_maxburst;
sdmac->word_size = dmaengine_cfg->dst_addr_width;
}
return sdma_config_channel(sdmac);
default:
return -ENOSYS;
}
return -EINVAL;
}
static enum dma_status sdma_tx_status(struct dma_chan *chan,
dma_cookie_t cookie,
struct dma_tx_state *txstate)
{
struct sdma_channel *sdmac = to_sdma_chan(chan);
dma_cookie_t last_used;
enum dma_status ret;
last_used = chan->cookie;
ret = dma_async_is_complete(cookie, sdmac->last_completed, last_used);
dma_set_tx_state(txstate, sdmac->last_completed, last_used, 0);
return ret;
}
static void sdma_issue_pending(struct dma_chan *chan)
{
/*
* Nothing to do. We only have a single descriptor
*/
}
static int __init sdma_init(struct sdma_engine *sdma,
void *ram_code, int ram_code_size)
{
int i, ret;
dma_addr_t ccb_phys;
switch (sdma->version) {
case 1:
sdma->num_events = 32;
break;
case 2:
sdma->num_events = 48;
break;
default:
dev_err(sdma->dev, "Unknown version %d. aborting\n", sdma->version);
return -ENODEV;
}
clk_enable(sdma->clk);
/* Be sure SDMA has not started yet */
__raw_writel(0, sdma->regs + SDMA_H_C0PTR);
sdma->channel_control = dma_alloc_coherent(NULL,
MAX_DMA_CHANNELS * sizeof (struct sdma_channel_control) +
sizeof(struct sdma_context_data),
&ccb_phys, GFP_KERNEL);
if (!sdma->channel_control) {
ret = -ENOMEM;
goto err_dma_alloc;
}
sdma->context = (void *)sdma->channel_control +
MAX_DMA_CHANNELS * sizeof (struct sdma_channel_control);
sdma->context_phys = ccb_phys +
MAX_DMA_CHANNELS * sizeof (struct sdma_channel_control);
/* Zero-out the CCB structures array just allocated */
memset(sdma->channel_control, 0,
MAX_DMA_CHANNELS * sizeof (struct sdma_channel_control));
/* disable all channels */
for (i = 0; i < sdma->num_events; i++)
__raw_writel(0, sdma->regs + chnenbl_ofs(sdma, i));
/* All channels have priority 0 */
for (i = 0; i < MAX_DMA_CHANNELS; i++)
__raw_writel(0, sdma->regs + SDMA_CHNPRI_0 + i * 4);
ret = sdma_request_channel(&sdma->channel[0]);
if (ret)
goto err_dma_alloc;
sdma_config_ownership(&sdma->channel[0], false, true, false);
/* Set Command Channel (Channel Zero) */
__raw_writel(0x4050, sdma->regs + SDMA_CHN0ADDR);
/* Set bits of CONFIG register but with static context switching */
/* FIXME: Check whether to set ACR bit depending on clock ratios */
__raw_writel(0, sdma->regs + SDMA_H_CONFIG);
__raw_writel(ccb_phys, sdma->regs + SDMA_H_C0PTR);
/* download the RAM image for SDMA */
sdma_load_script(sdma, ram_code,
ram_code_size,
sdma->script_addrs->ram_code_start_addr);
/* Set bits of CONFIG register with given context switching mode */
__raw_writel(SDMA_H_CONFIG_CSM, sdma->regs + SDMA_H_CONFIG);
/* Initializes channel's priorities */
sdma_set_channel_priority(&sdma->channel[0], 7);
clk_disable(sdma->clk);
return 0;
err_dma_alloc:
clk_disable(sdma->clk);
dev_err(sdma->dev, "initialisation failed with %d\n", ret);
return ret;
}
static int __init sdma_probe(struct platform_device *pdev)
{
int ret;
const struct firmware *fw;
const struct sdma_firmware_header *header;
const struct sdma_script_start_addrs *addr;
int irq;
unsigned short *ram_code;
struct resource *iores;
struct sdma_platform_data *pdata = pdev->dev.platform_data;
char *fwname;
int i;
dma_cap_mask_t mask;
struct sdma_engine *sdma;
sdma = kzalloc(sizeof(*sdma), GFP_KERNEL);
if (!sdma)
return -ENOMEM;
sdma->dev = &pdev->dev;
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq = platform_get_irq(pdev, 0);
if (!iores || irq < 0 || !pdata) {
ret = -EINVAL;
goto err_irq;
}
if (!request_mem_region(iores->start, resource_size(iores), pdev->name)) {
ret = -EBUSY;
goto err_request_region;
}
sdma->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(sdma->clk)) {
ret = PTR_ERR(sdma->clk);
goto err_clk;
}
sdma->regs = ioremap(iores->start, resource_size(iores));
if (!sdma->regs) {
ret = -ENOMEM;
goto err_ioremap;
}
ret = request_irq(irq, sdma_int_handler, 0, "sdma", sdma);
if (ret)
goto err_request_irq;
fwname = kasprintf(GFP_KERNEL, "sdma-%s-to%d.bin",
pdata->cpu_name, pdata->to_version);
if (!fwname) {
ret = -ENOMEM;
goto err_cputype;
}
ret = request_firmware(&fw, fwname, &pdev->dev);
if (ret) {
dev_err(&pdev->dev, "request firmware \"%s\" failed with %d\n",
fwname, ret);
kfree(fwname);
goto err_cputype;
}
kfree(fwname);
if (fw->size < sizeof(*header))
goto err_firmware;
header = (struct sdma_firmware_header *)fw->data;
if (header->magic != SDMA_FIRMWARE_MAGIC)
goto err_firmware;
if (header->ram_code_start + header->ram_code_size > fw->size)
goto err_firmware;
addr = (void *)header + header->script_addrs_start;
ram_code = (void *)header + header->ram_code_start;
sdma->script_addrs = kmalloc(sizeof(*addr), GFP_KERNEL);
if (!sdma->script_addrs)
goto err_firmware;
memcpy(sdma->script_addrs, addr, sizeof(*addr));
sdma->version = pdata->sdma_version;
INIT_LIST_HEAD(&sdma->dma_device.channels);
/* Initialize channel parameters */
for (i = 0; i < MAX_DMA_CHANNELS; i++) {
struct sdma_channel *sdmac = &sdma->channel[i];
sdmac->sdma = sdma;
spin_lock_init(&sdmac->lock);
dma_cap_set(DMA_SLAVE, sdma->dma_device.cap_mask);
dma_cap_set(DMA_CYCLIC, sdma->dma_device.cap_mask);
sdmac->chan.device = &sdma->dma_device;
sdmac->chan.chan_id = i;
sdmac->channel = i;
/* Add the channel to the DMAC list */
list_add_tail(&sdmac->chan.device_node, &sdma->dma_device.channels);
}
ret = sdma_init(sdma, ram_code, header->ram_code_size);
if (ret)
goto err_init;
sdma->dma_device.dev = &pdev->dev;
sdma->dma_device.device_alloc_chan_resources = sdma_alloc_chan_resources;
sdma->dma_device.device_free_chan_resources = sdma_free_chan_resources;
sdma->dma_device.device_tx_status = sdma_tx_status;
sdma->dma_device.device_prep_slave_sg = sdma_prep_slave_sg;
sdma->dma_device.device_prep_dma_cyclic = sdma_prep_dma_cyclic;
sdma->dma_device.device_control = sdma_control;
sdma->dma_device.device_issue_pending = sdma_issue_pending;
ret = dma_async_device_register(&sdma->dma_device);
if (ret) {
dev_err(&pdev->dev, "unable to register\n");
goto err_init;
}
dev_info(&pdev->dev, "initialized (firmware %d.%d)\n",
header->version_major,
header->version_minor);
/* request channel 0. This is an internal control channel
* to the SDMA engine and not available to clients.
*/
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
dma_request_channel(mask, NULL, NULL);
release_firmware(fw);
return 0;
err_init:
kfree(sdma->script_addrs);
err_firmware:
release_firmware(fw);
err_cputype:
free_irq(irq, sdma);
err_request_irq:
iounmap(sdma->regs);
err_ioremap:
clk_put(sdma->clk);
err_clk:
release_mem_region(iores->start, resource_size(iores));
err_request_region:
err_irq:
kfree(sdma);
return 0;
}
static int __exit sdma_remove(struct platform_device *pdev)
{
return -EBUSY;
}
static struct platform_driver sdma_driver = {
.driver = {
.name = "imx-sdma",
},
.remove = __exit_p(sdma_remove),
};
static int __init sdma_module_init(void)
{
return platform_driver_probe(&sdma_driver, sdma_probe);
}
subsys_initcall(sdma_module_init);
MODULE_AUTHOR("Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>");
MODULE_DESCRIPTION("i.MX SDMA driver");
MODULE_LICENSE("GPL");
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
*/ */
#include <linux/pci.h> #include <linux/pci.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/pm_runtime.h>
#include <linux/intel_mid_dma.h> #include <linux/intel_mid_dma.h>
#define MAX_CHAN 4 /*max ch across controllers*/ #define MAX_CHAN 4 /*max ch across controllers*/
...@@ -91,13 +92,13 @@ static int get_block_ts(int len, int tx_width, int block_size) ...@@ -91,13 +92,13 @@ static int get_block_ts(int len, int tx_width, int block_size)
int byte_width = 0, block_ts = 0; int byte_width = 0, block_ts = 0;
switch (tx_width) { switch (tx_width) {
case LNW_DMA_WIDTH_8BIT: case DMA_SLAVE_BUSWIDTH_1_BYTE:
byte_width = 1; byte_width = 1;
break; break;
case LNW_DMA_WIDTH_16BIT: case DMA_SLAVE_BUSWIDTH_2_BYTES:
byte_width = 2; byte_width = 2;
break; break;
case LNW_DMA_WIDTH_32BIT: case DMA_SLAVE_BUSWIDTH_4_BYTES:
default: default:
byte_width = 4; byte_width = 4;
break; break;
...@@ -247,16 +248,17 @@ static void midc_dostart(struct intel_mid_dma_chan *midc, ...@@ -247,16 +248,17 @@ static void midc_dostart(struct intel_mid_dma_chan *midc,
struct middma_device *mid = to_middma_device(midc->chan.device); struct middma_device *mid = to_middma_device(midc->chan.device);
/* channel is idle */ /* channel is idle */
if (midc->in_use && test_ch_en(midc->dma_base, midc->ch_id)) { if (midc->busy && test_ch_en(midc->dma_base, midc->ch_id)) {
/*error*/ /*error*/
pr_err("ERR_MDMA: channel is busy in start\n"); pr_err("ERR_MDMA: channel is busy in start\n");
/* The tasklet will hopefully advance the queue... */ /* The tasklet will hopefully advance the queue... */
return; return;
} }
midc->busy = true;
/*write registers and en*/ /*write registers and en*/
iowrite32(first->sar, midc->ch_regs + SAR); iowrite32(first->sar, midc->ch_regs + SAR);
iowrite32(first->dar, midc->ch_regs + DAR); iowrite32(first->dar, midc->ch_regs + DAR);
iowrite32(first->lli_phys, midc->ch_regs + LLP);
iowrite32(first->cfg_hi, midc->ch_regs + CFG_HIGH); iowrite32(first->cfg_hi, midc->ch_regs + CFG_HIGH);
iowrite32(first->cfg_lo, midc->ch_regs + CFG_LOW); iowrite32(first->cfg_lo, midc->ch_regs + CFG_LOW);
iowrite32(first->ctl_lo, midc->ch_regs + CTL_LOW); iowrite32(first->ctl_lo, midc->ch_regs + CTL_LOW);
...@@ -264,9 +266,9 @@ static void midc_dostart(struct intel_mid_dma_chan *midc, ...@@ -264,9 +266,9 @@ static void midc_dostart(struct intel_mid_dma_chan *midc,
pr_debug("MDMA:TX SAR %x,DAR %x,CFGL %x,CFGH %x,CTLH %x, CTLL %x\n", pr_debug("MDMA:TX SAR %x,DAR %x,CFGL %x,CFGH %x,CTLH %x, CTLL %x\n",
(int)first->sar, (int)first->dar, first->cfg_hi, (int)first->sar, (int)first->dar, first->cfg_hi,
first->cfg_lo, first->ctl_hi, first->ctl_lo); first->cfg_lo, first->ctl_hi, first->ctl_lo);
first->status = DMA_IN_PROGRESS;
iowrite32(ENABLE_CHANNEL(midc->ch_id), mid->dma_base + DMA_CHAN_EN); iowrite32(ENABLE_CHANNEL(midc->ch_id), mid->dma_base + DMA_CHAN_EN);
first->status = DMA_IN_PROGRESS;
} }
/** /**
...@@ -283,20 +285,36 @@ static void midc_descriptor_complete(struct intel_mid_dma_chan *midc, ...@@ -283,20 +285,36 @@ static void midc_descriptor_complete(struct intel_mid_dma_chan *midc,
{ {
struct dma_async_tx_descriptor *txd = &desc->txd; struct dma_async_tx_descriptor *txd = &desc->txd;
dma_async_tx_callback callback_txd = NULL; dma_async_tx_callback callback_txd = NULL;
struct intel_mid_dma_lli *llitem;
void *param_txd = NULL; void *param_txd = NULL;
midc->completed = txd->cookie; midc->completed = txd->cookie;
callback_txd = txd->callback; callback_txd = txd->callback;
param_txd = txd->callback_param; param_txd = txd->callback_param;
list_move(&desc->desc_node, &midc->free_list); if (desc->lli != NULL) {
/*clear the DONE bit of completed LLI in memory*/
llitem = desc->lli + desc->current_lli;
llitem->ctl_hi &= CLEAR_DONE;
if (desc->current_lli < desc->lli_length-1)
(desc->current_lli)++;
else
desc->current_lli = 0;
}
spin_unlock_bh(&midc->lock); spin_unlock_bh(&midc->lock);
if (callback_txd) { if (callback_txd) {
pr_debug("MDMA: TXD callback set ... calling\n"); pr_debug("MDMA: TXD callback set ... calling\n");
callback_txd(param_txd); callback_txd(param_txd);
spin_lock_bh(&midc->lock); }
return; if (midc->raw_tfr) {
desc->status = DMA_SUCCESS;
if (desc->lli != NULL) {
pci_pool_free(desc->lli_pool, desc->lli,
desc->lli_phys);
pci_pool_destroy(desc->lli_pool);
}
list_move(&desc->desc_node, &midc->free_list);
midc->busy = false;
} }
spin_lock_bh(&midc->lock); spin_lock_bh(&midc->lock);
...@@ -317,14 +335,89 @@ static void midc_scan_descriptors(struct middma_device *mid, ...@@ -317,14 +335,89 @@ static void midc_scan_descriptors(struct middma_device *mid,
/*tx is complete*/ /*tx is complete*/
list_for_each_entry_safe(desc, _desc, &midc->active_list, desc_node) { list_for_each_entry_safe(desc, _desc, &midc->active_list, desc_node) {
if (desc->status == DMA_IN_PROGRESS) { if (desc->status == DMA_IN_PROGRESS)
desc->status = DMA_SUCCESS;
midc_descriptor_complete(midc, desc); midc_descriptor_complete(midc, desc);
}
} }
return; return;
} }
/**
* midc_lli_fill_sg - Helper function to convert
* SG list to Linked List Items.
*@midc: Channel
*@desc: DMA descriptor
*@sglist: Pointer to SG list
*@sglen: SG list length
*@flags: DMA transaction flags
*
* Walk through the SG list and convert the SG list into Linked
* List Items (LLI).
*/
static int midc_lli_fill_sg(struct intel_mid_dma_chan *midc,
struct intel_mid_dma_desc *desc,
struct scatterlist *sglist,
unsigned int sglen,
unsigned int flags)
{
struct intel_mid_dma_slave *mids;
struct scatterlist *sg;
dma_addr_t lli_next, sg_phy_addr;
struct intel_mid_dma_lli *lli_bloc_desc;
union intel_mid_dma_ctl_lo ctl_lo;
union intel_mid_dma_ctl_hi ctl_hi;
int i;
pr_debug("MDMA: Entered midc_lli_fill_sg\n");
mids = midc->mid_slave;
lli_bloc_desc = desc->lli;
lli_next = desc->lli_phys;
ctl_lo.ctl_lo = desc->ctl_lo;
ctl_hi.ctl_hi = desc->ctl_hi;
for_each_sg(sglist, sg, sglen, i) {
/*Populate CTL_LOW and LLI values*/
if (i != sglen - 1) {
lli_next = lli_next +
sizeof(struct intel_mid_dma_lli);
} else {
/*Check for circular list, otherwise terminate LLI to ZERO*/
if (flags & DMA_PREP_CIRCULAR_LIST) {
pr_debug("MDMA: LLI is configured in circular mode\n");
lli_next = desc->lli_phys;
} else {
lli_next = 0;
ctl_lo.ctlx.llp_dst_en = 0;
ctl_lo.ctlx.llp_src_en = 0;
}
}
/*Populate CTL_HI values*/
ctl_hi.ctlx.block_ts = get_block_ts(sg->length,
desc->width,
midc->dma->block_size);
/*Populate SAR and DAR values*/
sg_phy_addr = sg_phys(sg);
if (desc->dirn == DMA_TO_DEVICE) {
lli_bloc_desc->sar = sg_phy_addr;
lli_bloc_desc->dar = mids->dma_slave.dst_addr;
} else if (desc->dirn == DMA_FROM_DEVICE) {
lli_bloc_desc->sar = mids->dma_slave.src_addr;
lli_bloc_desc->dar = sg_phy_addr;
}
/*Copy values into block descriptor in system memroy*/
lli_bloc_desc->llp = lli_next;
lli_bloc_desc->ctl_lo = ctl_lo.ctl_lo;
lli_bloc_desc->ctl_hi = ctl_hi.ctl_hi;
lli_bloc_desc++;
}
/*Copy very first LLI values to descriptor*/
desc->ctl_lo = desc->lli->ctl_lo;
desc->ctl_hi = desc->lli->ctl_hi;
desc->sar = desc->lli->sar;
desc->dar = desc->lli->dar;
return 0;
}
/***************************************************************************** /*****************************************************************************
DMA engine callback Functions*/ DMA engine callback Functions*/
/** /**
...@@ -349,12 +442,12 @@ static dma_cookie_t intel_mid_dma_tx_submit(struct dma_async_tx_descriptor *tx) ...@@ -349,12 +442,12 @@ static dma_cookie_t intel_mid_dma_tx_submit(struct dma_async_tx_descriptor *tx)
desc->txd.cookie = cookie; desc->txd.cookie = cookie;
if (list_empty(&midc->active_list)) { if (list_empty(&midc->active_list))
midc_dostart(midc, desc);
list_add_tail(&desc->desc_node, &midc->active_list); list_add_tail(&desc->desc_node, &midc->active_list);
} else { else
list_add_tail(&desc->desc_node, &midc->queue); list_add_tail(&desc->desc_node, &midc->queue);
}
midc_dostart(midc, desc);
spin_unlock_bh(&midc->lock); spin_unlock_bh(&midc->lock);
return cookie; return cookie;
...@@ -414,6 +507,23 @@ static enum dma_status intel_mid_dma_tx_status(struct dma_chan *chan, ...@@ -414,6 +507,23 @@ static enum dma_status intel_mid_dma_tx_status(struct dma_chan *chan,
return ret; return ret;
} }
static int dma_slave_control(struct dma_chan *chan, unsigned long arg)
{
struct intel_mid_dma_chan *midc = to_intel_mid_dma_chan(chan);
struct dma_slave_config *slave = (struct dma_slave_config *)arg;
struct intel_mid_dma_slave *mid_slave;
BUG_ON(!midc);
BUG_ON(!slave);
pr_debug("MDMA: slave control called\n");
mid_slave = to_intel_mid_dma_slave(slave);
BUG_ON(!mid_slave);
midc->mid_slave = mid_slave;
return 0;
}
/** /**
* intel_mid_dma_device_control - DMA device control * intel_mid_dma_device_control - DMA device control
* @chan: chan for DMA control * @chan: chan for DMA control
...@@ -428,49 +538,41 @@ static int intel_mid_dma_device_control(struct dma_chan *chan, ...@@ -428,49 +538,41 @@ static int intel_mid_dma_device_control(struct dma_chan *chan,
struct intel_mid_dma_chan *midc = to_intel_mid_dma_chan(chan); struct intel_mid_dma_chan *midc = to_intel_mid_dma_chan(chan);
struct middma_device *mid = to_middma_device(chan->device); struct middma_device *mid = to_middma_device(chan->device);
struct intel_mid_dma_desc *desc, *_desc; struct intel_mid_dma_desc *desc, *_desc;
LIST_HEAD(list); union intel_mid_dma_cfg_lo cfg_lo;
if (cmd == DMA_SLAVE_CONFIG)
return dma_slave_control(chan, arg);
if (cmd != DMA_TERMINATE_ALL) if (cmd != DMA_TERMINATE_ALL)
return -ENXIO; return -ENXIO;
spin_lock_bh(&midc->lock); spin_lock_bh(&midc->lock);
if (midc->in_use == false) { if (midc->busy == false) {
spin_unlock_bh(&midc->lock); spin_unlock_bh(&midc->lock);
return 0; return 0;
} }
list_splice_init(&midc->free_list, &list); /*Suspend and disable the channel*/
midc->descs_allocated = 0; cfg_lo.cfg_lo = ioread32(midc->ch_regs + CFG_LOW);
midc->slave = NULL; cfg_lo.cfgx.ch_susp = 1;
iowrite32(cfg_lo.cfg_lo, midc->ch_regs + CFG_LOW);
iowrite32(DISABLE_CHANNEL(midc->ch_id), mid->dma_base + DMA_CHAN_EN);
midc->busy = false;
/* Disable interrupts */ /* Disable interrupts */
disable_dma_interrupt(midc); disable_dma_interrupt(midc);
midc->descs_allocated = 0;
spin_unlock_bh(&midc->lock); spin_unlock_bh(&midc->lock);
list_for_each_entry_safe(desc, _desc, &list, desc_node) { list_for_each_entry_safe(desc, _desc, &midc->active_list, desc_node) {
pr_debug("MDMA: freeing descriptor %p\n", desc); if (desc->lli != NULL) {
pci_pool_free(mid->dma_pool, desc, desc->txd.phys); pci_pool_free(desc->lli_pool, desc->lli,
desc->lli_phys);
pci_pool_destroy(desc->lli_pool);
}
list_move(&desc->desc_node, &midc->free_list);
} }
return 0; return 0;
} }
/**
* intel_mid_dma_prep_slave_sg - Prep slave sg txn
* @chan: chan for DMA transfer
* @sgl: scatter gather list
* @sg_len: length of sg txn
* @direction: DMA transfer dirtn
* @flags: DMA flags
*
* Do DMA sg txn: NOT supported now
*/
static struct dma_async_tx_descriptor *intel_mid_dma_prep_slave_sg(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_data_direction direction,
unsigned long flags)
{
/*not supported now*/
return NULL;
}
/** /**
* intel_mid_dma_prep_memcpy - Prep memcpy txn * intel_mid_dma_prep_memcpy - Prep memcpy txn
...@@ -495,23 +597,24 @@ static struct dma_async_tx_descriptor *intel_mid_dma_prep_memcpy( ...@@ -495,23 +597,24 @@ static struct dma_async_tx_descriptor *intel_mid_dma_prep_memcpy(
union intel_mid_dma_ctl_hi ctl_hi; union intel_mid_dma_ctl_hi ctl_hi;
union intel_mid_dma_cfg_lo cfg_lo; union intel_mid_dma_cfg_lo cfg_lo;
union intel_mid_dma_cfg_hi cfg_hi; union intel_mid_dma_cfg_hi cfg_hi;
enum intel_mid_dma_width width = 0; enum dma_slave_buswidth width;
pr_debug("MDMA: Prep for memcpy\n"); pr_debug("MDMA: Prep for memcpy\n");
WARN_ON(!chan); BUG_ON(!chan);
if (!len) if (!len)
return NULL; return NULL;
mids = chan->private;
WARN_ON(!mids);
midc = to_intel_mid_dma_chan(chan); midc = to_intel_mid_dma_chan(chan);
WARN_ON(!midc); BUG_ON(!midc);
mids = midc->mid_slave;
BUG_ON(!mids);
pr_debug("MDMA:called for DMA %x CH %d Length %zu\n", pr_debug("MDMA:called for DMA %x CH %d Length %zu\n",
midc->dma->pci_id, midc->ch_id, len); midc->dma->pci_id, midc->ch_id, len);
pr_debug("MDMA:Cfg passed Mode %x, Dirn %x, HS %x, Width %x\n", pr_debug("MDMA:Cfg passed Mode %x, Dirn %x, HS %x, Width %x\n",
mids->cfg_mode, mids->dirn, mids->hs_mode, mids->src_width); mids->cfg_mode, mids->dma_slave.direction,
mids->hs_mode, mids->dma_slave.src_addr_width);
/*calculate CFG_LO*/ /*calculate CFG_LO*/
if (mids->hs_mode == LNW_DMA_SW_HS) { if (mids->hs_mode == LNW_DMA_SW_HS) {
...@@ -530,13 +633,13 @@ static struct dma_async_tx_descriptor *intel_mid_dma_prep_memcpy( ...@@ -530,13 +633,13 @@ static struct dma_async_tx_descriptor *intel_mid_dma_prep_memcpy(
if (midc->dma->pimr_mask) { if (midc->dma->pimr_mask) {
cfg_hi.cfgx.protctl = 0x0; /*default value*/ cfg_hi.cfgx.protctl = 0x0; /*default value*/
cfg_hi.cfgx.fifo_mode = 1; cfg_hi.cfgx.fifo_mode = 1;
if (mids->dirn == DMA_TO_DEVICE) { if (mids->dma_slave.direction == DMA_TO_DEVICE) {
cfg_hi.cfgx.src_per = 0; cfg_hi.cfgx.src_per = 0;
if (mids->device_instance == 0) if (mids->device_instance == 0)
cfg_hi.cfgx.dst_per = 3; cfg_hi.cfgx.dst_per = 3;
if (mids->device_instance == 1) if (mids->device_instance == 1)
cfg_hi.cfgx.dst_per = 1; cfg_hi.cfgx.dst_per = 1;
} else if (mids->dirn == DMA_FROM_DEVICE) { } else if (mids->dma_slave.direction == DMA_FROM_DEVICE) {
if (mids->device_instance == 0) if (mids->device_instance == 0)
cfg_hi.cfgx.src_per = 2; cfg_hi.cfgx.src_per = 2;
if (mids->device_instance == 1) if (mids->device_instance == 1)
...@@ -552,7 +655,8 @@ static struct dma_async_tx_descriptor *intel_mid_dma_prep_memcpy( ...@@ -552,7 +655,8 @@ static struct dma_async_tx_descriptor *intel_mid_dma_prep_memcpy(
/*calculate CTL_HI*/ /*calculate CTL_HI*/
ctl_hi.ctlx.reser = 0; ctl_hi.ctlx.reser = 0;
width = mids->src_width; ctl_hi.ctlx.done = 0;
width = mids->dma_slave.src_addr_width;
ctl_hi.ctlx.block_ts = get_block_ts(len, width, midc->dma->block_size); ctl_hi.ctlx.block_ts = get_block_ts(len, width, midc->dma->block_size);
pr_debug("MDMA:calc len %d for block size %d\n", pr_debug("MDMA:calc len %d for block size %d\n",
...@@ -560,21 +664,21 @@ static struct dma_async_tx_descriptor *intel_mid_dma_prep_memcpy( ...@@ -560,21 +664,21 @@ static struct dma_async_tx_descriptor *intel_mid_dma_prep_memcpy(
/*calculate CTL_LO*/ /*calculate CTL_LO*/
ctl_lo.ctl_lo = 0; ctl_lo.ctl_lo = 0;
ctl_lo.ctlx.int_en = 1; ctl_lo.ctlx.int_en = 1;
ctl_lo.ctlx.dst_tr_width = mids->dst_width; ctl_lo.ctlx.dst_tr_width = mids->dma_slave.dst_addr_width;
ctl_lo.ctlx.src_tr_width = mids->src_width; ctl_lo.ctlx.src_tr_width = mids->dma_slave.src_addr_width;
ctl_lo.ctlx.dst_msize = mids->src_msize; ctl_lo.ctlx.dst_msize = mids->dma_slave.src_maxburst;
ctl_lo.ctlx.src_msize = mids->dst_msize; ctl_lo.ctlx.src_msize = mids->dma_slave.dst_maxburst;
if (mids->cfg_mode == LNW_DMA_MEM_TO_MEM) { if (mids->cfg_mode == LNW_DMA_MEM_TO_MEM) {
ctl_lo.ctlx.tt_fc = 0; ctl_lo.ctlx.tt_fc = 0;
ctl_lo.ctlx.sinc = 0; ctl_lo.ctlx.sinc = 0;
ctl_lo.ctlx.dinc = 0; ctl_lo.ctlx.dinc = 0;
} else { } else {
if (mids->dirn == DMA_TO_DEVICE) { if (mids->dma_slave.direction == DMA_TO_DEVICE) {
ctl_lo.ctlx.sinc = 0; ctl_lo.ctlx.sinc = 0;
ctl_lo.ctlx.dinc = 2; ctl_lo.ctlx.dinc = 2;
ctl_lo.ctlx.tt_fc = 1; ctl_lo.ctlx.tt_fc = 1;
} else if (mids->dirn == DMA_FROM_DEVICE) { } else if (mids->dma_slave.direction == DMA_FROM_DEVICE) {
ctl_lo.ctlx.sinc = 2; ctl_lo.ctlx.sinc = 2;
ctl_lo.ctlx.dinc = 0; ctl_lo.ctlx.dinc = 0;
ctl_lo.ctlx.tt_fc = 2; ctl_lo.ctlx.tt_fc = 2;
...@@ -597,7 +701,10 @@ static struct dma_async_tx_descriptor *intel_mid_dma_prep_memcpy( ...@@ -597,7 +701,10 @@ static struct dma_async_tx_descriptor *intel_mid_dma_prep_memcpy(
desc->ctl_lo = ctl_lo.ctl_lo; desc->ctl_lo = ctl_lo.ctl_lo;
desc->ctl_hi = ctl_hi.ctl_hi; desc->ctl_hi = ctl_hi.ctl_hi;
desc->width = width; desc->width = width;
desc->dirn = mids->dirn; desc->dirn = mids->dma_slave.direction;
desc->lli_phys = 0;
desc->lli = NULL;
desc->lli_pool = NULL;
return &desc->txd; return &desc->txd;
err_desc_get: err_desc_get:
...@@ -605,6 +712,85 @@ static struct dma_async_tx_descriptor *intel_mid_dma_prep_memcpy( ...@@ -605,6 +712,85 @@ static struct dma_async_tx_descriptor *intel_mid_dma_prep_memcpy(
midc_desc_put(midc, desc); midc_desc_put(midc, desc);
return NULL; return NULL;
} }
/**
* intel_mid_dma_prep_slave_sg - Prep slave sg txn
* @chan: chan for DMA transfer
* @sgl: scatter gather list
* @sg_len: length of sg txn
* @direction: DMA transfer dirtn
* @flags: DMA flags
*
* Prepares LLI based periphral transfer
*/
static struct dma_async_tx_descriptor *intel_mid_dma_prep_slave_sg(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_data_direction direction,
unsigned long flags)
{
struct intel_mid_dma_chan *midc = NULL;
struct intel_mid_dma_slave *mids = NULL;
struct intel_mid_dma_desc *desc = NULL;
struct dma_async_tx_descriptor *txd = NULL;
union intel_mid_dma_ctl_lo ctl_lo;
pr_debug("MDMA: Prep for slave SG\n");
if (!sg_len) {
pr_err("MDMA: Invalid SG length\n");
return NULL;
}
midc = to_intel_mid_dma_chan(chan);
BUG_ON(!midc);
mids = midc->mid_slave;
BUG_ON(!mids);
if (!midc->dma->pimr_mask) {
pr_debug("MDMA: SG list is not supported by this controller\n");
return NULL;
}
pr_debug("MDMA: SG Length = %d, direction = %d, Flags = %#lx\n",
sg_len, direction, flags);
txd = intel_mid_dma_prep_memcpy(chan, 0, 0, sgl->length, flags);
if (NULL == txd) {
pr_err("MDMA: Prep memcpy failed\n");
return NULL;
}
desc = to_intel_mid_dma_desc(txd);
desc->dirn = direction;
ctl_lo.ctl_lo = desc->ctl_lo;
ctl_lo.ctlx.llp_dst_en = 1;
ctl_lo.ctlx.llp_src_en = 1;
desc->ctl_lo = ctl_lo.ctl_lo;
desc->lli_length = sg_len;
desc->current_lli = 0;
/* DMA coherent memory pool for LLI descriptors*/
desc->lli_pool = pci_pool_create("intel_mid_dma_lli_pool",
midc->dma->pdev,
(sizeof(struct intel_mid_dma_lli)*sg_len),
32, 0);
if (NULL == desc->lli_pool) {
pr_err("MID_DMA:LLI pool create failed\n");
return NULL;
}
desc->lli = pci_pool_alloc(desc->lli_pool, GFP_KERNEL, &desc->lli_phys);
if (!desc->lli) {
pr_err("MID_DMA: LLI alloc failed\n");
pci_pool_destroy(desc->lli_pool);
return NULL;
}
midc_lli_fill_sg(midc, desc, sgl, sg_len, flags);
if (flags & DMA_PREP_INTERRUPT) {
iowrite32(UNMASK_INTR_REG(midc->ch_id),
midc->dma_base + MASK_BLOCK);
pr_debug("MDMA:Enabled Block interrupt\n");
}
return &desc->txd;
}
/** /**
* intel_mid_dma_free_chan_resources - Frees dma resources * intel_mid_dma_free_chan_resources - Frees dma resources
...@@ -618,11 +804,11 @@ static void intel_mid_dma_free_chan_resources(struct dma_chan *chan) ...@@ -618,11 +804,11 @@ static void intel_mid_dma_free_chan_resources(struct dma_chan *chan)
struct middma_device *mid = to_middma_device(chan->device); struct middma_device *mid = to_middma_device(chan->device);
struct intel_mid_dma_desc *desc, *_desc; struct intel_mid_dma_desc *desc, *_desc;
if (true == midc->in_use) { if (true == midc->busy) {
/*trying to free ch in use!!!!!*/ /*trying to free ch in use!!!!!*/
pr_err("ERR_MDMA: trying to free ch in use\n"); pr_err("ERR_MDMA: trying to free ch in use\n");
} }
pm_runtime_put(&mid->pdev->dev);
spin_lock_bh(&midc->lock); spin_lock_bh(&midc->lock);
midc->descs_allocated = 0; midc->descs_allocated = 0;
list_for_each_entry_safe(desc, _desc, &midc->active_list, desc_node) { list_for_each_entry_safe(desc, _desc, &midc->active_list, desc_node) {
...@@ -639,6 +825,7 @@ static void intel_mid_dma_free_chan_resources(struct dma_chan *chan) ...@@ -639,6 +825,7 @@ static void intel_mid_dma_free_chan_resources(struct dma_chan *chan)
} }
spin_unlock_bh(&midc->lock); spin_unlock_bh(&midc->lock);
midc->in_use = false; midc->in_use = false;
midc->busy = false;
/* Disable CH interrupts */ /* Disable CH interrupts */
iowrite32(MASK_INTR_REG(midc->ch_id), mid->dma_base + MASK_BLOCK); iowrite32(MASK_INTR_REG(midc->ch_id), mid->dma_base + MASK_BLOCK);
iowrite32(MASK_INTR_REG(midc->ch_id), mid->dma_base + MASK_ERR); iowrite32(MASK_INTR_REG(midc->ch_id), mid->dma_base + MASK_ERR);
...@@ -659,11 +846,20 @@ static int intel_mid_dma_alloc_chan_resources(struct dma_chan *chan) ...@@ -659,11 +846,20 @@ static int intel_mid_dma_alloc_chan_resources(struct dma_chan *chan)
dma_addr_t phys; dma_addr_t phys;
int i = 0; int i = 0;
pm_runtime_get_sync(&mid->pdev->dev);
if (mid->state == SUSPENDED) {
if (dma_resume(mid->pdev)) {
pr_err("ERR_MDMA: resume failed");
return -EFAULT;
}
}
/* ASSERT: channel is idle */ /* ASSERT: channel is idle */
if (test_ch_en(mid->dma_base, midc->ch_id)) { if (test_ch_en(mid->dma_base, midc->ch_id)) {
/*ch is not idle*/ /*ch is not idle*/
pr_err("ERR_MDMA: ch not idle\n"); pr_err("ERR_MDMA: ch not idle\n");
pm_runtime_put(&mid->pdev->dev);
return -EIO; return -EIO;
} }
midc->completed = chan->cookie = 1; midc->completed = chan->cookie = 1;
...@@ -674,6 +870,7 @@ static int intel_mid_dma_alloc_chan_resources(struct dma_chan *chan) ...@@ -674,6 +870,7 @@ static int intel_mid_dma_alloc_chan_resources(struct dma_chan *chan)
desc = pci_pool_alloc(mid->dma_pool, GFP_KERNEL, &phys); desc = pci_pool_alloc(mid->dma_pool, GFP_KERNEL, &phys);
if (!desc) { if (!desc) {
pr_err("ERR_MDMA: desc failed\n"); pr_err("ERR_MDMA: desc failed\n");
pm_runtime_put(&mid->pdev->dev);
return -ENOMEM; return -ENOMEM;
/*check*/ /*check*/
} }
...@@ -686,7 +883,8 @@ static int intel_mid_dma_alloc_chan_resources(struct dma_chan *chan) ...@@ -686,7 +883,8 @@ static int intel_mid_dma_alloc_chan_resources(struct dma_chan *chan)
list_add_tail(&desc->desc_node, &midc->free_list); list_add_tail(&desc->desc_node, &midc->free_list);
} }
spin_unlock_bh(&midc->lock); spin_unlock_bh(&midc->lock);
midc->in_use = false; midc->in_use = true;
midc->busy = false;
pr_debug("MID_DMA: Desc alloc done ret: %d desc\n", i); pr_debug("MID_DMA: Desc alloc done ret: %d desc\n", i);
return i; return i;
} }
...@@ -715,7 +913,7 @@ static void dma_tasklet(unsigned long data) ...@@ -715,7 +913,7 @@ static void dma_tasklet(unsigned long data)
{ {
struct middma_device *mid = NULL; struct middma_device *mid = NULL;
struct intel_mid_dma_chan *midc = NULL; struct intel_mid_dma_chan *midc = NULL;
u32 status; u32 status, raw_tfr, raw_block;
int i; int i;
mid = (struct middma_device *)data; mid = (struct middma_device *)data;
...@@ -724,8 +922,9 @@ static void dma_tasklet(unsigned long data) ...@@ -724,8 +922,9 @@ static void dma_tasklet(unsigned long data)
return; return;
} }
pr_debug("MDMA: in tasklet for device %x\n", mid->pci_id); pr_debug("MDMA: in tasklet for device %x\n", mid->pci_id);
status = ioread32(mid->dma_base + RAW_TFR); raw_tfr = ioread32(mid->dma_base + RAW_TFR);
pr_debug("MDMA:RAW_TFR %x\n", status); raw_block = ioread32(mid->dma_base + RAW_BLOCK);
status = raw_tfr | raw_block;
status &= mid->intr_mask; status &= mid->intr_mask;
while (status) { while (status) {
/*txn interrupt*/ /*txn interrupt*/
...@@ -741,15 +940,23 @@ static void dma_tasklet(unsigned long data) ...@@ -741,15 +940,23 @@ static void dma_tasklet(unsigned long data)
} }
pr_debug("MDMA:Tx complete interrupt %x, Ch No %d Index %d\n", pr_debug("MDMA:Tx complete interrupt %x, Ch No %d Index %d\n",
status, midc->ch_id, i); status, midc->ch_id, i);
midc->raw_tfr = raw_tfr;
midc->raw_block = raw_block;
spin_lock_bh(&midc->lock);
/*clearing this interrupts first*/ /*clearing this interrupts first*/
iowrite32((1 << midc->ch_id), mid->dma_base + CLEAR_TFR); iowrite32((1 << midc->ch_id), mid->dma_base + CLEAR_TFR);
iowrite32((1 << midc->ch_id), mid->dma_base + CLEAR_BLOCK); if (raw_block) {
iowrite32((1 << midc->ch_id),
spin_lock_bh(&midc->lock); mid->dma_base + CLEAR_BLOCK);
}
midc_scan_descriptors(mid, midc); midc_scan_descriptors(mid, midc);
pr_debug("MDMA:Scan of desc... complete, unmasking\n"); pr_debug("MDMA:Scan of desc... complete, unmasking\n");
iowrite32(UNMASK_INTR_REG(midc->ch_id), iowrite32(UNMASK_INTR_REG(midc->ch_id),
mid->dma_base + MASK_TFR); mid->dma_base + MASK_TFR);
if (raw_block) {
iowrite32(UNMASK_INTR_REG(midc->ch_id),
mid->dma_base + MASK_BLOCK);
}
spin_unlock_bh(&midc->lock); spin_unlock_bh(&midc->lock);
} }
...@@ -804,9 +1011,14 @@ static void dma_tasklet2(unsigned long data) ...@@ -804,9 +1011,14 @@ static void dma_tasklet2(unsigned long data)
static irqreturn_t intel_mid_dma_interrupt(int irq, void *data) static irqreturn_t intel_mid_dma_interrupt(int irq, void *data)
{ {
struct middma_device *mid = data; struct middma_device *mid = data;
u32 status; u32 tfr_status, err_status;
int call_tasklet = 0; int call_tasklet = 0;
tfr_status = ioread32(mid->dma_base + RAW_TFR);
err_status = ioread32(mid->dma_base + RAW_ERR);
if (!tfr_status && !err_status)
return IRQ_NONE;
/*DMA Interrupt*/ /*DMA Interrupt*/
pr_debug("MDMA:Got an interrupt on irq %d\n", irq); pr_debug("MDMA:Got an interrupt on irq %d\n", irq);
if (!mid) { if (!mid) {
...@@ -814,19 +1026,18 @@ static irqreturn_t intel_mid_dma_interrupt(int irq, void *data) ...@@ -814,19 +1026,18 @@ static irqreturn_t intel_mid_dma_interrupt(int irq, void *data)
return -EINVAL; return -EINVAL;
} }
status = ioread32(mid->dma_base + RAW_TFR); pr_debug("MDMA: Status %x, Mask %x\n", tfr_status, mid->intr_mask);
pr_debug("MDMA: Status %x, Mask %x\n", status, mid->intr_mask); tfr_status &= mid->intr_mask;
status &= mid->intr_mask; if (tfr_status) {
if (status) {
/*need to disable intr*/ /*need to disable intr*/
iowrite32((status << 8), mid->dma_base + MASK_TFR); iowrite32((tfr_status << INT_MASK_WE), mid->dma_base + MASK_TFR);
pr_debug("MDMA: Calling tasklet %x\n", status); iowrite32((tfr_status << INT_MASK_WE), mid->dma_base + MASK_BLOCK);
pr_debug("MDMA: Calling tasklet %x\n", tfr_status);
call_tasklet = 1; call_tasklet = 1;
} }
status = ioread32(mid->dma_base + RAW_ERR); err_status &= mid->intr_mask;
status &= mid->intr_mask; if (err_status) {
if (status) { iowrite32(MASK_INTR_REG(err_status), mid->dma_base + MASK_ERR);
iowrite32(MASK_INTR_REG(status), mid->dma_base + MASK_ERR);
call_tasklet = 1; call_tasklet = 1;
} }
if (call_tasklet) if (call_tasklet)
...@@ -856,7 +1067,6 @@ static int mid_setup_dma(struct pci_dev *pdev) ...@@ -856,7 +1067,6 @@ static int mid_setup_dma(struct pci_dev *pdev)
{ {
struct middma_device *dma = pci_get_drvdata(pdev); struct middma_device *dma = pci_get_drvdata(pdev);
int err, i; int err, i;
unsigned int irq_level;
/* DMA coherent memory pool for DMA descriptor allocations */ /* DMA coherent memory pool for DMA descriptor allocations */
dma->dma_pool = pci_pool_create("intel_mid_dma_desc_pool", pdev, dma->dma_pool = pci_pool_create("intel_mid_dma_desc_pool", pdev,
...@@ -884,6 +1094,7 @@ static int mid_setup_dma(struct pci_dev *pdev) ...@@ -884,6 +1094,7 @@ static int mid_setup_dma(struct pci_dev *pdev)
pr_debug("MDMA:Adding %d channel for this controller\n", dma->max_chan); pr_debug("MDMA:Adding %d channel for this controller\n", dma->max_chan);
/*init CH structures*/ /*init CH structures*/
dma->intr_mask = 0; dma->intr_mask = 0;
dma->state = RUNNING;
for (i = 0; i < dma->max_chan; i++) { for (i = 0; i < dma->max_chan; i++) {
struct intel_mid_dma_chan *midch = &dma->ch[i]; struct intel_mid_dma_chan *midch = &dma->ch[i];
...@@ -943,7 +1154,6 @@ static int mid_setup_dma(struct pci_dev *pdev) ...@@ -943,7 +1154,6 @@ static int mid_setup_dma(struct pci_dev *pdev)
/*register irq */ /*register irq */
if (dma->pimr_mask) { if (dma->pimr_mask) {
irq_level = IRQF_SHARED;
pr_debug("MDMA:Requesting irq shared for DMAC1\n"); pr_debug("MDMA:Requesting irq shared for DMAC1\n");
err = request_irq(pdev->irq, intel_mid_dma_interrupt1, err = request_irq(pdev->irq, intel_mid_dma_interrupt1,
IRQF_SHARED, "INTEL_MID_DMAC1", dma); IRQF_SHARED, "INTEL_MID_DMAC1", dma);
...@@ -951,10 +1161,9 @@ static int mid_setup_dma(struct pci_dev *pdev) ...@@ -951,10 +1161,9 @@ static int mid_setup_dma(struct pci_dev *pdev)
goto err_irq; goto err_irq;
} else { } else {
dma->intr_mask = 0x03; dma->intr_mask = 0x03;
irq_level = 0;
pr_debug("MDMA:Requesting irq for DMAC2\n"); pr_debug("MDMA:Requesting irq for DMAC2\n");
err = request_irq(pdev->irq, intel_mid_dma_interrupt2, err = request_irq(pdev->irq, intel_mid_dma_interrupt2,
0, "INTEL_MID_DMAC2", dma); IRQF_SHARED, "INTEL_MID_DMAC2", dma);
if (0 != err) if (0 != err)
goto err_irq; goto err_irq;
} }
...@@ -1070,6 +1279,9 @@ static int __devinit intel_mid_dma_probe(struct pci_dev *pdev, ...@@ -1070,6 +1279,9 @@ static int __devinit intel_mid_dma_probe(struct pci_dev *pdev,
if (err) if (err)
goto err_dma; goto err_dma;
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_allow(&pdev->dev);
return 0; return 0;
err_dma: err_dma:
...@@ -1104,6 +1316,85 @@ static void __devexit intel_mid_dma_remove(struct pci_dev *pdev) ...@@ -1104,6 +1316,85 @@ static void __devexit intel_mid_dma_remove(struct pci_dev *pdev)
pci_disable_device(pdev); pci_disable_device(pdev);
} }
/* Power Management */
/*
* dma_suspend - PCI suspend function
*
* @pci: PCI device structure
* @state: PM message
*
* This function is called by OS when a power event occurs
*/
int dma_suspend(struct pci_dev *pci, pm_message_t state)
{
int i;
struct middma_device *device = pci_get_drvdata(pci);
pr_debug("MDMA: dma_suspend called\n");
for (i = 0; i < device->max_chan; i++) {
if (device->ch[i].in_use)
return -EAGAIN;
}
device->state = SUSPENDED;
pci_set_drvdata(pci, device);
pci_save_state(pci);
pci_disable_device(pci);
pci_set_power_state(pci, PCI_D3hot);
return 0;
}
/**
* dma_resume - PCI resume function
*
* @pci: PCI device structure
*
* This function is called by OS when a power event occurs
*/
int dma_resume(struct pci_dev *pci)
{
int ret;
struct middma_device *device = pci_get_drvdata(pci);
pr_debug("MDMA: dma_resume called\n");
pci_set_power_state(pci, PCI_D0);
pci_restore_state(pci);
ret = pci_enable_device(pci);
if (ret) {
pr_err("MDMA: device cant be enabled for %x\n", pci->device);
return ret;
}
device->state = RUNNING;
iowrite32(REG_BIT0, device->dma_base + DMA_CFG);
pci_set_drvdata(pci, device);
return 0;
}
static int dma_runtime_suspend(struct device *dev)
{
struct pci_dev *pci_dev = to_pci_dev(dev);
return dma_suspend(pci_dev, PMSG_SUSPEND);
}
static int dma_runtime_resume(struct device *dev)
{
struct pci_dev *pci_dev = to_pci_dev(dev);
return dma_resume(pci_dev);
}
static int dma_runtime_idle(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct middma_device *device = pci_get_drvdata(pdev);
int i;
for (i = 0; i < device->max_chan; i++) {
if (device->ch[i].in_use)
return -EAGAIN;
}
return pm_schedule_suspend(dev, 0);
}
/****************************************************************************** /******************************************************************************
* PCI stuff * PCI stuff
*/ */
...@@ -1116,11 +1407,24 @@ static struct pci_device_id intel_mid_dma_ids[] = { ...@@ -1116,11 +1407,24 @@ static struct pci_device_id intel_mid_dma_ids[] = {
}; };
MODULE_DEVICE_TABLE(pci, intel_mid_dma_ids); MODULE_DEVICE_TABLE(pci, intel_mid_dma_ids);
static const struct dev_pm_ops intel_mid_dma_pm = {
.runtime_suspend = dma_runtime_suspend,
.runtime_resume = dma_runtime_resume,
.runtime_idle = dma_runtime_idle,
};
static struct pci_driver intel_mid_dma_pci = { static struct pci_driver intel_mid_dma_pci = {
.name = "Intel MID DMA", .name = "Intel MID DMA",
.id_table = intel_mid_dma_ids, .id_table = intel_mid_dma_ids,
.probe = intel_mid_dma_probe, .probe = intel_mid_dma_probe,
.remove = __devexit_p(intel_mid_dma_remove), .remove = __devexit_p(intel_mid_dma_remove),
#ifdef CONFIG_PM
.suspend = dma_suspend,
.resume = dma_resume,
.driver = {
.pm = &intel_mid_dma_pm,
},
#endif
}; };
static int __init intel_mid_dma_init(void) static int __init intel_mid_dma_init(void)
......
...@@ -29,11 +29,12 @@ ...@@ -29,11 +29,12 @@
#include <linux/dmapool.h> #include <linux/dmapool.h>
#include <linux/pci_ids.h> #include <linux/pci_ids.h>
#define INTEL_MID_DMA_DRIVER_VERSION "1.0.5" #define INTEL_MID_DMA_DRIVER_VERSION "1.1.0"
#define REG_BIT0 0x00000001 #define REG_BIT0 0x00000001
#define REG_BIT8 0x00000100 #define REG_BIT8 0x00000100
#define INT_MASK_WE 0x8
#define CLEAR_DONE 0xFFFFEFFF
#define UNMASK_INTR_REG(chan_num) \ #define UNMASK_INTR_REG(chan_num) \
((REG_BIT0 << chan_num) | (REG_BIT8 << chan_num)) ((REG_BIT0 << chan_num) | (REG_BIT8 << chan_num))
#define MASK_INTR_REG(chan_num) (REG_BIT8 << chan_num) #define MASK_INTR_REG(chan_num) (REG_BIT8 << chan_num)
...@@ -41,6 +42,9 @@ ...@@ -41,6 +42,9 @@
#define ENABLE_CHANNEL(chan_num) \ #define ENABLE_CHANNEL(chan_num) \
((REG_BIT0 << chan_num) | (REG_BIT8 << chan_num)) ((REG_BIT0 << chan_num) | (REG_BIT8 << chan_num))
#define DISABLE_CHANNEL(chan_num) \
(REG_BIT8 << chan_num)
#define DESCS_PER_CHANNEL 16 #define DESCS_PER_CHANNEL 16
/*DMA Registers*/ /*DMA Registers*/
/*registers associated with channel programming*/ /*registers associated with channel programming*/
...@@ -50,6 +54,7 @@ ...@@ -50,6 +54,7 @@
/*CH X REG = (DMA_CH_SIZE)*CH_NO + REG*/ /*CH X REG = (DMA_CH_SIZE)*CH_NO + REG*/
#define SAR 0x00 /* Source Address Register*/ #define SAR 0x00 /* Source Address Register*/
#define DAR 0x08 /* Destination Address Register*/ #define DAR 0x08 /* Destination Address Register*/
#define LLP 0x10 /* Linked List Pointer Register*/
#define CTL_LOW 0x18 /* Control Register*/ #define CTL_LOW 0x18 /* Control Register*/
#define CTL_HIGH 0x1C /* Control Register*/ #define CTL_HIGH 0x1C /* Control Register*/
#define CFG_LOW 0x40 /* Configuration Register Low*/ #define CFG_LOW 0x40 /* Configuration Register Low*/
...@@ -112,8 +117,8 @@ union intel_mid_dma_ctl_lo { ...@@ -112,8 +117,8 @@ union intel_mid_dma_ctl_lo {
union intel_mid_dma_ctl_hi { union intel_mid_dma_ctl_hi {
struct { struct {
u32 block_ts:12; /*block transfer size*/ u32 block_ts:12; /*block transfer size*/
/*configured by DMAC*/ u32 done:1; /*Done - updated by DMAC*/
u32 reser:20; u32 reser:19; /*configured by DMAC*/
} ctlx; } ctlx;
u32 ctl_hi; u32 ctl_hi;
...@@ -152,6 +157,7 @@ union intel_mid_dma_cfg_hi { ...@@ -152,6 +157,7 @@ union intel_mid_dma_cfg_hi {
u32 cfg_hi; u32 cfg_hi;
}; };
/** /**
* struct intel_mid_dma_chan - internal mid representation of a DMA channel * struct intel_mid_dma_chan - internal mid representation of a DMA channel
* @chan: dma_chan strcture represetation for mid chan * @chan: dma_chan strcture represetation for mid chan
...@@ -166,7 +172,10 @@ union intel_mid_dma_cfg_hi { ...@@ -166,7 +172,10 @@ union intel_mid_dma_cfg_hi {
* @slave: dma slave struture * @slave: dma slave struture
* @descs_allocated: total number of decsiptors allocated * @descs_allocated: total number of decsiptors allocated
* @dma: dma device struture pointer * @dma: dma device struture pointer
* @busy: bool representing if ch is busy (active txn) or not
* @in_use: bool representing if ch is in use or not * @in_use: bool representing if ch is in use or not
* @raw_tfr: raw trf interrupt recieved
* @raw_block: raw block interrupt recieved
*/ */
struct intel_mid_dma_chan { struct intel_mid_dma_chan {
struct dma_chan chan; struct dma_chan chan;
...@@ -178,10 +187,13 @@ struct intel_mid_dma_chan { ...@@ -178,10 +187,13 @@ struct intel_mid_dma_chan {
struct list_head active_list; struct list_head active_list;
struct list_head queue; struct list_head queue;
struct list_head free_list; struct list_head free_list;
struct intel_mid_dma_slave *slave;
unsigned int descs_allocated; unsigned int descs_allocated;
struct middma_device *dma; struct middma_device *dma;
bool busy;
bool in_use; bool in_use;
u32 raw_tfr;
u32 raw_block;
struct intel_mid_dma_slave *mid_slave;
}; };
static inline struct intel_mid_dma_chan *to_intel_mid_dma_chan( static inline struct intel_mid_dma_chan *to_intel_mid_dma_chan(
...@@ -190,6 +202,10 @@ static inline struct intel_mid_dma_chan *to_intel_mid_dma_chan( ...@@ -190,6 +202,10 @@ static inline struct intel_mid_dma_chan *to_intel_mid_dma_chan(
return container_of(chan, struct intel_mid_dma_chan, chan); return container_of(chan, struct intel_mid_dma_chan, chan);
} }
enum intel_mid_dma_state {
RUNNING = 0,
SUSPENDED,
};
/** /**
* struct middma_device - internal representation of a DMA device * struct middma_device - internal representation of a DMA device
* @pdev: PCI device * @pdev: PCI device
...@@ -205,6 +221,7 @@ static inline struct intel_mid_dma_chan *to_intel_mid_dma_chan( ...@@ -205,6 +221,7 @@ static inline struct intel_mid_dma_chan *to_intel_mid_dma_chan(
* @max_chan: max number of chs supported (from drv_data) * @max_chan: max number of chs supported (from drv_data)
* @block_size: Block size of DMA transfer supported (from drv_data) * @block_size: Block size of DMA transfer supported (from drv_data)
* @pimr_mask: MMIO register addr for periphral interrupt (from drv_data) * @pimr_mask: MMIO register addr for periphral interrupt (from drv_data)
* @state: dma PM device state
*/ */
struct middma_device { struct middma_device {
struct pci_dev *pdev; struct pci_dev *pdev;
...@@ -220,6 +237,7 @@ struct middma_device { ...@@ -220,6 +237,7 @@ struct middma_device {
int max_chan; int max_chan;
int block_size; int block_size;
unsigned int pimr_mask; unsigned int pimr_mask;
enum intel_mid_dma_state state;
}; };
static inline struct middma_device *to_middma_device(struct dma_device *common) static inline struct middma_device *to_middma_device(struct dma_device *common)
...@@ -238,14 +256,27 @@ struct intel_mid_dma_desc { ...@@ -238,14 +256,27 @@ struct intel_mid_dma_desc {
u32 cfg_lo; u32 cfg_lo;
u32 ctl_lo; u32 ctl_lo;
u32 ctl_hi; u32 ctl_hi;
struct pci_pool *lli_pool;
struct intel_mid_dma_lli *lli;
dma_addr_t lli_phys;
unsigned int lli_length;
unsigned int current_lli;
dma_addr_t next; dma_addr_t next;
enum dma_data_direction dirn; enum dma_data_direction dirn;
enum dma_status status; enum dma_status status;
enum intel_mid_dma_width width; /*width of DMA txn*/ enum dma_slave_buswidth width; /*width of DMA txn*/
enum intel_mid_dma_mode cfg_mode; /*mode configuration*/ enum intel_mid_dma_mode cfg_mode; /*mode configuration*/
}; };
struct intel_mid_dma_lli {
dma_addr_t sar;
dma_addr_t dar;
dma_addr_t llp;
u32 ctl_lo;
u32 ctl_hi;
} __attribute__ ((packed));
static inline int test_ch_en(void __iomem *dma, u32 ch_no) static inline int test_ch_en(void __iomem *dma, u32 ch_no)
{ {
u32 en_reg = ioread32(dma + DMA_CHAN_EN); u32 en_reg = ioread32(dma + DMA_CHAN_EN);
...@@ -257,4 +288,14 @@ static inline struct intel_mid_dma_desc *to_intel_mid_dma_desc ...@@ -257,4 +288,14 @@ static inline struct intel_mid_dma_desc *to_intel_mid_dma_desc
{ {
return container_of(txd, struct intel_mid_dma_desc, txd); return container_of(txd, struct intel_mid_dma_desc, txd);
} }
static inline struct intel_mid_dma_slave *to_intel_mid_dma_slave
(struct dma_slave_config *slave)
{
return container_of(slave, struct intel_mid_dma_slave, dma_slave);
}
int dma_resume(struct pci_dev *pci);
#endif /*__INTEL_MID_DMAC_REGS_H__*/ #endif /*__INTEL_MID_DMAC_REGS_H__*/
/* /*
* driver/dma/ste_dma40.c * Copyright (C) ST-Ericsson SA 2007-2010
* * Author: Per Forlin <per.forlin@stericsson.com> for ST-Ericsson
* Copyright (C) ST-Ericsson 2007-2010 * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson
* License terms: GNU General Public License (GPL) version 2 * License terms: GNU General Public License (GPL) version 2
* Author: Per Friden <per.friden@stericsson.com>
* Author: Jonas Aaberg <jonas.aberg@stericsson.com>
*
*/ */
#include <linux/kernel.h> #include <linux/kernel.h>
...@@ -14,6 +11,7 @@ ...@@ -14,6 +11,7 @@
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/err.h>
#include <plat/ste_dma40.h> #include <plat/ste_dma40.h>
...@@ -32,6 +30,11 @@ ...@@ -32,6 +30,11 @@
/* Hardware requirement on LCLA alignment */ /* Hardware requirement on LCLA alignment */
#define LCLA_ALIGNMENT 0x40000 #define LCLA_ALIGNMENT 0x40000
/* Max number of links per event group */
#define D40_LCLA_LINK_PER_EVENT_GRP 128
#define D40_LCLA_END D40_LCLA_LINK_PER_EVENT_GRP
/* Attempts before giving up to trying to get pages that are aligned */ /* Attempts before giving up to trying to get pages that are aligned */
#define MAX_LCLA_ALLOC_ATTEMPTS 256 #define MAX_LCLA_ALLOC_ATTEMPTS 256
...@@ -41,7 +44,7 @@ ...@@ -41,7 +44,7 @@
#define D40_ALLOC_LOG_FREE 0 #define D40_ALLOC_LOG_FREE 0
/* Hardware designer of the block */ /* Hardware designer of the block */
#define D40_PERIPHID2_DESIGNER 0x8 #define D40_HW_DESIGNER 0x8
/** /**
* enum 40_command - The different commands and/or statuses. * enum 40_command - The different commands and/or statuses.
...@@ -84,18 +87,17 @@ struct d40_lli_pool { ...@@ -84,18 +87,17 @@ struct d40_lli_pool {
* @lli_log: Same as above but for logical channels. * @lli_log: Same as above but for logical channels.
* @lli_pool: The pool with two entries pre-allocated. * @lli_pool: The pool with two entries pre-allocated.
* @lli_len: Number of llis of current descriptor. * @lli_len: Number of llis of current descriptor.
* @lli_count: Number of transfered llis. * @lli_current: Number of transfered llis.
* @lli_tx_len: Max number of LLIs per transfer, there can be * @lcla_alloc: Number of LCLA entries allocated.
* many transfer for one descriptor.
* @txd: DMA engine struct. Used for among other things for communication * @txd: DMA engine struct. Used for among other things for communication
* during a transfer. * during a transfer.
* @node: List entry. * @node: List entry.
* @dir: The transfer direction of this job.
* @is_in_client_list: true if the client owns this descriptor. * @is_in_client_list: true if the client owns this descriptor.
* @is_hw_linked: true if this job will automatically be continued for
* the previous one.
* *
* This descriptor is used for both logical and physical transfers. * This descriptor is used for both logical and physical transfers.
*/ */
struct d40_desc { struct d40_desc {
/* LLI physical */ /* LLI physical */
struct d40_phy_lli_bidir lli_phy; struct d40_phy_lli_bidir lli_phy;
...@@ -104,14 +106,14 @@ struct d40_desc { ...@@ -104,14 +106,14 @@ struct d40_desc {
struct d40_lli_pool lli_pool; struct d40_lli_pool lli_pool;
int lli_len; int lli_len;
int lli_count; int lli_current;
u32 lli_tx_len; int lcla_alloc;
struct dma_async_tx_descriptor txd; struct dma_async_tx_descriptor txd;
struct list_head node; struct list_head node;
enum dma_data_direction dir;
bool is_in_client_list; bool is_in_client_list;
bool is_hw_linked;
}; };
/** /**
...@@ -123,17 +125,14 @@ struct d40_desc { ...@@ -123,17 +125,14 @@ struct d40_desc {
* @pages: The number of pages needed for all physical channels. * @pages: The number of pages needed for all physical channels.
* Only used later for clean-up on error * Only used later for clean-up on error
* @lock: Lock to protect the content in this struct. * @lock: Lock to protect the content in this struct.
* @alloc_map: Bitmap mapping between physical channel and LCLA entries. * @alloc_map: big map over which LCLA entry is own by which job.
* @num_blocks: The number of entries of alloc_map. Equals to the
* number of physical channels.
*/ */
struct d40_lcla_pool { struct d40_lcla_pool {
void *base; void *base;
void *base_unaligned; void *base_unaligned;
int pages; int pages;
spinlock_t lock; spinlock_t lock;
u32 *alloc_map; struct d40_desc **alloc_map;
int num_blocks;
}; };
/** /**
...@@ -146,9 +145,7 @@ struct d40_lcla_pool { ...@@ -146,9 +145,7 @@ struct d40_lcla_pool {
* this physical channel. Can also be free or physically allocated. * this physical channel. Can also be free or physically allocated.
* @allocated_dst: Same as for src but is dst. * @allocated_dst: Same as for src but is dst.
* allocated_dst and allocated_src uses the D40_ALLOC* defines as well as * allocated_dst and allocated_src uses the D40_ALLOC* defines as well as
* event line number. Both allocated_src and allocated_dst can not be * event line number.
* allocated to a physical channel, since the interrupt handler has then
* no way of figure out which one the interrupt belongs to.
*/ */
struct d40_phy_res { struct d40_phy_res {
spinlock_t lock; spinlock_t lock;
...@@ -178,6 +175,7 @@ struct d40_base; ...@@ -178,6 +175,7 @@ struct d40_base;
* @active: Active descriptor. * @active: Active descriptor.
* @queue: Queued jobs. * @queue: Queued jobs.
* @dma_cfg: The client configuration of this dma channel. * @dma_cfg: The client configuration of this dma channel.
* @configured: whether the dma_cfg configuration is valid
* @base: Pointer to the device instance struct. * @base: Pointer to the device instance struct.
* @src_def_cfg: Default cfg register setting for src. * @src_def_cfg: Default cfg register setting for src.
* @dst_def_cfg: Default cfg register setting for dst. * @dst_def_cfg: Default cfg register setting for dst.
...@@ -201,12 +199,12 @@ struct d40_chan { ...@@ -201,12 +199,12 @@ struct d40_chan {
struct list_head active; struct list_head active;
struct list_head queue; struct list_head queue;
struct stedma40_chan_cfg dma_cfg; struct stedma40_chan_cfg dma_cfg;
bool configured;
struct d40_base *base; struct d40_base *base;
/* Default register configurations */ /* Default register configurations */
u32 src_def_cfg; u32 src_def_cfg;
u32 dst_def_cfg; u32 dst_def_cfg;
struct d40_def_lcsp log_def; struct d40_def_lcsp log_def;
struct d40_lcla_elem lcla;
struct d40_log_lli_full *lcpa; struct d40_log_lli_full *lcpa;
/* Runtime reconfiguration */ /* Runtime reconfiguration */
dma_addr_t runtime_addr; dma_addr_t runtime_addr;
...@@ -234,7 +232,6 @@ struct d40_chan { ...@@ -234,7 +232,6 @@ struct d40_chan {
* @dma_both: dma_device channels that can do both memcpy and slave transfers. * @dma_both: dma_device channels that can do both memcpy and slave transfers.
* @dma_slave: dma_device channels that can do only do slave transfers. * @dma_slave: dma_device channels that can do only do slave transfers.
* @dma_memcpy: dma_device channels that can do only do memcpy transfers. * @dma_memcpy: dma_device channels that can do only do memcpy transfers.
* @phy_chans: Room for all possible physical channels in system.
* @log_chans: Room for all possible logical channels in system. * @log_chans: Room for all possible logical channels in system.
* @lookup_log_chans: Used to map interrupt number to logical channel. Points * @lookup_log_chans: Used to map interrupt number to logical channel. Points
* to log_chans entries. * to log_chans entries.
...@@ -340,9 +337,6 @@ static int d40_pool_lli_alloc(struct d40_desc *d40d, ...@@ -340,9 +337,6 @@ static int d40_pool_lli_alloc(struct d40_desc *d40d,
align); align);
d40d->lli_phy.dst = PTR_ALIGN(d40d->lli_phy.src + lli_len, d40d->lli_phy.dst = PTR_ALIGN(d40d->lli_phy.src + lli_len,
align); align);
d40d->lli_phy.src_addr = virt_to_phys(d40d->lli_phy.src);
d40d->lli_phy.dst_addr = virt_to_phys(d40d->lli_phy.dst);
} }
return 0; return 0;
...@@ -357,22 +351,67 @@ static void d40_pool_lli_free(struct d40_desc *d40d) ...@@ -357,22 +351,67 @@ static void d40_pool_lli_free(struct d40_desc *d40d)
d40d->lli_log.dst = NULL; d40d->lli_log.dst = NULL;
d40d->lli_phy.src = NULL; d40d->lli_phy.src = NULL;
d40d->lli_phy.dst = NULL; d40d->lli_phy.dst = NULL;
d40d->lli_phy.src_addr = 0;
d40d->lli_phy.dst_addr = 0;
} }
static dma_cookie_t d40_assign_cookie(struct d40_chan *d40c, static int d40_lcla_alloc_one(struct d40_chan *d40c,
struct d40_desc *desc) struct d40_desc *d40d)
{ {
dma_cookie_t cookie = d40c->chan.cookie; unsigned long flags;
int i;
int ret = -EINVAL;
int p;
if (++cookie < 0) spin_lock_irqsave(&d40c->base->lcla_pool.lock, flags);
cookie = 1;
p = d40c->phy_chan->num * D40_LCLA_LINK_PER_EVENT_GRP;
d40c->chan.cookie = cookie; /*
desc->txd.cookie = cookie; * Allocate both src and dst at the same time, therefore the half
* start on 1 since 0 can't be used since zero is used as end marker.
*/
for (i = 1 ; i < D40_LCLA_LINK_PER_EVENT_GRP / 2; i++) {
if (!d40c->base->lcla_pool.alloc_map[p + i]) {
d40c->base->lcla_pool.alloc_map[p + i] = d40d;
d40d->lcla_alloc++;
ret = i;
break;
}
}
spin_unlock_irqrestore(&d40c->base->lcla_pool.lock, flags);
return ret;
}
static int d40_lcla_free_all(struct d40_chan *d40c,
struct d40_desc *d40d)
{
unsigned long flags;
int i;
int ret = -EINVAL;
if (d40c->log_num == D40_PHY_CHAN)
return 0;
spin_lock_irqsave(&d40c->base->lcla_pool.lock, flags);
for (i = 1 ; i < D40_LCLA_LINK_PER_EVENT_GRP / 2; i++) {
if (d40c->base->lcla_pool.alloc_map[d40c->phy_chan->num *
D40_LCLA_LINK_PER_EVENT_GRP + i] == d40d) {
d40c->base->lcla_pool.alloc_map[d40c->phy_chan->num *
D40_LCLA_LINK_PER_EVENT_GRP + i] = NULL;
d40d->lcla_alloc--;
if (d40d->lcla_alloc == 0) {
ret = 0;
break;
}
}
}
spin_unlock_irqrestore(&d40c->base->lcla_pool.lock, flags);
return ret;
return cookie;
} }
static void d40_desc_remove(struct d40_desc *d40d) static void d40_desc_remove(struct d40_desc *d40d)
...@@ -382,28 +421,35 @@ static void d40_desc_remove(struct d40_desc *d40d) ...@@ -382,28 +421,35 @@ static void d40_desc_remove(struct d40_desc *d40d)
static struct d40_desc *d40_desc_get(struct d40_chan *d40c) static struct d40_desc *d40_desc_get(struct d40_chan *d40c)
{ {
struct d40_desc *d; struct d40_desc *desc = NULL;
struct d40_desc *_d;
if (!list_empty(&d40c->client)) { if (!list_empty(&d40c->client)) {
struct d40_desc *d;
struct d40_desc *_d;
list_for_each_entry_safe(d, _d, &d40c->client, node) list_for_each_entry_safe(d, _d, &d40c->client, node)
if (async_tx_test_ack(&d->txd)) { if (async_tx_test_ack(&d->txd)) {
d40_pool_lli_free(d); d40_pool_lli_free(d);
d40_desc_remove(d); d40_desc_remove(d);
desc = d;
memset(desc, 0, sizeof(*desc));
break; break;
} }
} else {
d = kmem_cache_alloc(d40c->base->desc_slab, GFP_NOWAIT);
if (d != NULL) {
memset(d, 0, sizeof(struct d40_desc));
INIT_LIST_HEAD(&d->node);
}
} }
return d;
if (!desc)
desc = kmem_cache_zalloc(d40c->base->desc_slab, GFP_NOWAIT);
if (desc)
INIT_LIST_HEAD(&desc->node);
return desc;
} }
static void d40_desc_free(struct d40_chan *d40c, struct d40_desc *d40d) static void d40_desc_free(struct d40_chan *d40c, struct d40_desc *d40d)
{ {
d40_lcla_free_all(d40c, d40d);
kmem_cache_free(d40c->base->desc_slab, d40d); kmem_cache_free(d40c->base->desc_slab, d40d);
} }
...@@ -412,6 +458,59 @@ static void d40_desc_submit(struct d40_chan *d40c, struct d40_desc *desc) ...@@ -412,6 +458,59 @@ static void d40_desc_submit(struct d40_chan *d40c, struct d40_desc *desc)
list_add_tail(&desc->node, &d40c->active); list_add_tail(&desc->node, &d40c->active);
} }
static void d40_desc_load(struct d40_chan *d40c, struct d40_desc *d40d)
{
int curr_lcla = -EINVAL, next_lcla;
if (d40c->log_num == D40_PHY_CHAN) {
d40_phy_lli_write(d40c->base->virtbase,
d40c->phy_chan->num,
d40d->lli_phy.dst,
d40d->lli_phy.src);
d40d->lli_current = d40d->lli_len;
} else {
if ((d40d->lli_len - d40d->lli_current) > 1)
curr_lcla = d40_lcla_alloc_one(d40c, d40d);
d40_log_lli_lcpa_write(d40c->lcpa,
&d40d->lli_log.dst[d40d->lli_current],
&d40d->lli_log.src[d40d->lli_current],
curr_lcla);
d40d->lli_current++;
for (; d40d->lli_current < d40d->lli_len; d40d->lli_current++) {
struct d40_log_lli *lcla;
if (d40d->lli_current + 1 < d40d->lli_len)
next_lcla = d40_lcla_alloc_one(d40c, d40d);
else
next_lcla = -EINVAL;
lcla = d40c->base->lcla_pool.base +
d40c->phy_chan->num * 1024 +
8 * curr_lcla * 2;
d40_log_lli_lcla_write(lcla,
&d40d->lli_log.dst[d40d->lli_current],
&d40d->lli_log.src[d40d->lli_current],
next_lcla);
(void) dma_map_single(d40c->base->dev, lcla,
2 * sizeof(struct d40_log_lli),
DMA_TO_DEVICE);
curr_lcla = next_lcla;
if (curr_lcla == -EINVAL) {
d40d->lli_current++;
break;
}
}
}
}
static struct d40_desc *d40_first_active_get(struct d40_chan *d40c) static struct d40_desc *d40_first_active_get(struct d40_chan *d40c)
{ {
struct d40_desc *d; struct d40_desc *d;
...@@ -443,68 +542,26 @@ static struct d40_desc *d40_first_queued(struct d40_chan *d40c) ...@@ -443,68 +542,26 @@ static struct d40_desc *d40_first_queued(struct d40_chan *d40c)
return d; return d;
} }
/* Support functions for logical channels */ static struct d40_desc *d40_last_queued(struct d40_chan *d40c)
static int d40_lcla_id_get(struct d40_chan *d40c)
{ {
int src_id = 0; struct d40_desc *d;
int dst_id = 0;
struct d40_log_lli *lcla_lidx_base =
d40c->base->lcla_pool.base + d40c->phy_chan->num * 1024;
int i;
int lli_per_log = d40c->base->plat_data->llis_per_log;
unsigned long flags;
if (d40c->lcla.src_id >= 0 && d40c->lcla.dst_id >= 0)
return 0;
if (d40c->base->lcla_pool.num_blocks > 32)
return -EINVAL;
spin_lock_irqsave(&d40c->base->lcla_pool.lock, flags);
for (i = 0; i < d40c->base->lcla_pool.num_blocks; i++) {
if (!(d40c->base->lcla_pool.alloc_map[d40c->phy_chan->num] &
(0x1 << i))) {
d40c->base->lcla_pool.alloc_map[d40c->phy_chan->num] |=
(0x1 << i);
break;
}
}
src_id = i;
if (src_id >= d40c->base->lcla_pool.num_blocks)
goto err;
for (; i < d40c->base->lcla_pool.num_blocks; i++) { if (list_empty(&d40c->queue))
if (!(d40c->base->lcla_pool.alloc_map[d40c->phy_chan->num] & return NULL;
(0x1 << i))) { list_for_each_entry(d, &d40c->queue, node)
d40c->base->lcla_pool.alloc_map[d40c->phy_chan->num] |= if (list_is_last(&d->node, &d40c->queue))
(0x1 << i);
break; break;
} return d;
}
dst_id = i;
if (dst_id == src_id)
goto err;
d40c->lcla.src_id = src_id;
d40c->lcla.dst_id = dst_id;
d40c->lcla.dst = lcla_lidx_base + dst_id * lli_per_log + 1;
d40c->lcla.src = lcla_lidx_base + src_id * lli_per_log + 1;
spin_unlock_irqrestore(&d40c->base->lcla_pool.lock, flags);
return 0;
err:
spin_unlock_irqrestore(&d40c->base->lcla_pool.lock, flags);
return -EINVAL;
} }
/* Support functions for logical channels */
static int d40_channel_execute_command(struct d40_chan *d40c, static int d40_channel_execute_command(struct d40_chan *d40c,
enum d40_command command) enum d40_command command)
{ {
int status, i; u32 status;
int i;
void __iomem *active_reg; void __iomem *active_reg;
int ret = 0; int ret = 0;
unsigned long flags; unsigned long flags;
...@@ -567,35 +624,19 @@ static int d40_channel_execute_command(struct d40_chan *d40c, ...@@ -567,35 +624,19 @@ static int d40_channel_execute_command(struct d40_chan *d40c,
static void d40_term_all(struct d40_chan *d40c) static void d40_term_all(struct d40_chan *d40c)
{ {
struct d40_desc *d40d; struct d40_desc *d40d;
unsigned long flags;
/* Release active descriptors */ /* Release active descriptors */
while ((d40d = d40_first_active_get(d40c))) { while ((d40d = d40_first_active_get(d40c))) {
d40_desc_remove(d40d); d40_desc_remove(d40d);
/* Return desc to free-list */
d40_desc_free(d40c, d40d); d40_desc_free(d40c, d40d);
} }
/* Release queued descriptors waiting for transfer */ /* Release queued descriptors waiting for transfer */
while ((d40d = d40_first_queued(d40c))) { while ((d40d = d40_first_queued(d40c))) {
d40_desc_remove(d40d); d40_desc_remove(d40d);
/* Return desc to free-list */
d40_desc_free(d40c, d40d); d40_desc_free(d40c, d40d);
} }
spin_lock_irqsave(&d40c->base->lcla_pool.lock, flags);
d40c->base->lcla_pool.alloc_map[d40c->phy_chan->num] &=
(~(0x1 << d40c->lcla.dst_id));
d40c->base->lcla_pool.alloc_map[d40c->phy_chan->num] &=
(~(0x1 << d40c->lcla.src_id));
d40c->lcla.src_id = -1;
d40c->lcla.dst_id = -1;
spin_unlock_irqrestore(&d40c->base->lcla_pool.lock, flags);
d40c->pending_tx = 0; d40c->pending_tx = 0;
d40c->busy = false; d40c->busy = false;
...@@ -640,45 +681,47 @@ static void d40_config_set_event(struct d40_chan *d40c, bool do_enable) ...@@ -640,45 +681,47 @@ static void d40_config_set_event(struct d40_chan *d40c, bool do_enable)
static u32 d40_chan_has_events(struct d40_chan *d40c) static u32 d40_chan_has_events(struct d40_chan *d40c)
{ {
u32 val = 0; u32 val;
/* If SSLNK or SDLNK is zero all events are disabled */ val = readl(d40c->base->virtbase + D40_DREG_PCBASE +
if ((d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_MEM) || d40c->phy_chan->num * D40_DREG_PCDELTA +
(d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_PERIPH)) D40_CHAN_REG_SSLNK);
val = readl(d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA + val |= readl(d40c->base->virtbase + D40_DREG_PCBASE +
D40_CHAN_REG_SSLNK); d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SDLNK);
if (d40c->dma_cfg.dir != STEDMA40_PERIPH_TO_MEM)
val = readl(d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SDLNK);
return val; return val;
} }
static void d40_config_enable_lidx(struct d40_chan *d40c) static u32 d40_get_prmo(struct d40_chan *d40c)
{ {
/* Set LIDX for lcla */ static const unsigned int phy_map[] = {
writel((d40c->phy_chan->num << D40_SREG_ELEM_LOG_LIDX_POS) & [STEDMA40_PCHAN_BASIC_MODE]
D40_SREG_ELEM_LOG_LIDX_MASK, = D40_DREG_PRMO_PCHAN_BASIC,
d40c->base->virtbase + D40_DREG_PCBASE + [STEDMA40_PCHAN_MODULO_MODE]
d40c->phy_chan->num * D40_DREG_PCDELTA + D40_CHAN_REG_SDELT); = D40_DREG_PRMO_PCHAN_MODULO,
[STEDMA40_PCHAN_DOUBLE_DST_MODE]
writel((d40c->phy_chan->num << D40_SREG_ELEM_LOG_LIDX_POS) & = D40_DREG_PRMO_PCHAN_DOUBLE_DST,
D40_SREG_ELEM_LOG_LIDX_MASK, };
d40c->base->virtbase + D40_DREG_PCBASE + static const unsigned int log_map[] = {
d40c->phy_chan->num * D40_DREG_PCDELTA + D40_CHAN_REG_SSELT); [STEDMA40_LCHAN_SRC_PHY_DST_LOG]
= D40_DREG_PRMO_LCHAN_SRC_PHY_DST_LOG,
[STEDMA40_LCHAN_SRC_LOG_DST_PHY]
= D40_DREG_PRMO_LCHAN_SRC_LOG_DST_PHY,
[STEDMA40_LCHAN_SRC_LOG_DST_LOG]
= D40_DREG_PRMO_LCHAN_SRC_LOG_DST_LOG,
};
if (d40c->log_num == D40_PHY_CHAN)
return phy_map[d40c->dma_cfg.mode_opt];
else
return log_map[d40c->dma_cfg.mode_opt];
} }
static int d40_config_write(struct d40_chan *d40c) static void d40_config_write(struct d40_chan *d40c)
{ {
u32 addr_base; u32 addr_base;
u32 var; u32 var;
int res;
res = d40_channel_execute_command(d40c, D40_DMA_SUSPEND_REQ);
if (res)
return res;
/* Odd addresses are even addresses + 4 */ /* Odd addresses are even addresses + 4 */
addr_base = (d40c->phy_chan->num % 2) * 4; addr_base = (d40c->phy_chan->num % 2) * 4;
...@@ -688,8 +731,7 @@ static int d40_config_write(struct d40_chan *d40c) ...@@ -688,8 +731,7 @@ static int d40_config_write(struct d40_chan *d40c)
writel(var, d40c->base->virtbase + D40_DREG_PRMSE + addr_base); writel(var, d40c->base->virtbase + D40_DREG_PRMSE + addr_base);
/* Setup operational mode option register */ /* Setup operational mode option register */
var = ((d40c->dma_cfg.channel_type >> STEDMA40_INFO_CH_MODE_OPT_POS) & var = d40_get_prmo(d40c) << D40_CHAN_POS(d40c->phy_chan->num);
0x3) << D40_CHAN_POS(d40c->phy_chan->num);
writel(var, d40c->base->virtbase + D40_DREG_PRMOE + addr_base); writel(var, d40c->base->virtbase + D40_DREG_PRMOE + addr_base);
...@@ -704,41 +746,181 @@ static int d40_config_write(struct d40_chan *d40c) ...@@ -704,41 +746,181 @@ static int d40_config_write(struct d40_chan *d40c)
d40c->phy_chan->num * D40_DREG_PCDELTA + d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SDCFG); D40_CHAN_REG_SDCFG);
d40_config_enable_lidx(d40c); /* Set LIDX for lcla */
writel((d40c->phy_chan->num << D40_SREG_ELEM_LOG_LIDX_POS) &
D40_SREG_ELEM_LOG_LIDX_MASK,
d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SDELT);
writel((d40c->phy_chan->num << D40_SREG_ELEM_LOG_LIDX_POS) &
D40_SREG_ELEM_LOG_LIDX_MASK,
d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SSELT);
}
}
static u32 d40_residue(struct d40_chan *d40c)
{
u32 num_elt;
if (d40c->log_num != D40_PHY_CHAN)
num_elt = (readl(&d40c->lcpa->lcsp2) & D40_MEM_LCSP2_ECNT_MASK)
>> D40_MEM_LCSP2_ECNT_POS;
else
num_elt = (readl(d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SDELT) &
D40_SREG_ELEM_PHY_ECNT_MASK) >>
D40_SREG_ELEM_PHY_ECNT_POS;
return num_elt * (1 << d40c->dma_cfg.dst_info.data_width);
}
static bool d40_tx_is_linked(struct d40_chan *d40c)
{
bool is_link;
if (d40c->log_num != D40_PHY_CHAN)
is_link = readl(&d40c->lcpa->lcsp3) & D40_MEM_LCSP3_DLOS_MASK;
else
is_link = readl(d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SDLNK) &
D40_SREG_LNK_PHYS_LNK_MASK;
return is_link;
}
static int d40_pause(struct dma_chan *chan)
{
struct d40_chan *d40c =
container_of(chan, struct d40_chan, chan);
int res = 0;
unsigned long flags;
if (!d40c->busy)
return 0;
spin_lock_irqsave(&d40c->lock, flags);
res = d40_channel_execute_command(d40c, D40_DMA_SUSPEND_REQ);
if (res == 0) {
if (d40c->log_num != D40_PHY_CHAN) {
d40_config_set_event(d40c, false);
/* Resume the other logical channels if any */
if (d40_chan_has_events(d40c))
res = d40_channel_execute_command(d40c,
D40_DMA_RUN);
}
} }
spin_unlock_irqrestore(&d40c->lock, flags);
return res; return res;
} }
static void d40_desc_load(struct d40_chan *d40c, struct d40_desc *d40d) static int d40_resume(struct dma_chan *chan)
{ {
if (d40d->lli_phy.dst && d40d->lli_phy.src) { struct d40_chan *d40c =
d40_phy_lli_write(d40c->base->virtbase, container_of(chan, struct d40_chan, chan);
d40c->phy_chan->num, int res = 0;
d40d->lli_phy.dst, unsigned long flags;
d40d->lli_phy.src);
} else if (d40d->lli_log.dst && d40d->lli_log.src) { if (!d40c->busy)
struct d40_log_lli *src = d40d->lli_log.src; return 0;
struct d40_log_lli *dst = d40d->lli_log.dst;
int s; spin_lock_irqsave(&d40c->lock, flags);
src += d40d->lli_count; if (d40c->base->rev == 0)
dst += d40d->lli_count; if (d40c->log_num != D40_PHY_CHAN) {
s = d40_log_lli_write(d40c->lcpa, res = d40_channel_execute_command(d40c,
d40c->lcla.src, d40c->lcla.dst, D40_DMA_SUSPEND_REQ);
dst, src, goto no_suspend;
d40c->base->plat_data->llis_per_log);
/* If s equals to zero, the job is not linked */
if (s > 0) {
(void) dma_map_single(d40c->base->dev, d40c->lcla.src,
s * sizeof(struct d40_log_lli),
DMA_TO_DEVICE);
(void) dma_map_single(d40c->base->dev, d40c->lcla.dst,
s * sizeof(struct d40_log_lli),
DMA_TO_DEVICE);
} }
/* If bytes left to transfer or linked tx resume job */
if (d40_residue(d40c) || d40_tx_is_linked(d40c)) {
if (d40c->log_num != D40_PHY_CHAN)
d40_config_set_event(d40c, true);
res = d40_channel_execute_command(d40c, D40_DMA_RUN);
}
no_suspend:
spin_unlock_irqrestore(&d40c->lock, flags);
return res;
}
static void d40_tx_submit_log(struct d40_chan *d40c, struct d40_desc *d40d)
{
/* TODO: Write */
}
static void d40_tx_submit_phy(struct d40_chan *d40c, struct d40_desc *d40d)
{
struct d40_desc *d40d_prev = NULL;
int i;
u32 val;
if (!list_empty(&d40c->queue))
d40d_prev = d40_last_queued(d40c);
else if (!list_empty(&d40c->active))
d40d_prev = d40_first_active_get(d40c);
if (!d40d_prev)
return;
/* Here we try to join this job with previous jobs */
val = readl(d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SSLNK);
/* Figure out which link we're currently transmitting */
for (i = 0; i < d40d_prev->lli_len; i++)
if (val == d40d_prev->lli_phy.src[i].reg_lnk)
break;
val = readl(d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SSELT) >> D40_SREG_ELEM_LOG_ECNT_POS;
if (i == (d40d_prev->lli_len - 1) && val > 0) {
/* Change the current one */
writel(virt_to_phys(d40d->lli_phy.src),
d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SSLNK);
writel(virt_to_phys(d40d->lli_phy.dst),
d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SDLNK);
d40d->is_hw_linked = true;
} else if (i < d40d_prev->lli_len) {
(void) dma_unmap_single(d40c->base->dev,
virt_to_phys(d40d_prev->lli_phy.src),
d40d_prev->lli_pool.size,
DMA_TO_DEVICE);
/* Keep the settings */
val = d40d_prev->lli_phy.src[d40d_prev->lli_len - 1].reg_lnk &
~D40_SREG_LNK_PHYS_LNK_MASK;
d40d_prev->lli_phy.src[d40d_prev->lli_len - 1].reg_lnk =
val | virt_to_phys(d40d->lli_phy.src);
val = d40d_prev->lli_phy.dst[d40d_prev->lli_len - 1].reg_lnk &
~D40_SREG_LNK_PHYS_LNK_MASK;
d40d_prev->lli_phy.dst[d40d_prev->lli_len - 1].reg_lnk =
val | virt_to_phys(d40d->lli_phy.dst);
(void) dma_map_single(d40c->base->dev,
d40d_prev->lli_phy.src,
d40d_prev->lli_pool.size,
DMA_TO_DEVICE);
d40d->is_hw_linked = true;
} }
d40d->lli_count += d40d->lli_tx_len;
} }
static dma_cookie_t d40_tx_submit(struct dma_async_tx_descriptor *tx) static dma_cookie_t d40_tx_submit(struct dma_async_tx_descriptor *tx)
...@@ -749,14 +931,28 @@ static dma_cookie_t d40_tx_submit(struct dma_async_tx_descriptor *tx) ...@@ -749,14 +931,28 @@ static dma_cookie_t d40_tx_submit(struct dma_async_tx_descriptor *tx)
struct d40_desc *d40d = container_of(tx, struct d40_desc, txd); struct d40_desc *d40d = container_of(tx, struct d40_desc, txd);
unsigned long flags; unsigned long flags;
(void) d40_pause(&d40c->chan);
spin_lock_irqsave(&d40c->lock, flags); spin_lock_irqsave(&d40c->lock, flags);
tx->cookie = d40_assign_cookie(d40c, d40d); d40c->chan.cookie++;
if (d40c->chan.cookie < 0)
d40c->chan.cookie = 1;
d40d->txd.cookie = d40c->chan.cookie;
if (d40c->log_num == D40_PHY_CHAN)
d40_tx_submit_phy(d40c, d40d);
else
d40_tx_submit_log(d40c, d40d);
d40_desc_queue(d40c, d40d); d40_desc_queue(d40c, d40d);
spin_unlock_irqrestore(&d40c->lock, flags); spin_unlock_irqrestore(&d40c->lock, flags);
(void) d40_resume(&d40c->chan);
return tx->cookie; return tx->cookie;
} }
...@@ -796,14 +992,21 @@ static struct d40_desc *d40_queue_start(struct d40_chan *d40c) ...@@ -796,14 +992,21 @@ static struct d40_desc *d40_queue_start(struct d40_chan *d40c)
/* Add to active queue */ /* Add to active queue */
d40_desc_submit(d40c, d40d); d40_desc_submit(d40c, d40d);
/* Initiate DMA job */ /*
d40_desc_load(d40c, d40d); * If this job is already linked in hw,
* do not submit it.
*/
/* Start dma job */ if (!d40d->is_hw_linked) {
err = d40_start(d40c); /* Initiate DMA job */
d40_desc_load(d40c, d40d);
if (err) /* Start dma job */
return NULL; err = d40_start(d40c);
if (err)
return NULL;
}
} }
return d40d; return d40d;
...@@ -814,17 +1017,15 @@ static void dma_tc_handle(struct d40_chan *d40c) ...@@ -814,17 +1017,15 @@ static void dma_tc_handle(struct d40_chan *d40c)
{ {
struct d40_desc *d40d; struct d40_desc *d40d;
if (!d40c->phy_chan)
return;
/* Get first active entry from list */ /* Get first active entry from list */
d40d = d40_first_active_get(d40c); d40d = d40_first_active_get(d40c);
if (d40d == NULL) if (d40d == NULL)
return; return;
if (d40d->lli_count < d40d->lli_len) { d40_lcla_free_all(d40c, d40d);
if (d40d->lli_current < d40d->lli_len) {
d40_desc_load(d40c, d40d); d40_desc_load(d40c, d40d);
/* Start dma job */ /* Start dma job */
(void) d40_start(d40c); (void) d40_start(d40c);
...@@ -842,7 +1043,7 @@ static void dma_tc_handle(struct d40_chan *d40c) ...@@ -842,7 +1043,7 @@ static void dma_tc_handle(struct d40_chan *d40c)
static void dma_tasklet(unsigned long data) static void dma_tasklet(unsigned long data)
{ {
struct d40_chan *d40c = (struct d40_chan *) data; struct d40_chan *d40c = (struct d40_chan *) data;
struct d40_desc *d40d_fin; struct d40_desc *d40d;
unsigned long flags; unsigned long flags;
dma_async_tx_callback callback; dma_async_tx_callback callback;
void *callback_param; void *callback_param;
...@@ -850,12 +1051,12 @@ static void dma_tasklet(unsigned long data) ...@@ -850,12 +1051,12 @@ static void dma_tasklet(unsigned long data)
spin_lock_irqsave(&d40c->lock, flags); spin_lock_irqsave(&d40c->lock, flags);
/* Get first active entry from list */ /* Get first active entry from list */
d40d_fin = d40_first_active_get(d40c); d40d = d40_first_active_get(d40c);
if (d40d_fin == NULL) if (d40d == NULL)
goto err; goto err;
d40c->completed = d40d_fin->txd.cookie; d40c->completed = d40d->txd.cookie;
/* /*
* If terminating a channel pending_tx is set to zero. * If terminating a channel pending_tx is set to zero.
...@@ -867,19 +1068,19 @@ static void dma_tasklet(unsigned long data) ...@@ -867,19 +1068,19 @@ static void dma_tasklet(unsigned long data)
} }
/* Callback to client */ /* Callback to client */
callback = d40d_fin->txd.callback; callback = d40d->txd.callback;
callback_param = d40d_fin->txd.callback_param; callback_param = d40d->txd.callback_param;
if (async_tx_test_ack(&d40d_fin->txd)) { if (async_tx_test_ack(&d40d->txd)) {
d40_pool_lli_free(d40d_fin); d40_pool_lli_free(d40d);
d40_desc_remove(d40d_fin); d40_desc_remove(d40d);
/* Return desc to free-list */ d40_desc_free(d40c, d40d);
d40_desc_free(d40c, d40d_fin);
} else { } else {
if (!d40d_fin->is_in_client_list) { if (!d40d->is_in_client_list) {
d40_desc_remove(d40d_fin); d40_desc_remove(d40d);
list_add_tail(&d40d_fin->node, &d40c->client); d40_lcla_free_all(d40c, d40d);
d40d_fin->is_in_client_list = true; list_add_tail(&d40d->node, &d40c->client);
d40d->is_in_client_list = true;
} }
} }
...@@ -890,7 +1091,7 @@ static void dma_tasklet(unsigned long data) ...@@ -890,7 +1091,7 @@ static void dma_tasklet(unsigned long data)
spin_unlock_irqrestore(&d40c->lock, flags); spin_unlock_irqrestore(&d40c->lock, flags);
if (callback) if (callback && (d40d->txd.flags & DMA_PREP_INTERRUPT))
callback(callback_param); callback(callback_param);
return; return;
...@@ -919,7 +1120,6 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data) ...@@ -919,7 +1120,6 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data)
int i; int i;
u32 regs[ARRAY_SIZE(il)]; u32 regs[ARRAY_SIZE(il)];
u32 tmp;
u32 idx; u32 idx;
u32 row; u32 row;
long chan = -1; long chan = -1;
...@@ -946,9 +1146,7 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data) ...@@ -946,9 +1146,7 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data)
idx = chan & (BITS_PER_LONG - 1); idx = chan & (BITS_PER_LONG - 1);
/* ACK interrupt */ /* ACK interrupt */
tmp = readl(base->virtbase + il[row].clr); writel(1 << idx, base->virtbase + il[row].clr);
tmp |= 1 << idx;
writel(tmp, base->virtbase + il[row].clr);
if (il[row].offset == D40_PHY_CHAN) if (il[row].offset == D40_PHY_CHAN)
d40c = base->lookup_phy_chans[idx]; d40c = base->lookup_phy_chans[idx];
...@@ -971,24 +1169,47 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data) ...@@ -971,24 +1169,47 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data)
return IRQ_HANDLED; return IRQ_HANDLED;
} }
static int d40_validate_conf(struct d40_chan *d40c, static int d40_validate_conf(struct d40_chan *d40c,
struct stedma40_chan_cfg *conf) struct stedma40_chan_cfg *conf)
{ {
int res = 0; int res = 0;
u32 dst_event_group = D40_TYPE_TO_GROUP(conf->dst_dev_type); u32 dst_event_group = D40_TYPE_TO_GROUP(conf->dst_dev_type);
u32 src_event_group = D40_TYPE_TO_GROUP(conf->src_dev_type); u32 src_event_group = D40_TYPE_TO_GROUP(conf->src_dev_type);
bool is_log = (conf->channel_type & STEDMA40_CHANNEL_IN_OPER_MODE) bool is_log = conf->mode == STEDMA40_MODE_LOGICAL;
== STEDMA40_CHANNEL_IN_LOG_MODE;
if (!conf->dir) {
dev_err(&d40c->chan.dev->device, "[%s] Invalid direction.\n",
__func__);
res = -EINVAL;
}
if (conf->dst_dev_type != STEDMA40_DEV_DST_MEMORY &&
d40c->base->plat_data->dev_tx[conf->dst_dev_type] == 0 &&
d40c->runtime_addr == 0) {
dev_err(&d40c->chan.dev->device,
"[%s] Invalid TX channel address (%d)\n",
__func__, conf->dst_dev_type);
res = -EINVAL;
}
if (conf->src_dev_type != STEDMA40_DEV_SRC_MEMORY &&
d40c->base->plat_data->dev_rx[conf->src_dev_type] == 0 &&
d40c->runtime_addr == 0) {
dev_err(&d40c->chan.dev->device,
"[%s] Invalid RX channel address (%d)\n",
__func__, conf->src_dev_type);
res = -EINVAL;
}
if (d40c->dma_cfg.dir == STEDMA40_MEM_TO_PERIPH && if (conf->dir == STEDMA40_MEM_TO_PERIPH &&
dst_event_group == STEDMA40_DEV_DST_MEMORY) { dst_event_group == STEDMA40_DEV_DST_MEMORY) {
dev_err(&d40c->chan.dev->device, "[%s] Invalid dst\n", dev_err(&d40c->chan.dev->device, "[%s] Invalid dst\n",
__func__); __func__);
res = -EINVAL; res = -EINVAL;
} }
if (d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_MEM && if (conf->dir == STEDMA40_PERIPH_TO_MEM &&
src_event_group == STEDMA40_DEV_SRC_MEMORY) { src_event_group == STEDMA40_DEV_SRC_MEMORY) {
dev_err(&d40c->chan.dev->device, "[%s] Invalid src\n", dev_err(&d40c->chan.dev->device, "[%s] Invalid src\n",
__func__); __func__);
...@@ -1082,7 +1303,6 @@ static bool d40_alloc_mask_free(struct d40_phy_res *phy, bool is_src, ...@@ -1082,7 +1303,6 @@ static bool d40_alloc_mask_free(struct d40_phy_res *phy, bool is_src,
spin_lock_irqsave(&phy->lock, flags); spin_lock_irqsave(&phy->lock, flags);
if (!log_event_line) { if (!log_event_line) {
/* Physical interrupts are masked per physical full channel */
phy->allocated_dst = D40_ALLOC_FREE; phy->allocated_dst = D40_ALLOC_FREE;
phy->allocated_src = D40_ALLOC_FREE; phy->allocated_src = D40_ALLOC_FREE;
is_free = true; is_free = true;
...@@ -1119,10 +1339,7 @@ static int d40_allocate_channel(struct d40_chan *d40c) ...@@ -1119,10 +1339,7 @@ static int d40_allocate_channel(struct d40_chan *d40c)
int j; int j;
int log_num; int log_num;
bool is_src; bool is_src;
bool is_log = (d40c->dma_cfg.channel_type & bool is_log = d40c->dma_cfg.mode == STEDMA40_MODE_LOGICAL;
STEDMA40_CHANNEL_IN_OPER_MODE)
== STEDMA40_CHANNEL_IN_LOG_MODE;
phys = d40c->base->phy_res; phys = d40c->base->phy_res;
...@@ -1251,7 +1468,6 @@ static int d40_free_dma(struct d40_chan *d40c) ...@@ -1251,7 +1468,6 @@ static int d40_free_dma(struct d40_chan *d40c)
list_for_each_entry_safe(d, _d, &d40c->client, node) { list_for_each_entry_safe(d, _d, &d40c->client, node) {
d40_pool_lli_free(d); d40_pool_lli_free(d);
d40_desc_remove(d); d40_desc_remove(d);
/* Return desc to free-list */
d40_desc_free(d40c, d); d40_desc_free(d40c, d);
} }
...@@ -1324,37 +1540,12 @@ static int d40_free_dma(struct d40_chan *d40c) ...@@ -1324,37 +1540,12 @@ static int d40_free_dma(struct d40_chan *d40c)
return res; return res;
} }
d40c->phy_chan = NULL; d40c->phy_chan = NULL;
/* Invalidate channel type */ d40c->configured = false;
d40c->dma_cfg.channel_type = 0;
d40c->base->lookup_phy_chans[phy->num] = NULL; d40c->base->lookup_phy_chans[phy->num] = NULL;
return 0; return 0;
} }
static int d40_pause(struct dma_chan *chan)
{
struct d40_chan *d40c =
container_of(chan, struct d40_chan, chan);
int res;
unsigned long flags;
spin_lock_irqsave(&d40c->lock, flags);
res = d40_channel_execute_command(d40c, D40_DMA_SUSPEND_REQ);
if (res == 0) {
if (d40c->log_num != D40_PHY_CHAN) {
d40_config_set_event(d40c, false);
/* Resume the other logical channels if any */
if (d40_chan_has_events(d40c))
res = d40_channel_execute_command(d40c,
D40_DMA_RUN);
}
}
spin_unlock_irqrestore(&d40c->lock, flags);
return res;
}
static bool d40_is_paused(struct d40_chan *d40c) static bool d40_is_paused(struct d40_chan *d40c)
{ {
bool is_paused = false; bool is_paused = false;
...@@ -1381,16 +1572,22 @@ static bool d40_is_paused(struct d40_chan *d40c) ...@@ -1381,16 +1572,22 @@ static bool d40_is_paused(struct d40_chan *d40c)
} }
if (d40c->dma_cfg.dir == STEDMA40_MEM_TO_PERIPH || if (d40c->dma_cfg.dir == STEDMA40_MEM_TO_PERIPH ||
d40c->dma_cfg.dir == STEDMA40_MEM_TO_MEM) d40c->dma_cfg.dir == STEDMA40_MEM_TO_MEM) {
event = D40_TYPE_TO_EVENT(d40c->dma_cfg.dst_dev_type); event = D40_TYPE_TO_EVENT(d40c->dma_cfg.dst_dev_type);
else if (d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_MEM) status = readl(d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SDLNK);
} else if (d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_MEM) {
event = D40_TYPE_TO_EVENT(d40c->dma_cfg.src_dev_type); event = D40_TYPE_TO_EVENT(d40c->dma_cfg.src_dev_type);
else { status = readl(d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SSLNK);
} else {
dev_err(&d40c->chan.dev->device, dev_err(&d40c->chan.dev->device,
"[%s] Unknown direction\n", __func__); "[%s] Unknown direction\n", __func__);
goto _exit; goto _exit;
} }
status = d40_chan_has_events(d40c);
status = (status & D40_EVENTLINE_MASK(event)) >> status = (status & D40_EVENTLINE_MASK(event)) >>
D40_EVENTLINE_POS(event); D40_EVENTLINE_POS(event);
...@@ -1403,64 +1600,6 @@ static bool d40_is_paused(struct d40_chan *d40c) ...@@ -1403,64 +1600,6 @@ static bool d40_is_paused(struct d40_chan *d40c)
} }
static bool d40_tx_is_linked(struct d40_chan *d40c)
{
bool is_link;
if (d40c->log_num != D40_PHY_CHAN)
is_link = readl(&d40c->lcpa->lcsp3) & D40_MEM_LCSP3_DLOS_MASK;
else
is_link = readl(d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SDLNK) &
D40_SREG_LNK_PHYS_LNK_MASK;
return is_link;
}
static u32 d40_residue(struct d40_chan *d40c)
{
u32 num_elt;
if (d40c->log_num != D40_PHY_CHAN)
num_elt = (readl(&d40c->lcpa->lcsp2) & D40_MEM_LCSP2_ECNT_MASK)
>> D40_MEM_LCSP2_ECNT_POS;
else
num_elt = (readl(d40c->base->virtbase + D40_DREG_PCBASE +
d40c->phy_chan->num * D40_DREG_PCDELTA +
D40_CHAN_REG_SDELT) &
D40_SREG_ELEM_PHY_ECNT_MASK) >>
D40_SREG_ELEM_PHY_ECNT_POS;
return num_elt * (1 << d40c->dma_cfg.dst_info.data_width);
}
static int d40_resume(struct dma_chan *chan)
{
struct d40_chan *d40c =
container_of(chan, struct d40_chan, chan);
int res = 0;
unsigned long flags;
spin_lock_irqsave(&d40c->lock, flags);
if (d40c->base->rev == 0)
if (d40c->log_num != D40_PHY_CHAN) {
res = d40_channel_execute_command(d40c,
D40_DMA_SUSPEND_REQ);
goto no_suspend;
}
/* If bytes left to transfer or linked tx resume job */
if (d40_residue(d40c) || d40_tx_is_linked(d40c)) {
if (d40c->log_num != D40_PHY_CHAN)
d40_config_set_event(d40c, true);
res = d40_channel_execute_command(d40c, D40_DMA_RUN);
}
no_suspend:
spin_unlock_irqrestore(&d40c->lock, flags);
return res;
}
static u32 stedma40_residue(struct dma_chan *chan) static u32 stedma40_residue(struct dma_chan *chan)
{ {
struct d40_chan *d40c = struct d40_chan *d40c =
...@@ -1475,51 +1614,6 @@ static u32 stedma40_residue(struct dma_chan *chan) ...@@ -1475,51 +1614,6 @@ static u32 stedma40_residue(struct dma_chan *chan)
return bytes_left; return bytes_left;
} }
/* Public DMA functions in addition to the DMA engine framework */
int stedma40_set_psize(struct dma_chan *chan,
int src_psize,
int dst_psize)
{
struct d40_chan *d40c =
container_of(chan, struct d40_chan, chan);
unsigned long flags;
spin_lock_irqsave(&d40c->lock, flags);
if (d40c->log_num != D40_PHY_CHAN) {
d40c->log_def.lcsp1 &= ~D40_MEM_LCSP1_SCFG_PSIZE_MASK;
d40c->log_def.lcsp3 &= ~D40_MEM_LCSP1_SCFG_PSIZE_MASK;
d40c->log_def.lcsp1 |= src_psize <<
D40_MEM_LCSP1_SCFG_PSIZE_POS;
d40c->log_def.lcsp3 |= dst_psize <<
D40_MEM_LCSP1_SCFG_PSIZE_POS;
goto out;
}
if (src_psize == STEDMA40_PSIZE_PHY_1)
d40c->src_def_cfg &= ~(1 << D40_SREG_CFG_PHY_PEN_POS);
else {
d40c->src_def_cfg |= 1 << D40_SREG_CFG_PHY_PEN_POS;
d40c->src_def_cfg &= ~(STEDMA40_PSIZE_PHY_16 <<
D40_SREG_CFG_PSIZE_POS);
d40c->src_def_cfg |= src_psize << D40_SREG_CFG_PSIZE_POS;
}
if (dst_psize == STEDMA40_PSIZE_PHY_1)
d40c->dst_def_cfg &= ~(1 << D40_SREG_CFG_PHY_PEN_POS);
else {
d40c->dst_def_cfg |= 1 << D40_SREG_CFG_PHY_PEN_POS;
d40c->dst_def_cfg &= ~(STEDMA40_PSIZE_PHY_16 <<
D40_SREG_CFG_PSIZE_POS);
d40c->dst_def_cfg |= dst_psize << D40_SREG_CFG_PSIZE_POS;
}
out:
spin_unlock_irqrestore(&d40c->lock, flags);
return 0;
}
EXPORT_SYMBOL(stedma40_set_psize);
struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan, struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan,
struct scatterlist *sgl_dst, struct scatterlist *sgl_dst,
struct scatterlist *sgl_src, struct scatterlist *sgl_src,
...@@ -1545,21 +1639,10 @@ struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan, ...@@ -1545,21 +1639,10 @@ struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan,
goto err; goto err;
d40d->lli_len = sgl_len; d40d->lli_len = sgl_len;
d40d->lli_tx_len = d40d->lli_len; d40d->lli_current = 0;
d40d->txd.flags = dma_flags; d40d->txd.flags = dma_flags;
if (d40c->log_num != D40_PHY_CHAN) { if (d40c->log_num != D40_PHY_CHAN) {
if (d40d->lli_len > d40c->base->plat_data->llis_per_log)
d40d->lli_tx_len = d40c->base->plat_data->llis_per_log;
if (sgl_len > 1)
/*
* Check if there is space available in lcla. If not,
* split list into 1-length and run only in lcpa
* space.
*/
if (d40_lcla_id_get(d40c) != 0)
d40d->lli_tx_len = 1;
if (d40_pool_lli_alloc(d40d, sgl_len, true) < 0) { if (d40_pool_lli_alloc(d40d, sgl_len, true) < 0) {
dev_err(&d40c->chan.dev->device, dev_err(&d40c->chan.dev->device,
...@@ -1567,27 +1650,17 @@ struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan, ...@@ -1567,27 +1650,17 @@ struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan,
goto err; goto err;
} }
(void) d40_log_sg_to_lli(d40c->lcla.src_id, (void) d40_log_sg_to_lli(sgl_src,
sgl_src,
sgl_len, sgl_len,
d40d->lli_log.src, d40d->lli_log.src,
d40c->log_def.lcsp1, d40c->log_def.lcsp1,
d40c->dma_cfg.src_info.data_width, d40c->dma_cfg.src_info.data_width);
dma_flags & DMA_PREP_INTERRUPT,
d40d->lli_tx_len,
d40c->base->plat_data->llis_per_log);
(void) d40_log_sg_to_lli(d40c->lcla.dst_id, (void) d40_log_sg_to_lli(sgl_dst,
sgl_dst,
sgl_len, sgl_len,
d40d->lli_log.dst, d40d->lli_log.dst,
d40c->log_def.lcsp3, d40c->log_def.lcsp3,
d40c->dma_cfg.dst_info.data_width, d40c->dma_cfg.dst_info.data_width);
dma_flags & DMA_PREP_INTERRUPT,
d40d->lli_tx_len,
d40c->base->plat_data->llis_per_log);
} else { } else {
if (d40_pool_lli_alloc(d40d, sgl_len, false) < 0) { if (d40_pool_lli_alloc(d40d, sgl_len, false) < 0) {
dev_err(&d40c->chan.dev->device, dev_err(&d40c->chan.dev->device,
...@@ -1599,11 +1672,10 @@ struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan, ...@@ -1599,11 +1672,10 @@ struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan,
sgl_len, sgl_len,
0, 0,
d40d->lli_phy.src, d40d->lli_phy.src,
d40d->lli_phy.src_addr, virt_to_phys(d40d->lli_phy.src),
d40c->src_def_cfg, d40c->src_def_cfg,
d40c->dma_cfg.src_info.data_width, d40c->dma_cfg.src_info.data_width,
d40c->dma_cfg.src_info.psize, d40c->dma_cfg.src_info.psize);
true);
if (res < 0) if (res < 0)
goto err; goto err;
...@@ -1612,11 +1684,10 @@ struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan, ...@@ -1612,11 +1684,10 @@ struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan,
sgl_len, sgl_len,
0, 0,
d40d->lli_phy.dst, d40d->lli_phy.dst,
d40d->lli_phy.dst_addr, virt_to_phys(d40d->lli_phy.dst),
d40c->dst_def_cfg, d40c->dst_def_cfg,
d40c->dma_cfg.dst_info.data_width, d40c->dma_cfg.dst_info.data_width,
d40c->dma_cfg.dst_info.psize, d40c->dma_cfg.dst_info.psize);
true);
if (res < 0) if (res < 0)
goto err; goto err;
...@@ -1633,6 +1704,8 @@ struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan, ...@@ -1633,6 +1704,8 @@ struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan,
return &d40d->txd; return &d40d->txd;
err: err:
if (d40d)
d40_desc_free(d40c, d40d);
spin_unlock_irqrestore(&d40c->lock, flags); spin_unlock_irqrestore(&d40c->lock, flags);
return NULL; return NULL;
} }
...@@ -1652,6 +1725,9 @@ bool stedma40_filter(struct dma_chan *chan, void *data) ...@@ -1652,6 +1725,9 @@ bool stedma40_filter(struct dma_chan *chan, void *data)
} else } else
err = d40_config_memcpy(d40c); err = d40_config_memcpy(d40c);
if (!err)
d40c->configured = true;
return err == 0; return err == 0;
} }
EXPORT_SYMBOL(stedma40_filter); EXPORT_SYMBOL(stedma40_filter);
...@@ -1668,11 +1744,8 @@ static int d40_alloc_chan_resources(struct dma_chan *chan) ...@@ -1668,11 +1744,8 @@ static int d40_alloc_chan_resources(struct dma_chan *chan)
d40c->completed = chan->cookie = 1; d40c->completed = chan->cookie = 1;
/* /* If no dma configuration is set use default configuration (memcpy) */
* If no dma configuration is set (channel_type == 0) if (!d40c->configured) {
* use default configuration (memcpy)
*/
if (d40c->dma_cfg.channel_type == 0) {
err = d40_config_memcpy(d40c); err = d40_config_memcpy(d40c);
if (err) { if (err) {
dev_err(&d40c->chan.dev->device, dev_err(&d40c->chan.dev->device,
...@@ -1712,14 +1785,8 @@ static int d40_alloc_chan_resources(struct dma_chan *chan) ...@@ -1712,14 +1785,8 @@ static int d40_alloc_chan_resources(struct dma_chan *chan)
* resource is free. In case of multiple logical channels * resource is free. In case of multiple logical channels
* on the same physical resource, only the first write is necessary. * on the same physical resource, only the first write is necessary.
*/ */
if (is_free_phy) { if (is_free_phy)
err = d40_config_write(d40c); d40_config_write(d40c);
if (err) {
dev_err(&d40c->chan.dev->device,
"[%s] Failed to configure channel\n",
__func__);
}
}
fail: fail:
spin_unlock_irqrestore(&d40c->lock, flags); spin_unlock_irqrestore(&d40c->lock, flags);
return err; return err;
...@@ -1790,23 +1857,21 @@ static struct dma_async_tx_descriptor *d40_prep_memcpy(struct dma_chan *chan, ...@@ -1790,23 +1857,21 @@ static struct dma_async_tx_descriptor *d40_prep_memcpy(struct dma_chan *chan,
goto err; goto err;
} }
d40d->lli_len = 1; d40d->lli_len = 1;
d40d->lli_tx_len = 1; d40d->lli_current = 0;
d40_log_fill_lli(d40d->lli_log.src, d40_log_fill_lli(d40d->lli_log.src,
src, src,
size, size,
0,
d40c->log_def.lcsp1, d40c->log_def.lcsp1,
d40c->dma_cfg.src_info.data_width, d40c->dma_cfg.src_info.data_width,
false, true); true);
d40_log_fill_lli(d40d->lli_log.dst, d40_log_fill_lli(d40d->lli_log.dst,
dst, dst,
size, size,
0,
d40c->log_def.lcsp3, d40c->log_def.lcsp3,
d40c->dma_cfg.dst_info.data_width, d40c->dma_cfg.dst_info.data_width,
true, true); true);
} else { } else {
...@@ -1851,12 +1916,25 @@ static struct dma_async_tx_descriptor *d40_prep_memcpy(struct dma_chan *chan, ...@@ -1851,12 +1916,25 @@ static struct dma_async_tx_descriptor *d40_prep_memcpy(struct dma_chan *chan,
err_fill_lli: err_fill_lli:
dev_err(&d40c->chan.dev->device, dev_err(&d40c->chan.dev->device,
"[%s] Failed filling in PHY LLI\n", __func__); "[%s] Failed filling in PHY LLI\n", __func__);
d40_pool_lli_free(d40d);
err: err:
if (d40d)
d40_desc_free(d40c, d40d);
spin_unlock_irqrestore(&d40c->lock, flags); spin_unlock_irqrestore(&d40c->lock, flags);
return NULL; return NULL;
} }
static struct dma_async_tx_descriptor *
d40_prep_sg(struct dma_chan *chan,
struct scatterlist *dst_sg, unsigned int dst_nents,
struct scatterlist *src_sg, unsigned int src_nents,
unsigned long dma_flags)
{
if (dst_nents != src_nents)
return NULL;
return stedma40_memcpy_sg(chan, dst_sg, src_sg, dst_nents, dma_flags);
}
static int d40_prep_slave_sg_log(struct d40_desc *d40d, static int d40_prep_slave_sg_log(struct d40_desc *d40d,
struct d40_chan *d40c, struct d40_chan *d40c,
struct scatterlist *sgl, struct scatterlist *sgl,
...@@ -1874,19 +1952,7 @@ static int d40_prep_slave_sg_log(struct d40_desc *d40d, ...@@ -1874,19 +1952,7 @@ static int d40_prep_slave_sg_log(struct d40_desc *d40d,
} }
d40d->lli_len = sg_len; d40d->lli_len = sg_len;
if (d40d->lli_len <= d40c->base->plat_data->llis_per_log) d40d->lli_current = 0;
d40d->lli_tx_len = d40d->lli_len;
else
d40d->lli_tx_len = d40c->base->plat_data->llis_per_log;
if (sg_len > 1)
/*
* Check if there is space available in lcla.
* If not, split list into 1-length and run only
* in lcpa space.
*/
if (d40_lcla_id_get(d40c) != 0)
d40d->lli_tx_len = 1;
if (direction == DMA_FROM_DEVICE) if (direction == DMA_FROM_DEVICE)
if (d40c->runtime_addr) if (d40c->runtime_addr)
...@@ -1902,16 +1968,13 @@ static int d40_prep_slave_sg_log(struct d40_desc *d40d, ...@@ -1902,16 +1968,13 @@ static int d40_prep_slave_sg_log(struct d40_desc *d40d,
else else
return -EINVAL; return -EINVAL;
total_size = d40_log_sg_to_dev(&d40c->lcla, total_size = d40_log_sg_to_dev(sgl, sg_len,
sgl, sg_len,
&d40d->lli_log, &d40d->lli_log,
&d40c->log_def, &d40c->log_def,
d40c->dma_cfg.src_info.data_width, d40c->dma_cfg.src_info.data_width,
d40c->dma_cfg.dst_info.data_width, d40c->dma_cfg.dst_info.data_width,
direction, direction,
dma_flags & DMA_PREP_INTERRUPT, dev_addr);
dev_addr, d40d->lli_tx_len,
d40c->base->plat_data->llis_per_log);
if (total_size < 0) if (total_size < 0)
return -EINVAL; return -EINVAL;
...@@ -1937,7 +2000,7 @@ static int d40_prep_slave_sg_phy(struct d40_desc *d40d, ...@@ -1937,7 +2000,7 @@ static int d40_prep_slave_sg_phy(struct d40_desc *d40d,
} }
d40d->lli_len = sgl_len; d40d->lli_len = sgl_len;
d40d->lli_tx_len = sgl_len; d40d->lli_current = 0;
if (direction == DMA_FROM_DEVICE) { if (direction == DMA_FROM_DEVICE) {
dst_dev_addr = 0; dst_dev_addr = 0;
...@@ -1958,11 +2021,10 @@ static int d40_prep_slave_sg_phy(struct d40_desc *d40d, ...@@ -1958,11 +2021,10 @@ static int d40_prep_slave_sg_phy(struct d40_desc *d40d,
sgl_len, sgl_len,
src_dev_addr, src_dev_addr,
d40d->lli_phy.src, d40d->lli_phy.src,
d40d->lli_phy.src_addr, virt_to_phys(d40d->lli_phy.src),
d40c->src_def_cfg, d40c->src_def_cfg,
d40c->dma_cfg.src_info.data_width, d40c->dma_cfg.src_info.data_width,
d40c->dma_cfg.src_info.psize, d40c->dma_cfg.src_info.psize);
true);
if (res < 0) if (res < 0)
return res; return res;
...@@ -1970,11 +2032,10 @@ static int d40_prep_slave_sg_phy(struct d40_desc *d40d, ...@@ -1970,11 +2032,10 @@ static int d40_prep_slave_sg_phy(struct d40_desc *d40d,
sgl_len, sgl_len,
dst_dev_addr, dst_dev_addr,
d40d->lli_phy.dst, d40d->lli_phy.dst,
d40d->lli_phy.dst_addr, virt_to_phys(d40d->lli_phy.dst),
d40c->dst_def_cfg, d40c->dst_def_cfg,
d40c->dma_cfg.dst_info.data_width, d40c->dma_cfg.dst_info.data_width,
d40c->dma_cfg.dst_info.psize, d40c->dma_cfg.dst_info.psize);
true);
if (res < 0) if (res < 0)
return res; return res;
...@@ -2001,17 +2062,11 @@ static struct dma_async_tx_descriptor *d40_prep_slave_sg(struct dma_chan *chan, ...@@ -2001,17 +2062,11 @@ static struct dma_async_tx_descriptor *d40_prep_slave_sg(struct dma_chan *chan,
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
} }
if (d40c->dma_cfg.pre_transfer)
d40c->dma_cfg.pre_transfer(chan,
d40c->dma_cfg.pre_transfer_data,
sg_dma_len(sgl));
spin_lock_irqsave(&d40c->lock, flags); spin_lock_irqsave(&d40c->lock, flags);
d40d = d40_desc_get(d40c); d40d = d40_desc_get(d40c);
spin_unlock_irqrestore(&d40c->lock, flags);
if (d40d == NULL) if (d40d == NULL)
return NULL; goto err;
if (d40c->log_num != D40_PHY_CHAN) if (d40c->log_num != D40_PHY_CHAN)
err = d40_prep_slave_sg_log(d40d, d40c, sgl, sg_len, err = d40_prep_slave_sg_log(d40d, d40c, sgl, sg_len,
...@@ -2024,7 +2079,7 @@ static struct dma_async_tx_descriptor *d40_prep_slave_sg(struct dma_chan *chan, ...@@ -2024,7 +2079,7 @@ static struct dma_async_tx_descriptor *d40_prep_slave_sg(struct dma_chan *chan,
"[%s] Failed to prepare %s slave sg job: %d\n", "[%s] Failed to prepare %s slave sg job: %d\n",
__func__, __func__,
d40c->log_num != D40_PHY_CHAN ? "log" : "phy", err); d40c->log_num != D40_PHY_CHAN ? "log" : "phy", err);
return NULL; goto err;
} }
d40d->txd.flags = dma_flags; d40d->txd.flags = dma_flags;
...@@ -2033,7 +2088,14 @@ static struct dma_async_tx_descriptor *d40_prep_slave_sg(struct dma_chan *chan, ...@@ -2033,7 +2088,14 @@ static struct dma_async_tx_descriptor *d40_prep_slave_sg(struct dma_chan *chan,
d40d->txd.tx_submit = d40_tx_submit; d40d->txd.tx_submit = d40_tx_submit;
spin_unlock_irqrestore(&d40c->lock, flags);
return &d40d->txd; return &d40d->txd;
err:
if (d40d)
d40_desc_free(d40c, d40d);
spin_unlock_irqrestore(&d40c->lock, flags);
return NULL;
} }
static enum dma_status d40_tx_status(struct dma_chan *chan, static enum dma_status d40_tx_status(struct dma_chan *chan,
...@@ -2166,25 +2228,43 @@ static void d40_set_runtime_config(struct dma_chan *chan, ...@@ -2166,25 +2228,43 @@ static void d40_set_runtime_config(struct dma_chan *chan,
return; return;
} }
if (config_maxburst >= 16) if (d40c->log_num != D40_PHY_CHAN) {
psize = STEDMA40_PSIZE_LOG_16; if (config_maxburst >= 16)
else if (config_maxburst >= 8) psize = STEDMA40_PSIZE_LOG_16;
psize = STEDMA40_PSIZE_LOG_8; else if (config_maxburst >= 8)
else if (config_maxburst >= 4) psize = STEDMA40_PSIZE_LOG_8;
psize = STEDMA40_PSIZE_LOG_4; else if (config_maxburst >= 4)
else psize = STEDMA40_PSIZE_LOG_4;
psize = STEDMA40_PSIZE_LOG_1; else
psize = STEDMA40_PSIZE_LOG_1;
} else {
if (config_maxburst >= 16)
psize = STEDMA40_PSIZE_PHY_16;
else if (config_maxburst >= 8)
psize = STEDMA40_PSIZE_PHY_8;
else if (config_maxburst >= 4)
psize = STEDMA40_PSIZE_PHY_4;
else
psize = STEDMA40_PSIZE_PHY_1;
}
/* Set up all the endpoint configs */ /* Set up all the endpoint configs */
cfg->src_info.data_width = addr_width; cfg->src_info.data_width = addr_width;
cfg->src_info.psize = psize; cfg->src_info.psize = psize;
cfg->src_info.endianess = STEDMA40_LITTLE_ENDIAN; cfg->src_info.big_endian = false;
cfg->src_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL; cfg->src_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL;
cfg->dst_info.data_width = addr_width; cfg->dst_info.data_width = addr_width;
cfg->dst_info.psize = psize; cfg->dst_info.psize = psize;
cfg->dst_info.endianess = STEDMA40_LITTLE_ENDIAN; cfg->dst_info.big_endian = false;
cfg->dst_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL; cfg->dst_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL;
/* Fill in register values */
if (d40c->log_num != D40_PHY_CHAN)
d40_log_cfg(cfg, &d40c->log_def.lcsp1, &d40c->log_def.lcsp3);
else
d40_phy_cfg(cfg, &d40c->src_def_cfg,
&d40c->dst_def_cfg, false);
/* These settings will take precedence later */ /* These settings will take precedence later */
d40c->runtime_addr = config_addr; d40c->runtime_addr = config_addr;
d40c->runtime_direction = config->direction; d40c->runtime_direction = config->direction;
...@@ -2247,10 +2327,6 @@ static void __init d40_chan_init(struct d40_base *base, struct dma_device *dma, ...@@ -2247,10 +2327,6 @@ static void __init d40_chan_init(struct d40_base *base, struct dma_device *dma,
d40c->base = base; d40c->base = base;
d40c->chan.device = dma; d40c->chan.device = dma;
/* Invalidate lcla element */
d40c->lcla.src_id = -1;
d40c->lcla.dst_id = -1;
spin_lock_init(&d40c->lock); spin_lock_init(&d40c->lock);
d40c->log_num = D40_PHY_CHAN; d40c->log_num = D40_PHY_CHAN;
...@@ -2281,6 +2357,7 @@ static int __init d40_dmaengine_init(struct d40_base *base, ...@@ -2281,6 +2357,7 @@ static int __init d40_dmaengine_init(struct d40_base *base,
base->dma_slave.device_alloc_chan_resources = d40_alloc_chan_resources; base->dma_slave.device_alloc_chan_resources = d40_alloc_chan_resources;
base->dma_slave.device_free_chan_resources = d40_free_chan_resources; base->dma_slave.device_free_chan_resources = d40_free_chan_resources;
base->dma_slave.device_prep_dma_memcpy = d40_prep_memcpy; base->dma_slave.device_prep_dma_memcpy = d40_prep_memcpy;
base->dma_slave.device_prep_dma_sg = d40_prep_sg;
base->dma_slave.device_prep_slave_sg = d40_prep_slave_sg; base->dma_slave.device_prep_slave_sg = d40_prep_slave_sg;
base->dma_slave.device_tx_status = d40_tx_status; base->dma_slave.device_tx_status = d40_tx_status;
base->dma_slave.device_issue_pending = d40_issue_pending; base->dma_slave.device_issue_pending = d40_issue_pending;
...@@ -2301,10 +2378,12 @@ static int __init d40_dmaengine_init(struct d40_base *base, ...@@ -2301,10 +2378,12 @@ static int __init d40_dmaengine_init(struct d40_base *base,
dma_cap_zero(base->dma_memcpy.cap_mask); dma_cap_zero(base->dma_memcpy.cap_mask);
dma_cap_set(DMA_MEMCPY, base->dma_memcpy.cap_mask); dma_cap_set(DMA_MEMCPY, base->dma_memcpy.cap_mask);
dma_cap_set(DMA_SG, base->dma_slave.cap_mask);
base->dma_memcpy.device_alloc_chan_resources = d40_alloc_chan_resources; base->dma_memcpy.device_alloc_chan_resources = d40_alloc_chan_resources;
base->dma_memcpy.device_free_chan_resources = d40_free_chan_resources; base->dma_memcpy.device_free_chan_resources = d40_free_chan_resources;
base->dma_memcpy.device_prep_dma_memcpy = d40_prep_memcpy; base->dma_memcpy.device_prep_dma_memcpy = d40_prep_memcpy;
base->dma_slave.device_prep_dma_sg = d40_prep_sg;
base->dma_memcpy.device_prep_slave_sg = d40_prep_slave_sg; base->dma_memcpy.device_prep_slave_sg = d40_prep_slave_sg;
base->dma_memcpy.device_tx_status = d40_tx_status; base->dma_memcpy.device_tx_status = d40_tx_status;
base->dma_memcpy.device_issue_pending = d40_issue_pending; base->dma_memcpy.device_issue_pending = d40_issue_pending;
...@@ -2331,10 +2410,12 @@ static int __init d40_dmaengine_init(struct d40_base *base, ...@@ -2331,10 +2410,12 @@ static int __init d40_dmaengine_init(struct d40_base *base,
dma_cap_zero(base->dma_both.cap_mask); dma_cap_zero(base->dma_both.cap_mask);
dma_cap_set(DMA_SLAVE, base->dma_both.cap_mask); dma_cap_set(DMA_SLAVE, base->dma_both.cap_mask);
dma_cap_set(DMA_MEMCPY, base->dma_both.cap_mask); dma_cap_set(DMA_MEMCPY, base->dma_both.cap_mask);
dma_cap_set(DMA_SG, base->dma_slave.cap_mask);
base->dma_both.device_alloc_chan_resources = d40_alloc_chan_resources; base->dma_both.device_alloc_chan_resources = d40_alloc_chan_resources;
base->dma_both.device_free_chan_resources = d40_free_chan_resources; base->dma_both.device_free_chan_resources = d40_free_chan_resources;
base->dma_both.device_prep_dma_memcpy = d40_prep_memcpy; base->dma_both.device_prep_dma_memcpy = d40_prep_memcpy;
base->dma_slave.device_prep_dma_sg = d40_prep_sg;
base->dma_both.device_prep_slave_sg = d40_prep_slave_sg; base->dma_both.device_prep_slave_sg = d40_prep_slave_sg;
base->dma_both.device_tx_status = d40_tx_status; base->dma_both.device_tx_status = d40_tx_status;
base->dma_both.device_issue_pending = d40_issue_pending; base->dma_both.device_issue_pending = d40_issue_pending;
...@@ -2387,9 +2468,11 @@ static int __init d40_phy_res_init(struct d40_base *base) ...@@ -2387,9 +2468,11 @@ static int __init d40_phy_res_init(struct d40_base *base)
/* Mark disabled channels as occupied */ /* Mark disabled channels as occupied */
for (i = 0; base->plat_data->disabled_channels[i] != -1; i++) { for (i = 0; base->plat_data->disabled_channels[i] != -1; i++) {
base->phy_res[i].allocated_src = D40_ALLOC_PHY; int chan = base->plat_data->disabled_channels[i];
base->phy_res[i].allocated_dst = D40_ALLOC_PHY;
num_phy_chans_avail--; base->phy_res[chan].allocated_src = D40_ALLOC_PHY;
base->phy_res[chan].allocated_dst = D40_ALLOC_PHY;
num_phy_chans_avail--;
} }
dev_info(base->dev, "%d of %d physical DMA channels available\n", dev_info(base->dev, "%d of %d physical DMA channels available\n",
...@@ -2441,6 +2524,7 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) ...@@ -2441,6 +2524,7 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev)
int num_phy_chans; int num_phy_chans;
int i; int i;
u32 val; u32 val;
u32 rev;
clk = clk_get(&pdev->dev, NULL); clk = clk_get(&pdev->dev, NULL);
...@@ -2479,21 +2563,26 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) ...@@ -2479,21 +2563,26 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev)
} }
} }
/* Get silicon revision */ /* Get silicon revision and designer */
val = readl(virtbase + D40_DREG_PERIPHID2); val = readl(virtbase + D40_DREG_PERIPHID2);
if ((val & 0xf) != D40_PERIPHID2_DESIGNER) { if ((val & D40_DREG_PERIPHID2_DESIGNER_MASK) !=
D40_HW_DESIGNER) {
dev_err(&pdev->dev, dev_err(&pdev->dev,
"[%s] Unknown designer! Got %x wanted %x\n", "[%s] Unknown designer! Got %x wanted %x\n",
__func__, val & 0xf, D40_PERIPHID2_DESIGNER); __func__, val & D40_DREG_PERIPHID2_DESIGNER_MASK,
D40_HW_DESIGNER);
goto failure; goto failure;
} }
rev = (val & D40_DREG_PERIPHID2_REV_MASK) >>
D40_DREG_PERIPHID2_REV_POS;
/* The number of physical channels on this HW */ /* The number of physical channels on this HW */
num_phy_chans = 4 * (readl(virtbase + D40_DREG_ICFG) & 0x7) + 4; num_phy_chans = 4 * (readl(virtbase + D40_DREG_ICFG) & 0x7) + 4;
dev_info(&pdev->dev, "hardware revision: %d @ 0x%x\n", dev_info(&pdev->dev, "hardware revision: %d @ 0x%x\n",
(val >> 4) & 0xf, res->start); rev, res->start);
plat_data = pdev->dev.platform_data; plat_data = pdev->dev.platform_data;
...@@ -2515,7 +2604,7 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) ...@@ -2515,7 +2604,7 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev)
goto failure; goto failure;
} }
base->rev = (val >> 4) & 0xf; base->rev = rev;
base->clk = clk; base->clk = clk;
base->num_phy_chans = num_phy_chans; base->num_phy_chans = num_phy_chans;
base->num_log_chans = num_log_chans; base->num_log_chans = num_log_chans;
...@@ -2549,7 +2638,10 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) ...@@ -2549,7 +2638,10 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev)
if (!base->lookup_log_chans) if (!base->lookup_log_chans)
goto failure; goto failure;
} }
base->lcla_pool.alloc_map = kzalloc(num_phy_chans * sizeof(u32),
base->lcla_pool.alloc_map = kzalloc(num_phy_chans *
sizeof(struct d40_desc *) *
D40_LCLA_LINK_PER_EVENT_GRP,
GFP_KERNEL); GFP_KERNEL);
if (!base->lcla_pool.alloc_map) if (!base->lcla_pool.alloc_map)
goto failure; goto failure;
...@@ -2563,7 +2655,7 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) ...@@ -2563,7 +2655,7 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev)
return base; return base;
failure: failure:
if (clk) { if (!IS_ERR(clk)) {
clk_disable(clk); clk_disable(clk);
clk_put(clk); clk_put(clk);
} }
...@@ -2700,8 +2792,10 @@ static int __init d40_lcla_allocate(struct d40_base *base) ...@@ -2700,8 +2792,10 @@ static int __init d40_lcla_allocate(struct d40_base *base)
if (i < MAX_LCLA_ALLOC_ATTEMPTS) { if (i < MAX_LCLA_ALLOC_ATTEMPTS) {
base->lcla_pool.base = (void *)page_list[i]; base->lcla_pool.base = (void *)page_list[i];
} else { } else {
/* After many attempts, no succees with finding the correct /*
* alignment try with allocating a big buffer */ * After many attempts and no succees with finding the correct
* alignment, try with allocating a big buffer.
*/
dev_warn(base->dev, dev_warn(base->dev,
"[%s] Failed to get %d pages @ 18 bit align.\n", "[%s] Failed to get %d pages @ 18 bit align.\n",
__func__, base->lcla_pool.pages); __func__, base->lcla_pool.pages);
...@@ -2794,8 +2888,6 @@ static int __init d40_probe(struct platform_device *pdev) ...@@ -2794,8 +2888,6 @@ static int __init d40_probe(struct platform_device *pdev)
spin_lock_init(&base->lcla_pool.lock); spin_lock_init(&base->lcla_pool.lock);
base->lcla_pool.num_blocks = base->num_phy_chans;
base->irq = platform_get_irq(pdev, 0); base->irq = platform_get_irq(pdev, 0);
ret = request_irq(base->irq, d40_handle_interrupt, 0, D40_NAME, base); ret = request_irq(base->irq, d40_handle_interrupt, 0, D40_NAME, base);
...@@ -2823,8 +2915,9 @@ static int __init d40_probe(struct platform_device *pdev) ...@@ -2823,8 +2915,9 @@ static int __init d40_probe(struct platform_device *pdev)
if (!base->lcla_pool.base_unaligned && base->lcla_pool.base) if (!base->lcla_pool.base_unaligned && base->lcla_pool.base)
free_pages((unsigned long)base->lcla_pool.base, free_pages((unsigned long)base->lcla_pool.base,
base->lcla_pool.pages); base->lcla_pool.pages);
if (base->lcla_pool.base_unaligned)
kfree(base->lcla_pool.base_unaligned); kfree(base->lcla_pool.base_unaligned);
if (base->phy_lcpa) if (base->phy_lcpa)
release_mem_region(base->phy_lcpa, release_mem_region(base->phy_lcpa,
base->lcpa_size); base->lcpa_size);
......
/* /*
* driver/dma/ste_dma40_ll.c * Copyright (C) ST-Ericsson SA 2007-2010
* * Author: Per Friden <per.friden@stericsson.com> for ST-Ericsson
* Copyright (C) ST-Ericsson 2007-2010 * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson
* License terms: GNU General Public License (GPL) version 2 * License terms: GNU General Public License (GPL) version 2
* Author: Per Friden <per.friden@stericsson.com>
* Author: Jonas Aaberg <jonas.aberg@stericsson.com>
*/ */
#include <linux/kernel.h> #include <linux/kernel.h>
...@@ -39,16 +37,13 @@ void d40_log_cfg(struct stedma40_chan_cfg *cfg, ...@@ -39,16 +37,13 @@ void d40_log_cfg(struct stedma40_chan_cfg *cfg,
cfg->dir == STEDMA40_PERIPH_TO_PERIPH) cfg->dir == STEDMA40_PERIPH_TO_PERIPH)
l3 |= 1 << D40_MEM_LCSP3_DCFG_MST_POS; l3 |= 1 << D40_MEM_LCSP3_DCFG_MST_POS;
l3 |= 1 << D40_MEM_LCSP3_DCFG_TIM_POS;
l3 |= 1 << D40_MEM_LCSP3_DCFG_EIM_POS; l3 |= 1 << D40_MEM_LCSP3_DCFG_EIM_POS;
l3 |= cfg->dst_info.psize << D40_MEM_LCSP3_DCFG_PSIZE_POS; l3 |= cfg->dst_info.psize << D40_MEM_LCSP3_DCFG_PSIZE_POS;
l3 |= cfg->dst_info.data_width << D40_MEM_LCSP3_DCFG_ESIZE_POS; l3 |= cfg->dst_info.data_width << D40_MEM_LCSP3_DCFG_ESIZE_POS;
l3 |= 1 << D40_MEM_LCSP3_DTCP_POS;
l1 |= 1 << D40_MEM_LCSP1_SCFG_EIM_POS; l1 |= 1 << D40_MEM_LCSP1_SCFG_EIM_POS;
l1 |= cfg->src_info.psize << D40_MEM_LCSP1_SCFG_PSIZE_POS; l1 |= cfg->src_info.psize << D40_MEM_LCSP1_SCFG_PSIZE_POS;
l1 |= cfg->src_info.data_width << D40_MEM_LCSP1_SCFG_ESIZE_POS; l1 |= cfg->src_info.data_width << D40_MEM_LCSP1_SCFG_ESIZE_POS;
l1 |= 1 << D40_MEM_LCSP1_STCP_POS;
*lcsp1 = l1; *lcsp1 = l1;
*lcsp3 = l3; *lcsp3 = l3;
...@@ -113,13 +108,15 @@ void d40_phy_cfg(struct stedma40_chan_cfg *cfg, ...@@ -113,13 +108,15 @@ void d40_phy_cfg(struct stedma40_chan_cfg *cfg,
src |= 1 << D40_SREG_CFG_LOG_GIM_POS; src |= 1 << D40_SREG_CFG_LOG_GIM_POS;
} }
if (cfg->channel_type & STEDMA40_HIGH_PRIORITY_CHANNEL) { if (cfg->high_priority) {
src |= 1 << D40_SREG_CFG_PRI_POS; src |= 1 << D40_SREG_CFG_PRI_POS;
dst |= 1 << D40_SREG_CFG_PRI_POS; dst |= 1 << D40_SREG_CFG_PRI_POS;
} }
src |= cfg->src_info.endianess << D40_SREG_CFG_LBE_POS; if (cfg->src_info.big_endian)
dst |= cfg->dst_info.endianess << D40_SREG_CFG_LBE_POS; src |= 1 << D40_SREG_CFG_LBE_POS;
if (cfg->dst_info.big_endian)
dst |= 1 << D40_SREG_CFG_LBE_POS;
*src_cfg = src; *src_cfg = src;
*dst_cfg = dst; *dst_cfg = dst;
...@@ -197,8 +194,7 @@ int d40_phy_sg_to_lli(struct scatterlist *sg, ...@@ -197,8 +194,7 @@ int d40_phy_sg_to_lli(struct scatterlist *sg,
dma_addr_t lli_phys, dma_addr_t lli_phys,
u32 reg_cfg, u32 reg_cfg,
u32 data_width, u32 data_width,
int psize, int psize)
bool term_int)
{ {
int total_size = 0; int total_size = 0;
int i; int i;
...@@ -238,7 +234,7 @@ int d40_phy_sg_to_lli(struct scatterlist *sg, ...@@ -238,7 +234,7 @@ int d40_phy_sg_to_lli(struct scatterlist *sg,
} }
return total_size; return total_size;
err: err:
return err; return err;
} }
...@@ -271,11 +267,59 @@ void d40_phy_lli_write(void __iomem *virtbase, ...@@ -271,11 +267,59 @@ void d40_phy_lli_write(void __iomem *virtbase,
/* DMA logical lli operations */ /* DMA logical lli operations */
static void d40_log_lli_link(struct d40_log_lli *lli_dst,
struct d40_log_lli *lli_src,
int next)
{
u32 slos = 0;
u32 dlos = 0;
if (next != -EINVAL) {
slos = next * 2;
dlos = next * 2 + 1;
} else {
lli_dst->lcsp13 |= D40_MEM_LCSP1_SCFG_TIM_MASK;
lli_dst->lcsp13 |= D40_MEM_LCSP3_DTCP_MASK;
}
lli_src->lcsp13 = (lli_src->lcsp13 & ~D40_MEM_LCSP1_SLOS_MASK) |
(slos << D40_MEM_LCSP1_SLOS_POS);
lli_dst->lcsp13 = (lli_dst->lcsp13 & ~D40_MEM_LCSP1_SLOS_MASK) |
(dlos << D40_MEM_LCSP1_SLOS_POS);
}
void d40_log_lli_lcpa_write(struct d40_log_lli_full *lcpa,
struct d40_log_lli *lli_dst,
struct d40_log_lli *lli_src,
int next)
{
d40_log_lli_link(lli_dst, lli_src, next);
writel(lli_src->lcsp02, &lcpa[0].lcsp0);
writel(lli_src->lcsp13, &lcpa[0].lcsp1);
writel(lli_dst->lcsp02, &lcpa[0].lcsp2);
writel(lli_dst->lcsp13, &lcpa[0].lcsp3);
}
void d40_log_lli_lcla_write(struct d40_log_lli *lcla,
struct d40_log_lli *lli_dst,
struct d40_log_lli *lli_src,
int next)
{
d40_log_lli_link(lli_dst, lli_src, next);
writel(lli_src->lcsp02, &lcla[0].lcsp02);
writel(lli_src->lcsp13, &lcla[0].lcsp13);
writel(lli_dst->lcsp02, &lcla[1].lcsp02);
writel(lli_dst->lcsp13, &lcla[1].lcsp13);
}
void d40_log_fill_lli(struct d40_log_lli *lli, void d40_log_fill_lli(struct d40_log_lli *lli,
dma_addr_t data, u32 data_size, dma_addr_t data, u32 data_size,
u32 lli_next_off, u32 reg_cfg, u32 reg_cfg,
u32 data_width, u32 data_width,
bool term_int, bool addr_inc) bool addr_inc)
{ {
lli->lcsp13 = reg_cfg; lli->lcsp13 = reg_cfg;
...@@ -290,165 +334,69 @@ void d40_log_fill_lli(struct d40_log_lli *lli, ...@@ -290,165 +334,69 @@ void d40_log_fill_lli(struct d40_log_lli *lli,
if (addr_inc) if (addr_inc)
lli->lcsp13 |= D40_MEM_LCSP1_SCFG_INCR_MASK; lli->lcsp13 |= D40_MEM_LCSP1_SCFG_INCR_MASK;
lli->lcsp13 |= D40_MEM_LCSP3_DTCP_MASK;
/* If this scatter list entry is the last one, no next link */
lli->lcsp13 |= (lli_next_off << D40_MEM_LCSP1_SLOS_POS) &
D40_MEM_LCSP1_SLOS_MASK;
if (term_int)
lli->lcsp13 |= D40_MEM_LCSP1_SCFG_TIM_MASK;
else
lli->lcsp13 &= ~D40_MEM_LCSP1_SCFG_TIM_MASK;
} }
int d40_log_sg_to_dev(struct d40_lcla_elem *lcla, int d40_log_sg_to_dev(struct scatterlist *sg,
struct scatterlist *sg,
int sg_len, int sg_len,
struct d40_log_lli_bidir *lli, struct d40_log_lli_bidir *lli,
struct d40_def_lcsp *lcsp, struct d40_def_lcsp *lcsp,
u32 src_data_width, u32 src_data_width,
u32 dst_data_width, u32 dst_data_width,
enum dma_data_direction direction, enum dma_data_direction direction,
bool term_int, dma_addr_t dev_addr, int max_len, dma_addr_t dev_addr)
int llis_per_log)
{ {
int total_size = 0; int total_size = 0;
struct scatterlist *current_sg = sg; struct scatterlist *current_sg = sg;
int i; int i;
u32 next_lli_off_dst = 0;
u32 next_lli_off_src = 0;
for_each_sg(sg, current_sg, sg_len, i) { for_each_sg(sg, current_sg, sg_len, i) {
total_size += sg_dma_len(current_sg); total_size += sg_dma_len(current_sg);
/*
* If this scatter list entry is the last one or
* max length, terminate link.
*/
if (sg_len - 1 == i || ((i+1) % max_len == 0)) {
next_lli_off_src = 0;
next_lli_off_dst = 0;
} else {
if (next_lli_off_dst == 0 &&
next_lli_off_src == 0) {
/* The first lli will be at next_lli_off */
next_lli_off_dst = (lcla->dst_id *
llis_per_log + 1);
next_lli_off_src = (lcla->src_id *
llis_per_log + 1);
} else {
next_lli_off_dst++;
next_lli_off_src++;
}
}
if (direction == DMA_TO_DEVICE) { if (direction == DMA_TO_DEVICE) {
d40_log_fill_lli(&lli->src[i], d40_log_fill_lli(&lli->src[i],
sg_phys(current_sg), sg_phys(current_sg),
sg_dma_len(current_sg), sg_dma_len(current_sg),
next_lli_off_src,
lcsp->lcsp1, src_data_width, lcsp->lcsp1, src_data_width,
false,
true); true);
d40_log_fill_lli(&lli->dst[i], d40_log_fill_lli(&lli->dst[i],
dev_addr, dev_addr,
sg_dma_len(current_sg), sg_dma_len(current_sg),
next_lli_off_dst,
lcsp->lcsp3, dst_data_width, lcsp->lcsp3, dst_data_width,
/* No next == terminal interrupt */
term_int && !next_lli_off_dst,
false); false);
} else { } else {
d40_log_fill_lli(&lli->dst[i], d40_log_fill_lli(&lli->dst[i],
sg_phys(current_sg), sg_phys(current_sg),
sg_dma_len(current_sg), sg_dma_len(current_sg),
next_lli_off_dst,
lcsp->lcsp3, dst_data_width, lcsp->lcsp3, dst_data_width,
/* No next == terminal interrupt */
term_int && !next_lli_off_dst,
true); true);
d40_log_fill_lli(&lli->src[i], d40_log_fill_lli(&lli->src[i],
dev_addr, dev_addr,
sg_dma_len(current_sg), sg_dma_len(current_sg),
next_lli_off_src,
lcsp->lcsp1, src_data_width, lcsp->lcsp1, src_data_width,
false,
false); false);
} }
} }
return total_size; return total_size;
} }
int d40_log_sg_to_lli(int lcla_id, int d40_log_sg_to_lli(struct scatterlist *sg,
struct scatterlist *sg,
int sg_len, int sg_len,
struct d40_log_lli *lli_sg, struct d40_log_lli *lli_sg,
u32 lcsp13, /* src or dst*/ u32 lcsp13, /* src or dst*/
u32 data_width, u32 data_width)
bool term_int, int max_len, int llis_per_log)
{ {
int total_size = 0; int total_size = 0;
struct scatterlist *current_sg = sg; struct scatterlist *current_sg = sg;
int i; int i;
u32 next_lli_off = 0;
for_each_sg(sg, current_sg, sg_len, i) { for_each_sg(sg, current_sg, sg_len, i) {
total_size += sg_dma_len(current_sg); total_size += sg_dma_len(current_sg);
/*
* If this scatter list entry is the last one or
* max length, terminate link.
*/
if (sg_len - 1 == i || ((i+1) % max_len == 0))
next_lli_off = 0;
else {
if (next_lli_off == 0)
/* The first lli will be at next_lli_off */
next_lli_off = lcla_id * llis_per_log + 1;
else
next_lli_off++;
}
d40_log_fill_lli(&lli_sg[i], d40_log_fill_lli(&lli_sg[i],
sg_phys(current_sg), sg_phys(current_sg),
sg_dma_len(current_sg), sg_dma_len(current_sg),
next_lli_off,
lcsp13, data_width, lcsp13, data_width,
term_int && !next_lli_off,
true); true);
} }
return total_size; return total_size;
} }
int d40_log_lli_write(struct d40_log_lli_full *lcpa,
struct d40_log_lli *lcla_src,
struct d40_log_lli *lcla_dst,
struct d40_log_lli *lli_dst,
struct d40_log_lli *lli_src,
int llis_per_log)
{
u32 slos;
u32 dlos;
int i;
writel(lli_src->lcsp02, &lcpa->lcsp0);
writel(lli_src->lcsp13, &lcpa->lcsp1);
writel(lli_dst->lcsp02, &lcpa->lcsp2);
writel(lli_dst->lcsp13, &lcpa->lcsp3);
slos = lli_src->lcsp13 & D40_MEM_LCSP1_SLOS_MASK;
dlos = lli_dst->lcsp13 & D40_MEM_LCSP3_DLOS_MASK;
for (i = 0; (i < llis_per_log) && slos && dlos; i++) {
writel(lli_src[i + 1].lcsp02, &lcla_src[i].lcsp02);
writel(lli_src[i + 1].lcsp13, &lcla_src[i].lcsp13);
writel(lli_dst[i + 1].lcsp02, &lcla_dst[i].lcsp02);
writel(lli_dst[i + 1].lcsp13, &lcla_dst[i].lcsp13);
slos = lli_src[i + 1].lcsp13 & D40_MEM_LCSP1_SLOS_MASK;
dlos = lli_dst[i + 1].lcsp13 & D40_MEM_LCSP3_DLOS_MASK;
}
return i;
}
/* /*
* driver/dma/ste_dma40_ll.h * Copyright (C) ST-Ericsson SA 2007-2010
* * Author: Per Friden <per.friden@stericsson.com> for ST-Ericsson SA
* Copyright (C) ST-Ericsson 2007-2010 * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson SA
* License terms: GNU General Public License (GPL) version 2 * License terms: GNU General Public License (GPL) version 2
* Author: Per Friden <per.friden@stericsson.com>
* Author: Jonas Aaberg <jonas.aberg@stericsson.com>
*/ */
#ifndef STE_DMA40_LL_H #ifndef STE_DMA40_LL_H
#define STE_DMA40_LL_H #define STE_DMA40_LL_H
...@@ -132,6 +130,13 @@ ...@@ -132,6 +130,13 @@
#define D40_DREG_PRMSO 0x014 #define D40_DREG_PRMSO 0x014
#define D40_DREG_PRMOE 0x018 #define D40_DREG_PRMOE 0x018
#define D40_DREG_PRMOO 0x01C #define D40_DREG_PRMOO 0x01C
#define D40_DREG_PRMO_PCHAN_BASIC 0x1
#define D40_DREG_PRMO_PCHAN_MODULO 0x2
#define D40_DREG_PRMO_PCHAN_DOUBLE_DST 0x3
#define D40_DREG_PRMO_LCHAN_SRC_PHY_DST_LOG 0x1
#define D40_DREG_PRMO_LCHAN_SRC_LOG_DST_PHY 0x2
#define D40_DREG_PRMO_LCHAN_SRC_LOG_DST_LOG 0x3
#define D40_DREG_LCPA 0x020 #define D40_DREG_LCPA 0x020
#define D40_DREG_LCLA 0x024 #define D40_DREG_LCLA 0x024
#define D40_DREG_ACTIVE 0x050 #define D40_DREG_ACTIVE 0x050
...@@ -163,6 +168,9 @@ ...@@ -163,6 +168,9 @@
#define D40_DREG_PERIPHID0 0xFE0 #define D40_DREG_PERIPHID0 0xFE0
#define D40_DREG_PERIPHID1 0xFE4 #define D40_DREG_PERIPHID1 0xFE4
#define D40_DREG_PERIPHID2 0xFE8 #define D40_DREG_PERIPHID2 0xFE8
#define D40_DREG_PERIPHID2_REV_POS 4
#define D40_DREG_PERIPHID2_REV_MASK (0xf << D40_DREG_PERIPHID2_REV_POS)
#define D40_DREG_PERIPHID2_DESIGNER_MASK 0xf
#define D40_DREG_PERIPHID3 0xFEC #define D40_DREG_PERIPHID3 0xFEC
#define D40_DREG_CELLID0 0xFF0 #define D40_DREG_CELLID0 0xFF0
#define D40_DREG_CELLID1 0xFF4 #define D40_DREG_CELLID1 0xFF4
...@@ -199,8 +207,6 @@ struct d40_phy_lli { ...@@ -199,8 +207,6 @@ struct d40_phy_lli {
* *
* @src: Register settings for src channel. * @src: Register settings for src channel.
* @dst: Register settings for dst channel. * @dst: Register settings for dst channel.
* @dst_addr: Physical destination address.
* @src_addr: Physical source address.
* *
* All DMA transfers have a source and a destination. * All DMA transfers have a source and a destination.
*/ */
...@@ -208,8 +214,6 @@ struct d40_phy_lli { ...@@ -208,8 +214,6 @@ struct d40_phy_lli {
struct d40_phy_lli_bidir { struct d40_phy_lli_bidir {
struct d40_phy_lli *src; struct d40_phy_lli *src;
struct d40_phy_lli *dst; struct d40_phy_lli *dst;
dma_addr_t dst_addr;
dma_addr_t src_addr;
}; };
...@@ -271,29 +275,16 @@ struct d40_def_lcsp { ...@@ -271,29 +275,16 @@ struct d40_def_lcsp {
u32 lcsp1; u32 lcsp1;
}; };
/**
* struct d40_lcla_elem - Info for one LCA element.
*
* @src_id: logical channel src id
* @dst_id: logical channel dst id
* @src: LCPA formated src parameters
* @dst: LCPA formated dst parameters
*
*/
struct d40_lcla_elem {
int src_id;
int dst_id;
struct d40_log_lli *src;
struct d40_log_lli *dst;
};
/* Physical channels */ /* Physical channels */
void d40_phy_cfg(struct stedma40_chan_cfg *cfg, void d40_phy_cfg(struct stedma40_chan_cfg *cfg,
u32 *src_cfg, u32 *dst_cfg, bool is_log); u32 *src_cfg,
u32 *dst_cfg,
bool is_log);
void d40_log_cfg(struct stedma40_chan_cfg *cfg, void d40_log_cfg(struct stedma40_chan_cfg *cfg,
u32 *lcsp1, u32 *lcsp2); u32 *lcsp1,
u32 *lcsp2);
int d40_phy_sg_to_lli(struct scatterlist *sg, int d40_phy_sg_to_lli(struct scatterlist *sg,
int sg_len, int sg_len,
...@@ -302,8 +293,7 @@ int d40_phy_sg_to_lli(struct scatterlist *sg, ...@@ -302,8 +293,7 @@ int d40_phy_sg_to_lli(struct scatterlist *sg,
dma_addr_t lli_phys, dma_addr_t lli_phys,
u32 reg_cfg, u32 reg_cfg,
u32 data_width, u32 data_width,
int psize, int psize);
bool term_int);
int d40_phy_fill_lli(struct d40_phy_lli *lli, int d40_phy_fill_lli(struct d40_phy_lli *lli,
dma_addr_t data, dma_addr_t data,
...@@ -323,35 +313,35 @@ void d40_phy_lli_write(void __iomem *virtbase, ...@@ -323,35 +313,35 @@ void d40_phy_lli_write(void __iomem *virtbase,
/* Logical channels */ /* Logical channels */
void d40_log_fill_lli(struct d40_log_lli *lli, void d40_log_fill_lli(struct d40_log_lli *lli,
dma_addr_t data, u32 data_size, dma_addr_t data,
u32 lli_next_off, u32 reg_cfg, u32 data_size,
u32 reg_cfg,
u32 data_width, u32 data_width,
bool term_int, bool addr_inc); bool addr_inc);
int d40_log_sg_to_dev(struct d40_lcla_elem *lcla, int d40_log_sg_to_dev(struct scatterlist *sg,
struct scatterlist *sg,
int sg_len, int sg_len,
struct d40_log_lli_bidir *lli, struct d40_log_lli_bidir *lli,
struct d40_def_lcsp *lcsp, struct d40_def_lcsp *lcsp,
u32 src_data_width, u32 src_data_width,
u32 dst_data_width, u32 dst_data_width,
enum dma_data_direction direction, enum dma_data_direction direction,
bool term_int, dma_addr_t dev_addr, int max_len, dma_addr_t dev_addr);
int llis_per_log);
int d40_log_sg_to_lli(struct scatterlist *sg,
int d40_log_lli_write(struct d40_log_lli_full *lcpa,
struct d40_log_lli *lcla_src,
struct d40_log_lli *lcla_dst,
struct d40_log_lli *lli_dst,
struct d40_log_lli *lli_src,
int llis_per_log);
int d40_log_sg_to_lli(int lcla_id,
struct scatterlist *sg,
int sg_len, int sg_len,
struct d40_log_lli *lli_sg, struct d40_log_lli *lli_sg,
u32 lcsp13, /* src or dst*/ u32 lcsp13, /* src or dst*/
u32 data_width, u32 data_width);
bool term_int, int max_len, int llis_per_log);
void d40_log_lli_lcpa_write(struct d40_log_lli_full *lcpa,
struct d40_log_lli *lli_dst,
struct d40_log_lli *lli_src,
int next);
void d40_log_lli_lcla_write(struct d40_log_lli *lcla,
struct d40_log_lli *lli_dst,
struct d40_log_lli *lli_src,
int next);
#endif /* STE_DMA40_LLI_H */ #endif /* STE_DMA40_LLI_H */
...@@ -759,7 +759,7 @@ static int __devinit td_probe(struct platform_device *pdev) ...@@ -759,7 +759,7 @@ static int __devinit td_probe(struct platform_device *pdev)
pdata->channels + i; pdata->channels + i;
/* even channels are RX, odd are TX */ /* even channels are RX, odd are TX */
if (((i % 2) && pchan->rx) || (!(i % 2) && !pchan->rx)) { if ((i % 2) == pchan->rx) {
dev_err(&pdev->dev, "Wrong channel configuration\n"); dev_err(&pdev->dev, "Wrong channel configuration\n");
err = -EINVAL; err = -EINVAL;
goto err_tasklet_kill; goto err_tasklet_kill;
......
/*
* linux/amba/pl08x.h - ARM PrimeCell DMA Controller driver
*
* Copyright (C) 2005 ARM Ltd
* Copyright (C) 2010 ST-Ericsson SA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* pl08x information required by platform code
*
* Please credit ARM.com
* Documentation: ARM DDI 0196D
*
*/
#ifndef AMBA_PL08X_H
#define AMBA_PL08X_H
/* We need sizes of structs from this header */
#include <linux/dmaengine.h>
#include <linux/interrupt.h>
/**
* struct pl08x_channel_data - data structure to pass info between
* platform and PL08x driver regarding channel configuration
* @bus_id: name of this device channel, not just a device name since
* devices may have more than one channel e.g. "foo_tx"
* @min_signal: the minimum DMA signal number to be muxed in for this
* channel (for platforms supporting muxed signals). If you have
* static assignments, make sure this is set to the assigned signal
* number, PL08x have 16 possible signals in number 0 thru 15 so
* when these are not enough they often get muxed (in hardware)
* disabling simultaneous use of the same channel for two devices.
* @max_signal: the maximum DMA signal number to be muxed in for
* the channel. Set to the same as min_signal for
* devices with static assignments
* @muxval: a number usually used to poke into some mux regiser to
* mux in the signal to this channel
* @cctl_opt: default options for the channel control register
* @addr: source/target address in physical memory for this DMA channel,
* can be the address of a FIFO register for burst requests for example.
* This can be left undefined if the PrimeCell API is used for configuring
* this.
* @circular_buffer: whether the buffer passed in is circular and
* shall simply be looped round round (like a record baby round
* round round round)
* @single: the device connected to this channel will request single
* DMA transfers, not bursts. (Bursts are default.)
*/
struct pl08x_channel_data {
char *bus_id;
int min_signal;
int max_signal;
u32 muxval;
u32 cctl;
u32 ccfg;
dma_addr_t addr;
bool circular_buffer;
bool single;
};
/**
* Struct pl08x_bus_data - information of source or destination
* busses for a transfer
* @addr: current address
* @maxwidth: the maximum width of a transfer on this bus
* @buswidth: the width of this bus in bytes: 1, 2 or 4
* @fill_bytes: bytes required to fill to the next bus memory
* boundary
*/
struct pl08x_bus_data {
dma_addr_t addr;
u8 maxwidth;
u8 buswidth;
u32 fill_bytes;
};
/**
* struct pl08x_phy_chan - holder for the physical channels
* @id: physical index to this channel
* @lock: a lock to use when altering an instance of this struct
* @signal: the physical signal (aka channel) serving this
* physical channel right now
* @serving: the virtual channel currently being served by this
* physical channel
*/
struct pl08x_phy_chan {
unsigned int id;
void __iomem *base;
spinlock_t lock;
int signal;
struct pl08x_dma_chan *serving;
u32 csrc;
u32 cdst;
u32 clli;
u32 cctl;
u32 ccfg;
};
/**
* struct pl08x_txd - wrapper for struct dma_async_tx_descriptor
* @llis_bus: DMA memory address (physical) start for the LLIs
* @llis_va: virtual memory address start for the LLIs
*/
struct pl08x_txd {
struct dma_async_tx_descriptor tx;
struct list_head node;
enum dma_data_direction direction;
struct pl08x_bus_data srcbus;
struct pl08x_bus_data dstbus;
int len;
dma_addr_t llis_bus;
void *llis_va;
struct pl08x_channel_data *cd;
bool active;
/*
* Settings to be put into the physical channel when we
* trigger this txd
*/
u32 csrc;
u32 cdst;
u32 clli;
u32 cctl;
};
/**
* struct pl08x_dma_chan_state - holds the PL08x specific virtual
* channel states
* @PL08X_CHAN_IDLE: the channel is idle
* @PL08X_CHAN_RUNNING: the channel has allocated a physical transport
* channel and is running a transfer on it
* @PL08X_CHAN_PAUSED: the channel has allocated a physical transport
* channel, but the transfer is currently paused
* @PL08X_CHAN_WAITING: the channel is waiting for a physical transport
* channel to become available (only pertains to memcpy channels)
*/
enum pl08x_dma_chan_state {
PL08X_CHAN_IDLE,
PL08X_CHAN_RUNNING,
PL08X_CHAN_PAUSED,
PL08X_CHAN_WAITING,
};
/**
* struct pl08x_dma_chan - this structure wraps a DMA ENGINE channel
* @chan: wrappped abstract channel
* @phychan: the physical channel utilized by this channel, if there is one
* @tasklet: tasklet scheduled by the IRQ to handle actual work etc
* @name: name of channel
* @cd: channel platform data
* @runtime_addr: address for RX/TX according to the runtime config
* @runtime_direction: current direction of this channel according to
* runtime config
* @lc: last completed transaction on this channel
* @desc_list: queued transactions pending on this channel
* @at: active transaction on this channel
* @lockflags: sometimes we let a lock last between two function calls,
* especially prep/submit, and then we need to store the IRQ flags
* in the channel state, here
* @lock: a lock for this channel data
* @host: a pointer to the host (internal use)
* @state: whether the channel is idle, paused, running etc
* @slave: whether this channel is a device (slave) or for memcpy
* @waiting: a TX descriptor on this channel which is waiting for
* a physical channel to become available
*/
struct pl08x_dma_chan {
struct dma_chan chan;
struct pl08x_phy_chan *phychan;
struct tasklet_struct tasklet;
char *name;
struct pl08x_channel_data *cd;
dma_addr_t runtime_addr;
enum dma_data_direction runtime_direction;
atomic_t last_issued;
dma_cookie_t lc;
struct list_head desc_list;
struct pl08x_txd *at;
unsigned long lockflags;
spinlock_t lock;
void *host;
enum pl08x_dma_chan_state state;
bool slave;
struct pl08x_txd *waiting;
};
/**
* struct pl08x_platform_data - the platform configuration for the
* PL08x PrimeCells.
* @slave_channels: the channels defined for the different devices on the
* platform, all inclusive, including multiplexed channels. The available
* physical channels will be multiplexed around these signals as they
* are requested, just enumerate all possible channels.
* @get_signal: request a physical signal to be used for a DMA
* transfer immediately: if there is some multiplexing or similar blocking
* the use of the channel the transfer can be denied by returning
* less than zero, else it returns the allocated signal number
* @put_signal: indicate to the platform that this physical signal is not
* running any DMA transfer and multiplexing can be recycled
* @bus_bit_lli: Bit[0] of the address indicated which AHB bus master the
* LLI addresses are on 0/1 Master 1/2.
*/
struct pl08x_platform_data {
struct pl08x_channel_data *slave_channels;
unsigned int num_slave_channels;
struct pl08x_channel_data memcpy_channel;
int (*get_signal)(struct pl08x_dma_chan *);
void (*put_signal)(struct pl08x_dma_chan *);
};
#ifdef CONFIG_AMBA_PL08X
bool pl08x_filter_id(struct dma_chan *chan, void *chan_id);
#else
static inline bool pl08x_filter_id(struct dma_chan *chan, void *chan_id)
{
return false;
}
#endif
#endif /* AMBA_PL08X_H */
...@@ -64,13 +64,15 @@ enum dma_transaction_type { ...@@ -64,13 +64,15 @@ enum dma_transaction_type {
DMA_PQ_VAL, DMA_PQ_VAL,
DMA_MEMSET, DMA_MEMSET,
DMA_INTERRUPT, DMA_INTERRUPT,
DMA_SG,
DMA_PRIVATE, DMA_PRIVATE,
DMA_ASYNC_TX, DMA_ASYNC_TX,
DMA_SLAVE, DMA_SLAVE,
DMA_CYCLIC,
}; };
/* last transaction type for creation of the capabilities mask */ /* last transaction type for creation of the capabilities mask */
#define DMA_TX_TYPE_END (DMA_SLAVE + 1) #define DMA_TX_TYPE_END (DMA_CYCLIC + 1)
/** /**
...@@ -119,12 +121,15 @@ enum dma_ctrl_flags { ...@@ -119,12 +121,15 @@ enum dma_ctrl_flags {
* configuration data in statically from the platform). An additional * configuration data in statically from the platform). An additional
* argument of struct dma_slave_config must be passed in with this * argument of struct dma_slave_config must be passed in with this
* command. * command.
* @FSLDMA_EXTERNAL_START: this command will put the Freescale DMA controller
* into external start mode.
*/ */
enum dma_ctrl_cmd { enum dma_ctrl_cmd {
DMA_TERMINATE_ALL, DMA_TERMINATE_ALL,
DMA_PAUSE, DMA_PAUSE,
DMA_RESUME, DMA_RESUME,
DMA_SLAVE_CONFIG, DMA_SLAVE_CONFIG,
FSLDMA_EXTERNAL_START,
}; };
/** /**
...@@ -316,14 +321,14 @@ struct dma_async_tx_descriptor { ...@@ -316,14 +321,14 @@ struct dma_async_tx_descriptor {
dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx); dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx);
dma_async_tx_callback callback; dma_async_tx_callback callback;
void *callback_param; void *callback_param;
#ifndef CONFIG_ASYNC_TX_DISABLE_CHANNEL_SWITCH #ifdef CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH
struct dma_async_tx_descriptor *next; struct dma_async_tx_descriptor *next;
struct dma_async_tx_descriptor *parent; struct dma_async_tx_descriptor *parent;
spinlock_t lock; spinlock_t lock;
#endif #endif
}; };
#ifdef CONFIG_ASYNC_TX_DISABLE_CHANNEL_SWITCH #ifndef CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH
static inline void txd_lock(struct dma_async_tx_descriptor *txd) static inline void txd_lock(struct dma_async_tx_descriptor *txd)
{ {
} }
...@@ -422,6 +427,9 @@ struct dma_tx_state { ...@@ -422,6 +427,9 @@ struct dma_tx_state {
* @device_prep_dma_memset: prepares a memset operation * @device_prep_dma_memset: prepares a memset operation
* @device_prep_dma_interrupt: prepares an end of chain interrupt operation * @device_prep_dma_interrupt: prepares an end of chain interrupt operation
* @device_prep_slave_sg: prepares a slave dma operation * @device_prep_slave_sg: prepares a slave dma operation
* @device_prep_dma_cyclic: prepare a cyclic dma operation suitable for audio.
* The function takes a buffer of size buf_len. The callback function will
* be called after period_len bytes have been transferred.
* @device_control: manipulate all pending operations on a channel, returns * @device_control: manipulate all pending operations on a channel, returns
* zero or error code * zero or error code
* @device_tx_status: poll for transaction completion, the optional * @device_tx_status: poll for transaction completion, the optional
...@@ -473,11 +481,19 @@ struct dma_device { ...@@ -473,11 +481,19 @@ struct dma_device {
unsigned long flags); unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_interrupt)( struct dma_async_tx_descriptor *(*device_prep_dma_interrupt)(
struct dma_chan *chan, unsigned long flags); struct dma_chan *chan, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_sg)(
struct dma_chan *chan,
struct scatterlist *dst_sg, unsigned int dst_nents,
struct scatterlist *src_sg, unsigned int src_nents,
unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_slave_sg)( struct dma_async_tx_descriptor *(*device_prep_slave_sg)(
struct dma_chan *chan, struct scatterlist *sgl, struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_data_direction direction, unsigned int sg_len, enum dma_data_direction direction,
unsigned long flags); unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_cyclic)(
struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
size_t period_len, enum dma_data_direction direction);
int (*device_control)(struct dma_chan *chan, enum dma_ctrl_cmd cmd, int (*device_control)(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
unsigned long arg); unsigned long arg);
...@@ -487,6 +503,40 @@ struct dma_device { ...@@ -487,6 +503,40 @@ struct dma_device {
void (*device_issue_pending)(struct dma_chan *chan); void (*device_issue_pending)(struct dma_chan *chan);
}; };
static inline int dmaengine_device_control(struct dma_chan *chan,
enum dma_ctrl_cmd cmd,
unsigned long arg)
{
return chan->device->device_control(chan, cmd, arg);
}
static inline int dmaengine_slave_config(struct dma_chan *chan,
struct dma_slave_config *config)
{
return dmaengine_device_control(chan, DMA_SLAVE_CONFIG,
(unsigned long)config);
}
static inline int dmaengine_terminate_all(struct dma_chan *chan)
{
return dmaengine_device_control(chan, DMA_TERMINATE_ALL, 0);
}
static inline int dmaengine_pause(struct dma_chan *chan)
{
return dmaengine_device_control(chan, DMA_PAUSE, 0);
}
static inline int dmaengine_resume(struct dma_chan *chan)
{
return dmaengine_device_control(chan, DMA_RESUME, 0);
}
static inline int dmaengine_submit(struct dma_async_tx_descriptor *desc)
{
return desc->tx_submit(desc);
}
static inline bool dmaengine_check_align(u8 align, size_t off1, size_t off2, size_t len) static inline bool dmaengine_check_align(u8 align, size_t off1, size_t off2, size_t len)
{ {
size_t mask; size_t mask;
...@@ -606,11 +656,11 @@ static inline void net_dmaengine_put(void) ...@@ -606,11 +656,11 @@ static inline void net_dmaengine_put(void)
#ifdef CONFIG_ASYNC_TX_DMA #ifdef CONFIG_ASYNC_TX_DMA
#define async_dmaengine_get() dmaengine_get() #define async_dmaengine_get() dmaengine_get()
#define async_dmaengine_put() dmaengine_put() #define async_dmaengine_put() dmaengine_put()
#ifdef CONFIG_ASYNC_TX_DISABLE_CHANNEL_SWITCH #ifndef CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH
#define async_dma_find_channel(type) dma_find_channel(DMA_ASYNC_TX) #define async_dma_find_channel(type) dma_find_channel(DMA_ASYNC_TX)
#else #else
#define async_dma_find_channel(type) dma_find_channel(type) #define async_dma_find_channel(type) dma_find_channel(type)
#endif /* CONFIG_ASYNC_TX_DISABLE_CHANNEL_SWITCH */ #endif /* CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH */
#else #else
static inline void async_dmaengine_get(void) static inline void async_dmaengine_get(void)
{ {
......
...@@ -27,14 +27,7 @@ ...@@ -27,14 +27,7 @@
#include <linux/dmaengine.h> #include <linux/dmaengine.h>
/*DMA transaction width, src and dstn width would be same #define DMA_PREP_CIRCULAR_LIST (1 << 10)
The DMA length must be width aligned,
for 32 bit width the length must be 32 bit (4bytes) aligned only*/
enum intel_mid_dma_width {
LNW_DMA_WIDTH_8BIT = 0x0,
LNW_DMA_WIDTH_16BIT = 0x1,
LNW_DMA_WIDTH_32BIT = 0x2,
};
/*DMA mode configurations*/ /*DMA mode configurations*/
enum intel_mid_dma_mode { enum intel_mid_dma_mode {
...@@ -69,18 +62,15 @@ enum intel_mid_dma_msize { ...@@ -69,18 +62,15 @@ enum intel_mid_dma_msize {
* @cfg_mode: DMA data transfer mode (per-per/mem-per/mem-mem) * @cfg_mode: DMA data transfer mode (per-per/mem-per/mem-mem)
* @src_msize: Source DMA burst size * @src_msize: Source DMA burst size
* @dst_msize: Dst DMA burst size * @dst_msize: Dst DMA burst size
* @per_addr: Periphral address
* @device_instance: DMA peripheral device instance, we can have multiple * @device_instance: DMA peripheral device instance, we can have multiple
* peripheral device connected to single DMAC * peripheral device connected to single DMAC
*/ */
struct intel_mid_dma_slave { struct intel_mid_dma_slave {
enum dma_data_direction dirn;
enum intel_mid_dma_width src_width; /*width of DMA src txn*/
enum intel_mid_dma_width dst_width; /*width of DMA dst txn*/
enum intel_mid_dma_hs_mode hs_mode; /*handshaking*/ enum intel_mid_dma_hs_mode hs_mode; /*handshaking*/
enum intel_mid_dma_mode cfg_mode; /*mode configuration*/ enum intel_mid_dma_mode cfg_mode; /*mode configuration*/
enum intel_mid_dma_msize src_msize; /*size if src burst*/
enum intel_mid_dma_msize dst_msize; /*size of dst burst*/
unsigned int device_instance; /*0, 1 for periphral instance*/ unsigned int device_instance; /*0, 1 for periphral instance*/
struct dma_slave_config dma_slave;
}; };
#endif /*__INTEL_MID_DMA_H__*/ #endif /*__INTEL_MID_DMA_H__*/
...@@ -1217,6 +1217,19 @@ config ATOMIC64_SELFTEST ...@@ -1217,6 +1217,19 @@ config ATOMIC64_SELFTEST
If unsure, say N. If unsure, say N.
config ASYNC_RAID6_TEST
tristate "Self test for hardware accelerated raid6 recovery"
depends on ASYNC_RAID6_RECOV
select ASYNC_MEMCPY
---help---
This is a one-shot self test that permutes through the
recovery of all the possible two disk failure scenarios for a
N-disk array. Recovery is performed with the asynchronous
raid6 recovery routines, and will optionally use an offload
engine if one is available.
If unsure, say N.
source "samples/Kconfig" source "samples/Kconfig"
source "lib/Kconfig.kgdb" source "lib/Kconfig.kgdb"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment