Commit fba95699 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'next' of git://git.infradead.org/users/vkoul/slave-dma

* 'next' of git://git.infradead.org/users/vkoul/slave-dma: (63 commits)
  dmaengine: mid_dma: mask_peripheral_interrupt only when dmac is idle
  dmaengine/ep93xx_dma: add module.h include
  pch_dma: Reduce wasting memory
  pch_dma: Fix suspend issue
  dma/timberdale: free_irq() on an error path
  dma: shdma: transfer based runtime PM
  dmaengine: shdma: protect against the IRQ handler
  dmaengine i.MX DMA/SDMA: add missing include of linux/module.h
  dmaengine: delete redundant chan_id and chancnt initialization in dma drivers
  dmaengine/amba-pl08x: Check txd->llis_va before freeing dma_pool
  dmaengine/amba-pl08x: Add support for sg len greater than one for slave transfers
  serial: sh-sci: don't filter on DMA device, use only channel ID
  ARM: SAMSUNG: Remove Samsung specific enum type for dma direction
  ASoC: Samsung: Update DMA interface
  spi/s3c64xx: Merge dma control code
  spi/s3c64xx: Add support DMA engine API
  ARM: SAMSUNG: Remove S3C-PL330-DMA driver
  ARM: S5P64X0: Use generic DMA PL330 driver
  ARM: S5PC100: Use generic DMA PL330 driver
  ARM: S5PV210: Use generic DMA PL330 driver
  ...

Fix up fairly trivial conflicts in
 - arch/arm/mach-exynos4/{Kconfig,clock.c}
 - arch/arm/mach-s5p64x0/dma.c
parents 3d0a8d10 4598fc2c
......@@ -21,6 +21,9 @@
* OneNAND features.
*/
#ifndef ASM_PL080_H
#define ASM_PL080_H
#define PL080_INT_STATUS (0x00)
#define PL080_TC_STATUS (0x04)
#define PL080_TC_CLEAR (0x08)
......@@ -138,3 +141,4 @@ struct pl080s_lli {
u32 control1;
};
#endif /* ASM_PL080_H */
......@@ -11,7 +11,7 @@ if ARCH_EXYNOS4
config CPU_EXYNOS4210
bool
select S3C_PL330_DMA
select SAMSUNG_DMADEV
select ARM_CPU_SUSPEND if PM
help
Enable EXYNOS4210 CPU support
......
......@@ -111,6 +111,11 @@ struct clk clk_sclk_usbphy1 = {
.name = "sclk_usbphy1",
};
static struct clk dummy_apb_pclk = {
.name = "apb_pclk",
.id = -1,
};
static int exynos4_clksrc_mask_top_ctrl(struct clk *clk, int enable)
{
return s5p_gatectrl(S5P_CLKSRC_MASK_TOP, clk, enable);
......@@ -503,12 +508,12 @@ static struct clk init_clocks_off[] = {
.enable = exynos4_clk_ip_fsys_ctrl,
.ctrlbit = (1 << 9),
}, {
.name = "pdma",
.name = "dma",
.devname = "s3c-pl330.0",
.enable = exynos4_clk_ip_fsys_ctrl,
.ctrlbit = (1 << 0),
}, {
.name = "pdma",
.name = "dma",
.devname = "s3c-pl330.1",
.enable = exynos4_clk_ip_fsys_ctrl,
.ctrlbit = (1 << 1),
......@@ -1281,6 +1286,11 @@ void __init exynos4_register_clocks(void)
s3c_register_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
s3c_disable_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
<<<<<<< HEAD
register_syscore_ops(&exynos4_clock_syscore_ops);
=======
s3c24xx_register_clock(&dummy_apb_pclk);
>>>>>>> 4598fc2c94b68740e0269db03c98a1e7ad5af773
s3c_pwmclk_init();
}
......@@ -21,151 +21,228 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/amba/bus.h>
#include <linux/amba/pl330.h>
#include <asm/irq.h>
#include <plat/devs.h>
#include <plat/irqs.h>
#include <mach/map.h>
#include <mach/irqs.h>
#include <plat/s3c-pl330-pdata.h>
#include <mach/dma.h>
static u64 dma_dmamask = DMA_BIT_MASK(32);
static struct resource exynos4_pdma0_resource[] = {
[0] = {
.start = EXYNOS4_PA_PDMA0,
.end = EXYNOS4_PA_PDMA0 + SZ_4K,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_PDMA0,
.end = IRQ_PDMA0,
.flags = IORESOURCE_IRQ,
struct dma_pl330_peri pdma0_peri[28] = {
{
.peri_id = (u8)DMACH_PCM0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_PCM0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_PCM2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_PCM2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_MSM_REQ0,
}, {
.peri_id = (u8)DMACH_MSM_REQ2,
}, {
.peri_id = (u8)DMACH_SPI0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S0S_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART4_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART4_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SLIMBUS0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SLIMBUS0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SLIMBUS2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SLIMBUS2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SLIMBUS4_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SLIMBUS4_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_AC97_MICIN,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_AC97_PCMIN,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_AC97_PCMOUT,
.rqtype = MEMTODEV,
},
};
static struct s3c_pl330_platdata exynos4_pdma0_pdata = {
.peri = {
[0] = DMACH_PCM0_RX,
[1] = DMACH_PCM0_TX,
[2] = DMACH_PCM2_RX,
[3] = DMACH_PCM2_TX,
[4] = DMACH_MSM_REQ0,
[5] = DMACH_MSM_REQ2,
[6] = DMACH_SPI0_RX,
[7] = DMACH_SPI0_TX,
[8] = DMACH_SPI2_RX,
[9] = DMACH_SPI2_TX,
[10] = DMACH_I2S0S_TX,
[11] = DMACH_I2S0_RX,
[12] = DMACH_I2S0_TX,
[13] = DMACH_I2S2_RX,
[14] = DMACH_I2S2_TX,
[15] = DMACH_UART0_RX,
[16] = DMACH_UART0_TX,
[17] = DMACH_UART2_RX,
[18] = DMACH_UART2_TX,
[19] = DMACH_UART4_RX,
[20] = DMACH_UART4_TX,
[21] = DMACH_SLIMBUS0_RX,
[22] = DMACH_SLIMBUS0_TX,
[23] = DMACH_SLIMBUS2_RX,
[24] = DMACH_SLIMBUS2_TX,
[25] = DMACH_SLIMBUS4_RX,
[26] = DMACH_SLIMBUS4_TX,
[27] = DMACH_AC97_MICIN,
[28] = DMACH_AC97_PCMIN,
[29] = DMACH_AC97_PCMOUT,
[30] = DMACH_MAX,
[31] = DMACH_MAX,
},
struct dma_pl330_platdata exynos4_pdma0_pdata = {
.nr_valid_peri = ARRAY_SIZE(pdma0_peri),
.peri = pdma0_peri,
};
static struct platform_device exynos4_device_pdma0 = {
.name = "s3c-pl330",
.id = 0,
.num_resources = ARRAY_SIZE(exynos4_pdma0_resource),
.resource = exynos4_pdma0_resource,
struct amba_device exynos4_device_pdma0 = {
.dev = {
.init_name = "dma-pl330.0",
.dma_mask = &dma_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
.platform_data = &exynos4_pdma0_pdata,
},
};
static struct resource exynos4_pdma1_resource[] = {
[0] = {
.start = EXYNOS4_PA_PDMA1,
.end = EXYNOS4_PA_PDMA1 + SZ_4K,
.res = {
.start = EXYNOS4_PA_PDMA0,
.end = EXYNOS4_PA_PDMA0 + SZ_4K,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_PDMA1,
.end = IRQ_PDMA1,
.flags = IORESOURCE_IRQ,
},
.irq = {IRQ_PDMA0, NO_IRQ},
.periphid = 0x00041330,
};
static struct s3c_pl330_platdata exynos4_pdma1_pdata = {
.peri = {
[0] = DMACH_PCM0_RX,
[1] = DMACH_PCM0_TX,
[2] = DMACH_PCM1_RX,
[3] = DMACH_PCM1_TX,
[4] = DMACH_MSM_REQ1,
[5] = DMACH_MSM_REQ3,
[6] = DMACH_SPI1_RX,
[7] = DMACH_SPI1_TX,
[8] = DMACH_I2S0S_TX,
[9] = DMACH_I2S0_RX,
[10] = DMACH_I2S0_TX,
[11] = DMACH_I2S1_RX,
[12] = DMACH_I2S1_TX,
[13] = DMACH_UART0_RX,
[14] = DMACH_UART0_TX,
[15] = DMACH_UART1_RX,
[16] = DMACH_UART1_TX,
[17] = DMACH_UART3_RX,
[18] = DMACH_UART3_TX,
[19] = DMACH_SLIMBUS1_RX,
[20] = DMACH_SLIMBUS1_TX,
[21] = DMACH_SLIMBUS3_RX,
[22] = DMACH_SLIMBUS3_TX,
[23] = DMACH_SLIMBUS5_RX,
[24] = DMACH_SLIMBUS5_TX,
[25] = DMACH_SLIMBUS0AUX_RX,
[26] = DMACH_SLIMBUS0AUX_TX,
[27] = DMACH_SPDIF,
[28] = DMACH_MAX,
[29] = DMACH_MAX,
[30] = DMACH_MAX,
[31] = DMACH_MAX,
struct dma_pl330_peri pdma1_peri[25] = {
{
.peri_id = (u8)DMACH_PCM0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_PCM0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_PCM1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_PCM1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_MSM_REQ1,
}, {
.peri_id = (u8)DMACH_MSM_REQ3,
}, {
.peri_id = (u8)DMACH_SPI1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S0S_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART3_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART3_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SLIMBUS1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SLIMBUS1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SLIMBUS3_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SLIMBUS3_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SLIMBUS5_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SLIMBUS5_TX,
.rqtype = MEMTODEV,
},
};
static struct platform_device exynos4_device_pdma1 = {
.name = "s3c-pl330",
.id = 1,
.num_resources = ARRAY_SIZE(exynos4_pdma1_resource),
.resource = exynos4_pdma1_resource,
struct dma_pl330_platdata exynos4_pdma1_pdata = {
.nr_valid_peri = ARRAY_SIZE(pdma1_peri),
.peri = pdma1_peri,
};
struct amba_device exynos4_device_pdma1 = {
.dev = {
.init_name = "dma-pl330.1",
.dma_mask = &dma_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
.platform_data = &exynos4_pdma1_pdata,
},
};
static struct platform_device *exynos4_dmacs[] __initdata = {
&exynos4_device_pdma0,
&exynos4_device_pdma1,
.res = {
.start = EXYNOS4_PA_PDMA1,
.end = EXYNOS4_PA_PDMA1 + SZ_4K,
.flags = IORESOURCE_MEM,
},
.irq = {IRQ_PDMA1, NO_IRQ},
.periphid = 0x00041330,
};
static int __init exynos4_dma_init(void)
{
platform_add_devices(exynos4_dmacs, ARRAY_SIZE(exynos4_dmacs));
amba_device_register(&exynos4_device_pdma0, &iomem_resource);
return 0;
}
......
......@@ -20,7 +20,7 @@
#ifndef __MACH_DMA_H
#define __MACH_DMA_H
/* This platform uses the common S3C DMA API driver for PL330 */
#include <plat/s3c-dma-pl330.h>
/* This platform uses the common DMA API driver for PL330 */
#include <plat/dma-pl330.h>
#endif /* __MACH_DMA_H */
......@@ -13,7 +13,6 @@
#ifndef __ASM_ARCH_DMA_H
#define __ASM_ARCH_DMA_H __FILE__
#include <plat/dma.h>
#include <linux/sysdev.h>
#define MAX_DMA_TRANSFER_SIZE 0x100000 /* Data Unit is half word */
......@@ -51,6 +50,18 @@ enum dma_ch {
DMACH_MAX, /* the end entry */
};
static inline bool samsung_dma_has_circular(void)
{
return false;
}
static inline bool samsung_dma_is_dmadev(void)
{
return false;
}
#include <plat/dma.h>
#define DMACH_LOW_LEVEL (1<<28) /* use this to specifiy hardware ch no */
/* we have 4 dma channels */
......@@ -163,7 +174,7 @@ struct s3c2410_dma_chan {
struct s3c2410_dma_client *client;
/* channel configuration */
enum s3c2410_dmasrc source;
enum dma_data_direction source;
enum dma_ch req_ch;
unsigned long dev_addr;
unsigned long load_timeout;
......@@ -196,9 +207,4 @@ struct s3c2410_dma_chan {
typedef unsigned long dma_device_t;
static inline bool s3c_dma_has_circular(void)
{
return false;
}
#endif /* __ASM_ARCH_DMA_H */
......@@ -130,11 +130,11 @@ static struct s3c24xx_dma_map __initdata s3c2412_dma_mappings[] = {
static void s3c2412_dma_direction(struct s3c2410_dma_chan *chan,
struct s3c24xx_dma_map *map,
enum s3c2410_dmasrc dir)
enum dma_data_direction dir)
{
unsigned long chsel;
if (dir == S3C2410_DMASRC_HW)
if (dir == DMA_FROM_DEVICE)
chsel = map->channels_rx[0];
else
chsel = map->channels[0];
......
......@@ -147,14 +147,14 @@ static void s3c64xx_dma_fill_lli(struct s3c2410_dma_chan *chan,
u32 control0, control1;
switch (chan->source) {
case S3C2410_DMASRC_HW:
case DMA_FROM_DEVICE:
src = chan->dev_addr;
dst = data;
control0 = PL080_CONTROL_SRC_AHB2;
control0 |= PL080_CONTROL_DST_INCR;
break;
case S3C2410_DMASRC_MEM:
case DMA_TO_DEVICE:
src = data;
dst = chan->dev_addr;
control0 = PL080_CONTROL_DST_AHB2;
......@@ -416,7 +416,7 @@ EXPORT_SYMBOL(s3c2410_dma_enqueue);
int s3c2410_dma_devconfig(enum dma_ch channel,
enum s3c2410_dmasrc source,
enum dma_data_direction source,
unsigned long devaddr)
{
struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
......@@ -437,11 +437,11 @@ int s3c2410_dma_devconfig(enum dma_ch channel,
pr_debug("%s: peripheral %d\n", __func__, peripheral);
switch (source) {
case S3C2410_DMASRC_HW:
case DMA_FROM_DEVICE:
config = 2 << PL080_CONFIG_FLOW_CONTROL_SHIFT;
config |= peripheral << PL080_CONFIG_SRC_SEL_SHIFT;
break;
case S3C2410_DMASRC_MEM:
case DMA_TO_DEVICE:
config = 1 << PL080_CONFIG_FLOW_CONTROL_SHIFT;
config |= peripheral << PL080_CONFIG_DST_SEL_SHIFT;
break;
......
......@@ -58,11 +58,15 @@ enum dma_ch {
DMACH_MAX /* the end */
};
static __inline__ bool s3c_dma_has_circular(void)
static inline bool samsung_dma_has_circular(void)
{
return true;
}
static inline bool samsung_dma_is_dmadev(void)
{
return false;
}
#define S3C2410_DMAF_CIRCULAR (1 << 0)
#include <plat/dma.h>
......@@ -95,7 +99,7 @@ struct s3c2410_dma_chan {
unsigned char peripheral;
unsigned int flags;
enum s3c2410_dmasrc source;
enum dma_data_direction source;
dma_addr_t dev_addr;
......
......@@ -9,14 +9,14 @@ if ARCH_S5P64X0
config CPU_S5P6440
bool
select S3C_PL330_DMA
select SAMSUNG_DMADEV
select S5P_HRT
help
Enable S5P6440 CPU support
config CPU_S5P6450
bool
select S3C_PL330_DMA
select SAMSUNG_DMADEV
select S5P_HRT
help
Enable S5P6450 CPU support
......
......@@ -146,7 +146,7 @@ static struct clk init_clocks_off[] = {
.enable = s5p64x0_hclk0_ctrl,
.ctrlbit = (1 << 8),
}, {
.name = "pdma",
.name = "dma",
.parent = &clk_hclk_low.clk,
.enable = s5p64x0_hclk0_ctrl,
.ctrlbit = (1 << 12),
......@@ -499,6 +499,11 @@ static struct clksrc_clk *sysclks[] = {
&clk_pclk_low,
};
static struct clk dummy_apb_pclk = {
.name = "apb_pclk",
.id = -1,
};
void __init_or_cpufreq s5p6440_setup_clocks(void)
{
struct clk *xtal_clk;
......@@ -581,5 +586,7 @@ void __init s5p6440_register_clocks(void)
s3c_register_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
s3c_disable_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
s3c24xx_register_clock(&dummy_apb_pclk);
s3c_pwmclk_init();
}
......@@ -179,7 +179,7 @@ static struct clk init_clocks_off[] = {
.enable = s5p64x0_hclk0_ctrl,
.ctrlbit = (1 << 3),
}, {
.name = "pdma",
.name = "dma",
.parent = &clk_hclk_low.clk,
.enable = s5p64x0_hclk0_ctrl,
.ctrlbit = (1 << 12),
......@@ -553,6 +553,11 @@ static struct clksrc_clk *sysclks[] = {
&clk_sclk_audio0,
};
static struct clk dummy_apb_pclk = {
.name = "apb_pclk",
.id = -1,
};
void __init_or_cpufreq s5p6450_setup_clocks(void)
{
struct clk *xtal_clk;
......@@ -632,5 +637,7 @@ void __init s5p6450_register_clocks(void)
s3c_register_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
s3c_disable_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
s3c24xx_register_clock(&dummy_apb_pclk);
s3c_pwmclk_init();
}
......@@ -21,115 +21,208 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/amba/bus.h>
#include <linux/amba/pl330.h>
#include <asm/irq.h>
#include <mach/map.h>
#include <mach/irqs.h>
#include <mach/regs-clock.h>
#include <mach/dma.h>
#include <plat/cpu.h>
#include <plat/devs.h>
#include <plat/s3c-pl330-pdata.h>
#include <plat/irqs.h>
static u64 dma_dmamask = DMA_BIT_MASK(32);
static struct resource s5p64x0_pdma_resource[] = {
[0] = {
.start = S5P64X0_PA_PDMA,
.end = S5P64X0_PA_PDMA + SZ_4K,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_DMA0,
.end = IRQ_DMA0,
.flags = IORESOURCE_IRQ,
struct dma_pl330_peri s5p6440_pdma_peri[22] = {
{
.peri_id = (u8)DMACH_UART0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART3_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART3_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = DMACH_MAX,
}, {
.peri_id = DMACH_MAX,
}, {
.peri_id = (u8)DMACH_PCM0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_PCM0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_MAX,
}, {
.peri_id = (u8)DMACH_MAX,
}, {
.peri_id = (u8)DMACH_MAX,
}, {
.peri_id = (u8)DMACH_MAX,
}, {
.peri_id = (u8)DMACH_SPI1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI1_RX,
.rqtype = DEVTOMEM,
},
};
static struct s3c_pl330_platdata s5p6440_pdma_pdata = {
.peri = {
[0] = DMACH_UART0_RX,
[1] = DMACH_UART0_TX,
[2] = DMACH_UART1_RX,
[3] = DMACH_UART1_TX,
[4] = DMACH_UART2_RX,
[5] = DMACH_UART2_TX,
[6] = DMACH_UART3_RX,
[7] = DMACH_UART3_TX,
[8] = DMACH_MAX,
[9] = DMACH_MAX,
[10] = DMACH_PCM0_TX,
[11] = DMACH_PCM0_RX,
[12] = DMACH_I2S0_TX,
[13] = DMACH_I2S0_RX,
[14] = DMACH_SPI0_TX,
[15] = DMACH_SPI0_RX,
[16] = DMACH_MAX,
[17] = DMACH_MAX,
[18] = DMACH_MAX,
[19] = DMACH_MAX,
[20] = DMACH_SPI1_TX,
[21] = DMACH_SPI1_RX,
[22] = DMACH_MAX,
[23] = DMACH_MAX,
[24] = DMACH_MAX,
[25] = DMACH_MAX,
[26] = DMACH_MAX,
[27] = DMACH_MAX,
[28] = DMACH_MAX,
[29] = DMACH_PWM,
[30] = DMACH_MAX,
[31] = DMACH_MAX,
},
struct dma_pl330_platdata s5p6440_pdma_pdata = {
.nr_valid_peri = ARRAY_SIZE(s5p6440_pdma_peri),
.peri = s5p6440_pdma_peri,
};
static struct s3c_pl330_platdata s5p6450_pdma_pdata = {
.peri = {
[0] = DMACH_UART0_RX,
[1] = DMACH_UART0_TX,
[2] = DMACH_UART1_RX,
[3] = DMACH_UART1_TX,
[4] = DMACH_UART2_RX,
[5] = DMACH_UART2_TX,
[6] = DMACH_UART3_RX,
[7] = DMACH_UART3_TX,
[8] = DMACH_UART4_RX,
[9] = DMACH_UART4_TX,
[10] = DMACH_PCM0_TX,
[11] = DMACH_PCM0_RX,
[12] = DMACH_I2S0_TX,
[13] = DMACH_I2S0_RX,
[14] = DMACH_SPI0_TX,
[15] = DMACH_SPI0_RX,
[16] = DMACH_PCM1_TX,
[17] = DMACH_PCM1_RX,
[18] = DMACH_PCM2_TX,
[19] = DMACH_PCM2_RX,
[20] = DMACH_SPI1_TX,
[21] = DMACH_SPI1_RX,
[22] = DMACH_USI_TX,
[23] = DMACH_USI_RX,
[24] = DMACH_MAX,
[25] = DMACH_I2S1_TX,
[26] = DMACH_I2S1_RX,
[27] = DMACH_I2S2_TX,
[28] = DMACH_I2S2_RX,
[29] = DMACH_PWM,
[30] = DMACH_UART5_RX,
[31] = DMACH_UART5_TX,
struct dma_pl330_peri s5p6450_pdma_peri[32] = {
{
.peri_id = (u8)DMACH_UART0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART3_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART3_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART4_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART4_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_PCM0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_PCM0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_PCM1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_PCM1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_PCM2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_PCM2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_USI_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_USI_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_MAX,
}, {
.peri_id = (u8)DMACH_I2S1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_PWM,
}, {
.peri_id = (u8)DMACH_UART5_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART5_TX,
.rqtype = MEMTODEV,
},
};
static struct platform_device s5p64x0_device_pdma = {
.name = "s3c-pl330",
.id = -1,
.num_resources = ARRAY_SIZE(s5p64x0_pdma_resource),
.resource = s5p64x0_pdma_resource,
struct dma_pl330_platdata s5p6450_pdma_pdata = {
.nr_valid_peri = ARRAY_SIZE(s5p6450_pdma_peri),
.peri = s5p6450_pdma_peri,
};
struct amba_device s5p64x0_device_pdma = {
.dev = {
.init_name = "dma-pl330",
.dma_mask = &dma_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
},
.res = {
.start = S5P64X0_PA_PDMA,
.end = S5P64X0_PA_PDMA + SZ_4K,
.flags = IORESOURCE_MEM,
},
.irq = {IRQ_DMA0, NO_IRQ},
.periphid = 0x00041330,
};
static int __init s5p64x0_dma_init(void)
......@@ -139,7 +232,7 @@ static int __init s5p64x0_dma_init(void)
else
s5p64x0_device_pdma.dev.platform_data = &s5p6440_pdma_pdata;
platform_device_register(&s5p64x0_device_pdma);
amba_device_register(&s5p64x0_device_pdma, &iomem_resource);
return 0;
}
......
......@@ -20,7 +20,7 @@
#ifndef __MACH_DMA_H
#define __MACH_DMA_H
/* This platform uses the common S3C DMA API driver for PL330 */
#include <plat/s3c-dma-pl330.h>
/* This platform uses the common common DMA API driver for PL330 */
#include <plat/dma-pl330.h>
#endif /* __MACH_DMA_H */
......@@ -10,7 +10,7 @@ if ARCH_S5PC100
config CPU_S5PC100
bool
select S5P_EXT_INT
select S3C_PL330_DMA
select SAMSUNG_DMADEV
help
Enable S5PC100 CPU support
......
......@@ -33,6 +33,11 @@ static struct clk s5p_clk_otgphy = {
.name = "otg_phy",
};
static struct clk dummy_apb_pclk = {
.name = "apb_pclk",
.id = -1,
};
static struct clk *clk_src_mout_href_list[] = {
[0] = &s5p_clk_27m,
[1] = &clk_fin_hpll,
......@@ -454,13 +459,13 @@ static struct clk init_clocks_off[] = {
.enable = s5pc100_d1_0_ctrl,
.ctrlbit = (1 << 2),
}, {
.name = "pdma",
.name = "dma",
.devname = "s3c-pl330.1",
.parent = &clk_div_d1_bus.clk,
.enable = s5pc100_d1_0_ctrl,
.ctrlbit = (1 << 1),
}, {
.name = "pdma",
.name = "dma",
.devname = "s3c-pl330.0",
.parent = &clk_div_d1_bus.clk,
.enable = s5pc100_d1_0_ctrl,
......@@ -1276,5 +1281,7 @@ void __init s5pc100_register_clocks(void)
s3c_register_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
s3c_disable_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
s3c24xx_register_clock(&dummy_apb_pclk);
s3c_pwmclk_init();
}
/*
/* linux/arch/arm/mach-s5pc100/dma.c
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Copyright (C) 2010 Samsung Electronics Co. Ltd.
* Jaswinder Singh <jassi.brar@samsung.com>
*
......@@ -17,150 +21,245 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/amba/bus.h>
#include <linux/amba/pl330.h>
#include <asm/irq.h>
#include <plat/devs.h>
#include <plat/irqs.h>
#include <mach/map.h>
#include <mach/irqs.h>
#include <plat/s3c-pl330-pdata.h>
#include <mach/dma.h>
static u64 dma_dmamask = DMA_BIT_MASK(32);
static struct resource s5pc100_pdma0_resource[] = {
[0] = {
.start = S5PC100_PA_PDMA0,
.end = S5PC100_PA_PDMA0 + SZ_4K,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_PDMA0,
.end = IRQ_PDMA0,
.flags = IORESOURCE_IRQ,
struct dma_pl330_peri pdma0_peri[30] = {
{
.peri_id = (u8)DMACH_UART0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART3_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART3_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = DMACH_IRDA,
}, {
.peri_id = (u8)DMACH_I2S0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S0S_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_AC97_MICIN,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_AC97_PCMIN,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_AC97_PCMOUT,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_EXTERNAL,
}, {
.peri_id = (u8)DMACH_PWM,
}, {
.peri_id = (u8)DMACH_SPDIF,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_HSI_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_HSI_TX,
.rqtype = MEMTODEV,
},
};
static struct s3c_pl330_platdata s5pc100_pdma0_pdata = {
.peri = {
[0] = DMACH_UART0_RX,
[1] = DMACH_UART0_TX,
[2] = DMACH_UART1_RX,
[3] = DMACH_UART1_TX,
[4] = DMACH_UART2_RX,
[5] = DMACH_UART2_TX,
[6] = DMACH_UART3_RX,
[7] = DMACH_UART3_TX,
[8] = DMACH_IRDA,
[9] = DMACH_I2S0_RX,
[10] = DMACH_I2S0_TX,
[11] = DMACH_I2S0S_TX,
[12] = DMACH_I2S1_RX,
[13] = DMACH_I2S1_TX,
[14] = DMACH_I2S2_RX,
[15] = DMACH_I2S2_TX,
[16] = DMACH_SPI0_RX,
[17] = DMACH_SPI0_TX,
[18] = DMACH_SPI1_RX,
[19] = DMACH_SPI1_TX,
[20] = DMACH_SPI2_RX,
[21] = DMACH_SPI2_TX,
[22] = DMACH_AC97_MICIN,
[23] = DMACH_AC97_PCMIN,
[24] = DMACH_AC97_PCMOUT,
[25] = DMACH_EXTERNAL,
[26] = DMACH_PWM,
[27] = DMACH_SPDIF,
[28] = DMACH_HSI_RX,
[29] = DMACH_HSI_TX,
[30] = DMACH_MAX,
[31] = DMACH_MAX,
},
struct dma_pl330_platdata s5pc100_pdma0_pdata = {
.nr_valid_peri = ARRAY_SIZE(pdma0_peri),
.peri = pdma0_peri,
};
static struct platform_device s5pc100_device_pdma0 = {
.name = "s3c-pl330",
.id = 0,
.num_resources = ARRAY_SIZE(s5pc100_pdma0_resource),
.resource = s5pc100_pdma0_resource,
struct amba_device s5pc100_device_pdma0 = {
.dev = {
.init_name = "dma-pl330.0",
.dma_mask = &dma_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
.platform_data = &s5pc100_pdma0_pdata,
},
};
static struct resource s5pc100_pdma1_resource[] = {
[0] = {
.start = S5PC100_PA_PDMA1,
.end = S5PC100_PA_PDMA1 + SZ_4K,
.res = {
.start = S5PC100_PA_PDMA0,
.end = S5PC100_PA_PDMA0 + SZ_4K,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_PDMA1,
.end = IRQ_PDMA1,
.flags = IORESOURCE_IRQ,
},
.irq = {IRQ_PDMA0, NO_IRQ},
.periphid = 0x00041330,
};
static struct s3c_pl330_platdata s5pc100_pdma1_pdata = {
.peri = {
[0] = DMACH_UART0_RX,
[1] = DMACH_UART0_TX,
[2] = DMACH_UART1_RX,
[3] = DMACH_UART1_TX,
[4] = DMACH_UART2_RX,
[5] = DMACH_UART2_TX,
[6] = DMACH_UART3_RX,
[7] = DMACH_UART3_TX,
[8] = DMACH_IRDA,
[9] = DMACH_I2S0_RX,
[10] = DMACH_I2S0_TX,
[11] = DMACH_I2S0S_TX,
[12] = DMACH_I2S1_RX,
[13] = DMACH_I2S1_TX,
[14] = DMACH_I2S2_RX,
[15] = DMACH_I2S2_TX,
[16] = DMACH_SPI0_RX,
[17] = DMACH_SPI0_TX,
[18] = DMACH_SPI1_RX,
[19] = DMACH_SPI1_TX,
[20] = DMACH_SPI2_RX,
[21] = DMACH_SPI2_TX,
[22] = DMACH_PCM0_RX,
[23] = DMACH_PCM0_TX,
[24] = DMACH_PCM1_RX,
[25] = DMACH_PCM1_TX,
[26] = DMACH_MSM_REQ0,
[27] = DMACH_MSM_REQ1,
[28] = DMACH_MSM_REQ2,
[29] = DMACH_MSM_REQ3,
[30] = DMACH_MAX,
[31] = DMACH_MAX,
struct dma_pl330_peri pdma1_peri[30] = {
{
.peri_id = (u8)DMACH_UART0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART3_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART3_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = DMACH_IRDA,
}, {
.peri_id = (u8)DMACH_I2S0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S0S_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_PCM0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_PCM1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_PCM1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_PCM1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_MSM_REQ0,
}, {
.peri_id = (u8)DMACH_MSM_REQ1,
}, {
.peri_id = (u8)DMACH_MSM_REQ2,
}, {
.peri_id = (u8)DMACH_MSM_REQ3,
},
};
static struct platform_device s5pc100_device_pdma1 = {
.name = "s3c-pl330",
.id = 1,
.num_resources = ARRAY_SIZE(s5pc100_pdma1_resource),
.resource = s5pc100_pdma1_resource,
struct dma_pl330_platdata s5pc100_pdma1_pdata = {
.nr_valid_peri = ARRAY_SIZE(pdma1_peri),
.peri = pdma1_peri,
};
struct amba_device s5pc100_device_pdma1 = {
.dev = {
.init_name = "dma-pl330.1",
.dma_mask = &dma_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
.platform_data = &s5pc100_pdma1_pdata,
},
};
static struct platform_device *s5pc100_dmacs[] __initdata = {
&s5pc100_device_pdma0,
&s5pc100_device_pdma1,
.res = {
.start = S5PC100_PA_PDMA1,
.end = S5PC100_PA_PDMA1 + SZ_4K,
.flags = IORESOURCE_MEM,
},
.irq = {IRQ_PDMA1, NO_IRQ},
.periphid = 0x00041330,
};
static int __init s5pc100_dma_init(void)
{
platform_add_devices(s5pc100_dmacs, ARRAY_SIZE(s5pc100_dmacs));
amba_device_register(&s5pc100_device_pdma0, &iomem_resource);
return 0;
}
......
......@@ -20,7 +20,7 @@
#ifndef __MACH_DMA_H
#define __MACH_DMA_H
/* This platform uses the common S3C DMA API driver for PL330 */
#include <plat/s3c-dma-pl330.h>
/* This platform uses the common DMA API driver for PL330 */
#include <plat/dma-pl330.h>
#endif /* __MACH_DMA_H */
......@@ -11,7 +11,7 @@ if ARCH_S5PV210
config CPU_S5PV210
bool
select S3C_PL330_DMA
select SAMSUNG_DMADEV
select S5P_EXT_INT
select S5P_HRT
help
......
......@@ -203,6 +203,11 @@ static struct clk clk_pcmcdclk2 = {
.name = "pcmcdclk",
};
static struct clk dummy_apb_pclk = {
.name = "apb_pclk",
.id = -1,
};
static struct clk *clkset_vpllsrc_list[] = {
[0] = &clk_fin_vpll,
[1] = &clk_sclk_hdmi27m,
......@@ -289,13 +294,13 @@ static struct clk_ops clk_fout_apll_ops = {
static struct clk init_clocks_off[] = {
{
.name = "pdma",
.name = "dma",
.devname = "s3c-pl330.0",
.parent = &clk_hclk_psys.clk,
.enable = s5pv210_clk_ip0_ctrl,
.ctrlbit = (1 << 3),
}, {
.name = "pdma",
.name = "dma",
.devname = "s3c-pl330.1",
.parent = &clk_hclk_psys.clk,
.enable = s5pv210_clk_ip0_ctrl,
......@@ -1159,5 +1164,6 @@ void __init s5pv210_register_clocks(void)
s3c_register_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
s3c_disable_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
s3c24xx_register_clock(&dummy_apb_pclk);
s3c_pwmclk_init();
}
/*
/* linux/arch/arm/mach-s5pv210/dma.c
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Copyright (C) 2010 Samsung Electronics Co. Ltd.
* Jaswinder Singh <jassi.brar@samsung.com>
*
......@@ -17,151 +21,239 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/amba/bus.h>
#include <linux/amba/pl330.h>
#include <asm/irq.h>
#include <plat/devs.h>
#include <plat/irqs.h>
#include <mach/map.h>
#include <mach/irqs.h>
#include <plat/s3c-pl330-pdata.h>
#include <mach/dma.h>
static u64 dma_dmamask = DMA_BIT_MASK(32);
static struct resource s5pv210_pdma0_resource[] = {
[0] = {
.start = S5PV210_PA_PDMA0,
.end = S5PV210_PA_PDMA0 + SZ_4K,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_PDMA0,
.end = IRQ_PDMA0,
.flags = IORESOURCE_IRQ,
struct dma_pl330_peri pdma0_peri[28] = {
{
.peri_id = (u8)DMACH_UART0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART3_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART3_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = DMACH_MAX,
}, {
.peri_id = (u8)DMACH_I2S0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S0S_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_MAX,
}, {
.peri_id = (u8)DMACH_MAX,
}, {
.peri_id = (u8)DMACH_SPI0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_MAX,
}, {
.peri_id = (u8)DMACH_MAX,
}, {
.peri_id = (u8)DMACH_AC97_MICIN,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_AC97_PCMIN,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_AC97_PCMOUT,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_MAX,
}, {
.peri_id = (u8)DMACH_PWM,
}, {
.peri_id = (u8)DMACH_SPDIF,
.rqtype = MEMTODEV,
},
};
static struct s3c_pl330_platdata s5pv210_pdma0_pdata = {
.peri = {
[0] = DMACH_UART0_RX,
[1] = DMACH_UART0_TX,
[2] = DMACH_UART1_RX,
[3] = DMACH_UART1_TX,
[4] = DMACH_UART2_RX,
[5] = DMACH_UART2_TX,
[6] = DMACH_UART3_RX,
[7] = DMACH_UART3_TX,
[8] = DMACH_MAX,
[9] = DMACH_I2S0_RX,
[10] = DMACH_I2S0_TX,
[11] = DMACH_I2S0S_TX,
[12] = DMACH_I2S1_RX,
[13] = DMACH_I2S1_TX,
[14] = DMACH_MAX,
[15] = DMACH_MAX,
[16] = DMACH_SPI0_RX,
[17] = DMACH_SPI0_TX,
[18] = DMACH_SPI1_RX,
[19] = DMACH_SPI1_TX,
[20] = DMACH_MAX,
[21] = DMACH_MAX,
[22] = DMACH_AC97_MICIN,
[23] = DMACH_AC97_PCMIN,
[24] = DMACH_AC97_PCMOUT,
[25] = DMACH_MAX,
[26] = DMACH_PWM,
[27] = DMACH_SPDIF,
[28] = DMACH_MAX,
[29] = DMACH_MAX,
[30] = DMACH_MAX,
[31] = DMACH_MAX,
},
struct dma_pl330_platdata s5pv210_pdma0_pdata = {
.nr_valid_peri = ARRAY_SIZE(pdma0_peri),
.peri = pdma0_peri,
};
static struct platform_device s5pv210_device_pdma0 = {
.name = "s3c-pl330",
.id = 0,
.num_resources = ARRAY_SIZE(s5pv210_pdma0_resource),
.resource = s5pv210_pdma0_resource,
struct amba_device s5pv210_device_pdma0 = {
.dev = {
.init_name = "dma-pl330.0",
.dma_mask = &dma_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
.platform_data = &s5pv210_pdma0_pdata,
},
};
static struct resource s5pv210_pdma1_resource[] = {
[0] = {
.start = S5PV210_PA_PDMA1,
.end = S5PV210_PA_PDMA1 + SZ_4K,
.res = {
.start = S5PV210_PA_PDMA0,
.end = S5PV210_PA_PDMA0 + SZ_4K,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_PDMA1,
.end = IRQ_PDMA1,
.flags = IORESOURCE_IRQ,
},
.irq = {IRQ_PDMA0, NO_IRQ},
.periphid = 0x00041330,
};
static struct s3c_pl330_platdata s5pv210_pdma1_pdata = {
.peri = {
[0] = DMACH_UART0_RX,
[1] = DMACH_UART0_TX,
[2] = DMACH_UART1_RX,
[3] = DMACH_UART1_TX,
[4] = DMACH_UART2_RX,
[5] = DMACH_UART2_TX,
[6] = DMACH_UART3_RX,
[7] = DMACH_UART3_TX,
[8] = DMACH_MAX,
[9] = DMACH_I2S0_RX,
[10] = DMACH_I2S0_TX,
[11] = DMACH_I2S0S_TX,
[12] = DMACH_I2S1_RX,
[13] = DMACH_I2S1_TX,
[14] = DMACH_I2S2_RX,
[15] = DMACH_I2S2_TX,
[16] = DMACH_SPI0_RX,
[17] = DMACH_SPI0_TX,
[18] = DMACH_SPI1_RX,
[19] = DMACH_SPI1_TX,
[20] = DMACH_MAX,
[21] = DMACH_MAX,
[22] = DMACH_PCM0_RX,
[23] = DMACH_PCM0_TX,
[24] = DMACH_PCM1_RX,
[25] = DMACH_PCM1_TX,
[26] = DMACH_MSM_REQ0,
[27] = DMACH_MSM_REQ1,
[28] = DMACH_MSM_REQ2,
[29] = DMACH_MSM_REQ3,
[30] = DMACH_PCM2_RX,
[31] = DMACH_PCM2_TX,
struct dma_pl330_peri pdma1_peri[32] = {
{
.peri_id = (u8)DMACH_UART0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_UART3_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_UART3_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = DMACH_MAX,
}, {
.peri_id = (u8)DMACH_I2S0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S0S_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_I2S2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_I2S2_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_SPI1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_SPI1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_MAX,
}, {
.peri_id = (u8)DMACH_MAX,
}, {
.peri_id = (u8)DMACH_PCM0_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_PCM0_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_PCM1_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_PCM1_TX,
.rqtype = MEMTODEV,
}, {
.peri_id = (u8)DMACH_MSM_REQ0,
}, {
.peri_id = (u8)DMACH_MSM_REQ1,
}, {
.peri_id = (u8)DMACH_MSM_REQ2,
}, {
.peri_id = (u8)DMACH_MSM_REQ3,
}, {
.peri_id = (u8)DMACH_PCM2_RX,
.rqtype = DEVTOMEM,
}, {
.peri_id = (u8)DMACH_PCM2_TX,
.rqtype = MEMTODEV,
},
};
static struct platform_device s5pv210_device_pdma1 = {
.name = "s3c-pl330",
.id = 1,
.num_resources = ARRAY_SIZE(s5pv210_pdma1_resource),
.resource = s5pv210_pdma1_resource,
struct dma_pl330_platdata s5pv210_pdma1_pdata = {
.nr_valid_peri = ARRAY_SIZE(pdma1_peri),
.peri = pdma1_peri,
};
struct amba_device s5pv210_device_pdma1 = {
.dev = {
.init_name = "dma-pl330.1",
.dma_mask = &dma_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
.platform_data = &s5pv210_pdma1_pdata,
},
};
static struct platform_device *s5pv210_dmacs[] __initdata = {
&s5pv210_device_pdma0,
&s5pv210_device_pdma1,
.res = {
.start = S5PV210_PA_PDMA1,
.end = S5PV210_PA_PDMA1 + SZ_4K,
.flags = IORESOURCE_MEM,
},
.irq = {IRQ_PDMA1, NO_IRQ},
.periphid = 0x00041330,
};
static int __init s5pv210_dma_init(void)
{
platform_add_devices(s5pv210_dmacs, ARRAY_SIZE(s5pv210_dmacs));
amba_device_register(&s5pv210_device_pdma0, &iomem_resource);
return 0;
}
......
......@@ -20,7 +20,7 @@
#ifndef __MACH_DMA_H
#define __MACH_DMA_H
/* This platform uses the common S3C DMA API driver for PL330 */
#include <plat/s3c-dma-pl330.h>
/* This platform uses the common DMA API driver for PL330 */
#include <plat/dma-pl330.h>
#endif /* __MACH_DMA_H */
......@@ -1094,14 +1094,14 @@ EXPORT_SYMBOL(s3c2410_dma_config);
*
* configure the dma source/destination hardware type and address
*
* source: S3C2410_DMASRC_HW: source is hardware
* S3C2410_DMASRC_MEM: source is memory
* source: DMA_FROM_DEVICE: source is hardware
* DMA_TO_DEVICE: source is memory
*
* devaddr: physical address of the source
*/
int s3c2410_dma_devconfig(enum dma_ch channel,
enum s3c2410_dmasrc source,
enum dma_data_direction source,
unsigned long devaddr)
{
struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
......@@ -1131,7 +1131,7 @@ int s3c2410_dma_devconfig(enum dma_ch channel,
hwcfg |= S3C2410_DISRCC_INC;
switch (source) {
case S3C2410_DMASRC_HW:
case DMA_FROM_DEVICE:
/* source is hardware */
pr_debug("%s: hw source, devaddr=%08lx, hwcfg=%d\n",
__func__, devaddr, hwcfg);
......@@ -1142,7 +1142,7 @@ int s3c2410_dma_devconfig(enum dma_ch channel,
chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST);
break;
case S3C2410_DMASRC_MEM:
case DMA_TO_DEVICE:
/* source is memory */
pr_debug("%s: mem source, devaddr=%08lx, hwcfg=%d\n",
__func__, devaddr, hwcfg);
......
......@@ -295,11 +295,14 @@ config S3C_DMA
help
Internal configuration for S3C DMA core
config S3C_PL330_DMA
config SAMSUNG_DMADEV
bool
select PL330
select DMADEVICES
select PL330_DMA if (CPU_EXYNOS4210 || CPU_S5PV210 || CPU_S5PC100 || \
CPU_S5P6450 || CPU_S5P6440)
select ARM_AMBA
help
S3C DMA API Driver for PL330 DMAC.
Use DMA device engine for PL330 DMAC.
comment "Power management"
......
......@@ -62,9 +62,9 @@ obj-$(CONFIG_SAMSUNG_DEV_BACKLIGHT) += dev-backlight.o
# DMA support
obj-$(CONFIG_S3C_DMA) += dma.o
obj-$(CONFIG_S3C_DMA) += dma.o s3c-dma-ops.o
obj-$(CONFIG_S3C_PL330_DMA) += s3c-pl330.o
obj-$(CONFIG_SAMSUNG_DMADEV) += dma-ops.o
# PM support
......
/* linux/arch/arm/plat-samsung/dma-ops.c
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Samsung DMA Operations
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/amba/pl330.h>
#include <linux/scatterlist.h>
#include <mach/dma.h>
static inline bool pl330_filter(struct dma_chan *chan, void *param)
{
struct dma_pl330_peri *peri = chan->private;
return peri->peri_id == (unsigned)param;
}
static unsigned samsung_dmadev_request(enum dma_ch dma_ch,
struct samsung_dma_info *info)
{
struct dma_chan *chan;
dma_cap_mask_t mask;
struct dma_slave_config slave_config;
dma_cap_zero(mask);
dma_cap_set(info->cap, mask);
chan = dma_request_channel(mask, pl330_filter, (void *)dma_ch);
if (info->direction == DMA_FROM_DEVICE) {
memset(&slave_config, 0, sizeof(struct dma_slave_config));
slave_config.direction = info->direction;
slave_config.src_addr = info->fifo;
slave_config.src_addr_width = info->width;
slave_config.src_maxburst = 1;
dmaengine_slave_config(chan, &slave_config);
} else if (info->direction == DMA_TO_DEVICE) {
memset(&slave_config, 0, sizeof(struct dma_slave_config));
slave_config.direction = info->direction;
slave_config.dst_addr = info->fifo;
slave_config.dst_addr_width = info->width;
slave_config.dst_maxburst = 1;
dmaengine_slave_config(chan, &slave_config);
}
return (unsigned)chan;
}
static int samsung_dmadev_release(unsigned ch,
struct s3c2410_dma_client *client)
{
dma_release_channel((struct dma_chan *)ch);
return 0;
}
static int samsung_dmadev_prepare(unsigned ch,
struct samsung_dma_prep_info *info)
{
struct scatterlist sg;
struct dma_chan *chan = (struct dma_chan *)ch;
struct dma_async_tx_descriptor *desc;
switch (info->cap) {
case DMA_SLAVE:
sg_init_table(&sg, 1);
sg_dma_len(&sg) = info->len;
sg_set_page(&sg, pfn_to_page(PFN_DOWN(info->buf)),
info->len, offset_in_page(info->buf));
sg_dma_address(&sg) = info->buf;
desc = chan->device->device_prep_slave_sg(chan,
&sg, 1, info->direction, DMA_PREP_INTERRUPT);
break;
case DMA_CYCLIC:
desc = chan->device->device_prep_dma_cyclic(chan,
info->buf, info->len, info->period, info->direction);
break;
default:
dev_err(&chan->dev->device, "unsupported format\n");
return -EFAULT;
}
if (!desc) {
dev_err(&chan->dev->device, "cannot prepare cyclic dma\n");
return -EFAULT;
}
desc->callback = info->fp;
desc->callback_param = info->fp_param;
dmaengine_submit((struct dma_async_tx_descriptor *)desc);
return 0;
}
static inline int samsung_dmadev_trigger(unsigned ch)
{
dma_async_issue_pending((struct dma_chan *)ch);
return 0;
}
static inline int samsung_dmadev_flush(unsigned ch)
{
return dmaengine_terminate_all((struct dma_chan *)ch);
}
struct samsung_dma_ops dmadev_ops = {
.request = samsung_dmadev_request,
.release = samsung_dmadev_release,
.prepare = samsung_dmadev_prepare,
.trigger = samsung_dmadev_trigger,
.started = NULL,
.flush = samsung_dmadev_flush,
.stop = samsung_dmadev_flush,
};
void *samsung_dmadev_get_ops(void)
{
return &dmadev_ops;
}
EXPORT_SYMBOL(samsung_dmadev_get_ops);
/* arch/arm/plat-samsung/include/plat/dma-ops.h
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Samsung DMA support
*
* 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 __SAMSUNG_DMA_OPS_H_
#define __SAMSUNG_DMA_OPS_H_ __FILE__
#include <linux/dmaengine.h>
struct samsung_dma_prep_info {
enum dma_transaction_type cap;
enum dma_data_direction direction;
dma_addr_t buf;
unsigned long period;
unsigned long len;
void (*fp)(void *data);
void *fp_param;
};
struct samsung_dma_info {
enum dma_transaction_type cap;
enum dma_data_direction direction;
enum dma_slave_buswidth width;
dma_addr_t fifo;
struct s3c2410_dma_client *client;
};
struct samsung_dma_ops {
unsigned (*request)(enum dma_ch ch, struct samsung_dma_info *info);
int (*release)(unsigned ch, struct s3c2410_dma_client *client);
int (*prepare)(unsigned ch, struct samsung_dma_prep_info *info);
int (*trigger)(unsigned ch);
int (*started)(unsigned ch);
int (*flush)(unsigned ch);
int (*stop)(unsigned ch);
};
extern void *samsung_dmadev_get_ops(void);
extern void *s3c_dma_get_ops(void);
static inline void *__samsung_dma_get_ops(void)
{
if (samsung_dma_is_dmadev())
return samsung_dmadev_get_ops();
else
return s3c_dma_get_ops();
}
/*
* samsung_dma_get_ops
* get the set of samsung dma operations
*/
#define samsung_dma_get_ops() __samsung_dma_get_ops()
#endif /* __SAMSUNG_DMA_OPS_H_ */
......@@ -8,11 +8,8 @@
* (at your option) any later version.
*/
#ifndef __S3C_DMA_PL330_H_
#define __S3C_DMA_PL330_H_
#define S3C2410_DMAF_AUTOSTART (1 << 0)
#define S3C2410_DMAF_CIRCULAR (1 << 1)
#ifndef __DMA_PL330_H_
#define __DMA_PL330_H_ __FILE__
/*
* PL330 can assign any channel to communicate with
......@@ -20,7 +17,7 @@
* For the sake of consistency across client drivers,
* We keep the channel names unchanged and only add
* missing peripherals are added.
* Order is not important since S3C PL330 API driver
* Order is not important since DMA PL330 API driver
* use these just as IDs.
*/
enum dma_ch {
......@@ -88,11 +85,20 @@ enum dma_ch {
DMACH_MAX,
};
static inline bool s3c_dma_has_circular(void)
struct s3c2410_dma_client {
char *name;
};
static inline bool samsung_dma_has_circular(void)
{
return true;
}
static inline bool samsung_dma_is_dmadev(void)
{
return true;
}
#include <plat/dma.h>
#include <plat/dma-ops.h>
#endif /* __S3C_DMA_PL330_H_ */
#endif /* __DMA_PL330_H_ */
......@@ -41,7 +41,7 @@ struct s3c24xx_dma_selection {
void (*direction)(struct s3c2410_dma_chan *chan,
struct s3c24xx_dma_map *map,
enum s3c2410_dmasrc dir);
enum dma_data_direction dir);
};
extern int s3c24xx_dma_init_map(struct s3c24xx_dma_selection *sel);
......
......@@ -10,17 +10,14 @@
* published by the Free Software Foundation.
*/
#include <linux/dma-mapping.h>
enum s3c2410_dma_buffresult {
S3C2410_RES_OK,
S3C2410_RES_ERR,
S3C2410_RES_ABORT
};
enum s3c2410_dmasrc {
S3C2410_DMASRC_HW, /* source is memory */
S3C2410_DMASRC_MEM /* source is hardware */
};
/* enum s3c2410_chan_op
*
* operation codes passed to the DMA code by the user, and also used
......@@ -112,7 +109,7 @@ extern int s3c2410_dma_config(enum dma_ch channel, int xferunit);
*/
extern int s3c2410_dma_devconfig(enum dma_ch channel,
enum s3c2410_dmasrc source, unsigned long devaddr);
enum dma_data_direction source, unsigned long devaddr);
/* s3c2410_dma_getposition
*
......@@ -126,3 +123,4 @@ extern int s3c2410_dma_set_opfn(enum dma_ch, s3c2410_dma_opfn_t rtn);
extern int s3c2410_dma_set_buffdone_fn(enum dma_ch, s3c2410_dma_cbfn_t rtn);
#include <plat/dma-ops.h>
/* linux/arch/arm/plat-samsung/include/plat/s3c-pl330-pdata.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 __S3C_PL330_PDATA_H
#define __S3C_PL330_PDATA_H
#include <plat/s3c-dma-pl330.h>
/*
* Every PL330 DMAC has max 32 peripheral interfaces,
* of which some may be not be really used in your
* DMAC's configuration.
* Populate this array of 32 peri i/fs with relevant
* channel IDs for used peri i/f and DMACH_MAX for
* those unused.
*
* The platforms just need to provide this info
* to the S3C DMA API driver for PL330.
*/
struct s3c_pl330_platdata {
enum dma_ch peri[32];
};
#endif /* __S3C_PL330_PDATA_H */
/* linux/arch/arm/plat-samsung/s3c-dma-ops.c
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Samsung S3C-DMA Operations
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <mach/dma.h>
struct cb_data {
void (*fp) (void *);
void *fp_param;
unsigned ch;
struct list_head node;
};
static LIST_HEAD(dma_list);
static void s3c_dma_cb(struct s3c2410_dma_chan *channel, void *param,
int size, enum s3c2410_dma_buffresult res)
{
struct cb_data *data = param;
data->fp(data->fp_param);
}
static unsigned s3c_dma_request(enum dma_ch dma_ch,
struct samsung_dma_info *info)
{
struct cb_data *data;
if (s3c2410_dma_request(dma_ch, info->client, NULL) < 0) {
s3c2410_dma_free(dma_ch, info->client);
return 0;
}
data = kzalloc(sizeof(struct cb_data), GFP_KERNEL);
data->ch = dma_ch;
list_add_tail(&data->node, &dma_list);
s3c2410_dma_devconfig(dma_ch, info->direction, info->fifo);
if (info->cap == DMA_CYCLIC)
s3c2410_dma_setflags(dma_ch, S3C2410_DMAF_CIRCULAR);
s3c2410_dma_config(dma_ch, info->width);
return (unsigned)dma_ch;
}
static int s3c_dma_release(unsigned ch, struct s3c2410_dma_client *client)
{
struct cb_data *data;
list_for_each_entry(data, &dma_list, node)
if (data->ch == ch)
break;
list_del(&data->node);
s3c2410_dma_free(ch, client);
kfree(data);
return 0;
}
static int s3c_dma_prepare(unsigned ch, struct samsung_dma_prep_info *info)
{
struct cb_data *data;
int len = (info->cap == DMA_CYCLIC) ? info->period : info->len;
list_for_each_entry(data, &dma_list, node)
if (data->ch == ch)
break;
if (!data->fp) {
s3c2410_dma_set_buffdone_fn(ch, s3c_dma_cb);
data->fp = info->fp;
data->fp_param = info->fp_param;
}
s3c2410_dma_enqueue(ch, (void *)data, info->buf, len);
return 0;
}
static inline int s3c_dma_trigger(unsigned ch)
{
return s3c2410_dma_ctrl(ch, S3C2410_DMAOP_START);
}
static inline int s3c_dma_started(unsigned ch)
{
return s3c2410_dma_ctrl(ch, S3C2410_DMAOP_STARTED);
}
static inline int s3c_dma_flush(unsigned ch)
{
return s3c2410_dma_ctrl(ch, S3C2410_DMAOP_FLUSH);
}
static inline int s3c_dma_stop(unsigned ch)
{
return s3c2410_dma_ctrl(ch, S3C2410_DMAOP_STOP);
}
static struct samsung_dma_ops s3c_dma_ops = {
.request = s3c_dma_request,
.release = s3c_dma_release,
.prepare = s3c_dma_prepare,
.trigger = s3c_dma_trigger,
.started = s3c_dma_started,
.flush = s3c_dma_flush,
.stop = s3c_dma_stop,
};
void *s3c_dma_get_ops(void)
{
return &s3c_dma_ops;
}
EXPORT_SYMBOL(s3c_dma_get_ops);
/* linux/arch/arm/plat-samsung/s3c-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/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <asm/hardware/pl330.h>
#include <plat/s3c-pl330-pdata.h>
/**
* struct s3c_pl330_dmac - Logical representation of a PL330 DMAC.
* @busy_chan: Number of channels currently busy.
* @peri: List of IDs of peripherals this DMAC can work with.
* @node: To attach to the global list of DMACs.
* @pi: PL330 configuration info for the DMAC.
* @kmcache: Pool to quickly allocate xfers for all channels in the dmac.
* @clk: Pointer of DMAC operation clock.
*/
struct s3c_pl330_dmac {
unsigned busy_chan;
enum dma_ch *peri;
struct list_head node;
struct pl330_info *pi;
struct kmem_cache *kmcache;
struct clk *clk;
};
/**
* struct s3c_pl330_xfer - A request submitted by S3C DMA clients.
* @token: Xfer ID provided by the client.
* @node: To attach to the list of xfers on a channel.
* @px: Xfer for PL330 core.
* @chan: Owner channel of this xfer.
*/
struct s3c_pl330_xfer {
void *token;
struct list_head node;
struct pl330_xfer px;
struct s3c_pl330_chan *chan;
};
/**
* struct s3c_pl330_chan - Logical channel to communicate with
* a Physical peripheral.
* @pl330_chan_id: Token of a hardware channel thread of PL330 DMAC.
* NULL if the channel is available to be acquired.
* @id: ID of the peripheral that this channel can communicate with.
* @options: Options specified by the client.
* @sdaddr: Address provided via s3c2410_dma_devconfig.
* @node: To attach to the global list of channels.
* @lrq: Pointer to the last submitted pl330_req to PL330 core.
* @xfer_list: To manage list of xfers enqueued.
* @req: Two requests to communicate with the PL330 engine.
* @callback_fn: Callback function to the client.
* @rqcfg: Channel configuration for the xfers.
* @xfer_head: Pointer to the xfer to be next executed.
* @dmac: Pointer to the DMAC that manages this channel, NULL if the
* channel is available to be acquired.
* @client: Client of this channel. NULL if the
* channel is available to be acquired.
*/
struct s3c_pl330_chan {
void *pl330_chan_id;
enum dma_ch id;
unsigned int options;
unsigned long sdaddr;
struct list_head node;
struct pl330_req *lrq;
struct list_head xfer_list;
struct pl330_req req[2];
s3c2410_dma_cbfn_t callback_fn;
struct pl330_reqcfg rqcfg;
struct s3c_pl330_xfer *xfer_head;
struct s3c_pl330_dmac *dmac;
struct s3c2410_dma_client *client;
};
/* All DMACs in the platform */
static LIST_HEAD(dmac_list);
/* All channels to peripherals in the platform */
static LIST_HEAD(chan_list);
/*
* Since we add resources(DMACs and Channels) to the global pool,
* we need to guard access to the resources using a global lock
*/
static DEFINE_SPINLOCK(res_lock);
/* Returns the channel with ID 'id' in the chan_list */
static struct s3c_pl330_chan *id_to_chan(const enum dma_ch id)
{
struct s3c_pl330_chan *ch;
list_for_each_entry(ch, &chan_list, node)
if (ch->id == id)
return ch;
return NULL;
}
/* Allocate a new channel with ID 'id' and add to chan_list */
static void chan_add(const enum dma_ch id)
{
struct s3c_pl330_chan *ch = id_to_chan(id);
/* Return if the channel already exists */
if (ch)
return;
ch = kmalloc(sizeof(*ch), GFP_KERNEL);
/* Return silently to work with other channels */
if (!ch)
return;
ch->id = id;
ch->dmac = NULL;
list_add_tail(&ch->node, &chan_list);
}
/* If the channel is not yet acquired by any client */
static bool chan_free(struct s3c_pl330_chan *ch)
{
if (!ch)
return false;
/* Channel points to some DMAC only when it's acquired */
return ch->dmac ? false : true;
}
/*
* Returns 0 is peripheral i/f is invalid or not present on the dmac.
* Index + 1, otherwise.
*/
static unsigned iface_of_dmac(struct s3c_pl330_dmac *dmac, enum dma_ch ch_id)
{
enum dma_ch *id = dmac->peri;
int i;
/* Discount invalid markers */
if (ch_id == DMACH_MAX)
return 0;
for (i = 0; i < PL330_MAX_PERI; i++)
if (id[i] == ch_id)
return i + 1;
return 0;
}
/* If all channel threads of the DMAC are busy */
static inline bool dmac_busy(struct s3c_pl330_dmac *dmac)
{
struct pl330_info *pi = dmac->pi;
return (dmac->busy_chan < pi->pcfg.num_chan) ? false : true;
}
/*
* Returns the number of free channels that
* can be handled by this dmac only.
*/
static unsigned ch_onlyby_dmac(struct s3c_pl330_dmac *dmac)
{
enum dma_ch *id = dmac->peri;
struct s3c_pl330_dmac *d;
struct s3c_pl330_chan *ch;
unsigned found, count = 0;
enum dma_ch p;
int i;
for (i = 0; i < PL330_MAX_PERI; i++) {
p = id[i];
ch = id_to_chan(p);
if (p == DMACH_MAX || !chan_free(ch))
continue;
found = 0;
list_for_each_entry(d, &dmac_list, node) {
if (d != dmac && iface_of_dmac(d, ch->id)) {
found = 1;
break;
}
}
if (!found)
count++;
}
return count;
}
/*
* Measure of suitability of 'dmac' handling 'ch'
*
* 0 indicates 'dmac' can not handle 'ch' either
* because it is not supported by the hardware or
* because all dmac channels are currently busy.
*
* >0 vlaue indicates 'dmac' has the capability.
* The bigger the value the more suitable the dmac.
*/
#define MAX_SUIT UINT_MAX
#define MIN_SUIT 0
static unsigned suitablility(struct s3c_pl330_dmac *dmac,
struct s3c_pl330_chan *ch)
{
struct pl330_info *pi = dmac->pi;
enum dma_ch *id = dmac->peri;
struct s3c_pl330_dmac *d;
unsigned s;
int i;
s = MIN_SUIT;
/* If all the DMAC channel threads are busy */
if (dmac_busy(dmac))
return s;
for (i = 0; i < PL330_MAX_PERI; i++)
if (id[i] == ch->id)
break;
/* If the 'dmac' can't talk to 'ch' */
if (i == PL330_MAX_PERI)
return s;
s = MAX_SUIT;
list_for_each_entry(d, &dmac_list, node) {
/*
* If some other dmac can talk to this
* peri and has some channel free.
*/
if (d != dmac && iface_of_dmac(d, ch->id) && !dmac_busy(d)) {
s = 0;
break;
}
}
if (s)
return s;
s = 100;
/* Good if free chans are more, bad otherwise */
s += (pi->pcfg.num_chan - dmac->busy_chan) - ch_onlyby_dmac(dmac);
return s;
}
/* More than one DMAC may have capability to transfer data with the
* peripheral. This function assigns most suitable DMAC to manage the
* channel and hence communicate with the peripheral.
*/
static struct s3c_pl330_dmac *map_chan_to_dmac(struct s3c_pl330_chan *ch)
{
struct s3c_pl330_dmac *d, *dmac = NULL;
unsigned sn, sl = MIN_SUIT;
list_for_each_entry(d, &dmac_list, node) {
sn = suitablility(d, ch);
if (sn == MAX_SUIT)
return d;
if (sn > sl)
dmac = d;
}
return dmac;
}
/* Acquire the channel for peripheral 'id' */
static struct s3c_pl330_chan *chan_acquire(const enum dma_ch id)
{
struct s3c_pl330_chan *ch = id_to_chan(id);
struct s3c_pl330_dmac *dmac;
/* If the channel doesn't exist or is already acquired */
if (!ch || !chan_free(ch)) {
ch = NULL;
goto acq_exit;
}
dmac = map_chan_to_dmac(ch);
/* If couldn't map */
if (!dmac) {
ch = NULL;
goto acq_exit;
}
dmac->busy_chan++;
ch->dmac = dmac;
acq_exit:
return ch;
}
/* Delete xfer from the queue */
static inline void del_from_queue(struct s3c_pl330_xfer *xfer)
{
struct s3c_pl330_xfer *t;
struct s3c_pl330_chan *ch;
int found;
if (!xfer)
return;
ch = xfer->chan;
/* Make sure xfer is in the queue */
found = 0;
list_for_each_entry(t, &ch->xfer_list, node)
if (t == xfer) {
found = 1;
break;
}
if (!found)
return;
/* If xfer is last entry in the queue */
if (xfer->node.next == &ch->xfer_list)
t = list_entry(ch->xfer_list.next,
struct s3c_pl330_xfer, node);
else
t = list_entry(xfer->node.next,
struct s3c_pl330_xfer, node);
/* If there was only one node left */
if (t == xfer)
ch->xfer_head = NULL;
else if (ch->xfer_head == xfer)
ch->xfer_head = t;
list_del(&xfer->node);
}
/* Provides pointer to the next xfer in the queue.
* If CIRCULAR option is set, the list is left intact,
* otherwise the xfer is removed from the list.
* Forced delete 'pluck' can be set to override the CIRCULAR option.
*/
static struct s3c_pl330_xfer *get_from_queue(struct s3c_pl330_chan *ch,
int pluck)
{
struct s3c_pl330_xfer *xfer = ch->xfer_head;
if (!xfer)
return NULL;
/* If xfer is last entry in the queue */
if (xfer->node.next == &ch->xfer_list)
ch->xfer_head = list_entry(ch->xfer_list.next,
struct s3c_pl330_xfer, node);
else
ch->xfer_head = list_entry(xfer->node.next,
struct s3c_pl330_xfer, node);
if (pluck || !(ch->options & S3C2410_DMAF_CIRCULAR))
del_from_queue(xfer);
return xfer;
}
static inline void add_to_queue(struct s3c_pl330_chan *ch,
struct s3c_pl330_xfer *xfer, int front)
{
struct pl330_xfer *xt;
/* If queue empty */
if (ch->xfer_head == NULL)
ch->xfer_head = xfer;
xt = &ch->xfer_head->px;
/* If the head already submitted (CIRCULAR head) */
if (ch->options & S3C2410_DMAF_CIRCULAR &&
(xt == ch->req[0].x || xt == ch->req[1].x))
ch->xfer_head = xfer;
/* If this is a resubmission, it should go at the head */
if (front) {
ch->xfer_head = xfer;
list_add(&xfer->node, &ch->xfer_list);
} else {
list_add_tail(&xfer->node, &ch->xfer_list);
}
}
static inline void _finish_off(struct s3c_pl330_xfer *xfer,
enum s3c2410_dma_buffresult res, int ffree)
{
struct s3c_pl330_chan *ch;
if (!xfer)
return;
ch = xfer->chan;
/* Do callback */
if (ch->callback_fn)
ch->callback_fn(NULL, xfer->token, xfer->px.bytes, res);
/* Force Free or if buffer is not needed anymore */
if (ffree || !(ch->options & S3C2410_DMAF_CIRCULAR))
kmem_cache_free(ch->dmac->kmcache, xfer);
}
static inline int s3c_pl330_submit(struct s3c_pl330_chan *ch,
struct pl330_req *r)
{
struct s3c_pl330_xfer *xfer;
int ret = 0;
/* If already submitted */
if (r->x)
return 0;
xfer = get_from_queue(ch, 0);
if (xfer) {
r->x = &xfer->px;
/* Use max bandwidth for M<->M xfers */
if (r->rqtype == MEMTOMEM) {
struct pl330_info *pi = xfer->chan->dmac->pi;
int burst = 1 << ch->rqcfg.brst_size;
u32 bytes = r->x->bytes;
int bl;
bl = pi->pcfg.data_bus_width / 8;
bl *= pi->pcfg.data_buf_dep;
bl /= burst;
/* src/dst_burst_len can't be more than 16 */
if (bl > 16)
bl = 16;
while (bl > 1) {
if (!(bytes % (bl * burst)))
break;
bl--;
}
ch->rqcfg.brst_len = bl;
} else {
ch->rqcfg.brst_len = 1;
}
ret = pl330_submit_req(ch->pl330_chan_id, r);
/* If submission was successful */
if (!ret) {
ch->lrq = r; /* latest submitted req */
return 0;
}
r->x = NULL;
/* If both of the PL330 ping-pong buffers filled */
if (ret == -EAGAIN) {
dev_err(ch->dmac->pi->dev, "%s:%d!\n",
__func__, __LINE__);
/* Queue back again */
add_to_queue(ch, xfer, 1);
ret = 0;
} else {
dev_err(ch->dmac->pi->dev, "%s:%d!\n",
__func__, __LINE__);
_finish_off(xfer, S3C2410_RES_ERR, 0);
}
}
return ret;
}
static void s3c_pl330_rq(struct s3c_pl330_chan *ch,
struct pl330_req *r, enum pl330_op_err err)
{
unsigned long flags;
struct s3c_pl330_xfer *xfer;
struct pl330_xfer *xl = r->x;
enum s3c2410_dma_buffresult res;
spin_lock_irqsave(&res_lock, flags);
r->x = NULL;
s3c_pl330_submit(ch, r);
spin_unlock_irqrestore(&res_lock, flags);
/* Map result to S3C DMA API */
if (err == PL330_ERR_NONE)
res = S3C2410_RES_OK;
else if (err == PL330_ERR_ABORT)
res = S3C2410_RES_ABORT;
else
res = S3C2410_RES_ERR;
/* If last request had some xfer */
if (xl) {
xfer = container_of(xl, struct s3c_pl330_xfer, px);
_finish_off(xfer, res, 0);
} else {
dev_info(ch->dmac->pi->dev, "%s:%d No Xfer?!\n",
__func__, __LINE__);
}
}
static void s3c_pl330_rq0(void *token, enum pl330_op_err err)
{
struct pl330_req *r = token;
struct s3c_pl330_chan *ch = container_of(r,
struct s3c_pl330_chan, req[0]);
s3c_pl330_rq(ch, r, err);
}
static void s3c_pl330_rq1(void *token, enum pl330_op_err err)
{
struct pl330_req *r = token;
struct s3c_pl330_chan *ch = container_of(r,
struct s3c_pl330_chan, req[1]);
s3c_pl330_rq(ch, r, err);
}
/* Release an acquired channel */
static void chan_release(struct s3c_pl330_chan *ch)
{
struct s3c_pl330_dmac *dmac;
if (chan_free(ch))
return;
dmac = ch->dmac;
ch->dmac = NULL;
dmac->busy_chan--;
}
int s3c2410_dma_ctrl(enum dma_ch id, enum s3c2410_chan_op op)
{
struct s3c_pl330_xfer *xfer;
enum pl330_chan_op pl330op;
struct s3c_pl330_chan *ch;
unsigned long flags;
int idx, ret;
spin_lock_irqsave(&res_lock, flags);
ch = id_to_chan(id);
if (!ch || chan_free(ch)) {
ret = -EINVAL;
goto ctrl_exit;
}
switch (op) {
case S3C2410_DMAOP_START:
/* Make sure both reqs are enqueued */
idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
s3c_pl330_submit(ch, &ch->req[idx]);
s3c_pl330_submit(ch, &ch->req[1 - idx]);
pl330op = PL330_OP_START;
break;
case S3C2410_DMAOP_STOP:
pl330op = PL330_OP_ABORT;
break;
case S3C2410_DMAOP_FLUSH:
pl330op = PL330_OP_FLUSH;
break;
case S3C2410_DMAOP_PAUSE:
case S3C2410_DMAOP_RESUME:
case S3C2410_DMAOP_TIMEOUT:
case S3C2410_DMAOP_STARTED:
spin_unlock_irqrestore(&res_lock, flags);
return 0;
default:
spin_unlock_irqrestore(&res_lock, flags);
return -EINVAL;
}
ret = pl330_chan_ctrl(ch->pl330_chan_id, pl330op);
if (pl330op == PL330_OP_START) {
spin_unlock_irqrestore(&res_lock, flags);
return ret;
}
idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
/* Abort the current xfer */
if (ch->req[idx].x) {
xfer = container_of(ch->req[idx].x,
struct s3c_pl330_xfer, px);
/* Drop xfer during FLUSH */
if (pl330op == PL330_OP_FLUSH)
del_from_queue(xfer);
ch->req[idx].x = NULL;
spin_unlock_irqrestore(&res_lock, flags);
_finish_off(xfer, S3C2410_RES_ABORT,
pl330op == PL330_OP_FLUSH ? 1 : 0);
spin_lock_irqsave(&res_lock, flags);
}
/* Flush the whole queue */
if (pl330op == PL330_OP_FLUSH) {
if (ch->req[1 - idx].x) {
xfer = container_of(ch->req[1 - idx].x,
struct s3c_pl330_xfer, px);
del_from_queue(xfer);
ch->req[1 - idx].x = NULL;
spin_unlock_irqrestore(&res_lock, flags);
_finish_off(xfer, S3C2410_RES_ABORT, 1);
spin_lock_irqsave(&res_lock, flags);
}
/* Finish off the remaining in the queue */
xfer = ch->xfer_head;
while (xfer) {
del_from_queue(xfer);
spin_unlock_irqrestore(&res_lock, flags);
_finish_off(xfer, S3C2410_RES_ABORT, 1);
spin_lock_irqsave(&res_lock, flags);
xfer = ch->xfer_head;
}
}
ctrl_exit:
spin_unlock_irqrestore(&res_lock, flags);
return ret;
}
EXPORT_SYMBOL(s3c2410_dma_ctrl);
int s3c2410_dma_enqueue(enum dma_ch id, void *token,
dma_addr_t addr, int size)
{
struct s3c_pl330_chan *ch;
struct s3c_pl330_xfer *xfer;
unsigned long flags;
int idx, ret = 0;
spin_lock_irqsave(&res_lock, flags);
ch = id_to_chan(id);
/* Error if invalid or free channel */
if (!ch || chan_free(ch)) {
ret = -EINVAL;
goto enq_exit;
}
/* Error if size is unaligned */
if (ch->rqcfg.brst_size && size % (1 << ch->rqcfg.brst_size)) {
ret = -EINVAL;
goto enq_exit;
}
xfer = kmem_cache_alloc(ch->dmac->kmcache, GFP_ATOMIC);
if (!xfer) {
ret = -ENOMEM;
goto enq_exit;
}
xfer->token = token;
xfer->chan = ch;
xfer->px.bytes = size;
xfer->px.next = NULL; /* Single request */
/* For S3C DMA API, direction is always fixed for all xfers */
if (ch->req[0].rqtype == MEMTODEV) {
xfer->px.src_addr = addr;
xfer->px.dst_addr = ch->sdaddr;
} else {
xfer->px.src_addr = ch->sdaddr;
xfer->px.dst_addr = addr;
}
add_to_queue(ch, xfer, 0);
/* Try submitting on either request */
idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
if (!ch->req[idx].x)
s3c_pl330_submit(ch, &ch->req[idx]);
else
s3c_pl330_submit(ch, &ch->req[1 - idx]);
spin_unlock_irqrestore(&res_lock, flags);
if (ch->options & S3C2410_DMAF_AUTOSTART)
s3c2410_dma_ctrl(id, S3C2410_DMAOP_START);
return 0;
enq_exit:
spin_unlock_irqrestore(&res_lock, flags);
return ret;
}
EXPORT_SYMBOL(s3c2410_dma_enqueue);
int s3c2410_dma_request(enum dma_ch id,
struct s3c2410_dma_client *client,
void *dev)
{
struct s3c_pl330_dmac *dmac;
struct s3c_pl330_chan *ch;
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&res_lock, flags);
ch = chan_acquire(id);
if (!ch) {
ret = -EBUSY;
goto req_exit;
}
dmac = ch->dmac;
ch->pl330_chan_id = pl330_request_channel(dmac->pi);
if (!ch->pl330_chan_id) {
chan_release(ch);
ret = -EBUSY;
goto req_exit;
}
ch->client = client;
ch->options = 0; /* Clear any option */
ch->callback_fn = NULL; /* Clear any callback */
ch->lrq = NULL;
ch->rqcfg.brst_size = 2; /* Default word size */
ch->rqcfg.swap = SWAP_NO;
ch->rqcfg.scctl = SCCTRL0; /* Noncacheable and nonbufferable */
ch->rqcfg.dcctl = DCCTRL0; /* Noncacheable and nonbufferable */
ch->rqcfg.privileged = 0;
ch->rqcfg.insnaccess = 0;
/* Set invalid direction */
ch->req[0].rqtype = DEVTODEV;
ch->req[1].rqtype = ch->req[0].rqtype;
ch->req[0].cfg = &ch->rqcfg;
ch->req[1].cfg = ch->req[0].cfg;
ch->req[0].peri = iface_of_dmac(dmac, id) - 1; /* Original index */
ch->req[1].peri = ch->req[0].peri;
ch->req[0].token = &ch->req[0];
ch->req[0].xfer_cb = s3c_pl330_rq0;
ch->req[1].token = &ch->req[1];
ch->req[1].xfer_cb = s3c_pl330_rq1;
ch->req[0].x = NULL;
ch->req[1].x = NULL;
/* Reset xfer list */
INIT_LIST_HEAD(&ch->xfer_list);
ch->xfer_head = NULL;
req_exit:
spin_unlock_irqrestore(&res_lock, flags);
return ret;
}
EXPORT_SYMBOL(s3c2410_dma_request);
int s3c2410_dma_free(enum dma_ch id, struct s3c2410_dma_client *client)
{
struct s3c_pl330_chan *ch;
struct s3c_pl330_xfer *xfer;
unsigned long flags;
int ret = 0;
unsigned idx;
spin_lock_irqsave(&res_lock, flags);
ch = id_to_chan(id);
if (!ch || chan_free(ch))
goto free_exit;
/* Refuse if someone else wanted to free the channel */
if (ch->client != client) {
ret = -EBUSY;
goto free_exit;
}
/* Stop any active xfer, Flushe the queue and do callbacks */
pl330_chan_ctrl(ch->pl330_chan_id, PL330_OP_FLUSH);
/* Abort the submitted requests */
idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
if (ch->req[idx].x) {
xfer = container_of(ch->req[idx].x,
struct s3c_pl330_xfer, px);
ch->req[idx].x = NULL;
del_from_queue(xfer);
spin_unlock_irqrestore(&res_lock, flags);
_finish_off(xfer, S3C2410_RES_ABORT, 1);
spin_lock_irqsave(&res_lock, flags);
}
if (ch->req[1 - idx].x) {
xfer = container_of(ch->req[1 - idx].x,
struct s3c_pl330_xfer, px);
ch->req[1 - idx].x = NULL;
del_from_queue(xfer);
spin_unlock_irqrestore(&res_lock, flags);
_finish_off(xfer, S3C2410_RES_ABORT, 1);
spin_lock_irqsave(&res_lock, flags);
}
/* Pluck and Abort the queued requests in order */
do {
xfer = get_from_queue(ch, 1);
spin_unlock_irqrestore(&res_lock, flags);
_finish_off(xfer, S3C2410_RES_ABORT, 1);
spin_lock_irqsave(&res_lock, flags);
} while (xfer);
ch->client = NULL;
pl330_release_channel(ch->pl330_chan_id);
ch->pl330_chan_id = NULL;
chan_release(ch);
free_exit:
spin_unlock_irqrestore(&res_lock, flags);
return ret;
}
EXPORT_SYMBOL(s3c2410_dma_free);
int s3c2410_dma_config(enum dma_ch id, int xferunit)
{
struct s3c_pl330_chan *ch;
struct pl330_info *pi;
unsigned long flags;
int i, dbwidth, ret = 0;
spin_lock_irqsave(&res_lock, flags);
ch = id_to_chan(id);
if (!ch || chan_free(ch)) {
ret = -EINVAL;
goto cfg_exit;
}
pi = ch->dmac->pi;
dbwidth = pi->pcfg.data_bus_width / 8;
/* Max size of xfer can be pcfg.data_bus_width */
if (xferunit > dbwidth) {
ret = -EINVAL;
goto cfg_exit;
}
i = 0;
while (xferunit != (1 << i))
i++;
/* If valid value */
if (xferunit == (1 << i))
ch->rqcfg.brst_size = i;
else
ret = -EINVAL;
cfg_exit:
spin_unlock_irqrestore(&res_lock, flags);
return ret;
}
EXPORT_SYMBOL(s3c2410_dma_config);
/* Options that are supported by this driver */
#define S3C_PL330_FLAGS (S3C2410_DMAF_CIRCULAR | S3C2410_DMAF_AUTOSTART)
int s3c2410_dma_setflags(enum dma_ch id, unsigned int options)
{
struct s3c_pl330_chan *ch;
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&res_lock, flags);
ch = id_to_chan(id);
if (!ch || chan_free(ch) || options & ~(S3C_PL330_FLAGS))
ret = -EINVAL;
else
ch->options = options;
spin_unlock_irqrestore(&res_lock, flags);
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_setflags);
int s3c2410_dma_set_buffdone_fn(enum dma_ch id, s3c2410_dma_cbfn_t rtn)
{
struct s3c_pl330_chan *ch;
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&res_lock, flags);
ch = id_to_chan(id);
if (!ch || chan_free(ch))
ret = -EINVAL;
else
ch->callback_fn = rtn;
spin_unlock_irqrestore(&res_lock, flags);
return ret;
}
EXPORT_SYMBOL(s3c2410_dma_set_buffdone_fn);
int s3c2410_dma_devconfig(enum dma_ch id, enum s3c2410_dmasrc source,
unsigned long address)
{
struct s3c_pl330_chan *ch;
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&res_lock, flags);
ch = id_to_chan(id);
if (!ch || chan_free(ch)) {
ret = -EINVAL;
goto devcfg_exit;
}
switch (source) {
case S3C2410_DMASRC_HW: /* P->M */
ch->req[0].rqtype = DEVTOMEM;
ch->req[1].rqtype = DEVTOMEM;
ch->rqcfg.src_inc = 0;
ch->rqcfg.dst_inc = 1;
break;
case S3C2410_DMASRC_MEM: /* M->P */
ch->req[0].rqtype = MEMTODEV;
ch->req[1].rqtype = MEMTODEV;
ch->rqcfg.src_inc = 1;
ch->rqcfg.dst_inc = 0;
break;
default:
ret = -EINVAL;
goto devcfg_exit;
}
ch->sdaddr = address;
devcfg_exit:
spin_unlock_irqrestore(&res_lock, flags);
return ret;
}
EXPORT_SYMBOL(s3c2410_dma_devconfig);
int s3c2410_dma_getposition(enum dma_ch id, dma_addr_t *src, dma_addr_t *dst)
{
struct s3c_pl330_chan *ch = id_to_chan(id);
struct pl330_chanstatus status;
int ret;
if (!ch || chan_free(ch))
return -EINVAL;
ret = pl330_chan_status(ch->pl330_chan_id, &status);
if (ret < 0)
return ret;
*src = status.src_addr;
*dst = status.dst_addr;
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_getposition);
static irqreturn_t pl330_irq_handler(int irq, void *data)
{
if (pl330_update(data))
return IRQ_HANDLED;
else
return IRQ_NONE;
}
static int pl330_probe(struct platform_device *pdev)
{
struct s3c_pl330_dmac *s3c_pl330_dmac;
struct s3c_pl330_platdata *pl330pd;
struct pl330_info *pl330_info;
struct resource *res;
int i, ret, irq;
pl330pd = pdev->dev.platform_data;
/* Can't do without the list of _32_ peripherals */
if (!pl330pd || !pl330pd->peri) {
dev_err(&pdev->dev, "platform data missing!\n");
return -ENODEV;
}
pl330_info = kzalloc(sizeof(*pl330_info), GFP_KERNEL);
if (!pl330_info)
return -ENOMEM;
pl330_info->pl330_data = NULL;
pl330_info->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
ret = -ENODEV;
goto probe_err1;
}
request_mem_region(res->start, resource_size(res), pdev->name);
pl330_info->base = ioremap(res->start, resource_size(res));
if (!pl330_info->base) {
ret = -ENXIO;
goto probe_err2;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
ret = irq;
goto probe_err3;
}
ret = request_irq(irq, pl330_irq_handler, 0,
dev_name(&pdev->dev), pl330_info);
if (ret)
goto probe_err4;
/* Allocate a new DMAC */
s3c_pl330_dmac = kmalloc(sizeof(*s3c_pl330_dmac), GFP_KERNEL);
if (!s3c_pl330_dmac) {
ret = -ENOMEM;
goto probe_err5;
}
/* Get operation clock and enable it */
s3c_pl330_dmac->clk = clk_get(&pdev->dev, "pdma");
if (IS_ERR(s3c_pl330_dmac->clk)) {
dev_err(&pdev->dev, "Cannot get operation clock.\n");
ret = -EINVAL;
goto probe_err6;
}
clk_enable(s3c_pl330_dmac->clk);
ret = pl330_add(pl330_info);
if (ret)
goto probe_err7;
/* Hook the info */
s3c_pl330_dmac->pi = pl330_info;
/* No busy channels */
s3c_pl330_dmac->busy_chan = 0;
s3c_pl330_dmac->kmcache = kmem_cache_create(dev_name(&pdev->dev),
sizeof(struct s3c_pl330_xfer), 0, 0, NULL);
if (!s3c_pl330_dmac->kmcache) {
ret = -ENOMEM;
goto probe_err8;
}
/* Get the list of peripherals */
s3c_pl330_dmac->peri = pl330pd->peri;
/* Attach to the list of DMACs */
list_add_tail(&s3c_pl330_dmac->node, &dmac_list);
/* Create a channel for each peripheral in the DMAC
* that is, if it doesn't already exist
*/
for (i = 0; i < PL330_MAX_PERI; i++)
if (s3c_pl330_dmac->peri[i] != DMACH_MAX)
chan_add(s3c_pl330_dmac->peri[i]);
printk(KERN_INFO
"Loaded driver for PL330 DMAC-%d %s\n", pdev->id, pdev->name);
printk(KERN_INFO
"\tDBUFF-%ux%ubytes Num_Chans-%u Num_Peri-%u Num_Events-%u\n",
pl330_info->pcfg.data_buf_dep,
pl330_info->pcfg.data_bus_width / 8, pl330_info->pcfg.num_chan,
pl330_info->pcfg.num_peri, pl330_info->pcfg.num_events);
return 0;
probe_err8:
pl330_del(pl330_info);
probe_err7:
clk_disable(s3c_pl330_dmac->clk);
clk_put(s3c_pl330_dmac->clk);
probe_err6:
kfree(s3c_pl330_dmac);
probe_err5:
free_irq(irq, pl330_info);
probe_err4:
probe_err3:
iounmap(pl330_info->base);
probe_err2:
release_mem_region(res->start, resource_size(res));
probe_err1:
kfree(pl330_info);
return ret;
}
static int pl330_remove(struct platform_device *pdev)
{
struct s3c_pl330_dmac *dmac, *d;
struct s3c_pl330_chan *ch;
unsigned long flags;
int del, found;
if (!pdev->dev.platform_data)
return -EINVAL;
spin_lock_irqsave(&res_lock, flags);
found = 0;
list_for_each_entry(d, &dmac_list, node)
if (d->pi->dev == &pdev->dev) {
found = 1;
break;
}
if (!found) {
spin_unlock_irqrestore(&res_lock, flags);
return 0;
}
dmac = d;
/* Remove all Channels that are managed only by this DMAC */
list_for_each_entry(ch, &chan_list, node) {
/* Only channels that are handled by this DMAC */
if (iface_of_dmac(dmac, ch->id))
del = 1;
else
continue;
/* Don't remove if some other DMAC has it too */
list_for_each_entry(d, &dmac_list, node)
if (d != dmac && iface_of_dmac(d, ch->id)) {
del = 0;
break;
}
if (del) {
spin_unlock_irqrestore(&res_lock, flags);
s3c2410_dma_free(ch->id, ch->client);
spin_lock_irqsave(&res_lock, flags);
list_del(&ch->node);
kfree(ch);
}
}
/* Disable operation clock */
clk_disable(dmac->clk);
clk_put(dmac->clk);
/* Remove the DMAC */
list_del(&dmac->node);
kfree(dmac);
spin_unlock_irqrestore(&res_lock, flags);
return 0;
}
static struct platform_driver pl330_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "s3c-pl330",
},
.probe = pl330_probe,
.remove = pl330_remove,
};
static int __init pl330_init(void)
{
return platform_driver_register(&pl330_driver);
}
module_init(pl330_init);
static void __exit pl330_exit(void)
{
platform_driver_unregister(&pl330_driver);
return;
}
module_exit(pl330_exit);
MODULE_AUTHOR("Jaswinder Singh <jassi.brar@samsung.com>");
MODULE_DESCRIPTION("Driver for PL330 DMA Controller");
MODULE_LICENSE("GPL");
......@@ -193,7 +193,8 @@ config ARCH_HAS_ASYNC_TX_FIND_CHANNEL
config PL330_DMA
tristate "DMA API Driver for PL330"
select DMA_ENGINE
depends on PL330
depends on ARM_AMBA
select PL330
help
Select if your platform has one or more PL330 DMACs.
You need to provide platform specific settings via
......
......@@ -66,32 +66,29 @@
* after the final transfer signalled by LBREQ or LSREQ. The DMAC
* will then move to the next LLI entry.
*
* Only the former works sanely with scatter lists, so we only implement
* the DMAC flow control method. However, peripherals which use the LBREQ
* and LSREQ signals (eg, MMCI) are unable to use this mode, which through
* these hardware restrictions prevents them from using scatter DMA.
*
* 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/interrupt.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/dmapool.h>
#include <linux/dmaengine.h>
#include <linux/amba/bus.h>
#include <linux/amba/pl08x.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dmaengine.h>
#include <linux/dmapool.h>
#include <linux/dma-mapping.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <asm/hardware/pl080.h>
#define DRIVER_NAME "pl08xdmac"
static struct amba_driver pl08x_amba_driver;
/**
* struct vendor_data - vendor-specific config parameters for PL08x derivatives
* @channels: the number of channels available in this variant
......@@ -126,7 +123,8 @@ struct pl08x_lli {
* @phy_chans: array of data for the physical channels
* @pool: a pool for the LLI descriptors
* @pool_ctr: counter of LLIs in the pool
* @lli_buses: bitmask to or in to LLI pointer selecting AHB port for LLI fetches
* @lli_buses: bitmask to or in to LLI pointer selecting AHB port for LLI
* fetches
* @mem_buses: set to indicate memory transfers on AHB2.
* @lock: a spinlock for this struct
*/
......@@ -149,14 +147,6 @@ struct pl08x_driver_data {
* 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)
/* Size (bytes) of each LLI buffer allocated for one transfer */
# define PL08X_LLI_TSFR_SIZE 0x2000
......@@ -272,7 +262,6 @@ static void pl08x_resume_phy_chan(struct pl08x_phy_chan *ch)
writel(val, ch->base + PL080_CH_CONFIG);
}
/*
* pl08x_terminate_phy_chan() stops the channel, clears the FIFO and
* clears any pending interrupt status. This should not be used for
......@@ -363,7 +352,9 @@ static u32 pl08x_getbytes_chan(struct pl08x_dma_chan *plchan)
if (!list_empty(&plchan->pend_list)) {
struct pl08x_txd *txdi;
list_for_each_entry(txdi, &plchan->pend_list, node) {
bytes += txdi->len;
struct pl08x_sg *dsg;
list_for_each_entry(dsg, &txd->dsg_list, node)
bytes += dsg->len;
}
}
......@@ -407,6 +398,7 @@ pl08x_get_phy_channel(struct pl08x_driver_data *pl08x,
return NULL;
}
pm_runtime_get_sync(&pl08x->adev->dev);
return ch;
}
......@@ -420,6 +412,8 @@ static inline void pl08x_put_phy_channel(struct pl08x_driver_data *pl08x,
/* Stop the channel and clear its interrupts */
pl08x_terminate_phy_chan(pl08x, ch);
pm_runtime_put(&pl08x->adev->dev);
/* Mark it as free */
ch->serving = NULL;
spin_unlock_irqrestore(&ch->lock, flags);
......@@ -499,36 +493,30 @@ struct pl08x_lli_build_data {
};
/*
* 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
* Autoselect a master bus to use for the transfer. Slave will be the chosen as
* victim in case src & dest are not similarly aligned. i.e. If after aligning
* masters address with width requirements of transfer (by sending few byte by
* byte data), slave is still not aligned, then its width will be reduced to
* BYTE.
* - prefers the destination bus if both available
* - prefers bus with fixed address (i.e. peripheral)
*/
static void pl08x_choose_master_bus(struct pl08x_lli_build_data *bd,
struct pl08x_bus_data **mbus, struct pl08x_bus_data **sbus, u32 cctl)
{
if (!(cctl & PL080_CONTROL_DST_INCR)) {
*mbus = &bd->srcbus;
*sbus = &bd->dstbus;
} else if (!(cctl & PL080_CONTROL_SRC_INCR)) {
*mbus = &bd->dstbus;
*sbus = &bd->srcbus;
} else {
if (bd->dstbus.buswidth == 4) {
*mbus = &bd->dstbus;
*sbus = &bd->srcbus;
} else if (bd->srcbus.buswidth == 4) {
} else if (!(cctl & PL080_CONTROL_SRC_INCR)) {
*mbus = &bd->srcbus;
*sbus = &bd->dstbus;
} else if (bd->dstbus.buswidth == 2) {
} else {
if (bd->dstbus.buswidth >= bd->srcbus.buswidth) {
*mbus = &bd->dstbus;
*sbus = &bd->srcbus;
} else if (bd->srcbus.buswidth == 2) {
} else {
*mbus = &bd->srcbus;
*sbus = &bd->dstbus;
} else {
/* bd->srcbus.buswidth == 1 */
*mbus = &bd->dstbus;
*sbus = &bd->srcbus;
}
}
}
......@@ -547,7 +535,8 @@ static void pl08x_fill_lli_for_desc(struct pl08x_lli_build_data *bd,
llis_va[num_llis].cctl = cctl;
llis_va[num_llis].src = bd->srcbus.addr;
llis_va[num_llis].dst = bd->dstbus.addr;
llis_va[num_llis].lli = llis_bus + (num_llis + 1) * sizeof(struct pl08x_lli);
llis_va[num_llis].lli = llis_bus + (num_llis + 1) *
sizeof(struct pl08x_lli);
llis_va[num_llis].lli |= bd->lli_bus;
if (cctl & PL080_CONTROL_SRC_INCR)
......@@ -560,16 +549,12 @@ static void pl08x_fill_lli_for_desc(struct pl08x_lli_build_data *bd,
bd->remainder -= len;
}
/*
* Return number of bytes to fill to boundary, or len.
* This calculation works for any value of addr.
*/
static inline size_t pl08x_pre_boundary(u32 addr, size_t len)
static inline void prep_byte_width_lli(struct pl08x_lli_build_data *bd,
u32 *cctl, u32 len, int num_llis, size_t *total_bytes)
{
size_t boundary_len = PL08X_BOUNDARY_SIZE -
(addr & (PL08X_BOUNDARY_SIZE - 1));
return min(boundary_len, len);
*cctl = pl08x_cctl_bits(*cctl, 1, 1, len);
pl08x_fill_lli_for_desc(bd, num_llis, len, *cctl);
(*total_bytes) += len;
}
/*
......@@ -583,13 +568,12 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
struct pl08x_bus_data *mbus, *sbus;
struct pl08x_lli_build_data bd;
int num_llis = 0;
u32 cctl;
size_t max_bytes_per_lli;
size_t total_bytes = 0;
u32 cctl, early_bytes = 0;
size_t max_bytes_per_lli, total_bytes;
struct pl08x_lli *llis_va;
struct pl08x_sg *dsg;
txd->llis_va = dma_pool_alloc(pl08x->pool, GFP_NOWAIT,
&txd->llis_bus);
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;
......@@ -597,13 +581,9 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
pl08x->pool_ctr++;
/* Get the default CCTL */
cctl = txd->cctl;
bd.txd = txd;
bd.srcbus.addr = txd->src_addr;
bd.dstbus.addr = txd->dst_addr;
bd.lli_bus = (pl08x->lli_buses & PL08X_AHB2) ? PL080_LLI_LM_AHB2 : 0;
cctl = txd->cctl;
/* Find maximum width of the source bus */
bd.srcbus.maxwidth =
......@@ -615,59 +595,97 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
pl08x_get_bytes_for_cctl((cctl & PL080_CONTROL_DWIDTH_MASK) >>
PL080_CONTROL_DWIDTH_SHIFT);
/* Set up the bus widths to the maximum */
list_for_each_entry(dsg, &txd->dsg_list, node) {
total_bytes = 0;
cctl = txd->cctl;
bd.srcbus.addr = dsg->src_addr;
bd.dstbus.addr = dsg->dst_addr;
bd.remainder = dsg->len;
bd.srcbus.buswidth = bd.srcbus.maxwidth;
bd.dstbus.buswidth = bd.dstbus.maxwidth;
/*
* Bytes transferred == tsize * MIN(buswidths), not max(buswidths)
*/
max_bytes_per_lli = min(bd.srcbus.buswidth, bd.dstbus.buswidth) *
PL080_CONTROL_TRANSFER_SIZE_MASK;
/* We need to count this down to zero */
bd.remainder = txd->len;
/*
* Choose bus to align to
* - prefers destination bus if both available
* - if fixed address on one bus chooses other
*/
pl08x_choose_master_bus(&bd, &mbus, &sbus, cctl);
dev_vdbg(&pl08x->adev->dev, "src=0x%08x%s/%u dst=0x%08x%s/%u len=%zu llimax=%zu\n",
dev_vdbg(&pl08x->adev->dev, "src=0x%08x%s/%u dst=0x%08x%s/%u len=%zu\n",
bd.srcbus.addr, cctl & PL080_CONTROL_SRC_INCR ? "+" : "",
bd.srcbus.buswidth,
bd.dstbus.addr, cctl & PL080_CONTROL_DST_INCR ? "+" : "",
bd.dstbus.buswidth,
bd.remainder, max_bytes_per_lli);
bd.remainder);
dev_vdbg(&pl08x->adev->dev, "mbus=%s sbus=%s\n",
mbus == &bd.srcbus ? "src" : "dst",
sbus == &bd.srcbus ? "src" : "dst");
if (txd->len < mbus->buswidth) {
/* Less than a bus width available - send as single bytes */
while (bd.remainder) {
dev_vdbg(&pl08x->adev->dev,
"%s single byte LLIs for a transfer of "
"less than a bus width (remain 0x%08x)\n",
__func__, bd.remainder);
cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
pl08x_fill_lli_for_desc(&bd, num_llis++, 1, cctl);
total_bytes++;
/*
* Zero length is only allowed if all these requirements are
* met:
* - flow controller is peripheral.
* - src.addr is aligned to src.width
* - dst.addr is aligned to dst.width
*
* sg_len == 1 should be true, as there can be two cases here:
*
* - Memory addresses are contiguous and are not scattered.
* Here, Only one sg will be passed by user driver, with
* memory address and zero length. We pass this to controller
* and after the transfer it will receive the last burst
* request from peripheral and so transfer finishes.
*
* - Memory addresses are scattered and are not contiguous.
* Here, Obviously as DMA controller doesn't know when a lli's
* transfer gets over, it can't load next lli. So in this
* case, there has to be an assumption that only one lli is
* supported. Thus, we can't have scattered addresses.
*/
if (!bd.remainder) {
u32 fc = (txd->ccfg & PL080_CONFIG_FLOW_CONTROL_MASK) >>
PL080_CONFIG_FLOW_CONTROL_SHIFT;
if (!((fc >= PL080_FLOW_SRC2DST_DST) &&
(fc <= PL080_FLOW_SRC2DST_SRC))) {
dev_err(&pl08x->adev->dev, "%s sg len can't be zero",
__func__);
return 0;
}
} else {
/* Make one byte LLIs until master bus is aligned */
while ((mbus->addr) % (mbus->buswidth)) {
if ((bd.srcbus.addr % bd.srcbus.buswidth) ||
(bd.srcbus.addr % bd.srcbus.buswidth)) {
dev_err(&pl08x->adev->dev,
"%s src & dst address must be aligned to src"
" & dst width if peripheral is flow controller",
__func__);
return 0;
}
cctl = pl08x_cctl_bits(cctl, bd.srcbus.buswidth,
bd.dstbus.buswidth, 0);
pl08x_fill_lli_for_desc(&bd, num_llis++, 0, cctl);
break;
}
/*
* Send byte by byte for following cases
* - Less than a bus width available
* - until master bus is aligned
*/
if (bd.remainder < mbus->buswidth)
early_bytes = bd.remainder;
else if ((mbus->addr) % (mbus->buswidth)) {
early_bytes = mbus->buswidth - (mbus->addr) %
(mbus->buswidth);
if ((bd.remainder - early_bytes) < mbus->buswidth)
early_bytes = bd.remainder;
}
if (early_bytes) {
dev_vdbg(&pl08x->adev->dev,
"%s adjustment lli for less than bus width "
"(remain 0x%08x)\n",
"%s byte width LLIs (remain 0x%08x)\n",
__func__, bd.remainder);
cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
pl08x_fill_lli_for_desc(&bd, num_llis++, 1, cctl);
total_bytes++;
prep_byte_width_lli(&bd, &cctl, early_bytes, num_llis++,
&total_bytes);
}
if (bd.remainder) {
/*
* Master now aligned
* - if slave is not then we must set its width down
......@@ -680,142 +698,67 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
sbus->buswidth = 1;
}
/*
* Bytes transferred = tsize * src width, not
* MIN(buswidths)
*/
max_bytes_per_lli = bd.srcbus.buswidth *
PL080_CONTROL_TRANSFER_SIZE_MASK;
dev_vdbg(&pl08x->adev->dev,
"%s max bytes per lli = %zu\n",
__func__, max_bytes_per_lli);
/*
* Make largest possible LLIs until less than one bus
* width left
*/
while (bd.remainder > (mbus->buswidth - 1)) {
size_t lli_len, target_len, tsize, odd_bytes;
size_t lli_len, tsize, width;
/*
* If enough left try to send max possible,
* otherwise try to send the remainder
*/
target_len = min(bd.remainder, max_bytes_per_lli);
/*
* Set bus lengths for incrementing buses to the
* number of bytes which fill to next memory boundary,
* limiting on the target length calculated above.
*/
if (cctl & PL080_CONTROL_SRC_INCR)
bd.srcbus.fill_bytes =
pl08x_pre_boundary(bd.srcbus.addr,
target_len);
else
bd.srcbus.fill_bytes = target_len;
if (cctl & PL080_CONTROL_DST_INCR)
bd.dstbus.fill_bytes =
pl08x_pre_boundary(bd.dstbus.addr,
target_len);
else
bd.dstbus.fill_bytes = target_len;
/* Find the nearest */
lli_len = min(bd.srcbus.fill_bytes,
bd.dstbus.fill_bytes);
BUG_ON(lli_len > bd.remainder);
if (lli_len <= 0) {
dev_err(&pl08x->adev->dev,
"%s lli_len is %zu, <= 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, and that we are working in
* multiples of the bus widths.
*/
odd_bytes = lli_len % mbus->buswidth;
lli_len -= odd_bytes;
lli_len = min(bd.remainder, max_bytes_per_lli);
}
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
* Check against maximum bus alignment:
* Calculate actual transfer size in relation to
* bus width an get a maximum remainder of the
* highest bus width - 1
*/
/* FIXME: use round_down()? */
tsize = lli_len / min(mbus->buswidth,
sbus->buswidth);
lli_len = tsize * min(mbus->buswidth,
sbus->buswidth);
width = max(mbus->buswidth, sbus->buswidth);
lli_len = (lli_len / width) * width;
tsize = lli_len / bd.srcbus.buswidth;
if (target_len != lli_len) {
dev_vdbg(&pl08x->adev->dev,
"%s can't send what we want. Desired 0x%08zx, lli of 0x%08zx bytes in txd of 0x%08zx\n",
__func__, target_len, lli_len, txd->len);
}
cctl = pl08x_cctl_bits(cctl,
bd.srcbus.buswidth,
bd.dstbus.buswidth,
tsize);
dev_vdbg(&pl08x->adev->dev,
"%s fill lli with single lli chunk of size 0x%08zx (remainder 0x%08zx)\n",
"%s fill lli with single lli chunk of "
"size 0x%08zx (remainder 0x%08zx)\n",
__func__, lli_len, bd.remainder);
cctl = pl08x_cctl_bits(cctl, bd.srcbus.buswidth,
bd.dstbus.buswidth, tsize);
pl08x_fill_lli_for_desc(&bd, num_llis++,
lli_len, cctl);
total_bytes += lli_len;
}
if (odd_bytes) {
/*
* Creep past the boundary, maintaining
* master alignment
*/
int j;
for (j = 0; (j < mbus->buswidth)
&& (bd.remainder); j++) {
cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
dev_vdbg(&pl08x->adev->dev,
"%s align with boundary, single byte (remain 0x%08zx)\n",
__func__, bd.remainder);
pl08x_fill_lli_for_desc(&bd,
num_llis++, 1, cctl);
total_bytes++;
}
}
}
/*
* Send any odd bytes
*/
while (bd.remainder) {
cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
if (bd.remainder) {
dev_vdbg(&pl08x->adev->dev,
"%s align with boundary, single odd byte (remain %zu)\n",
"%s align with boundary, send odd bytes (remain %zu)\n",
__func__, bd.remainder);
pl08x_fill_lli_for_desc(&bd, num_llis++, 1, cctl);
total_bytes++;
prep_byte_width_lli(&bd, &cctl, bd.remainder,
num_llis++, &total_bytes);
}
}
if (total_bytes != txd->len) {
if (total_bytes != dsg->len) {
dev_err(&pl08x->adev->dev,
"%s size of encoded lli:s don't match total txd, transferred 0x%08zx from size 0x%08zx\n",
__func__, total_bytes, txd->len);
__func__, total_bytes, dsg->len);
return 0;
}
......@@ -825,6 +768,7 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
__func__, (u32) MAX_NUM_TSFR_LLIS);
return 0;
}
}
llis_va = txd->llis_va;
/* The final LLI terminates the LLI. */
......@@ -856,11 +800,19 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
static void pl08x_free_txd(struct pl08x_driver_data *pl08x,
struct pl08x_txd *txd)
{
struct pl08x_sg *dsg, *_dsg;
/* Free the LLI */
if (txd->llis_va)
dma_pool_free(pl08x->pool, txd->llis_va, txd->llis_bus);
pl08x->pool_ctr--;
list_for_each_entry_safe(dsg, _dsg, &txd->dsg_list, node) {
list_del(&dsg->node);
kfree(dsg);
}
kfree(txd);
}
......@@ -917,9 +869,7 @@ static int prep_phy_channel(struct pl08x_dma_chan *plchan,
* 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) {
if (plchan->slave && pl08x->pd->get_signal) {
ret = pl08x->pd->get_signal(plchan);
if (ret < 0) {
dev_dbg(&pl08x->adev->dev,
......@@ -1008,10 +958,8 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_interrupt(
* 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)
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;
......@@ -1253,7 +1201,9 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan,
num_llis = pl08x_fill_llis_for_desc(pl08x, txd);
if (!num_llis) {
kfree(txd);
spin_lock_irqsave(&plchan->lock, flags);
pl08x_free_txd(pl08x, txd);
spin_unlock_irqrestore(&plchan->lock, flags);
return -EINVAL;
}
......@@ -1301,13 +1251,14 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan,
static struct pl08x_txd *pl08x_get_txd(struct pl08x_dma_chan *plchan,
unsigned long flags)
{
struct pl08x_txd *txd = kzalloc(sizeof(struct pl08x_txd), GFP_NOWAIT);
struct pl08x_txd *txd = kzalloc(sizeof(*txd), GFP_NOWAIT);
if (txd) {
dma_async_tx_descriptor_init(&txd->tx, &plchan->chan);
txd->tx.flags = flags;
txd->tx.tx_submit = pl08x_tx_submit;
INIT_LIST_HEAD(&txd->node);
INIT_LIST_HEAD(&txd->dsg_list);
/* Always enable error and terminal interrupts */
txd->ccfg = PL080_CONFIG_ERR_IRQ_MASK |
......@@ -1326,6 +1277,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy(
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
struct pl08x_driver_data *pl08x = plchan->host;
struct pl08x_txd *txd;
struct pl08x_sg *dsg;
int ret;
txd = pl08x_get_txd(plchan, flags);
......@@ -1335,10 +1287,19 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy(
return NULL;
}
dsg = kzalloc(sizeof(struct pl08x_sg), GFP_NOWAIT);
if (!dsg) {
pl08x_free_txd(pl08x, txd);
dev_err(&pl08x->adev->dev, "%s no memory for pl080 sg\n",
__func__);
return NULL;
}
list_add_tail(&dsg->node, &txd->dsg_list);
txd->direction = DMA_NONE;
txd->src_addr = src;
txd->dst_addr = dest;
txd->len = len;
dsg->src_addr = src;
dsg->dst_addr = dest;
dsg->len = len;
/* Set platform data for m2m */
txd->ccfg |= PL080_FLOW_MEM2MEM << PL080_CONFIG_FLOW_CONTROL_SHIFT;
......@@ -1367,16 +1328,10 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
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();
}
struct pl08x_sg *dsg;
struct scatterlist *sg;
dma_addr_t slave_addr;
int ret, tmp;
dev_dbg(&pl08x->adev->dev, "%s prepare transaction of %d bytes from %s\n",
__func__, sgl->length, plchan->name);
......@@ -1398,24 +1353,49 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
* channel target address dynamically at runtime.
*/
txd->direction = direction;
txd->len = sgl->length;
if (direction == DMA_TO_DEVICE) {
txd->ccfg |= PL080_FLOW_MEM2PER << PL080_CONFIG_FLOW_CONTROL_SHIFT;
txd->cctl = plchan->dst_cctl;
txd->src_addr = sgl->dma_address;
txd->dst_addr = plchan->dst_addr;
slave_addr = plchan->dst_addr;
} else if (direction == DMA_FROM_DEVICE) {
txd->ccfg |= PL080_FLOW_PER2MEM << PL080_CONFIG_FLOW_CONTROL_SHIFT;
txd->cctl = plchan->src_cctl;
txd->src_addr = plchan->src_addr;
txd->dst_addr = sgl->dma_address;
slave_addr = plchan->src_addr;
} else {
pl08x_free_txd(pl08x, txd);
dev_err(&pl08x->adev->dev,
"%s direction unsupported\n", __func__);
return NULL;
}
if (plchan->cd->device_fc)
tmp = (direction == DMA_TO_DEVICE) ? PL080_FLOW_MEM2PER_PER :
PL080_FLOW_PER2MEM_PER;
else
tmp = (direction == DMA_TO_DEVICE) ? PL080_FLOW_MEM2PER :
PL080_FLOW_PER2MEM;
txd->ccfg |= tmp << PL080_CONFIG_FLOW_CONTROL_SHIFT;
for_each_sg(sgl, sg, sg_len, tmp) {
dsg = kzalloc(sizeof(struct pl08x_sg), GFP_NOWAIT);
if (!dsg) {
pl08x_free_txd(pl08x, txd);
dev_err(&pl08x->adev->dev, "%s no mem for pl080 sg\n",
__func__);
return NULL;
}
list_add_tail(&dsg->node, &txd->dsg_list);
dsg->len = sg_dma_len(sg);
if (direction == DMA_TO_DEVICE) {
dsg->src_addr = sg_phys(sg);
dsg->dst_addr = slave_addr;
} else {
dsg->src_addr = slave_addr;
dsg->dst_addr = sg_phys(sg);
}
}
ret = pl08x_prep_channel_resources(plchan, txd);
if (ret)
return NULL;
......@@ -1489,9 +1469,15 @@ static int pl08x_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
bool pl08x_filter_id(struct dma_chan *chan, void *chan_id)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
struct pl08x_dma_chan *plchan;
char *name = chan_id;
/* Reject channels for devices not bound to this driver */
if (chan->device->dev->driver != &pl08x_amba_driver.drv)
return false;
plchan = to_pl08x_chan(chan);
/* Check that the channel is not taken! */
if (!strcmp(plchan->name, name))
return true;
......@@ -1507,33 +1493,33 @@ bool pl08x_filter_id(struct dma_chan *chan, void *chan_id)
*/
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 implicitly clear bit 1 and that means little-endian mode */
val |= PL080_CONFIG_ENABLE;
writel(val, pl08x->base + PL080_CONFIG);
writel(PL080_CONFIG_ENABLE, pl08x->base + PL080_CONFIG);
}
static void pl08x_unmap_buffers(struct pl08x_txd *txd)
{
struct device *dev = txd->tx.chan->device->dev;
struct pl08x_sg *dsg;
if (!(txd->tx.flags & DMA_COMPL_SKIP_SRC_UNMAP)) {
if (txd->tx.flags & DMA_COMPL_SRC_UNMAP_SINGLE)
dma_unmap_single(dev, txd->src_addr, txd->len,
list_for_each_entry(dsg, &txd->dsg_list, node)
dma_unmap_single(dev, dsg->src_addr, dsg->len,
DMA_TO_DEVICE);
else
dma_unmap_page(dev, txd->src_addr, txd->len,
else {
list_for_each_entry(dsg, &txd->dsg_list, node)
dma_unmap_page(dev, dsg->src_addr, dsg->len,
DMA_TO_DEVICE);
}
}
if (!(txd->tx.flags & DMA_COMPL_SKIP_DEST_UNMAP)) {
if (txd->tx.flags & DMA_COMPL_DEST_UNMAP_SINGLE)
dma_unmap_single(dev, txd->dst_addr, txd->len,
list_for_each_entry(dsg, &txd->dsg_list, node)
dma_unmap_single(dev, dsg->dst_addr, dsg->len,
DMA_FROM_DEVICE);
else
dma_unmap_page(dev, txd->dst_addr, txd->len,
list_for_each_entry(dsg, &txd->dsg_list, node)
dma_unmap_page(dev, dsg->dst_addr, dsg->len,
DMA_FROM_DEVICE);
}
}
......@@ -1630,38 +1616,40 @@ static void pl08x_tasklet(unsigned long data)
static irqreturn_t pl08x_irq(int irq, void *dev)
{
struct pl08x_driver_data *pl08x = dev;
u32 mask = 0;
u32 val;
int i;
u32 mask = 0, err, tc, 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);
/* check & clear - ERR & TC interrupts */
err = readl(pl08x->base + PL080_ERR_STATUS);
if (err) {
dev_err(&pl08x->adev->dev, "%s error interrupt, register value 0x%08x\n",
__func__, err);
writel(err, pl08x->base + PL080_ERR_CLEAR);
}
val = readl(pl08x->base + PL080_INT_STATUS);
tc = readl(pl08x->base + PL080_INT_STATUS);
if (tc)
writel(tc, pl08x->base + PL080_TC_CLEAR);
if (!err && !tc)
return IRQ_NONE;
for (i = 0; i < pl08x->vd->channels; i++) {
if ((1 << i) & val) {
if (((1 << i) & err) || ((1 << i) & tc)) {
/* Locate physical channel */
struct pl08x_phy_chan *phychan = &pl08x->phy_chans[i];
struct pl08x_dma_chan *plchan = phychan->serving;
if (!plchan) {
dev_err(&pl08x->adev->dev,
"%s Error TC interrupt on unused channel: 0x%08x\n",
__func__, i);
continue;
}
/* 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;
}
......@@ -1685,9 +1673,7 @@ static void pl08x_dma_slave_init(struct pl08x_dma_chan *chan)
* 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 dma_device *dmadev, unsigned int channels, bool slave)
{
struct pl08x_dma_chan *chan;
int i;
......@@ -1700,7 +1686,7 @@ static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x,
* to cope with that situation.
*/
for (i = 0; i < channels; i++) {
chan = kzalloc(sizeof(struct pl08x_dma_chan), GFP_KERNEL);
chan = kzalloc(sizeof(*chan), GFP_KERNEL);
if (!chan) {
dev_err(&pl08x->adev->dev,
"%s no memory for channel\n", __func__);
......@@ -1728,7 +1714,7 @@ static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x,
kfree(chan);
continue;
}
dev_info(&pl08x->adev->dev,
dev_dbg(&pl08x->adev->dev,
"initialize virtual channel \"%s\"\n",
chan->name);
......@@ -1837,8 +1823,8 @@ static const struct file_operations pl08x_debugfs_operations = {
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,
(void) debugfs_create_file(dev_name(&pl08x->adev->dev),
S_IFREG | S_IRUGO, NULL, pl08x,
&pl08x_debugfs_operations);
}
......@@ -1860,12 +1846,15 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id)
return ret;
/* Create the driver state holder */
pl08x = kzalloc(sizeof(struct pl08x_driver_data), GFP_KERNEL);
pl08x = kzalloc(sizeof(*pl08x), GFP_KERNEL);
if (!pl08x) {
ret = -ENOMEM;
goto out_no_pl08x;
}
pm_runtime_set_active(&adev->dev);
pm_runtime_enable(&adev->dev);
/* Initialize memcpy engine */
dma_cap_set(DMA_MEMCPY, pl08x->memcpy.cap_mask);
pl08x->memcpy.dev = &adev->dev;
......@@ -1939,7 +1928,7 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id)
}
/* Initialize physical channels */
pl08x->phy_chans = kmalloc((vd->channels * sizeof(struct pl08x_phy_chan)),
pl08x->phy_chans = kmalloc((vd->channels * sizeof(*pl08x->phy_chans)),
GFP_KERNEL);
if (!pl08x->phy_chans) {
dev_err(&adev->dev, "%s failed to allocate "
......@@ -1956,9 +1945,8 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id)
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");
dev_dbg(&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 */
......@@ -1974,8 +1962,7 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id)
/* Register slave channels */
ret = pl08x_dma_init_virtual_channels(pl08x, &pl08x->slave,
pl08x->pd->num_slave_channels,
true);
pl08x->pd->num_slave_channels, true);
if (ret <= 0) {
dev_warn(&pl08x->adev->dev,
"%s failed to enumerate slave channels - %d\n",
......@@ -2005,6 +1992,8 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id)
dev_info(&pl08x->adev->dev, "DMA: PL%03x rev%u at 0x%08llx irq %d\n",
amba_part(adev), amba_rev(adev),
(unsigned long long)adev->res.start, adev->irq[0]);
pm_runtime_put(&adev->dev);
return 0;
out_no_slave_reg:
......@@ -2023,6 +2012,9 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id)
dma_pool_destroy(pl08x->pool);
out_no_lli_pool:
out_no_platdata:
pm_runtime_put(&adev->dev);
pm_runtime_disable(&adev->dev);
kfree(pl08x);
out_no_pl08x:
amba_release_regions(adev);
......
......@@ -107,10 +107,11 @@ static struct at_desc *atc_desc_get(struct at_dma_chan *atchan)
{
struct at_desc *desc, *_desc;
struct at_desc *ret = NULL;
unsigned long flags;
unsigned int i = 0;
LIST_HEAD(tmp_list);
spin_lock_bh(&atchan->lock);
spin_lock_irqsave(&atchan->lock, flags);
list_for_each_entry_safe(desc, _desc, &atchan->free_list, desc_node) {
i++;
if (async_tx_test_ack(&desc->txd)) {
......@@ -121,7 +122,7 @@ static struct at_desc *atc_desc_get(struct at_dma_chan *atchan)
dev_dbg(chan2dev(&atchan->chan_common),
"desc %p not ACKed\n", desc);
}
spin_unlock_bh(&atchan->lock);
spin_unlock_irqrestore(&atchan->lock, flags);
dev_vdbg(chan2dev(&atchan->chan_common),
"scanned %u descriptors on freelist\n", i);
......@@ -129,9 +130,9 @@ static struct at_desc *atc_desc_get(struct at_dma_chan *atchan)
if (!ret) {
ret = atc_alloc_descriptor(&atchan->chan_common, GFP_ATOMIC);
if (ret) {
spin_lock_bh(&atchan->lock);
spin_lock_irqsave(&atchan->lock, flags);
atchan->descs_allocated++;
spin_unlock_bh(&atchan->lock);
spin_unlock_irqrestore(&atchan->lock, flags);
} else {
dev_err(chan2dev(&atchan->chan_common),
"not enough descriptors available\n");
......@@ -150,8 +151,9 @@ static void atc_desc_put(struct at_dma_chan *atchan, struct at_desc *desc)
{
if (desc) {
struct at_desc *child;
unsigned long flags;
spin_lock_bh(&atchan->lock);
spin_lock_irqsave(&atchan->lock, flags);
list_for_each_entry(child, &desc->tx_list, desc_node)
dev_vdbg(chan2dev(&atchan->chan_common),
"moving child desc %p to freelist\n",
......@@ -160,7 +162,7 @@ static void atc_desc_put(struct at_dma_chan *atchan, struct at_desc *desc)
dev_vdbg(chan2dev(&atchan->chan_common),
"moving desc %p to freelist\n", desc);
list_add(&desc->desc_node, &atchan->free_list);
spin_unlock_bh(&atchan->lock);
spin_unlock_irqrestore(&atchan->lock, flags);
}
}
......@@ -299,7 +301,7 @@ atc_chain_complete(struct at_dma_chan *atchan, struct at_desc *desc)
/* for cyclic transfers,
* no need to replay callback function while stopping */
if (!test_bit(ATC_IS_CYCLIC, &atchan->status)) {
if (!atc_chan_is_cyclic(atchan)) {
dma_async_tx_callback callback = txd->callback;
void *param = txd->callback_param;
......@@ -471,16 +473,17 @@ static void atc_handle_cyclic(struct at_dma_chan *atchan)
static void atc_tasklet(unsigned long data)
{
struct at_dma_chan *atchan = (struct at_dma_chan *)data;
unsigned long flags;
spin_lock(&atchan->lock);
spin_lock_irqsave(&atchan->lock, flags);
if (test_and_clear_bit(ATC_IS_ERROR, &atchan->status))
atc_handle_error(atchan);
else if (test_bit(ATC_IS_CYCLIC, &atchan->status))
else if (atc_chan_is_cyclic(atchan))
atc_handle_cyclic(atchan);
else
atc_advance_work(atchan);
spin_unlock(&atchan->lock);
spin_unlock_irqrestore(&atchan->lock, flags);
}
static irqreturn_t at_dma_interrupt(int irq, void *dev_id)
......@@ -539,8 +542,9 @@ static dma_cookie_t atc_tx_submit(struct dma_async_tx_descriptor *tx)
struct at_desc *desc = txd_to_at_desc(tx);
struct at_dma_chan *atchan = to_at_dma_chan(tx->chan);
dma_cookie_t cookie;
unsigned long flags;
spin_lock_bh(&atchan->lock);
spin_lock_irqsave(&atchan->lock, flags);
cookie = atc_assign_cookie(atchan, desc);
if (list_empty(&atchan->active_list)) {
......@@ -554,7 +558,7 @@ static dma_cookie_t atc_tx_submit(struct dma_async_tx_descriptor *tx)
list_add_tail(&desc->desc_node, &atchan->queue);
}
spin_unlock_bh(&atchan->lock);
spin_unlock_irqrestore(&atchan->lock, flags);
return cookie;
}
......@@ -927,28 +931,29 @@ static int atc_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
struct at_dma_chan *atchan = to_at_dma_chan(chan);
struct at_dma *atdma = to_at_dma(chan->device);
int chan_id = atchan->chan_common.chan_id;
unsigned long flags;
LIST_HEAD(list);
dev_vdbg(chan2dev(chan), "atc_control (%d)\n", cmd);
if (cmd == DMA_PAUSE) {
spin_lock_bh(&atchan->lock);
spin_lock_irqsave(&atchan->lock, flags);
dma_writel(atdma, CHER, AT_DMA_SUSP(chan_id));
set_bit(ATC_IS_PAUSED, &atchan->status);
spin_unlock_bh(&atchan->lock);
spin_unlock_irqrestore(&atchan->lock, flags);
} else if (cmd == DMA_RESUME) {
if (!test_bit(ATC_IS_PAUSED, &atchan->status))
if (!atc_chan_is_paused(atchan))
return 0;
spin_lock_bh(&atchan->lock);
spin_lock_irqsave(&atchan->lock, flags);
dma_writel(atdma, CHDR, AT_DMA_RES(chan_id));
clear_bit(ATC_IS_PAUSED, &atchan->status);
spin_unlock_bh(&atchan->lock);
spin_unlock_irqrestore(&atchan->lock, flags);
} else if (cmd == DMA_TERMINATE_ALL) {
struct at_desc *desc, *_desc;
/*
......@@ -957,7 +962,7 @@ static int atc_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
* channel. We still have to poll the channel enable bit due
* to AHB/HSB limitations.
*/
spin_lock_bh(&atchan->lock);
spin_lock_irqsave(&atchan->lock, flags);
/* disabling channel: must also remove suspend state */
dma_writel(atdma, CHDR, AT_DMA_RES(chan_id) | atchan->mask);
......@@ -978,7 +983,7 @@ static int atc_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
/* if channel dedicated to cyclic operations, free it */
clear_bit(ATC_IS_CYCLIC, &atchan->status);
spin_unlock_bh(&atchan->lock);
spin_unlock_irqrestore(&atchan->lock, flags);
} else {
return -ENXIO;
}
......@@ -1004,9 +1009,10 @@ atc_tx_status(struct dma_chan *chan,
struct at_dma_chan *atchan = to_at_dma_chan(chan);
dma_cookie_t last_used;
dma_cookie_t last_complete;
unsigned long flags;
enum dma_status ret;
spin_lock_bh(&atchan->lock);
spin_lock_irqsave(&atchan->lock, flags);
last_complete = atchan->completed_cookie;
last_used = chan->cookie;
......@@ -1021,7 +1027,7 @@ atc_tx_status(struct dma_chan *chan,
ret = dma_async_is_complete(cookie, last_complete, last_used);
}
spin_unlock_bh(&atchan->lock);
spin_unlock_irqrestore(&atchan->lock, flags);
if (ret != DMA_SUCCESS)
dma_set_tx_state(txstate, last_complete, last_used,
......@@ -1029,7 +1035,7 @@ atc_tx_status(struct dma_chan *chan,
else
dma_set_tx_state(txstate, last_complete, last_used, 0);
if (test_bit(ATC_IS_PAUSED, &atchan->status))
if (atc_chan_is_paused(atchan))
ret = DMA_PAUSED;
dev_vdbg(chan2dev(chan), "tx_status %d: cookie = %d (d%d, u%d)\n",
......@@ -1046,18 +1052,19 @@ atc_tx_status(struct dma_chan *chan,
static void atc_issue_pending(struct dma_chan *chan)
{
struct at_dma_chan *atchan = to_at_dma_chan(chan);
unsigned long flags;
dev_vdbg(chan2dev(chan), "issue_pending\n");
/* Not needed for cyclic transfers */
if (test_bit(ATC_IS_CYCLIC, &atchan->status))
if (atc_chan_is_cyclic(atchan))
return;
spin_lock_bh(&atchan->lock);
spin_lock_irqsave(&atchan->lock, flags);
if (!atc_chan_is_enabled(atchan)) {
atc_advance_work(atchan);
}
spin_unlock_bh(&atchan->lock);
spin_unlock_irqrestore(&atchan->lock, flags);
}
/**
......@@ -1073,6 +1080,7 @@ static int atc_alloc_chan_resources(struct dma_chan *chan)
struct at_dma *atdma = to_at_dma(chan->device);
struct at_desc *desc;
struct at_dma_slave *atslave;
unsigned long flags;
int i;
u32 cfg;
LIST_HEAD(tmp_list);
......@@ -1116,11 +1124,11 @@ static int atc_alloc_chan_resources(struct dma_chan *chan)
list_add_tail(&desc->desc_node, &tmp_list);
}
spin_lock_bh(&atchan->lock);
spin_lock_irqsave(&atchan->lock, flags);
atchan->descs_allocated = i;
list_splice(&tmp_list, &atchan->free_list);
atchan->completed_cookie = chan->cookie = 1;
spin_unlock_bh(&atchan->lock);
spin_unlock_irqrestore(&atchan->lock, flags);
/* channel parameters */
channel_writel(atchan, CFG, cfg);
......@@ -1260,12 +1268,11 @@ static int __init at_dma_probe(struct platform_device *pdev)
/* initialize channels related values */
INIT_LIST_HEAD(&atdma->dma_common.channels);
for (i = 0; i < pdata->nr_channels; i++, atdma->dma_common.chancnt++) {
for (i = 0; i < pdata->nr_channels; i++) {
struct at_dma_chan *atchan = &atdma->chan[i];
atchan->chan_common.device = &atdma->dma_common;
atchan->chan_common.cookie = atchan->completed_cookie = 1;
atchan->chan_common.chan_id = i;
list_add_tail(&atchan->chan_common.device_node,
&atdma->dma_common.channels);
......@@ -1293,22 +1300,20 @@ static int __init at_dma_probe(struct platform_device *pdev)
if (dma_has_cap(DMA_MEMCPY, atdma->dma_common.cap_mask))
atdma->dma_common.device_prep_dma_memcpy = atc_prep_dma_memcpy;
if (dma_has_cap(DMA_SLAVE, atdma->dma_common.cap_mask))
if (dma_has_cap(DMA_SLAVE, atdma->dma_common.cap_mask)) {
atdma->dma_common.device_prep_slave_sg = atc_prep_slave_sg;
if (dma_has_cap(DMA_CYCLIC, atdma->dma_common.cap_mask))
/* controller can do slave DMA: can trigger cyclic transfers */
dma_cap_set(DMA_CYCLIC, atdma->dma_common.cap_mask);
atdma->dma_common.device_prep_dma_cyclic = atc_prep_dma_cyclic;
if (dma_has_cap(DMA_SLAVE, atdma->dma_common.cap_mask) ||
dma_has_cap(DMA_CYCLIC, atdma->dma_common.cap_mask))
atdma->dma_common.device_control = atc_control;
}
dma_writel(atdma, EN, AT_DMA_ENABLE);
dev_info(&pdev->dev, "Atmel AHB DMA Controller ( %s%s), %d channels\n",
dma_has_cap(DMA_MEMCPY, atdma->dma_common.cap_mask) ? "cpy " : "",
dma_has_cap(DMA_SLAVE, atdma->dma_common.cap_mask) ? "slave " : "",
atdma->dma_common.chancnt);
pdata->nr_channels);
dma_async_device_register(&atdma->dma_common);
......@@ -1377,27 +1382,112 @@ static void at_dma_shutdown(struct platform_device *pdev)
clk_disable(atdma->clk);
}
static int at_dma_prepare(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct at_dma *atdma = platform_get_drvdata(pdev);
struct dma_chan *chan, *_chan;
list_for_each_entry_safe(chan, _chan, &atdma->dma_common.channels,
device_node) {
struct at_dma_chan *atchan = to_at_dma_chan(chan);
/* wait for transaction completion (except in cyclic case) */
if (atc_chan_is_enabled(atchan) && !atc_chan_is_cyclic(atchan))
return -EAGAIN;
}
return 0;
}
static void atc_suspend_cyclic(struct at_dma_chan *atchan)
{
struct dma_chan *chan = &atchan->chan_common;
/* Channel should be paused by user
* do it anyway even if it is not done already */
if (!atc_chan_is_paused(atchan)) {
dev_warn(chan2dev(chan),
"cyclic channel not paused, should be done by channel user\n");
atc_control(chan, DMA_PAUSE, 0);
}
/* now preserve additional data for cyclic operations */
/* next descriptor address in the cyclic list */
atchan->save_dscr = channel_readl(atchan, DSCR);
vdbg_dump_regs(atchan);
}
static int at_dma_suspend_noirq(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct at_dma *atdma = platform_get_drvdata(pdev);
struct dma_chan *chan, *_chan;
at_dma_off(platform_get_drvdata(pdev));
/* preserve data */
list_for_each_entry_safe(chan, _chan, &atdma->dma_common.channels,
device_node) {
struct at_dma_chan *atchan = to_at_dma_chan(chan);
if (atc_chan_is_cyclic(atchan))
atc_suspend_cyclic(atchan);
atchan->save_cfg = channel_readl(atchan, CFG);
}
atdma->save_imr = dma_readl(atdma, EBCIMR);
/* disable DMA controller */
at_dma_off(atdma);
clk_disable(atdma->clk);
return 0;
}
static void atc_resume_cyclic(struct at_dma_chan *atchan)
{
struct at_dma *atdma = to_at_dma(atchan->chan_common.device);
/* restore channel status for cyclic descriptors list:
* next descriptor in the cyclic list at the time of suspend */
channel_writel(atchan, SADDR, 0);
channel_writel(atchan, DADDR, 0);
channel_writel(atchan, CTRLA, 0);
channel_writel(atchan, CTRLB, 0);
channel_writel(atchan, DSCR, atchan->save_dscr);
dma_writel(atdma, CHER, atchan->mask);
/* channel pause status should be removed by channel user
* We cannot take the initiative to do it here */
vdbg_dump_regs(atchan);
}
static int at_dma_resume_noirq(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct at_dma *atdma = platform_get_drvdata(pdev);
struct dma_chan *chan, *_chan;
/* bring back DMA controller */
clk_enable(atdma->clk);
dma_writel(atdma, EN, AT_DMA_ENABLE);
/* clear any pending interrupt */
while (dma_readl(atdma, EBCISR))
cpu_relax();
/* restore saved data */
dma_writel(atdma, EBCIER, atdma->save_imr);
list_for_each_entry_safe(chan, _chan, &atdma->dma_common.channels,
device_node) {
struct at_dma_chan *atchan = to_at_dma_chan(chan);
channel_writel(atchan, CFG, atchan->save_cfg);
if (atc_chan_is_cyclic(atchan))
atc_resume_cyclic(atchan);
}
return 0;
}
static const struct dev_pm_ops at_dma_dev_pm_ops = {
.prepare = at_dma_prepare,
.suspend_noirq = at_dma_suspend_noirq,
.resume_noirq = at_dma_resume_noirq,
};
......
......@@ -204,6 +204,9 @@ enum atc_status {
* @status: transmit status information from irq/prep* functions
* to tasklet (use atomic operations)
* @tasklet: bottom half to finish transaction work
* @save_cfg: configuration register that is saved on suspend/resume cycle
* @save_dscr: for cyclic operations, preserve next descriptor address in
* the cyclic list on suspend/resume cycle
* @lock: serializes enqueue/dequeue operations to descriptors lists
* @completed_cookie: identifier for the most recently completed operation
* @active_list: list of descriptors dmaengine is being running on
......@@ -218,6 +221,8 @@ struct at_dma_chan {
u8 mask;
unsigned long status;
struct tasklet_struct tasklet;
u32 save_cfg;
u32 save_dscr;
spinlock_t lock;
......@@ -248,6 +253,7 @@ static inline struct at_dma_chan *to_at_dma_chan(struct dma_chan *dchan)
* @chan_common: common dmaengine dma_device object members
* @ch_regs: memory mapped register base
* @clk: dma controller clock
* @save_imr: interrupt mask register that is saved on suspend/resume cycle
* @all_chan_mask: all channels availlable in a mask
* @dma_desc_pool: base of DMA descriptor region (DMA address)
* @chan: channels table to store at_dma_chan structures
......@@ -256,6 +262,7 @@ struct at_dma {
struct dma_device dma_common;
void __iomem *regs;
struct clk *clk;
u32 save_imr;
u8 all_chan_mask;
......@@ -355,6 +362,23 @@ static inline int atc_chan_is_enabled(struct at_dma_chan *atchan)
return !!(dma_readl(atdma, CHSR) & atchan->mask);
}
/**
* atc_chan_is_paused - test channel pause/resume status
* @atchan: channel we want to test status
*/
static inline int atc_chan_is_paused(struct at_dma_chan *atchan)
{
return test_bit(ATC_IS_PAUSED, &atchan->status);
}
/**
* atc_chan_is_cyclic - test if given channel has cyclic property set
* @atchan: channel we want to test status
*/
static inline int atc_chan_is_cyclic(struct at_dma_chan *atchan)
{
return test_bit(ATC_IS_CYCLIC, &atchan->status);
}
/**
* set_desc_eol - set end-of-link to descriptor so it will end transfer
......
......@@ -10,6 +10,7 @@
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/freezer.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/module.h>
......@@ -251,6 +252,7 @@ static int dmatest_func(void *data)
int i;
thread_name = current->comm;
set_freezable_with_signal();
ret = -ENOMEM;
......@@ -305,7 +307,8 @@ static int dmatest_func(void *data)
dma_addr_t dma_srcs[src_cnt];
dma_addr_t dma_dsts[dst_cnt];
struct completion cmp;
unsigned long tmo = msecs_to_jiffies(timeout);
unsigned long start, tmo, end = 0 /* compiler... */;
bool reload = true;
u8 align = 0;
total_tests++;
......@@ -404,7 +407,17 @@ static int dmatest_func(void *data)
}
dma_async_issue_pending(chan);
tmo = wait_for_completion_timeout(&cmp, tmo);
do {
start = jiffies;
if (reload)
end = start + msecs_to_jiffies(timeout);
else if (end <= start)
end = start + 1;
tmo = wait_for_completion_interruptible_timeout(&cmp,
end - start);
reload = try_to_freeze();
} while (tmo == -ERESTARTSYS);
status = dma_async_is_tx_complete(chan, cookie, NULL, NULL);
if (tmo == 0) {
......@@ -477,6 +490,8 @@ static int dmatest_func(void *data)
pr_notice("%s: terminating after %u tests, %u failures (status %d)\n",
thread_name, total_tests, failed_tests, ret);
/* terminate all transfers on specified channels */
chan->device->device_control(chan, DMA_TERMINATE_ALL, 0);
if (iterations > 0)
while (!kthread_should_stop()) {
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wait_dmatest_exit);
......@@ -499,6 +514,10 @@ static void dmatest_cleanup_channel(struct dmatest_chan *dtc)
list_del(&thread->node);
kfree(thread);
}
/* terminate all transfers on specified channels */
dtc->chan->device->device_control(dtc->chan, DMA_TERMINATE_ALL, 0);
kfree(dtc);
}
......
......@@ -1407,12 +1407,11 @@ static int __init dw_probe(struct platform_device *pdev)
dw->all_chan_mask = (1 << pdata->nr_channels) - 1;
INIT_LIST_HEAD(&dw->dma.channels);
for (i = 0; i < pdata->nr_channels; i++, dw->dma.chancnt++) {
for (i = 0; i < pdata->nr_channels; i++) {
struct dw_dma_chan *dwc = &dw->chan[i];
dwc->chan.device = &dw->dma;
dwc->chan.cookie = dwc->completed = 1;
dwc->chan.chan_id = i;
if (pdata->chan_allocation_order == CHAN_ALLOCATION_ASCENDING)
list_add_tail(&dwc->chan.device_node,
&dw->dma.channels);
......@@ -1468,7 +1467,7 @@ static int __init dw_probe(struct platform_device *pdev)
dma_writel(dw, CFG, DW_CFG_DMA_EN);
printk(KERN_INFO "%s: DesignWare DMA Controller, %d channels\n",
dev_name(&pdev->dev), dw->dma.chancnt);
dev_name(&pdev->dev), pdata->nr_channels);
dma_async_device_register(&dw->dma);
......
......@@ -22,6 +22,7 @@
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/dmaengine.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
......
......@@ -14,6 +14,7 @@
* http://www.gnu.org/copyleft/gpl.html
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
......
......@@ -18,6 +18,7 @@
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
......@@ -318,6 +319,7 @@ struct sdma_engine {
dma_addr_t context_phys;
struct dma_device dma_device;
struct clk *clk;
struct mutex channel_0_lock;
struct sdma_script_start_addrs *script_addrs;
};
......@@ -415,11 +417,15 @@ static int sdma_load_script(struct sdma_engine *sdma, void *buf, int size,
dma_addr_t buf_phys;
int ret;
mutex_lock(&sdma->channel_0_lock);
buf_virt = dma_alloc_coherent(NULL,
size,
&buf_phys, GFP_KERNEL);
if (!buf_virt)
return -ENOMEM;
if (!buf_virt) {
ret = -ENOMEM;
goto err_out;
}
bd0->mode.command = C0_SETPM;
bd0->mode.status = BD_DONE | BD_INTR | BD_WRAP | BD_EXTD;
......@@ -433,6 +439,9 @@ static int sdma_load_script(struct sdma_engine *sdma, void *buf, int size,
dma_free_coherent(NULL, size, buf_virt, buf_phys);
err_out:
mutex_unlock(&sdma->channel_0_lock);
return ret;
}
......@@ -656,6 +665,8 @@ static int sdma_load_context(struct sdma_channel *sdmac)
dev_dbg(sdma->dev, "event_mask0 = 0x%08x\n", sdmac->event_mask0);
dev_dbg(sdma->dev, "event_mask1 = 0x%08x\n", sdmac->event_mask1);
mutex_lock(&sdma->channel_0_lock);
memset(context, 0, sizeof(*context));
context->channel_state.pc = load_address;
......@@ -676,6 +687,8 @@ static int sdma_load_context(struct sdma_channel *sdmac)
ret = sdma_run_channel(&sdma->channel[0]);
mutex_unlock(&sdma->channel_0_lock);
return ret;
}
......@@ -1131,18 +1144,17 @@ static void sdma_add_scripts(struct sdma_engine *sdma,
saddr_arr[i] = addr_arr[i];
}
static int __init sdma_get_firmware(struct sdma_engine *sdma,
const char *fw_name)
static void sdma_load_firmware(const struct firmware *fw, void *context)
{
const struct firmware *fw;
struct sdma_engine *sdma = context;
const struct sdma_firmware_header *header;
int ret;
const struct sdma_script_start_addrs *addr;
unsigned short *ram_code;
ret = request_firmware(&fw, fw_name, sdma->dev);
if (ret)
return ret;
if (!fw) {
dev_err(sdma->dev, "firmware not found\n");
return;
}
if (fw->size < sizeof(*header))
goto err_firmware;
......@@ -1172,6 +1184,16 @@ static int __init sdma_get_firmware(struct sdma_engine *sdma,
err_firmware:
release_firmware(fw);
}
static int __init sdma_get_firmware(struct sdma_engine *sdma,
const char *fw_name)
{
int ret;
ret = request_firmware_nowait(THIS_MODULE,
FW_ACTION_HOTPLUG, fw_name, sdma->dev,
GFP_KERNEL, sdma, sdma_load_firmware);
return ret;
}
......@@ -1269,11 +1291,14 @@ static int __init sdma_probe(struct platform_device *pdev)
struct sdma_platform_data *pdata = pdev->dev.platform_data;
int i;
struct sdma_engine *sdma;
s32 *saddr_arr;
sdma = kzalloc(sizeof(*sdma), GFP_KERNEL);
if (!sdma)
return -ENOMEM;
mutex_init(&sdma->channel_0_lock);
sdma->dev = &pdev->dev;
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
......@@ -1310,6 +1335,11 @@ static int __init sdma_probe(struct platform_device *pdev)
goto err_alloc;
}
/* initially no scripts available */
saddr_arr = (s32 *)sdma->script_addrs;
for (i = 0; i < SDMA_SCRIPT_ADDRS_ARRAY_SIZE_V1; i++)
saddr_arr[i] = -EINVAL;
if (of_id)
pdev->id_entry = of_id->data;
sdma->devtype = pdev->id_entry->driver_data;
......
......@@ -115,16 +115,15 @@ DMAC1 interrupt Functions*/
/**
* dmac1_mask_periphral_intr - mask the periphral interrupt
* @midc: dma channel for which masking is required
* @mid: dma device for which masking is required
*
* Masks the DMA periphral interrupt
* this is valid for DMAC1 family controllers only
* This controller should have periphral mask registers already mapped
*/
static void dmac1_mask_periphral_intr(struct intel_mid_dma_chan *midc)
static void dmac1_mask_periphral_intr(struct middma_device *mid)
{
u32 pimr;
struct middma_device *mid = to_middma_device(midc->chan.device);
if (mid->pimr_mask) {
pimr = readl(mid->mask_reg + LNW_PERIPHRAL_MASK);
......@@ -184,7 +183,6 @@ static void enable_dma_interrupt(struct intel_mid_dma_chan *midc)
static void disable_dma_interrupt(struct intel_mid_dma_chan *midc)
{
/*Check LPE PISR, make sure fwd is disabled*/
dmac1_mask_periphral_intr(midc);
iowrite32(MASK_INTR_REG(midc->ch_id), midc->dma_base + MASK_BLOCK);
iowrite32(MASK_INTR_REG(midc->ch_id), midc->dma_base + MASK_TFR);
iowrite32(MASK_INTR_REG(midc->ch_id), midc->dma_base + MASK_ERR);
......@@ -1114,7 +1112,6 @@ static int mid_setup_dma(struct pci_dev *pdev)
midch->chan.device = &dma->common;
midch->chan.cookie = 1;
midch->chan.chan_id = i;
midch->ch_id = dma->chan_base + i;
pr_debug("MDMA:Init CH %d, ID %d\n", i, midch->ch_id);
......@@ -1150,7 +1147,6 @@ static int mid_setup_dma(struct pci_dev *pdev)
dma_cap_set(DMA_SLAVE, dma->common.cap_mask);
dma_cap_set(DMA_PRIVATE, dma->common.cap_mask);
dma->common.dev = &pdev->dev;
dma->common.chancnt = dma->max_chan;
dma->common.device_alloc_chan_resources =
intel_mid_dma_alloc_chan_resources;
......@@ -1350,6 +1346,7 @@ int dma_suspend(struct pci_dev *pci, pm_message_t state)
if (device->ch[i].in_use)
return -EAGAIN;
}
dmac1_mask_periphral_intr(device);
device->state = SUSPENDED;
pci_save_state(pci);
pci_disable_device(pci);
......
......@@ -741,7 +741,6 @@ static int __devinit mpc_dma_probe(struct platform_device *op)
mchan = &mdma->channels[i];
mchan->chan.device = dma;
mchan->chan.chan_id = i;
mchan->chan.cookie = 1;
mchan->completed_cookie = mchan->chan.cookie;
......
......@@ -130,6 +130,23 @@ struct mxs_dma_engine {
struct mxs_dma_chan mxs_chans[MXS_DMA_CHANNELS];
};
static inline void mxs_dma_clkgate(struct mxs_dma_chan *mxs_chan, int enable)
{
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
int chan_id = mxs_chan->chan.chan_id;
int set_clr = enable ? MXS_CLR_ADDR : MXS_SET_ADDR;
/* enable apbh channel clock */
if (dma_is_apbh()) {
if (apbh_is_old())
writel(1 << (chan_id + BP_APBH_CTRL0_CLKGATE_CHANNEL),
mxs_dma->base + HW_APBHX_CTRL0 + set_clr);
else
writel(1 << chan_id,
mxs_dma->base + HW_APBHX_CTRL0 + set_clr);
}
}
static void mxs_dma_reset_chan(struct mxs_dma_chan *mxs_chan)
{
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
......@@ -148,38 +165,21 @@ static void mxs_dma_enable_chan(struct mxs_dma_chan *mxs_chan)
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
int chan_id = mxs_chan->chan.chan_id;
/* clkgate needs to be enabled before writing other registers */
mxs_dma_clkgate(mxs_chan, 1);
/* set cmd_addr up */
writel(mxs_chan->ccw_phys,
mxs_dma->base + HW_APBHX_CHn_NXTCMDAR(chan_id));
/* enable apbh channel clock */
if (dma_is_apbh()) {
if (apbh_is_old())
writel(1 << (chan_id + BP_APBH_CTRL0_CLKGATE_CHANNEL),
mxs_dma->base + HW_APBHX_CTRL0 + MXS_CLR_ADDR);
else
writel(1 << chan_id,
mxs_dma->base + HW_APBHX_CTRL0 + MXS_CLR_ADDR);
}
/* write 1 to SEMA to kick off the channel */
writel(1, mxs_dma->base + HW_APBHX_CHn_SEMA(chan_id));
}
static void mxs_dma_disable_chan(struct mxs_dma_chan *mxs_chan)
{
struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
int chan_id = mxs_chan->chan.chan_id;
/* disable apbh channel clock */
if (dma_is_apbh()) {
if (apbh_is_old())
writel(1 << (chan_id + BP_APBH_CTRL0_CLKGATE_CHANNEL),
mxs_dma->base + HW_APBHX_CTRL0 + MXS_SET_ADDR);
else
writel(1 << chan_id,
mxs_dma->base + HW_APBHX_CTRL0 + MXS_SET_ADDR);
}
mxs_dma_clkgate(mxs_chan, 0);
mxs_chan->status = DMA_SUCCESS;
}
......@@ -338,7 +338,10 @@ static int mxs_dma_alloc_chan_resources(struct dma_chan *chan)
if (ret)
goto err_clk;
/* clkgate needs to be enabled for reset to finish */
mxs_dma_clkgate(mxs_chan, 1);
mxs_dma_reset_chan(mxs_chan);
mxs_dma_clkgate(mxs_chan, 0);
dma_async_tx_descriptor_init(&mxs_chan->desc, chan);
mxs_chan->desc.tx_submit = mxs_dma_tx_submit;
......
......@@ -60,7 +60,7 @@
#define DMA_DESC_FOLLOW_WITHOUT_IRQ 0x2
#define DMA_DESC_FOLLOW_WITH_IRQ 0x3
#define MAX_CHAN_NR 8
#define MAX_CHAN_NR 12
#define DMA_MASK_CTL0_MODE 0x33333333
#define DMA_MASK_CTL2_MODE 0x00003333
......@@ -872,8 +872,7 @@ static int __devinit pch_dma_probe(struct pci_dev *pdev,
int i;
nr_channels = id->driver_data;
pd = kzalloc(sizeof(struct pch_dma)+
sizeof(struct pch_dma_chan) * nr_channels, GFP_KERNEL);
pd = kzalloc(sizeof(*pd), GFP_KERNEL);
if (!pd)
return -ENOMEM;
......@@ -926,7 +925,6 @@ static int __devinit pch_dma_probe(struct pci_dev *pdev,
}
pd->dma.dev = &pdev->dev;
pd->dma.chancnt = nr_channels;
INIT_LIST_HEAD(&pd->dma.channels);
......@@ -935,7 +933,6 @@ static int __devinit pch_dma_probe(struct pci_dev *pdev,
pd_chan->chan.device = &pd->dma;
pd_chan->chan.cookie = 1;
pd_chan->chan.chan_id = i;
pd_chan->membase = &regs->desc[i];
......
......@@ -17,6 +17,8 @@
#include <linux/interrupt.h>
#include <linux/amba/bus.h>
#include <linux/amba/pl330.h>
#include <linux/pm_runtime.h>
#include <linux/scatterlist.h>
#define NR_DEFAULT_DESC 16
......@@ -68,6 +70,14 @@ struct dma_pl330_chan {
* NULL if the channel is available to be acquired.
*/
void *pl330_chid;
/* For D-to-M and M-to-D channels */
int burst_sz; /* the peripheral fifo width */
int burst_len; /* the number of burst */
dma_addr_t fifo_addr;
/* for cyclic capability */
bool cyclic;
};
struct dma_pl330_dmac {
......@@ -83,6 +93,8 @@ struct dma_pl330_dmac {
/* Peripheral channels connected to this DMAC */
struct dma_pl330_chan *peripherals; /* keep at end */
struct clk *clk;
};
struct dma_pl330_desc {
......@@ -152,6 +164,31 @@ static inline void free_desc_list(struct list_head *list)
spin_unlock_irqrestore(&pdmac->pool_lock, flags);
}
static inline void handle_cyclic_desc_list(struct list_head *list)
{
struct dma_pl330_desc *desc;
struct dma_pl330_chan *pch;
unsigned long flags;
if (list_empty(list))
return;
list_for_each_entry(desc, list, node) {
dma_async_tx_callback callback;
/* Change status to reload it */
desc->status = PREP;
pch = desc->pchan;
callback = desc->txd.callback;
if (callback)
callback(desc->txd.callback_param);
}
spin_lock_irqsave(&pch->lock, flags);
list_splice_tail_init(list, &pch->work_list);
spin_unlock_irqrestore(&pch->lock, flags);
}
static inline void fill_queue(struct dma_pl330_chan *pch)
{
struct dma_pl330_desc *desc;
......@@ -205,6 +242,9 @@ static void pl330_tasklet(unsigned long data)
spin_unlock_irqrestore(&pch->lock, flags);
if (pch->cyclic)
handle_cyclic_desc_list(&list);
else
free_desc_list(&list);
}
......@@ -236,6 +276,7 @@ static int pl330_alloc_chan_resources(struct dma_chan *chan)
spin_lock_irqsave(&pch->lock, flags);
pch->completed = chan->cookie = 1;
pch->cyclic = false;
pch->pl330_chid = pl330_request_channel(&pdmac->pif);
if (!pch->pl330_chid) {
......@@ -253,25 +294,52 @@ static int pl330_alloc_chan_resources(struct dma_chan *chan)
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;
struct dma_pl330_desc *desc, *_dt;
unsigned long flags;
struct dma_pl330_dmac *pdmac = pch->dmac;
struct dma_slave_config *slave_config;
LIST_HEAD(list);
/* Only supports DMA_TERMINATE_ALL */
if (cmd != DMA_TERMINATE_ALL)
return -ENXIO;
switch (cmd) {
case DMA_TERMINATE_ALL:
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)
list_for_each_entry_safe(desc, _dt, &pch->work_list , node) {
desc->status = DONE;
pch->completed = desc->txd.cookie;
list_move_tail(&desc->node, &list);
}
list_splice_tail_init(&list, &pdmac->desc_pool);
spin_unlock_irqrestore(&pch->lock, flags);
pl330_tasklet((unsigned long) pch);
break;
case DMA_SLAVE_CONFIG:
slave_config = (struct dma_slave_config *)arg;
if (slave_config->direction == DMA_TO_DEVICE) {
if (slave_config->dst_addr)
pch->fifo_addr = slave_config->dst_addr;
if (slave_config->dst_addr_width)
pch->burst_sz = __ffs(slave_config->dst_addr_width);
if (slave_config->dst_maxburst)
pch->burst_len = slave_config->dst_maxburst;
} else if (slave_config->direction == DMA_FROM_DEVICE) {
if (slave_config->src_addr)
pch->fifo_addr = slave_config->src_addr;
if (slave_config->src_addr_width)
pch->burst_sz = __ffs(slave_config->src_addr_width);
if (slave_config->src_maxburst)
pch->burst_len = slave_config->src_maxburst;
}
break;
default:
dev_err(pch->dmac->pif.dev, "Not supported command.\n");
return -ENXIO;
}
return 0;
}
......@@ -288,6 +356,9 @@ static void pl330_free_chan_resources(struct dma_chan *chan)
pl330_release_channel(pch->pl330_chid);
pch->pl330_chid = NULL;
if (pch->cyclic)
list_splice_tail_init(&pch->work_list, &pch->dmac->desc_pool);
spin_unlock_irqrestore(&pch->lock, flags);
}
......@@ -453,7 +524,7 @@ static struct dma_pl330_desc *pl330_get_desc(struct dma_pl330_chan *pch)
if (peri) {
desc->req.rqtype = peri->rqtype;
desc->req.peri = peri->peri_id;
desc->req.peri = pch->chan.chan_id;
} else {
desc->req.rqtype = MEMTOMEM;
desc->req.peri = 0;
......@@ -524,6 +595,51 @@ static inline int get_burst_len(struct dma_pl330_desc *desc, size_t len)
return burst_len;
}
static struct dma_async_tx_descriptor *pl330_prep_dma_cyclic(
struct dma_chan *chan, dma_addr_t dma_addr, size_t len,
size_t period_len, enum dma_data_direction direction)
{
struct dma_pl330_desc *desc;
struct dma_pl330_chan *pch = to_pchan(chan);
dma_addr_t dst;
dma_addr_t src;
desc = pl330_get_desc(pch);
if (!desc) {
dev_err(pch->dmac->pif.dev, "%s:%d Unable to fetch desc\n",
__func__, __LINE__);
return NULL;
}
switch (direction) {
case DMA_TO_DEVICE:
desc->rqcfg.src_inc = 1;
desc->rqcfg.dst_inc = 0;
src = dma_addr;
dst = pch->fifo_addr;
break;
case DMA_FROM_DEVICE:
desc->rqcfg.src_inc = 0;
desc->rqcfg.dst_inc = 1;
src = pch->fifo_addr;
dst = dma_addr;
break;
default:
dev_err(pch->dmac->pif.dev, "%s:%d Invalid dma direction\n",
__func__, __LINE__);
return NULL;
}
desc->rqcfg.brst_size = pch->burst_sz;
desc->rqcfg.brst_len = 1;
pch->cyclic = true;
fill_px(&desc->px, dst, src, period_len);
return &desc->txd;
}
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)
......@@ -579,7 +695,7 @@ pl330_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
struct dma_pl330_peri *peri = chan->private;
struct scatterlist *sg;
unsigned long flags;
int i, burst_size;
int i;
dma_addr_t addr;
if (unlikely(!pch || !sgl || !sg_len || !peri))
......@@ -595,8 +711,7 @@ pl330_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
return NULL;
}
addr = peri->fifo_addr;
burst_size = peri->burst_sz;
addr = pch->fifo_addr;
first = NULL;
......@@ -644,7 +759,7 @@ pl330_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
sg_dma_address(sg), addr, sg_dma_len(sg));
}
desc->rqcfg.brst_size = burst_size;
desc->rqcfg.brst_size = pch->burst_sz;
desc->rqcfg.brst_len = 1;
}
......@@ -696,6 +811,30 @@ pl330_probe(struct amba_device *adev, const struct amba_id *id)
goto probe_err1;
}
pdmac->clk = clk_get(&adev->dev, "dma");
if (IS_ERR(pdmac->clk)) {
dev_err(&adev->dev, "Cannot get operation clock.\n");
ret = -EINVAL;
goto probe_err1;
}
amba_set_drvdata(adev, pdmac);
#ifdef CONFIG_PM_RUNTIME
/* to use the runtime PM helper functions */
pm_runtime_enable(&adev->dev);
/* enable the power domain */
if (pm_runtime_get_sync(&adev->dev)) {
dev_err(&adev->dev, "failed to get runtime pm\n");
ret = -ENODEV;
goto probe_err1;
}
#else
/* enable dma clk */
clk_enable(pdmac->clk);
#endif
irq = adev->irq[0];
ret = request_irq(irq, pl330_irq_handler, 0,
dev_name(&adev->dev), pi);
......@@ -732,6 +871,7 @@ pl330_probe(struct amba_device *adev, const struct amba_id *id)
case MEMTODEV:
case DEVTOMEM:
dma_cap_set(DMA_SLAVE, pd->cap_mask);
dma_cap_set(DMA_CYCLIC, pd->cap_mask);
break;
default:
dev_err(&adev->dev, "DEVTODEV Not Supported\n");
......@@ -747,11 +887,9 @@ pl330_probe(struct amba_device *adev, const struct amba_id *id)
spin_lock_init(&pch->lock);
pch->pl330_chid = NULL;
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);
}
......@@ -760,6 +898,7 @@ pl330_probe(struct amba_device *adev, const struct amba_id *id)
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_prep_dma_cyclic = pl330_prep_dma_cyclic;
pd->device_tx_status = pl330_tx_status;
pd->device_prep_slave_sg = pl330_prep_slave_sg;
pd->device_control = pl330_control;
......@@ -771,8 +910,6 @@ pl330_probe(struct amba_device *adev, const struct amba_id *id)
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,
......@@ -833,6 +970,13 @@ static int __devexit pl330_remove(struct amba_device *adev)
res = &adev->res;
release_mem_region(res->start, resource_size(res));
#ifdef CONFIG_PM_RUNTIME
pm_runtime_put(&adev->dev);
pm_runtime_disable(&adev->dev);
#else
clk_disable(pdmac->clk);
#endif
kfree(pdmac);
return 0;
......@@ -846,10 +990,49 @@ static struct amba_id pl330_ids[] = {
{ 0, 0 },
};
#ifdef CONFIG_PM_RUNTIME
static int pl330_runtime_suspend(struct device *dev)
{
struct dma_pl330_dmac *pdmac = dev_get_drvdata(dev);
if (!pdmac) {
dev_err(dev, "failed to get dmac\n");
return -ENODEV;
}
clk_disable(pdmac->clk);
return 0;
}
static int pl330_runtime_resume(struct device *dev)
{
struct dma_pl330_dmac *pdmac = dev_get_drvdata(dev);
if (!pdmac) {
dev_err(dev, "failed to get dmac\n");
return -ENODEV;
}
clk_enable(pdmac->clk);
return 0;
}
#else
#define pl330_runtime_suspend NULL
#define pl330_runtime_resume NULL
#endif /* CONFIG_PM_RUNTIME */
static const struct dev_pm_ops pl330_pm_ops = {
.runtime_suspend = pl330_runtime_suspend,
.runtime_resume = pl330_runtime_resume,
};
static struct amba_driver pl330_driver = {
.drv = {
.owner = THIS_MODULE,
.name = "dma-pl330",
.pm = &pl330_pm_ops,
},
.id_table = pl330_ids,
.probe = pl330_probe,
......
......@@ -259,14 +259,23 @@ static int dmae_set_dmars(struct sh_dmae_chan *sh_chan, u16 val)
return 0;
}
static void sh_chan_xfer_ld_queue(struct sh_dmae_chan *sh_chan);
static dma_cookie_t sh_dmae_tx_submit(struct dma_async_tx_descriptor *tx)
{
struct sh_desc *desc = tx_to_sh_desc(tx), *chunk, *last = desc, *c;
struct sh_dmae_chan *sh_chan = to_sh_chan(tx->chan);
struct sh_dmae_slave *param = tx->chan->private;
dma_async_tx_callback callback = tx->callback;
dma_cookie_t cookie;
bool power_up;
spin_lock_irq(&sh_chan->desc_lock);
spin_lock_bh(&sh_chan->desc_lock);
if (list_empty(&sh_chan->ld_queue))
power_up = true;
else
power_up = false;
cookie = sh_chan->common.cookie;
cookie++;
......@@ -302,7 +311,38 @@ static dma_cookie_t sh_dmae_tx_submit(struct dma_async_tx_descriptor *tx)
tx->cookie, &last->async_tx, sh_chan->id,
desc->hw.sar, desc->hw.tcr, desc->hw.dar);
spin_unlock_bh(&sh_chan->desc_lock);
if (power_up) {
sh_chan->pm_state = DMAE_PM_BUSY;
pm_runtime_get(sh_chan->dev);
spin_unlock_irq(&sh_chan->desc_lock);
pm_runtime_barrier(sh_chan->dev);
spin_lock_irq(&sh_chan->desc_lock);
/* Have we been reset, while waiting? */
if (sh_chan->pm_state != DMAE_PM_ESTABLISHED) {
dev_dbg(sh_chan->dev, "Bring up channel %d\n",
sh_chan->id);
if (param) {
const struct sh_dmae_slave_config *cfg =
param->config;
dmae_set_dmars(sh_chan, cfg->mid_rid);
dmae_set_chcr(sh_chan, cfg->chcr);
} else {
dmae_init(sh_chan);
}
if (sh_chan->pm_state == DMAE_PM_PENDING)
sh_chan_xfer_ld_queue(sh_chan);
sh_chan->pm_state = DMAE_PM_ESTABLISHED;
}
}
spin_unlock_irq(&sh_chan->desc_lock);
return cookie;
}
......@@ -346,8 +386,6 @@ static int sh_dmae_alloc_chan_resources(struct dma_chan *chan)
struct sh_dmae_slave *param = chan->private;
int ret;
pm_runtime_get_sync(sh_chan->dev);
/*
* This relies on the guarantee from dmaengine that alloc_chan_resources
* never runs concurrently with itself or free_chan_resources.
......@@ -367,31 +405,20 @@ static int sh_dmae_alloc_chan_resources(struct dma_chan *chan)
}
param->config = cfg;
dmae_set_dmars(sh_chan, cfg->mid_rid);
dmae_set_chcr(sh_chan, cfg->chcr);
} else {
dmae_init(sh_chan);
}
spin_lock_bh(&sh_chan->desc_lock);
while (sh_chan->descs_allocated < NR_DESCS_PER_CHANNEL) {
spin_unlock_bh(&sh_chan->desc_lock);
desc = kzalloc(sizeof(struct sh_desc), GFP_KERNEL);
if (!desc) {
spin_lock_bh(&sh_chan->desc_lock);
if (!desc)
break;
}
dma_async_tx_descriptor_init(&desc->async_tx,
&sh_chan->common);
desc->async_tx.tx_submit = sh_dmae_tx_submit;
desc->mark = DESC_IDLE;
spin_lock_bh(&sh_chan->desc_lock);
list_add(&desc->node, &sh_chan->ld_free);
sh_chan->descs_allocated++;
}
spin_unlock_bh(&sh_chan->desc_lock);
if (!sh_chan->descs_allocated) {
ret = -ENOMEM;
......@@ -405,7 +432,7 @@ static int sh_dmae_alloc_chan_resources(struct dma_chan *chan)
clear_bit(param->slave_id, sh_dmae_slave_used);
etestused:
efindslave:
pm_runtime_put(sh_chan->dev);
chan->private = NULL;
return ret;
}
......@@ -417,7 +444,6 @@ static void sh_dmae_free_chan_resources(struct dma_chan *chan)
struct sh_dmae_chan *sh_chan = to_sh_chan(chan);
struct sh_desc *desc, *_desc;
LIST_HEAD(list);
int descs = sh_chan->descs_allocated;
/* Protect against ISR */
spin_lock_irq(&sh_chan->desc_lock);
......@@ -437,15 +463,12 @@ static void sh_dmae_free_chan_resources(struct dma_chan *chan)
chan->private = NULL;
}
spin_lock_bh(&sh_chan->desc_lock);
spin_lock_irq(&sh_chan->desc_lock);
list_splice_init(&sh_chan->ld_free, &list);
sh_chan->descs_allocated = 0;
spin_unlock_bh(&sh_chan->desc_lock);
if (descs > 0)
pm_runtime_put(sh_chan->dev);
spin_unlock_irq(&sh_chan->desc_lock);
list_for_each_entry_safe(desc, _desc, &list, node)
kfree(desc);
......@@ -534,6 +557,7 @@ static struct dma_async_tx_descriptor *sh_dmae_prep_sg(struct sh_dmae_chan *sh_c
struct sh_desc *first = NULL, *new = NULL /* compiler... */;
LIST_HEAD(tx_list);
int chunks = 0;
unsigned long irq_flags;
int i;
if (!sg_len)
......@@ -544,7 +568,7 @@ static struct dma_async_tx_descriptor *sh_dmae_prep_sg(struct sh_dmae_chan *sh_c
(SH_DMA_TCR_MAX + 1);
/* Have to lock the whole loop to protect against concurrent release */
spin_lock_bh(&sh_chan->desc_lock);
spin_lock_irqsave(&sh_chan->desc_lock, irq_flags);
/*
* Chaining:
......@@ -590,7 +614,7 @@ static struct dma_async_tx_descriptor *sh_dmae_prep_sg(struct sh_dmae_chan *sh_c
/* Put them back on the free list, so, they don't get lost */
list_splice_tail(&tx_list, &sh_chan->ld_free);
spin_unlock_bh(&sh_chan->desc_lock);
spin_unlock_irqrestore(&sh_chan->desc_lock, irq_flags);
return &first->async_tx;
......@@ -599,7 +623,7 @@ static struct dma_async_tx_descriptor *sh_dmae_prep_sg(struct sh_dmae_chan *sh_c
new->mark = DESC_IDLE;
list_splice(&tx_list, &sh_chan->ld_free);
spin_unlock_bh(&sh_chan->desc_lock);
spin_unlock_irqrestore(&sh_chan->desc_lock, irq_flags);
return NULL;
}
......@@ -661,6 +685,7 @@ static int sh_dmae_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
unsigned long arg)
{
struct sh_dmae_chan *sh_chan = to_sh_chan(chan);
unsigned long flags;
/* Only supports DMA_TERMINATE_ALL */
if (cmd != DMA_TERMINATE_ALL)
......@@ -669,7 +694,7 @@ static int sh_dmae_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
if (!chan)
return -EINVAL;
spin_lock_bh(&sh_chan->desc_lock);
spin_lock_irqsave(&sh_chan->desc_lock, flags);
dmae_halt(sh_chan);
if (!list_empty(&sh_chan->ld_queue)) {
......@@ -678,9 +703,8 @@ static int sh_dmae_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
struct sh_desc, node);
desc->partial = (desc->hw.tcr - sh_dmae_readl(sh_chan, TCR)) <<
sh_chan->xmit_shift;
}
spin_unlock_bh(&sh_chan->desc_lock);
spin_unlock_irqrestore(&sh_chan->desc_lock, flags);
sh_dmae_chan_ld_cleanup(sh_chan, true);
......@@ -695,8 +719,9 @@ static dma_async_tx_callback __ld_cleanup(struct sh_dmae_chan *sh_chan, bool all
dma_cookie_t cookie = 0;
dma_async_tx_callback callback = NULL;
void *param = NULL;
unsigned long flags;
spin_lock_bh(&sh_chan->desc_lock);
spin_lock_irqsave(&sh_chan->desc_lock, flags);
list_for_each_entry_safe(desc, _desc, &sh_chan->ld_queue, node) {
struct dma_async_tx_descriptor *tx = &desc->async_tx;
......@@ -762,7 +787,13 @@ static dma_async_tx_callback __ld_cleanup(struct sh_dmae_chan *sh_chan, bool all
async_tx_test_ack(&desc->async_tx)) || all) {
/* Remove from ld_queue list */
desc->mark = DESC_IDLE;
list_move(&desc->node, &sh_chan->ld_free);
if (list_empty(&sh_chan->ld_queue)) {
dev_dbg(sh_chan->dev, "Bring down channel %d\n", sh_chan->id);
pm_runtime_put(sh_chan->dev);
}
}
}
......@@ -773,7 +804,7 @@ static dma_async_tx_callback __ld_cleanup(struct sh_dmae_chan *sh_chan, bool all
*/
sh_chan->completed_cookie = sh_chan->common.cookie;
spin_unlock_bh(&sh_chan->desc_lock);
spin_unlock_irqrestore(&sh_chan->desc_lock, flags);
if (callback)
callback(param);
......@@ -792,14 +823,14 @@ static void sh_dmae_chan_ld_cleanup(struct sh_dmae_chan *sh_chan, bool all)
;
}
/* Called under spin_lock_irq(&sh_chan->desc_lock) */
static void sh_chan_xfer_ld_queue(struct sh_dmae_chan *sh_chan)
{
struct sh_desc *desc;
spin_lock_bh(&sh_chan->desc_lock);
/* DMA work check */
if (dmae_is_busy(sh_chan))
goto sh_chan_xfer_ld_queue_end;
return;
/* Find the first not transferred descriptor */
list_for_each_entry(desc, &sh_chan->ld_queue, node)
......@@ -812,15 +843,18 @@ static void sh_chan_xfer_ld_queue(struct sh_dmae_chan *sh_chan)
dmae_start(sh_chan);
break;
}
sh_chan_xfer_ld_queue_end:
spin_unlock_bh(&sh_chan->desc_lock);
}
static void sh_dmae_memcpy_issue_pending(struct dma_chan *chan)
{
struct sh_dmae_chan *sh_chan = to_sh_chan(chan);
spin_lock_irq(&sh_chan->desc_lock);
if (sh_chan->pm_state == DMAE_PM_ESTABLISHED)
sh_chan_xfer_ld_queue(sh_chan);
else
sh_chan->pm_state = DMAE_PM_PENDING;
spin_unlock_irq(&sh_chan->desc_lock);
}
static enum dma_status sh_dmae_tx_status(struct dma_chan *chan,
......@@ -831,6 +865,7 @@ static enum dma_status sh_dmae_tx_status(struct dma_chan *chan,
dma_cookie_t last_used;
dma_cookie_t last_complete;
enum dma_status status;
unsigned long flags;
sh_dmae_chan_ld_cleanup(sh_chan, false);
......@@ -841,7 +876,7 @@ static enum dma_status sh_dmae_tx_status(struct dma_chan *chan,
BUG_ON(last_complete < 0);
dma_set_tx_state(txstate, last_complete, last_used, 0);
spin_lock_bh(&sh_chan->desc_lock);
spin_lock_irqsave(&sh_chan->desc_lock, flags);
status = dma_async_is_complete(cookie, last_complete, last_used);
......@@ -859,7 +894,7 @@ static enum dma_status sh_dmae_tx_status(struct dma_chan *chan,
}
}
spin_unlock_bh(&sh_chan->desc_lock);
spin_unlock_irqrestore(&sh_chan->desc_lock, flags);
return status;
}
......@@ -912,6 +947,12 @@ static bool sh_dmae_reset(struct sh_dmae_device *shdev)
list_splice_init(&sh_chan->ld_queue, &dl);
if (!list_empty(&dl)) {
dev_dbg(sh_chan->dev, "Bring down channel %d\n", sh_chan->id);
pm_runtime_put(sh_chan->dev);
}
sh_chan->pm_state = DMAE_PM_ESTABLISHED;
spin_unlock(&sh_chan->desc_lock);
/* Complete all */
......@@ -952,7 +993,7 @@ static void dmae_do_tasklet(unsigned long data)
u32 sar_buf = sh_dmae_readl(sh_chan, SAR);
u32 dar_buf = sh_dmae_readl(sh_chan, DAR);
spin_lock(&sh_chan->desc_lock);
spin_lock_irq(&sh_chan->desc_lock);
list_for_each_entry(desc, &sh_chan->ld_queue, node) {
if (desc->mark == DESC_SUBMITTED &&
((desc->direction == DMA_FROM_DEVICE &&
......@@ -965,10 +1006,10 @@ static void dmae_do_tasklet(unsigned long data)
break;
}
}
spin_unlock(&sh_chan->desc_lock);
/* Next desc */
sh_chan_xfer_ld_queue(sh_chan);
spin_unlock_irq(&sh_chan->desc_lock);
sh_dmae_chan_ld_cleanup(sh_chan, false);
}
......@@ -1036,7 +1077,9 @@ static int __devinit sh_dmae_chan_probe(struct sh_dmae_device *shdev, int id,
return -ENOMEM;
}
/* copy struct dma_device */
new_sh_chan->pm_state = DMAE_PM_ESTABLISHED;
/* reference struct dma_device */
new_sh_chan->common.device = &shdev->common;
new_sh_chan->dev = shdev->common.dev;
......
......@@ -23,6 +23,12 @@
struct device;
enum dmae_pm_state {
DMAE_PM_ESTABLISHED,
DMAE_PM_BUSY,
DMAE_PM_PENDING,
};
struct sh_dmae_chan {
dma_cookie_t completed_cookie; /* The maximum cookie completed */
spinlock_t desc_lock; /* Descriptor operation lock */
......@@ -38,6 +44,7 @@ struct sh_dmae_chan {
u32 __iomem *base;
char dev_id[16]; /* unique name per DMAC of channel */
int pm_error;
enum dmae_pm_state pm_state;
};
struct sh_dmae_device {
......
......@@ -753,7 +753,7 @@ static int __devinit td_probe(struct platform_device *pdev)
INIT_LIST_HEAD(&td->dma.channels);
for (i = 0; i < pdata->nr_channels; i++, td->dma.chancnt++) {
for (i = 0; i < pdata->nr_channels; i++) {
struct timb_dma_chan *td_chan = &td->channels[i];
struct timb_dma_platform_data_channel *pchan =
pdata->channels + i;
......@@ -762,12 +762,11 @@ static int __devinit td_probe(struct platform_device *pdev)
if ((i % 2) == pchan->rx) {
dev_err(&pdev->dev, "Wrong channel configuration\n");
err = -EINVAL;
goto err_tasklet_kill;
goto err_free_irq;
}
td_chan->chan.device = &td->dma;
td_chan->chan.cookie = 1;
td_chan->chan.chan_id = i;
spin_lock_init(&td_chan->lock);
INIT_LIST_HEAD(&td_chan->active_list);
INIT_LIST_HEAD(&td_chan->queue);
......
......@@ -913,9 +913,9 @@ static void finalize_request(struct s3cmci_host *host)
}
static void s3cmci_dma_setup(struct s3cmci_host *host,
enum s3c2410_dmasrc source)
enum dma_data_direction source)
{
static enum s3c2410_dmasrc last_source = -1;
static enum dma_data_direction last_source = -1;
static int setup_ok;
if (last_source == source)
......@@ -1087,7 +1087,7 @@ static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR);
s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW);
s3cmci_dma_setup(host, rw ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
......
......@@ -131,6 +131,12 @@
#define RXBUSY (1<<2)
#define TXBUSY (1<<3)
struct s3c64xx_spi_dma_data {
unsigned ch;
enum dma_data_direction direction;
enum dma_ch dmach;
};
/**
* struct s3c64xx_spi_driver_data - Runtime info holder for SPI driver.
* @clk: Pointer to the spi clock.
......@@ -164,13 +170,14 @@ struct s3c64xx_spi_driver_data {
struct work_struct work;
struct list_head queue;
spinlock_t lock;
enum dma_ch rx_dmach;
enum dma_ch tx_dmach;
unsigned long sfr_start;
struct completion xfer_completion;
unsigned state;
unsigned cur_mode, cur_bpw;
unsigned cur_speed;
struct s3c64xx_spi_dma_data rx_dma;
struct s3c64xx_spi_dma_data tx_dma;
struct samsung_dma_ops *ops;
};
static struct s3c2410_dma_client s3c64xx_spi_dma_client = {
......@@ -226,6 +233,78 @@ static void flush_fifo(struct s3c64xx_spi_driver_data *sdd)
writel(val, regs + S3C64XX_SPI_CH_CFG);
}
static void s3c64xx_spi_dmacb(void *data)
{
struct s3c64xx_spi_driver_data *sdd;
struct s3c64xx_spi_dma_data *dma = data;
unsigned long flags;
if (dma->direction == DMA_FROM_DEVICE)
sdd = container_of(data,
struct s3c64xx_spi_driver_data, rx_dma);
else
sdd = container_of(data,
struct s3c64xx_spi_driver_data, tx_dma);
spin_lock_irqsave(&sdd->lock, flags);
if (dma->direction == DMA_FROM_DEVICE) {
sdd->state &= ~RXBUSY;
if (!(sdd->state & TXBUSY))
complete(&sdd->xfer_completion);
} else {
sdd->state &= ~TXBUSY;
if (!(sdd->state & RXBUSY))
complete(&sdd->xfer_completion);
}
spin_unlock_irqrestore(&sdd->lock, flags);
}
static void prepare_dma(struct s3c64xx_spi_dma_data *dma,
unsigned len, dma_addr_t buf)
{
struct s3c64xx_spi_driver_data *sdd;
struct samsung_dma_prep_info info;
if (dma->direction == DMA_FROM_DEVICE)
sdd = container_of((void *)dma,
struct s3c64xx_spi_driver_data, rx_dma);
else
sdd = container_of((void *)dma,
struct s3c64xx_spi_driver_data, tx_dma);
info.cap = DMA_SLAVE;
info.len = len;
info.fp = s3c64xx_spi_dmacb;
info.fp_param = dma;
info.direction = dma->direction;
info.buf = buf;
sdd->ops->prepare(dma->ch, &info);
sdd->ops->trigger(dma->ch);
}
static int acquire_dma(struct s3c64xx_spi_driver_data *sdd)
{
struct samsung_dma_info info;
sdd->ops = samsung_dma_get_ops();
info.cap = DMA_SLAVE;
info.client = &s3c64xx_spi_dma_client;
info.width = sdd->cur_bpw / 8;
info.direction = sdd->rx_dma.direction;
info.fifo = sdd->sfr_start + S3C64XX_SPI_RX_DATA;
sdd->rx_dma.ch = sdd->ops->request(sdd->rx_dma.dmach, &info);
info.direction = sdd->tx_dma.direction;
info.fifo = sdd->sfr_start + S3C64XX_SPI_TX_DATA;
sdd->tx_dma.ch = sdd->ops->request(sdd->tx_dma.dmach, &info);
return 1;
}
static void enable_datapath(struct s3c64xx_spi_driver_data *sdd,
struct spi_device *spi,
struct spi_transfer *xfer, int dma_mode)
......@@ -258,10 +337,7 @@ static void enable_datapath(struct s3c64xx_spi_driver_data *sdd,
chcfg |= S3C64XX_SPI_CH_TXCH_ON;
if (dma_mode) {
modecfg |= S3C64XX_SPI_MODE_TXDMA_ON;
s3c2410_dma_config(sdd->tx_dmach, sdd->cur_bpw / 8);
s3c2410_dma_enqueue(sdd->tx_dmach, (void *)sdd,
xfer->tx_dma, xfer->len);
s3c2410_dma_ctrl(sdd->tx_dmach, S3C2410_DMAOP_START);
prepare_dma(&sdd->tx_dma, xfer->len, xfer->tx_dma);
} else {
switch (sdd->cur_bpw) {
case 32:
......@@ -293,10 +369,7 @@ static void enable_datapath(struct s3c64xx_spi_driver_data *sdd,
writel(((xfer->len * 8 / sdd->cur_bpw) & 0xffff)
| S3C64XX_SPI_PACKET_CNT_EN,
regs + S3C64XX_SPI_PACKET_CNT);
s3c2410_dma_config(sdd->rx_dmach, sdd->cur_bpw / 8);
s3c2410_dma_enqueue(sdd->rx_dmach, (void *)sdd,
xfer->rx_dma, xfer->len);
s3c2410_dma_ctrl(sdd->rx_dmach, S3C2410_DMAOP_START);
prepare_dma(&sdd->rx_dma, xfer->len, xfer->rx_dma);
}
}
......@@ -482,46 +555,6 @@ static void s3c64xx_spi_config(struct s3c64xx_spi_driver_data *sdd)
}
}
static void s3c64xx_spi_dma_rxcb(struct s3c2410_dma_chan *chan, void *buf_id,
int size, enum s3c2410_dma_buffresult res)
{
struct s3c64xx_spi_driver_data *sdd = buf_id;
unsigned long flags;
spin_lock_irqsave(&sdd->lock, flags);
if (res == S3C2410_RES_OK)
sdd->state &= ~RXBUSY;
else
dev_err(&sdd->pdev->dev, "DmaAbrtRx-%d\n", size);
/* If the other done */
if (!(sdd->state & TXBUSY))
complete(&sdd->xfer_completion);
spin_unlock_irqrestore(&sdd->lock, flags);
}
static void s3c64xx_spi_dma_txcb(struct s3c2410_dma_chan *chan, void *buf_id,
int size, enum s3c2410_dma_buffresult res)
{
struct s3c64xx_spi_driver_data *sdd = buf_id;
unsigned long flags;
spin_lock_irqsave(&sdd->lock, flags);
if (res == S3C2410_RES_OK)
sdd->state &= ~TXBUSY;
else
dev_err(&sdd->pdev->dev, "DmaAbrtTx-%d \n", size);
/* If the other done */
if (!(sdd->state & RXBUSY))
complete(&sdd->xfer_completion);
spin_unlock_irqrestore(&sdd->lock, flags);
}
#define XFER_DMAADDR_INVALID DMA_BIT_MASK(32)
static int s3c64xx_spi_map_mssg(struct s3c64xx_spi_driver_data *sdd,
......@@ -696,12 +729,10 @@ static void handle_msg(struct s3c64xx_spi_driver_data *sdd,
if (use_dma) {
if (xfer->tx_buf != NULL
&& (sdd->state & TXBUSY))
s3c2410_dma_ctrl(sdd->tx_dmach,
S3C2410_DMAOP_FLUSH);
sdd->ops->stop(sdd->tx_dma.ch);
if (xfer->rx_buf != NULL
&& (sdd->state & RXBUSY))
s3c2410_dma_ctrl(sdd->rx_dmach,
S3C2410_DMAOP_FLUSH);
sdd->ops->stop(sdd->rx_dma.ch);
}
goto out;
......@@ -739,30 +770,6 @@ static void handle_msg(struct s3c64xx_spi_driver_data *sdd,
msg->complete(msg->context);
}
static int acquire_dma(struct s3c64xx_spi_driver_data *sdd)
{
if (s3c2410_dma_request(sdd->rx_dmach,
&s3c64xx_spi_dma_client, NULL) < 0) {
dev_err(&sdd->pdev->dev, "cannot get RxDMA\n");
return 0;
}
s3c2410_dma_set_buffdone_fn(sdd->rx_dmach, s3c64xx_spi_dma_rxcb);
s3c2410_dma_devconfig(sdd->rx_dmach, S3C2410_DMASRC_HW,
sdd->sfr_start + S3C64XX_SPI_RX_DATA);
if (s3c2410_dma_request(sdd->tx_dmach,
&s3c64xx_spi_dma_client, NULL) < 0) {
dev_err(&sdd->pdev->dev, "cannot get TxDMA\n");
s3c2410_dma_free(sdd->rx_dmach, &s3c64xx_spi_dma_client);
return 0;
}
s3c2410_dma_set_buffdone_fn(sdd->tx_dmach, s3c64xx_spi_dma_txcb);
s3c2410_dma_devconfig(sdd->tx_dmach, S3C2410_DMASRC_MEM,
sdd->sfr_start + S3C64XX_SPI_TX_DATA);
return 1;
}
static void s3c64xx_spi_work(struct work_struct *work)
{
struct s3c64xx_spi_driver_data *sdd = container_of(work,
......@@ -799,8 +806,8 @@ static void s3c64xx_spi_work(struct work_struct *work)
spin_unlock_irqrestore(&sdd->lock, flags);
/* Free DMA channels */
s3c2410_dma_free(sdd->tx_dmach, &s3c64xx_spi_dma_client);
s3c2410_dma_free(sdd->rx_dmach, &s3c64xx_spi_dma_client);
sdd->ops->release(sdd->rx_dma.ch, &s3c64xx_spi_dma_client);
sdd->ops->release(sdd->tx_dma.ch, &s3c64xx_spi_dma_client);
}
static int s3c64xx_spi_transfer(struct spi_device *spi,
......@@ -1017,8 +1024,10 @@ static int __init s3c64xx_spi_probe(struct platform_device *pdev)
sdd->cntrlr_info = sci;
sdd->pdev = pdev;
sdd->sfr_start = mem_res->start;
sdd->tx_dmach = dmatx_res->start;
sdd->rx_dmach = dmarx_res->start;
sdd->tx_dma.dmach = dmatx_res->start;
sdd->tx_dma.direction = DMA_TO_DEVICE;
sdd->rx_dma.dmach = dmarx_res->start;
sdd->rx_dma.direction = DMA_FROM_DEVICE;
sdd->cur_bpw = 8;
......@@ -1106,7 +1115,7 @@ static int __init s3c64xx_spi_probe(struct platform_device *pdev)
pdev->id, master->num_chipselect);
dev_dbg(&pdev->dev, "\tIOmem=[0x%x-0x%x]\tDMA=[Rx-%d, Tx-%d]\n",
mem_res->end, mem_res->start,
sdd->rx_dmach, sdd->tx_dmach);
sdd->rx_dma.dmach, sdd->tx_dma.dmach);
return 0;
......
......@@ -1446,12 +1446,8 @@ static bool filter(struct dma_chan *chan, void *slave)
dev_dbg(chan->device->dev, "%s: slave ID %d\n", __func__,
param->slave_id);
if (param->dma_dev == chan->device->dev) {
chan->private = param;
return true;
} else {
return false;
}
}
static void rx_timer_fn(unsigned long arg)
......@@ -1477,10 +1473,10 @@ static void sci_request_dma(struct uart_port *port)
dma_cap_mask_t mask;
int nent;
dev_dbg(port->dev, "%s: port %d DMA %p\n", __func__,
port->line, s->cfg->dma_dev);
dev_dbg(port->dev, "%s: port %d\n", __func__,
port->line);
if (!s->cfg->dma_dev)
if (s->cfg->dma_slave_tx <= 0 || s->cfg->dma_slave_rx <= 0)
return;
dma_cap_zero(mask);
......@@ -1490,7 +1486,6 @@ static void sci_request_dma(struct uart_port *port)
/* Slave ID, e.g., SHDMA_SLAVE_SCIF0_TX */
param->slave_id = s->cfg->dma_slave_tx;
param->dma_dev = s->cfg->dma_dev;
s->cookie_tx = -EINVAL;
chan = dma_request_channel(mask, filter, param);
......@@ -1519,7 +1514,6 @@ static void sci_request_dma(struct uart_port *port)
/* Slave ID, e.g., SHDMA_SLAVE_SCIF0_RX */
param->slave_id = s->cfg->dma_slave_rx;
param->dma_dev = s->cfg->dma_dev;
chan = dma_request_channel(mask, filter, param);
dev_dbg(port->dev, "%s: RX: got channel %p\n", __func__, chan);
......@@ -1564,9 +1558,6 @@ static void sci_free_dma(struct uart_port *port)
{
struct sci_port *s = to_sci_port(port);
if (!s->cfg->dma_dev)
return;
if (s->chan_tx)
sci_tx_dma_release(s, false);
if (s->chan_rx)
......@@ -1981,9 +1972,9 @@ static int __devinit sci_init_single(struct platform_device *dev,
port->serial_in = sci_serial_in;
port->serial_out = sci_serial_out;
if (p->dma_dev)
dev_dbg(port->dev, "DMA device %p, tx %d, rx %d\n",
p->dma_dev, p->dma_slave_tx, p->dma_slave_rx);
if (p->dma_slave_tx > 0 && p->dma_slave_rx > 0)
dev_dbg(port->dev, "DMA tx %d, rx %d\n",
p->dma_slave_tx, p->dma_slave_rx);
return 0;
}
......
......@@ -47,6 +47,9 @@ enum {
* @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
* @device_fc: Flow Controller Settings for ccfg register. Only valid for slave
* channels. Fill with 'true' if peripheral should be flow controller. Direction
* will be selected at Runtime.
* @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
......@@ -65,6 +68,7 @@ struct pl08x_channel_data {
int max_signal;
u32 muxval;
u32 cctl;
bool device_fc;
dma_addr_t addr;
bool circular_buffer;
bool single;
......@@ -77,13 +81,11 @@ struct pl08x_channel_data {
* @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;
size_t fill_bytes;
};
/**
......@@ -103,18 +105,36 @@ struct pl08x_phy_chan {
struct pl08x_dma_chan *serving;
};
/**
* struct pl08x_sg - structure containing data per sg
* @src_addr: src address of sg
* @dst_addr: dst address of sg
* @len: transfer len in bytes
* @node: node for txd's dsg_list
*/
struct pl08x_sg {
dma_addr_t src_addr;
dma_addr_t dst_addr;
size_t len;
struct list_head node;
};
/**
* struct pl08x_txd - wrapper for struct dma_async_tx_descriptor
* @tx: async tx descriptor
* @node: node for txd list for channels
* @dsg_list: list of children sg's
* @direction: direction of transfer
* @llis_bus: DMA memory address (physical) start for the LLIs
* @llis_va: virtual memory address start for the LLIs
* @cctl: control reg values for current txd
* @ccfg: config reg values for current txd
*/
struct pl08x_txd {
struct dma_async_tx_descriptor tx;
struct list_head node;
struct list_head dsg_list;
enum dma_data_direction direction;
dma_addr_t src_addr;
dma_addr_t dst_addr;
size_t len;
dma_addr_t llis_bus;
struct pl08x_lli *llis_va;
/* Default cctl value for LLIs */
......
......@@ -19,12 +19,8 @@ struct dma_pl330_peri {
* Peri_Req i/f of the DMAC that is
* peripheral could be reached from.
*/
u8 peri_id; /* {0, 31} */
u8 peri_id; /* specific dma id */
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 {
......
......@@ -24,8 +24,7 @@
#include <linux/device.h>
#include <linux/uio.h>
#include <linux/dma-direction.h>
struct scatterlist;
#include <linux/scatterlist.h>
/**
* typedef dma_cookie_t - an opaque DMA cookie
......@@ -519,6 +518,16 @@ static inline int dmaengine_slave_config(struct dma_chan *chan,
(unsigned long)config);
}
static inline struct dma_async_tx_descriptor *dmaengine_prep_slave_single(
struct dma_chan *chan, void *buf, size_t len,
enum dma_data_direction dir, unsigned long flags)
{
struct scatterlist sg;
sg_init_one(&sg, buf, len);
return chan->device->device_prep_slave_sg(chan, &sg, 1, dir, flags);
}
static inline int dmaengine_terminate_all(struct dma_chan *chan)
{
return dmaengine_device_control(chan, DMA_TERMINATE_ALL, 0);
......
......@@ -131,8 +131,6 @@ struct plat_sci_port {
struct plat_sci_port_ops *ops;
struct device *dma_dev;
unsigned int dma_slave_tx;
unsigned int dma_slave_rx;
};
......
......@@ -271,7 +271,10 @@ static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED);
if (!dma_data->ops)
dma_data->ops = samsung_dma_get_ops();
dma_data->ops->started(dma_data->channel);
return 0;
}
......@@ -317,7 +320,10 @@ static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream,
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED);
if (!dma_data->ops)
dma_data->ops = samsung_dma_get_ops();
dma_data->ops->started(dma_data->channel);
return 0;
}
......
......@@ -54,7 +54,6 @@ struct runtime_data {
spinlock_t lock;
int state;
unsigned int dma_loaded;
unsigned int dma_limit;
unsigned int dma_period;
dma_addr_t dma_start;
dma_addr_t dma_pos;
......@@ -62,77 +61,79 @@ struct runtime_data {
struct s3c_dma_params *params;
};
static void audio_buffdone(void *data);
/* dma_enqueue
*
* place a dma buffer onto the queue for the dma system
* to handle.
*/
*/
static void dma_enqueue(struct snd_pcm_substream *substream)
{
struct runtime_data *prtd = substream->runtime->private_data;
dma_addr_t pos = prtd->dma_pos;
unsigned int limit;
int ret;
struct samsung_dma_prep_info dma_info;
pr_debug("Entered %s\n", __func__);
if (s3c_dma_has_circular())
limit = (prtd->dma_end - prtd->dma_start) / prtd->dma_period;
else
limit = prtd->dma_limit;
pr_debug("%s: loaded %d, limit %d\n",
__func__, prtd->dma_loaded, limit);
while (prtd->dma_loaded < limit) {
unsigned long len = prtd->dma_period;
dma_info.cap = (samsung_dma_has_circular() ? DMA_CYCLIC : DMA_SLAVE);
dma_info.direction =
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK
? DMA_TO_DEVICE : DMA_FROM_DEVICE);
dma_info.fp = audio_buffdone;
dma_info.fp_param = substream;
dma_info.period = prtd->dma_period;
dma_info.len = prtd->dma_period*limit;
while (prtd->dma_loaded < limit) {
pr_debug("dma_loaded: %d\n", prtd->dma_loaded);
if ((pos + len) > prtd->dma_end) {
len = prtd->dma_end - pos;
pr_debug("%s: corrected dma len %ld\n", __func__, len);
if ((pos + dma_info.period) > prtd->dma_end) {
dma_info.period = prtd->dma_end - pos;
pr_debug("%s: corrected dma len %ld\n",
__func__, dma_info.period);
}
ret = s3c2410_dma_enqueue(prtd->params->channel,
substream, pos, len);
dma_info.buf = pos;
prtd->params->ops->prepare(prtd->params->ch, &dma_info);
if (ret == 0) {
prtd->dma_loaded++;
pos += prtd->dma_period;
if (pos >= prtd->dma_end)
pos = prtd->dma_start;
} else
break;
}
prtd->dma_pos = pos;
}
static void audio_buffdone(struct s3c2410_dma_chan *channel,
void *dev_id, int size,
enum s3c2410_dma_buffresult result)
static void audio_buffdone(void *data)
{
struct snd_pcm_substream *substream = dev_id;
struct runtime_data *prtd;
struct snd_pcm_substream *substream = data;
struct runtime_data *prtd = substream->runtime->private_data;
pr_debug("Entered %s\n", __func__);
if (result == S3C2410_RES_ABORT || result == S3C2410_RES_ERR)
return;
prtd = substream->runtime->private_data;
if (prtd->state & ST_RUNNING) {
prtd->dma_pos += prtd->dma_period;
if (prtd->dma_pos >= prtd->dma_end)
prtd->dma_pos = prtd->dma_start;
if (substream)
snd_pcm_period_elapsed(substream);
spin_lock(&prtd->lock);
if (prtd->state & ST_RUNNING && !s3c_dma_has_circular()) {
if (!samsung_dma_has_circular()) {
prtd->dma_loaded--;
dma_enqueue(substream);
}
spin_unlock(&prtd->lock);
}
}
static int dma_hw_params(struct snd_pcm_substream *substream,
......@@ -144,8 +145,7 @@ static int dma_hw_params(struct snd_pcm_substream *substream,
unsigned long totbytes = params_buffer_bytes(params);
struct s3c_dma_params *dma =
snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
int ret = 0;
struct samsung_dma_info dma_info;
pr_debug("Entered %s\n", __func__);
......@@ -163,30 +163,26 @@ static int dma_hw_params(struct snd_pcm_substream *substream,
pr_debug("params %p, client %p, channel %d\n", prtd->params,
prtd->params->client, prtd->params->channel);
ret = s3c2410_dma_request(prtd->params->channel,
prtd->params->client, NULL);
if (ret < 0) {
printk(KERN_ERR "failed to get dma channel\n");
return ret;
}
/* use the circular buffering if we have it available. */
if (s3c_dma_has_circular())
s3c2410_dma_setflags(prtd->params->channel,
S3C2410_DMAF_CIRCULAR);
prtd->params->ops = samsung_dma_get_ops();
dma_info.cap = (samsung_dma_has_circular() ?
DMA_CYCLIC : DMA_SLAVE);
dma_info.client = prtd->params->client;
dma_info.direction =
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK
? DMA_TO_DEVICE : DMA_FROM_DEVICE);
dma_info.width = prtd->params->dma_size;
dma_info.fifo = prtd->params->dma_addr;
prtd->params->ch = prtd->params->ops->request(
prtd->params->channel, &dma_info);
}
s3c2410_dma_set_buffdone_fn(prtd->params->channel,
audio_buffdone);
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
runtime->dma_bytes = totbytes;
spin_lock_irq(&prtd->lock);
prtd->dma_loaded = 0;
prtd->dma_limit = runtime->hw.periods_min;
prtd->dma_period = params_period_bytes(params);
prtd->dma_start = runtime->dma_addr;
prtd->dma_pos = prtd->dma_start;
......@@ -206,7 +202,8 @@ static int dma_hw_free(struct snd_pcm_substream *substream)
snd_pcm_set_runtime_buffer(substream, NULL);
if (prtd->params) {
s3c2410_dma_free(prtd->params->channel, prtd->params->client);
prtd->params->ops->release(prtd->params->ch,
prtd->params->client);
prtd->params = NULL;
}
......@@ -225,23 +222,9 @@ static int dma_prepare(struct snd_pcm_substream *substream)
if (!prtd->params)
return 0;
/* channel needs configuring for mem=>device, increment memory addr,
* sync to pclk, half-word transfers to the IIS-FIFO. */
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
s3c2410_dma_devconfig(prtd->params->channel,
S3C2410_DMASRC_MEM,
prtd->params->dma_addr);
} else {
s3c2410_dma_devconfig(prtd->params->channel,
S3C2410_DMASRC_HW,
prtd->params->dma_addr);
}
s3c2410_dma_config(prtd->params->channel,
prtd->params->dma_size);
/* flush the DMA channel */
s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH);
prtd->params->ops->flush(prtd->params->ch);
prtd->dma_loaded = 0;
prtd->dma_pos = prtd->dma_start;
......@@ -265,14 +248,14 @@ static int dma_trigger(struct snd_pcm_substream *substream, int cmd)
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
prtd->state |= ST_RUNNING;
s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START);
prtd->params->ops->trigger(prtd->params->ch);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
prtd->state &= ~ST_RUNNING;
s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STOP);
prtd->params->ops->stop(prtd->params->ch);
break;
default:
......@@ -291,21 +274,12 @@ dma_pointer(struct snd_pcm_substream *substream)
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
unsigned long res;
dma_addr_t src, dst;
pr_debug("Entered %s\n", __func__);
spin_lock(&prtd->lock);
s3c2410_dma_getposition(prtd->params->channel, &src, &dst);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
res = dst - prtd->dma_start;
else
res = src - prtd->dma_start;
spin_unlock(&prtd->lock);
res = prtd->dma_pos - prtd->dma_start;
pr_debug("Pointer %x %x\n", src, dst);
pr_debug("Pointer offset: %lu\n", res);
/* we seem to be getting the odd error from the pcm library due
* to out-of-bounds pointers. this is maybe due to the dma engine
......
......@@ -6,7 +6,7 @@
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* ALSA PCM interface for the Samsung S3C24xx CPU
* ALSA PCM interface for the Samsung SoC
*/
#ifndef _S3C_AUDIO_H
......@@ -17,6 +17,8 @@ struct s3c_dma_params {
int channel; /* Channel ID */
dma_addr_t dma_addr;
int dma_size; /* Size of the DMA transfer */
unsigned ch;
struct samsung_dma_ops *ops;
};
#endif
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