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:
  DMAENGINE: DMA40 U8500 platform configuration
  DMA: PL330: Add dma api driver
This commit is contained in:
Linus Torvalds 2010-05-30 09:12:43 -07:00
commit e38c1e54ce
10 changed files with 1204 additions and 1 deletions

View file

@ -411,7 +411,7 @@ static struct clk_lookup u8500_common_clks[] = {
CLK(apetraceclk, "apetrace", NULL),
CLK(mcdeclk, "mcde", NULL),
CLK(ipi2clk, "ipi2", NULL),
CLK(dmaclk, "dma40", NULL),
CLK(dmaclk, "dma40.0", NULL),
CLK(b2r2clk, "b2r2", NULL),
CLK(tvclk, "tv", NULL),
};

View file

@ -32,6 +32,7 @@ static struct platform_device *platform_devs[] __initdata = {
&u8500_gpio_devs[6],
&u8500_gpio_devs[7],
&u8500_gpio_devs[8],
&u8500_dma40_device,
};
/* minimum static i/o mapping required to boot U8500 platforms */
@ -71,6 +72,9 @@ void __init u8500_init_devices(void)
{
ux500_init_devices();
if (cpu_is_u8500ed())
dma40_u8500ed_fixup();
/* Register the platform devices */
platform_add_devices(platform_devs, ARRAY_SIZE(platform_devs));

View file

@ -12,9 +12,13 @@
#include <linux/gpio.h>
#include <linux/amba/bus.h>
#include <plat/ste_dma40.h>
#include <mach/hardware.h>
#include <mach/setup.h>
#include "ste-dma40-db8500.h"
static struct nmk_gpio_platform_data u8500_gpio_data[] = {
GPIO_DATA("GPIO-0-31", 0),
GPIO_DATA("GPIO-32-63", 32), /* 37..63 not routed to pin */
@ -105,3 +109,108 @@ struct platform_device u8500_i2c4_device = {
.resource = u8500_i2c4_resources,
.num_resources = ARRAY_SIZE(u8500_i2c4_resources),
};
static struct resource dma40_resources[] = {
[0] = {
.start = U8500_DMA_BASE,
.end = U8500_DMA_BASE + SZ_4K - 1,
.flags = IORESOURCE_MEM,
.name = "base",
},
[1] = {
.start = U8500_DMA_LCPA_BASE,
.end = U8500_DMA_LCPA_BASE + SZ_4K - 1,
.flags = IORESOURCE_MEM,
.name = "lcpa",
},
[2] = {
.start = U8500_DMA_LCLA_BASE,
.end = U8500_DMA_LCLA_BASE + 16 * 1024 - 1,
.flags = IORESOURCE_MEM,
.name = "lcla",
},
[3] = {
.start = IRQ_DMA,
.end = IRQ_DMA,
.flags = IORESOURCE_IRQ}
};
/* Default configuration for physcial memcpy */
struct stedma40_chan_cfg dma40_memcpy_conf_phy = {
.channel_type = (STEDMA40_CHANNEL_IN_PHY_MODE |
STEDMA40_LOW_PRIORITY_CHANNEL |
STEDMA40_PCHAN_BASIC_MODE),
.dir = STEDMA40_MEM_TO_MEM,
.src_info.endianess = STEDMA40_LITTLE_ENDIAN,
.src_info.data_width = STEDMA40_BYTE_WIDTH,
.src_info.psize = STEDMA40_PSIZE_PHY_1,
.dst_info.endianess = STEDMA40_LITTLE_ENDIAN,
.dst_info.data_width = STEDMA40_BYTE_WIDTH,
.dst_info.psize = STEDMA40_PSIZE_PHY_1,
};
/* Default configuration for logical memcpy */
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,
.src_info.endianess = STEDMA40_LITTLE_ENDIAN,
.src_info.data_width = STEDMA40_BYTE_WIDTH,
.src_info.psize = STEDMA40_PSIZE_LOG_1,
.dst_info.endianess = STEDMA40_LITTLE_ENDIAN,
.dst_info.data_width = STEDMA40_BYTE_WIDTH,
.dst_info.psize = STEDMA40_PSIZE_LOG_1,
};
/*
* Mapping between destination event lines and physical device address.
* The event line is tied to a device and therefor the address is constant.
*/
static const dma_addr_t dma40_tx_map[STEDMA40_NR_DEV];
/* Mapping between source event lines and physical device address */
static const dma_addr_t dma40_rx_map[STEDMA40_NR_DEV];
/* Reserved event lines for memcpy only */
static int dma40_memcpy_event[] = {
STEDMA40_MEMCPY_TX_1,
STEDMA40_MEMCPY_TX_2,
STEDMA40_MEMCPY_TX_3,
STEDMA40_MEMCPY_TX_4,
};
static struct stedma40_platform_data dma40_plat_data = {
.dev_len = STEDMA40_NR_DEV,
.dev_rx = dma40_rx_map,
.dev_tx = dma40_tx_map,
.memcpy = dma40_memcpy_event,
.memcpy_len = ARRAY_SIZE(dma40_memcpy_event),
.memcpy_conf_phy = &dma40_memcpy_conf_phy,
.memcpy_conf_log = &dma40_memcpy_conf_log,
.llis_per_log = 8,
};
struct platform_device u8500_dma40_device = {
.dev = {
.platform_data = &dma40_plat_data,
},
.name = "dma40",
.id = 0,
.num_resources = ARRAY_SIZE(dma40_resources),
.resource = dma40_resources
};
void dma40_u8500ed_fixup(void)
{
dma40_plat_data.memcpy = NULL;
dma40_plat_data.memcpy_len = 0;
dma40_resources[0].start = U8500_DMA_BASE_ED;
dma40_resources[0].end = U8500_DMA_BASE_ED + SZ_4K - 1;
}

View file

@ -7,6 +7,18 @@
#ifndef __MACH_DB8500_REGS_H
#define __MACH_DB8500_REGS_H
/* Base address and bank offsets for ESRAM */
#define U8500_ESRAM_BASE 0x40000000
#define U8500_ESRAM_BANK_SIZE 0x00020000
#define U8500_ESRAM_BANK0 U8500_ESRAM_BASE
#define U8500_ESRAM_BANK1 (U8500_ESRAM_BASE + U8500_ESRAM_BANK_SIZE)
#define U8500_ESRAM_BANK2 (U8500_ESRAM_BANK1 + U8500_ESRAM_BANK_SIZE)
#define U8500_ESRAM_BANK3 (U8500_ESRAM_BANK2 + U8500_ESRAM_BANK_SIZE)
#define U8500_ESRAM_BANK4 (U8500_ESRAM_BANK3 + U8500_ESRAM_BANK_SIZE)
/* Use bank 4 for DMA LCLA and LCPA */
#define U8500_DMA_LCLA_BASE U8500_ESRAM_BANK4
#define U8500_DMA_LCPA_BASE (U8500_ESRAM_BANK4 + 0x4000)
#define U8500_PER3_BASE 0x80000000
#define U8500_STM_BASE 0x80100000
#define U8500_STM_REG_BASE (U8500_STM_BASE + 0xF000)

View file

@ -25,5 +25,8 @@ extern struct platform_device ux500_i2c3_device;
extern struct platform_device u8500_i2c0_device;
extern struct platform_device u8500_i2c4_device;
extern struct platform_device u8500_dma40_device;
void dma40_u8500ed_fixup(void);
#endif

View file

@ -0,0 +1,154 @@
/*
* arch/arm/mach-ux500/ste_dma40_db8500.h
* DB8500-SoC-specific configuration for DMA40
*
* Copyright (C) ST-Ericsson 2007-2010
* 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_DB8500_H
#define STE_DMA40_DB8500_H
#define STEDMA40_NR_DEV 64
enum dma_src_dev_type {
STEDMA40_DEV_SPI0_RX = 0,
STEDMA40_DEV_SD_MMC0_RX = 1,
STEDMA40_DEV_SD_MMC1_RX = 2,
STEDMA40_DEV_SD_MMC2_RX = 3,
STEDMA40_DEV_I2C1_RX = 4,
STEDMA40_DEV_I2C3_RX = 5,
STEDMA40_DEV_I2C2_RX = 6,
STEDMA40_DEV_I2C4_RX = 7, /* Only on V1 */
STEDMA40_DEV_SSP0_RX = 8,
STEDMA40_DEV_SSP1_RX = 9,
STEDMA40_DEV_MCDE_RX = 10,
STEDMA40_DEV_UART2_RX = 11,
STEDMA40_DEV_UART1_RX = 12,
STEDMA40_DEV_UART0_RX = 13,
STEDMA40_DEV_MSP2_RX = 14,
STEDMA40_DEV_I2C0_RX = 15,
STEDMA40_DEV_USB_OTG_IEP_8 = 16,
STEDMA40_DEV_USB_OTG_IEP_1_9 = 17,
STEDMA40_DEV_USB_OTG_IEP_2_10 = 18,
STEDMA40_DEV_USB_OTG_IEP_3_11 = 19,
STEDMA40_DEV_SLIM0_CH0_RX_HSI_RX_CH0 = 20,
STEDMA40_DEV_SLIM0_CH1_RX_HSI_RX_CH1 = 21,
STEDMA40_DEV_SLIM0_CH2_RX_HSI_RX_CH2 = 22,
STEDMA40_DEV_SLIM0_CH3_RX_HSI_RX_CH3 = 23,
STEDMA40_DEV_SRC_SXA0_RX_TX = 24,
STEDMA40_DEV_SRC_SXA1_RX_TX = 25,
STEDMA40_DEV_SRC_SXA2_RX_TX = 26,
STEDMA40_DEV_SRC_SXA3_RX_TX = 27,
STEDMA40_DEV_SD_MM2_RX = 28,
STEDMA40_DEV_SD_MM0_RX = 29,
STEDMA40_DEV_MSP1_RX = 30,
/*
* This channel is either SlimBus or MSP,
* never both at the same time.
*/
STEDMA40_SLIM0_CH0_RX = 31,
STEDMA40_DEV_MSP0_RX = 31,
STEDMA40_DEV_SD_MM1_RX = 32,
STEDMA40_DEV_SPI2_RX = 33,
STEDMA40_DEV_I2C3_RX2 = 34,
STEDMA40_DEV_SPI1_RX = 35,
STEDMA40_DEV_USB_OTG_IEP_4_12 = 36,
STEDMA40_DEV_USB_OTG_IEP_5_13 = 37,
STEDMA40_DEV_USB_OTG_IEP_6_14 = 38,
STEDMA40_DEV_USB_OTG_IEP_7_15 = 39,
STEDMA40_DEV_SPI3_RX = 40,
STEDMA40_DEV_SD_MM3_RX = 41,
STEDMA40_DEV_SD_MM4_RX = 42,
STEDMA40_DEV_SD_MM5_RX = 43,
STEDMA40_DEV_SRC_SXA4_RX_TX = 44,
STEDMA40_DEV_SRC_SXA5_RX_TX = 45,
STEDMA40_DEV_SRC_SXA6_RX_TX = 46,
STEDMA40_DEV_SRC_SXA7_RX_TX = 47,
STEDMA40_DEV_CAC1_RX = 48,
/* RX channels 49 and 50 are unused */
STEDMA40_DEV_MSHC_RX = 51,
STEDMA40_DEV_SLIM1_CH0_RX_HSI_RX_CH4 = 52,
STEDMA40_DEV_SLIM1_CH1_RX_HSI_RX_CH5 = 53,
STEDMA40_DEV_SLIM1_CH2_RX_HSI_RX_CH6 = 54,
STEDMA40_DEV_SLIM1_CH3_RX_HSI_RX_CH7 = 55,
/* RX channels 56 thru 60 are unused */
STEDMA40_DEV_CAC0_RX = 61,
/* RX channels 62 and 63 are unused */
};
enum dma_dest_dev_type {
STEDMA40_DEV_SPI0_TX = 0,
STEDMA40_DEV_SD_MMC0_TX = 1,
STEDMA40_DEV_SD_MMC1_TX = 2,
STEDMA40_DEV_SD_MMC2_TX = 3,
STEDMA40_DEV_I2C1_TX = 4,
STEDMA40_DEV_I2C3_TX = 5,
STEDMA40_DEV_I2C2_TX = 6,
STEDMA50_DEV_I2C4_TX = 7, /* Only on V1 */
STEDMA40_DEV_SSP0_TX = 8,
STEDMA40_DEV_SSP1_TX = 9,
/* TX channel 10 is unused */
STEDMA40_DEV_UART2_TX = 11,
STEDMA40_DEV_UART1_TX = 12,
STEDMA40_DEV_UART0_TX= 13,
STEDMA40_DEV_MSP2_TX = 14,
STEDMA40_DEV_I2C0_TX = 15,
STEDMA40_DEV_USB_OTG_OEP_8 = 16,
STEDMA40_DEV_USB_OTG_OEP_1_9 = 17,
STEDMA40_DEV_USB_OTG_OEP_2_10= 18,
STEDMA40_DEV_USB_OTG_OEP_3_11 = 19,
STEDMA40_DEV_SLIM0_CH0_TX_HSI_TX_CH0 = 20,
STEDMA40_DEV_SLIM0_CH1_TX_HSI_TX_CH1 = 21,
STEDMA40_DEV_SLIM0_CH2_TX_HSI_TX_CH2 = 22,
STEDMA40_DEV_SLIM0_CH3_TX_HSI_TX_CH3 = 23,
STEDMA40_DEV_DST_SXA0_RX_TX = 24,
STEDMA40_DEV_DST_SXA1_RX_TX = 25,
STEDMA40_DEV_DST_SXA2_RX_TX = 26,
STEDMA40_DEV_DST_SXA3_RX_TX = 27,
STEDMA40_DEV_SD_MM2_TX = 28,
STEDMA40_DEV_SD_MM0_TX = 29,
STEDMA40_DEV_MSP1_TX = 30,
/*
* This channel is either SlimBus or MSP,
* never both at the same time.
*/
STEDMA40_SLIM0_CH0_TX = 31,
STEDMA40_DEV_MSP0_TX = 31,
STEDMA40_DEV_SD_MM1_TX = 32,
STEDMA40_DEV_SPI2_TX = 33,
/* Secondary I2C3 channel */
STEDMA40_DEV_I2C3_TX2 = 34,
STEDMA40_DEV_SPI1_TX = 35,
STEDMA40_DEV_USB_OTG_OEP_4_12 = 36,
STEDMA40_DEV_USB_OTG_OEP_5_13 = 37,
STEDMA40_DEV_USB_OTG_OEP_6_14 = 38,
STEDMA40_DEV_USB_OTG_OEP_7_15 = 39,
STEDMA40_DEV_SPI3_TX = 40,
STEDMA40_DEV_SD_MM3_TX = 41,
STEDMA40_DEV_SD_MM4_TX = 42,
STEDMA40_DEV_SD_MM5_TX = 43,
STEDMA40_DEV_DST_SXA4_RX_TX = 44,
STEDMA40_DEV_DST_SXA5_RX_TX = 45,
STEDMA40_DEV_DST_SXA6_RX_TX = 46,
STEDMA40_DEV_DST_SXA7_RX_TX = 47,
STEDMA40_DEV_CAC1_TX = 48,
STEDMA40_DEV_CAC1_TX_HAC1_TX = 49,
STEDMA40_DEV_HAC1_TX = 50,
STEDMA40_MEMXCPY_TX_0 = 51,
STEDMA40_DEV_SLIM1_CH0_TX_HSI_TX_CH4 = 52,
STEDMA40_DEV_SLIM1_CH1_TX_HSI_TX_CH5 = 53,
STEDMA40_DEV_SLIM1_CH2_TX_HSI_TX_CH6 = 54,
STEDMA40_DEV_SLIM1_CH3_TX_HSI_TX_CH7 = 55,
STEDMA40_MEMCPY_TX_1 = 56,
STEDMA40_MEMCPY_TX_2 = 57,
STEDMA40_MEMCPY_TX_3 = 58,
STEDMA40_MEMCPY_TX_4 = 59,
STEDMA40_MEMCPY_TX_5 = 60,
STEDMA40_DEV_CAC0_TX = 61,
STEDMA40_DEV_CAC0_TX_HAC0_TX = 62,
STEDMA40_DEV_HAC0_TX = 63,
};
#endif

View file

@ -166,6 +166,15 @@ config TIMB_DMA
config ARCH_HAS_ASYNC_TX_FIND_CHANNEL
bool
config PL330_DMA
tristate "DMA API Driver for PL330"
select DMA_ENGINE
depends on PL330
help
Select if your platform has one or more PL330 DMACs.
You need to provide platform specific settings via
platform_data for a dma-pl330 device.
config DMA_ENGINE
bool

View file

@ -22,3 +22,4 @@ obj-$(CONFIG_COH901318) += coh901318.o coh901318_lli.o
obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/
obj-$(CONFIG_TIMB_DMA) += timb_dma.o
obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o
obj-$(CONFIG_PL330_DMA) += pl330.o

866
drivers/dma/pl330.c Normal file
View file

@ -0,0 +1,866 @@
/* linux/drivers/dma/pl330.c
*
* Copyright (C) 2010 Samsung Electronics Co. Ltd.
* Jaswinder Singh <jassi.brar@samsung.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.
*/
#include <linux/io.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/dmaengine.h>
#include <linux/interrupt.h>
#include <linux/amba/bus.h>
#include <linux/amba/pl330.h>
#define NR_DEFAULT_DESC 16
enum desc_status {
/* In the DMAC pool */
FREE,
/*
* Allocted to some channel during prep_xxx
* Also may be sitting on the work_list.
*/
PREP,
/*
* Sitting on the work_list and already submitted
* to the PL330 core. Not more than two descriptors
* of a channel can be BUSY at any time.
*/
BUSY,
/*
* Sitting on the channel work_list but xfer done
* by PL330 core
*/
DONE,
};
struct dma_pl330_chan {
/* Schedule desc completion */
struct tasklet_struct task;
/* DMA-Engine Channel */
struct dma_chan chan;
/* Last completed cookie */
dma_cookie_t completed;
/* List of to be xfered descriptors */
struct list_head work_list;
/* Pointer to the DMAC that manages this channel,
* NULL if the channel is available to be acquired.
* As the parent, this DMAC also provides descriptors
* to the channel.
*/
struct dma_pl330_dmac *dmac;
/* To protect channel manipulation */
spinlock_t lock;
/* Token of a hardware channel thread of PL330 DMAC
* NULL if the channel is available to be acquired.
*/
void *pl330_chid;
};
struct dma_pl330_dmac {
struct pl330_info pif;
/* DMA-Engine Device */
struct dma_device ddma;
/* Pool of descriptors available for the DMAC's channels */
struct list_head desc_pool;
/* To protect desc_pool manipulation */
spinlock_t pool_lock;
/* Peripheral channels connected to this DMAC */
struct dma_pl330_chan peripherals[0]; /* keep at end */
};
struct dma_pl330_desc {
/* To attach to a queue as child */
struct list_head node;
/* Descriptor for the DMA Engine API */
struct dma_async_tx_descriptor txd;
/* Xfer for PL330 core */
struct pl330_xfer px;
struct pl330_reqcfg rqcfg;
struct pl330_req req;
enum desc_status status;
/* The channel which currently holds this desc */
struct dma_pl330_chan *pchan;
};
static inline struct dma_pl330_chan *
to_pchan(struct dma_chan *ch)
{
if (!ch)
return NULL;
return container_of(ch, struct dma_pl330_chan, chan);
}
static inline struct dma_pl330_desc *
to_desc(struct dma_async_tx_descriptor *tx)
{
return container_of(tx, struct dma_pl330_desc, txd);
}
static inline void free_desc_list(struct list_head *list)
{
struct dma_pl330_dmac *pdmac;
struct dma_pl330_desc *desc;
struct dma_pl330_chan *pch;
unsigned long flags;
if (list_empty(list))
return;
/* Finish off the work list */
list_for_each_entry(desc, list, node) {
dma_async_tx_callback callback;
void *param;
/* All desc in a list belong to same channel */
pch = desc->pchan;
callback = desc->txd.callback;
param = desc->txd.callback_param;
if (callback)
callback(param);
desc->pchan = NULL;
}
pdmac = pch->dmac;
spin_lock_irqsave(&pdmac->pool_lock, flags);
list_splice_tail_init(list, &pdmac->desc_pool);
spin_unlock_irqrestore(&pdmac->pool_lock, flags);
}
static inline void fill_queue(struct dma_pl330_chan *pch)
{
struct dma_pl330_desc *desc;
int ret;
list_for_each_entry(desc, &pch->work_list, node) {
/* If already submitted */
if (desc->status == BUSY)
break;
ret = pl330_submit_req(pch->pl330_chid,
&desc->req);
if (!ret) {
desc->status = BUSY;
break;
} else if (ret == -EAGAIN) {
/* QFull or DMAC Dying */
break;
} else {
/* Unacceptable request */
desc->status = DONE;
dev_err(pch->dmac->pif.dev, "%s:%d Bad Desc(%d)\n",
__func__, __LINE__, desc->txd.cookie);
tasklet_schedule(&pch->task);
}
}
}
static void pl330_tasklet(unsigned long data)
{
struct dma_pl330_chan *pch = (struct dma_pl330_chan *)data;
struct dma_pl330_desc *desc, *_dt;
unsigned long flags;
LIST_HEAD(list);
spin_lock_irqsave(&pch->lock, flags);
/* Pick up ripe tomatoes */
list_for_each_entry_safe(desc, _dt, &pch->work_list, node)
if (desc->status == DONE) {
pch->completed = desc->txd.cookie;
list_move_tail(&desc->node, &list);
}
/* Try to submit a req imm. next to the last completed cookie */
fill_queue(pch);
/* Make sure the PL330 Channel thread is active */
pl330_chan_ctrl(pch->pl330_chid, PL330_OP_START);
spin_unlock_irqrestore(&pch->lock, flags);
free_desc_list(&list);
}
static void dma_pl330_rqcb(void *token, enum pl330_op_err err)
{
struct dma_pl330_desc *desc = token;
struct dma_pl330_chan *pch = desc->pchan;
unsigned long flags;
/* If desc aborted */
if (!pch)
return;
spin_lock_irqsave(&pch->lock, flags);
desc->status = DONE;
spin_unlock_irqrestore(&pch->lock, flags);
tasklet_schedule(&pch->task);
}
static int pl330_alloc_chan_resources(struct dma_chan *chan)
{
struct dma_pl330_chan *pch = to_pchan(chan);
struct dma_pl330_dmac *pdmac = pch->dmac;
unsigned long flags;
spin_lock_irqsave(&pch->lock, flags);
pch->completed = chan->cookie = 1;
pch->pl330_chid = pl330_request_channel(&pdmac->pif);
if (!pch->pl330_chid) {
spin_unlock_irqrestore(&pch->lock, flags);
return 0;
}
tasklet_init(&pch->task, pl330_tasklet, (unsigned long) pch);
spin_unlock_irqrestore(&pch->lock, flags);
return 1;
}
static int pl330_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, unsigned long arg)
{
struct dma_pl330_chan *pch = to_pchan(chan);
struct dma_pl330_desc *desc;
unsigned long flags;
/* Only supports DMA_TERMINATE_ALL */
if (cmd != DMA_TERMINATE_ALL)
return -ENXIO;
spin_lock_irqsave(&pch->lock, flags);
/* FLUSH the PL330 Channel thread */
pl330_chan_ctrl(pch->pl330_chid, PL330_OP_FLUSH);
/* Mark all desc done */
list_for_each_entry(desc, &pch->work_list, node)
desc->status = DONE;
spin_unlock_irqrestore(&pch->lock, flags);
pl330_tasklet((unsigned long) pch);
return 0;
}
static void pl330_free_chan_resources(struct dma_chan *chan)
{
struct dma_pl330_chan *pch = to_pchan(chan);
unsigned long flags;
spin_lock_irqsave(&pch->lock, flags);
tasklet_kill(&pch->task);
pl330_release_channel(pch->pl330_chid);
pch->pl330_chid = NULL;
spin_unlock_irqrestore(&pch->lock, flags);
}
static enum dma_status
pl330_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
struct dma_tx_state *txstate)
{
struct dma_pl330_chan *pch = to_pchan(chan);
dma_cookie_t last_done, last_used;
int ret;
last_done = pch->completed;
last_used = chan->cookie;
ret = dma_async_is_complete(cookie, last_done, last_used);
dma_set_tx_state(txstate, last_done, last_used, 0);
return ret;
}
static void pl330_issue_pending(struct dma_chan *chan)
{
pl330_tasklet((unsigned long) to_pchan(chan));
}
/*
* We returned the last one of the circular list of descriptor(s)
* from prep_xxx, so the argument to submit corresponds to the last
* descriptor of the list.
*/
static dma_cookie_t pl330_tx_submit(struct dma_async_tx_descriptor *tx)
{
struct dma_pl330_desc *desc, *last = to_desc(tx);
struct dma_pl330_chan *pch = to_pchan(tx->chan);
dma_cookie_t cookie;
unsigned long flags;
spin_lock_irqsave(&pch->lock, flags);
/* Assign cookies to all nodes */
cookie = tx->chan->cookie;
while (!list_empty(&last->node)) {
desc = list_entry(last->node.next, struct dma_pl330_desc, node);
if (++cookie < 0)
cookie = 1;
desc->txd.cookie = cookie;
list_move_tail(&desc->node, &pch->work_list);
}
if (++cookie < 0)
cookie = 1;
last->txd.cookie = cookie;
list_add_tail(&last->node, &pch->work_list);
tx->chan->cookie = cookie;
spin_unlock_irqrestore(&pch->lock, flags);
return cookie;
}
static inline void _init_desc(struct dma_pl330_desc *desc)
{
desc->pchan = NULL;
desc->req.x = &desc->px;
desc->req.token = desc;
desc->rqcfg.swap = SWAP_NO;
desc->rqcfg.privileged = 0;
desc->rqcfg.insnaccess = 0;
desc->rqcfg.scctl = SCCTRL0;
desc->rqcfg.dcctl = DCCTRL0;
desc->req.cfg = &desc->rqcfg;
desc->req.xfer_cb = dma_pl330_rqcb;
desc->txd.tx_submit = pl330_tx_submit;
INIT_LIST_HEAD(&desc->node);
}
/* Returns the number of descriptors added to the DMAC pool */
int add_desc(struct dma_pl330_dmac *pdmac, gfp_t flg, int count)
{
struct dma_pl330_desc *desc;
unsigned long flags;
int i;
if (!pdmac)
return 0;
desc = kmalloc(count * sizeof(*desc), flg);
if (!desc)
return 0;
spin_lock_irqsave(&pdmac->pool_lock, flags);
for (i = 0; i < count; i++) {
_init_desc(&desc[i]);
list_add_tail(&desc[i].node, &pdmac->desc_pool);
}
spin_unlock_irqrestore(&pdmac->pool_lock, flags);
return count;
}
static struct dma_pl330_desc *
pluck_desc(struct dma_pl330_dmac *pdmac)
{
struct dma_pl330_desc *desc = NULL;
unsigned long flags;
if (!pdmac)
return NULL;
spin_lock_irqsave(&pdmac->pool_lock, flags);
if (!list_empty(&pdmac->desc_pool)) {
desc = list_entry(pdmac->desc_pool.next,
struct dma_pl330_desc, node);
list_del_init(&desc->node);
desc->status = PREP;
desc->txd.callback = NULL;
}
spin_unlock_irqrestore(&pdmac->pool_lock, flags);
return desc;
}
static struct dma_pl330_desc *pl330_get_desc(struct dma_pl330_chan *pch)
{
struct dma_pl330_dmac *pdmac = pch->dmac;
struct dma_pl330_peri *peri = pch->chan.private;
struct dma_pl330_desc *desc;
/* Pluck one desc from the pool of DMAC */
desc = pluck_desc(pdmac);
/* If the DMAC pool is empty, alloc new */
if (!desc) {
if (!add_desc(pdmac, GFP_ATOMIC, 1))
return NULL;
/* Try again */
desc = pluck_desc(pdmac);
if (!desc) {
dev_err(pch->dmac->pif.dev,
"%s:%d ALERT!\n", __func__, __LINE__);
return NULL;
}
}
/* Initialize the descriptor */
desc->pchan = pch;
desc->txd.cookie = 0;
async_tx_ack(&desc->txd);
desc->req.rqtype = peri->rqtype;
desc->req.peri = peri->peri_id;
dma_async_tx_descriptor_init(&desc->txd, &pch->chan);
return desc;
}
static inline void fill_px(struct pl330_xfer *px,
dma_addr_t dst, dma_addr_t src, size_t len)
{
px->next = NULL;
px->bytes = len;
px->dst_addr = dst;
px->src_addr = src;
}
static struct dma_pl330_desc *
__pl330_prep_dma_memcpy(struct dma_pl330_chan *pch, dma_addr_t dst,
dma_addr_t src, size_t len)
{
struct dma_pl330_desc *desc = pl330_get_desc(pch);
if (!desc) {
dev_err(pch->dmac->pif.dev, "%s:%d Unable to fetch desc\n",
__func__, __LINE__);
return NULL;
}
/*
* Ideally we should lookout for reqs bigger than
* those that can be programmed with 256 bytes of
* MC buffer, but considering a req size is seldom
* going to be word-unaligned and more than 200MB,
* we take it easy.
* Also, should the limit is reached we'd rather
* have the platform increase MC buffer size than
* complicating this API driver.
*/
fill_px(&desc->px, dst, src, len);
return desc;
}
/* Call after fixing burst size */
static inline int get_burst_len(struct dma_pl330_desc *desc, size_t len)
{
struct dma_pl330_chan *pch = desc->pchan;
struct pl330_info *pi = &pch->dmac->pif;
int burst_len;
burst_len = pi->pcfg.data_bus_width / 8;
burst_len *= pi->pcfg.data_buf_dep;
burst_len >>= desc->rqcfg.brst_size;
/* src/dst_burst_len can't be more than 16 */
if (burst_len > 16)
burst_len = 16;
while (burst_len > 1) {
if (!(len % (burst_len << desc->rqcfg.brst_size)))
break;
burst_len--;
}
return burst_len;
}
static struct dma_async_tx_descriptor *
pl330_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dst,
dma_addr_t src, size_t len, unsigned long flags)
{
struct dma_pl330_desc *desc;
struct dma_pl330_chan *pch = to_pchan(chan);
struct dma_pl330_peri *peri = chan->private;
struct pl330_info *pi;
int burst;
if (unlikely(!pch || !len || !peri))
return NULL;
if (peri->rqtype != MEMTOMEM)
return NULL;
pi = &pch->dmac->pif;
desc = __pl330_prep_dma_memcpy(pch, dst, src, len);
if (!desc)
return NULL;
desc->rqcfg.src_inc = 1;
desc->rqcfg.dst_inc = 1;
/* Select max possible burst size */
burst = pi->pcfg.data_bus_width / 8;
while (burst > 1) {
if (!(len % burst))
break;
burst /= 2;
}
desc->rqcfg.brst_size = 0;
while (burst != (1 << desc->rqcfg.brst_size))
desc->rqcfg.brst_size++;
desc->rqcfg.brst_len = get_burst_len(desc, len);
desc->txd.flags = flags;
return &desc->txd;
}
static struct dma_async_tx_descriptor *
pl330_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_data_direction direction,
unsigned long flg)
{
struct dma_pl330_desc *first, *desc = NULL;
struct dma_pl330_chan *pch = to_pchan(chan);
struct dma_pl330_peri *peri = chan->private;
struct scatterlist *sg;
unsigned long flags;
int i, burst_size;
dma_addr_t addr;
if (unlikely(!pch || !sgl || !sg_len))
return NULL;
/* Make sure the direction is consistent */
if ((direction == DMA_TO_DEVICE &&
peri->rqtype != MEMTODEV) ||
(direction == DMA_FROM_DEVICE &&
peri->rqtype != DEVTOMEM)) {
dev_err(pch->dmac->pif.dev, "%s:%d Invalid Direction\n",
__func__, __LINE__);
return NULL;
}
addr = peri->fifo_addr;
burst_size = peri->burst_sz;
first = NULL;
for_each_sg(sgl, sg, sg_len, i) {
desc = pl330_get_desc(pch);
if (!desc) {
struct dma_pl330_dmac *pdmac = pch->dmac;
dev_err(pch->dmac->pif.dev,
"%s:%d Unable to fetch desc\n",
__func__, __LINE__);
if (!first)
return NULL;
spin_lock_irqsave(&pdmac->pool_lock, flags);
while (!list_empty(&first->node)) {
desc = list_entry(first->node.next,
struct dma_pl330_desc, node);
list_move_tail(&desc->node, &pdmac->desc_pool);
}
list_move_tail(&first->node, &pdmac->desc_pool);
spin_unlock_irqrestore(&pdmac->pool_lock, flags);
return NULL;
}
if (!first)
first = desc;
else
list_add_tail(&desc->node, &first->node);
if (direction == DMA_TO_DEVICE) {
desc->rqcfg.src_inc = 1;
desc->rqcfg.dst_inc = 0;
fill_px(&desc->px,
addr, sg_dma_address(sg), sg_dma_len(sg));
} else {
desc->rqcfg.src_inc = 0;
desc->rqcfg.dst_inc = 1;
fill_px(&desc->px,
sg_dma_address(sg), addr, sg_dma_len(sg));
}
desc->rqcfg.brst_size = burst_size;
desc->rqcfg.brst_len = 1;
}
/* Return the last desc in the chain */
desc->txd.flags = flg;
return &desc->txd;
}
static irqreturn_t pl330_irq_handler(int irq, void *data)
{
if (pl330_update(data))
return IRQ_HANDLED;
else
return IRQ_NONE;
}
static int __devinit
pl330_probe(struct amba_device *adev, struct amba_id *id)
{
struct dma_pl330_platdata *pdat;
struct dma_pl330_dmac *pdmac;
struct dma_pl330_chan *pch;
struct pl330_info *pi;
struct dma_device *pd;
struct resource *res;
int i, ret, irq;
pdat = adev->dev.platform_data;
if (!pdat || !pdat->nr_valid_peri) {
dev_err(&adev->dev, "platform data missing\n");
return -ENODEV;
}
/* Allocate a new DMAC and its Channels */
pdmac = kzalloc(pdat->nr_valid_peri * sizeof(*pch)
+ sizeof(*pdmac), GFP_KERNEL);
if (!pdmac) {
dev_err(&adev->dev, "unable to allocate mem\n");
return -ENOMEM;
}
pi = &pdmac->pif;
pi->dev = &adev->dev;
pi->pl330_data = NULL;
pi->mcbufsz = pdat->mcbuf_sz;
res = &adev->res;
request_mem_region(res->start, resource_size(res), "dma-pl330");
pi->base = ioremap(res->start, resource_size(res));
if (!pi->base) {
ret = -ENXIO;
goto probe_err1;
}
irq = adev->irq[0];
ret = request_irq(irq, pl330_irq_handler, 0,
dev_name(&adev->dev), pi);
if (ret)
goto probe_err2;
ret = pl330_add(pi);
if (ret)
goto probe_err3;
INIT_LIST_HEAD(&pdmac->desc_pool);
spin_lock_init(&pdmac->pool_lock);
/* Create a descriptor pool of default size */
if (!add_desc(pdmac, GFP_KERNEL, NR_DEFAULT_DESC))
dev_warn(&adev->dev, "unable to allocate desc\n");
pd = &pdmac->ddma;
INIT_LIST_HEAD(&pd->channels);
/* Initialize channel parameters */
for (i = 0; i < pdat->nr_valid_peri; i++) {
struct dma_pl330_peri *peri = &pdat->peri[i];
pch = &pdmac->peripherals[i];
switch (peri->rqtype) {
case MEMTOMEM:
dma_cap_set(DMA_MEMCPY, pd->cap_mask);
break;
case MEMTODEV:
case DEVTOMEM:
dma_cap_set(DMA_SLAVE, pd->cap_mask);
break;
default:
dev_err(&adev->dev, "DEVTODEV Not Supported\n");
continue;
}
INIT_LIST_HEAD(&pch->work_list);
spin_lock_init(&pch->lock);
pch->pl330_chid = NULL;
pch->chan.private = peri;
pch->chan.device = pd;
pch->chan.chan_id = i;
pch->dmac = pdmac;
/* Add the channel to the DMAC list */
pd->chancnt++;
list_add_tail(&pch->chan.device_node, &pd->channels);
}
pd->dev = &adev->dev;
pd->device_alloc_chan_resources = pl330_alloc_chan_resources;
pd->device_free_chan_resources = pl330_free_chan_resources;
pd->device_prep_dma_memcpy = pl330_prep_dma_memcpy;
pd->device_tx_status = pl330_tx_status;
pd->device_prep_slave_sg = pl330_prep_slave_sg;
pd->device_control = pl330_control;
pd->device_issue_pending = pl330_issue_pending;
ret = dma_async_device_register(pd);
if (ret) {
dev_err(&adev->dev, "unable to register DMAC\n");
goto probe_err4;
}
amba_set_drvdata(adev, pdmac);
dev_info(&adev->dev,
"Loaded driver for PL330 DMAC-%d\n", adev->periphid);
dev_info(&adev->dev,
"\tDBUFF-%ux%ubytes Num_Chans-%u Num_Peri-%u Num_Events-%u\n",
pi->pcfg.data_buf_dep,
pi->pcfg.data_bus_width / 8, pi->pcfg.num_chan,
pi->pcfg.num_peri, pi->pcfg.num_events);
return 0;
probe_err4:
pl330_del(pi);
probe_err3:
free_irq(irq, pi);
probe_err2:
iounmap(pi->base);
probe_err1:
release_mem_region(res->start, resource_size(res));
kfree(pdmac);
return ret;
}
static int __devexit pl330_remove(struct amba_device *adev)
{
struct dma_pl330_dmac *pdmac = amba_get_drvdata(adev);
struct dma_pl330_chan *pch, *_p;
struct pl330_info *pi;
struct resource *res;
int irq;
if (!pdmac)
return 0;
amba_set_drvdata(adev, NULL);
/* Idle the DMAC */
list_for_each_entry_safe(pch, _p, &pdmac->ddma.channels,
chan.device_node) {
/* Remove the channel */
list_del(&pch->chan.device_node);
/* Flush the channel */
pl330_control(&pch->chan, DMA_TERMINATE_ALL, 0);
pl330_free_chan_resources(&pch->chan);
}
pi = &pdmac->pif;
pl330_del(pi);
irq = adev->irq[0];
free_irq(irq, pi);
iounmap(pi->base);
res = &adev->res;
release_mem_region(res->start, resource_size(res));
kfree(pdmac);
return 0;
}
static struct amba_id pl330_ids[] = {
{
.id = 0x00041330,
.mask = 0x000fffff,
},
{ 0, 0 },
};
static struct amba_driver pl330_driver = {
.drv = {
.owner = THIS_MODULE,
.name = "dma-pl330",
},
.id_table = pl330_ids,
.probe = pl330_probe,
.remove = pl330_remove,
};
static int __init pl330_init(void)
{
return amba_driver_register(&pl330_driver);
}
module_init(pl330_init);
static void __exit pl330_exit(void)
{
amba_driver_unregister(&pl330_driver);
return;
}
module_exit(pl330_exit);
MODULE_AUTHOR("Jaswinder Singh <jassi.brar@samsung.com>");
MODULE_DESCRIPTION("API Driver for PL330 DMAC");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,45 @@
/* linux/include/linux/amba/pl330.h
*
* Copyright (C) 2010 Samsung Electronics Co. Ltd.
* Jaswinder Singh <jassi.brar@samsung.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.
*/
#ifndef __AMBA_PL330_H_
#define __AMBA_PL330_H_
#include <asm/hardware/pl330.h>
struct dma_pl330_peri {
/*
* Peri_Req i/f of the DMAC that is
* peripheral could be reached from.
*/
u8 peri_id; /* {0, 31} */
enum pl330_reqtype rqtype;
/* For M->D and D->M Channels */
int burst_sz; /* in power of 2 */
dma_addr_t fifo_addr;
};
struct dma_pl330_platdata {
/*
* Number of valid peripherals connected to DMAC.
* This may be different from the value read from
* CR0, as the PL330 implementation might have 'holes'
* in the peri list or the peri could also be reached
* from another DMAC which the platform prefers.
*/
u8 nr_valid_peri;
/* Array of valid peripherals */
struct dma_pl330_peri *peri;
/* Bytes to allocate for MC buffer */
unsigned mcbuf_sz;
};
#endif /* __AMBA_PL330_H_ */