From 0fd602235dd702d16722857da748d15c26b81ed1 Mon Sep 17 00:00:00 2001 From: Narayanan Date: Tue, 13 Sep 2011 17:00:22 +0530 Subject: [PATCH 01/16] dmaengine: ste_dma40: reset priority bit for logical channels This patch sets the SSCFG/SDCFG bit[7] PRI only for physical channel requests with high priority. For logical channels, this bit will be zero. Signed-off-by: Narayanan G Reviewed-by: Rabin Vincent Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40_ll.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/dma/ste_dma40_ll.c b/drivers/dma/ste_dma40_ll.c index 851ad56e8409..d64b72ae2dc8 100644 --- a/drivers/dma/ste_dma40_ll.c +++ b/drivers/dma/ste_dma40_ll.c @@ -102,17 +102,18 @@ void d40_phy_cfg(struct stedma40_chan_cfg *cfg, src |= cfg->src_info.data_width << D40_SREG_CFG_ESIZE_POS; dst |= cfg->dst_info.data_width << D40_SREG_CFG_ESIZE_POS; + /* Set the priority bit to high for the physical channel */ + if (cfg->high_priority) { + src |= 1 << D40_SREG_CFG_PRI_POS; + dst |= 1 << D40_SREG_CFG_PRI_POS; + } + } else { /* Logical channel */ dst |= 1 << D40_SREG_CFG_LOG_GIM_POS; src |= 1 << D40_SREG_CFG_LOG_GIM_POS; } - if (cfg->high_priority) { - src |= 1 << D40_SREG_CFG_PRI_POS; - dst |= 1 << D40_SREG_CFG_PRI_POS; - } - if (cfg->src_info.big_endian) src |= 1 << D40_SREG_CFG_LBE_POS; if (cfg->dst_info.big_endian) From 8a5d2039ab9050a8a2e649eaf3ca4e372a7709f1 Mon Sep 17 00:00:00 2001 From: Per Forlin Date: Wed, 28 Sep 2011 09:32:20 +0200 Subject: [PATCH 02/16] dmaengine: ste_dma40: use writel_relaxed for lcxa lcpa and lcla are written often and the cache_sync() overhead in writel is costly, especially for wlan where every single network packet (in RX mode) corresponds to a separate DMA transfer. Signed-off-by: Per Forlin Reviewed-by: Narayanan Gopalakrishnan Reviewed-by: Rabin Vincent Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40_ll.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/dma/ste_dma40_ll.c b/drivers/dma/ste_dma40_ll.c index d64b72ae2dc8..1cfe7ab50c6b 100644 --- a/drivers/dma/ste_dma40_ll.c +++ b/drivers/dma/ste_dma40_ll.c @@ -332,10 +332,10 @@ void d40_log_lli_lcpa_write(struct d40_log_lli_full *lcpa, { d40_log_lli_link(lli_dst, lli_src, next, flags); - writel(lli_src->lcsp02, &lcpa[0].lcsp0); - writel(lli_src->lcsp13, &lcpa[0].lcsp1); - writel(lli_dst->lcsp02, &lcpa[0].lcsp2); - writel(lli_dst->lcsp13, &lcpa[0].lcsp3); + writel_relaxed(lli_src->lcsp02, &lcpa[0].lcsp0); + writel_relaxed(lli_src->lcsp13, &lcpa[0].lcsp1); + writel_relaxed(lli_dst->lcsp02, &lcpa[0].lcsp2); + writel_relaxed(lli_dst->lcsp13, &lcpa[0].lcsp3); } void d40_log_lli_lcla_write(struct d40_log_lli *lcla, @@ -345,10 +345,10 @@ void d40_log_lli_lcla_write(struct d40_log_lli *lcla, { d40_log_lli_link(lli_dst, lli_src, next, flags); - writel(lli_src->lcsp02, &lcla[0].lcsp02); - writel(lli_src->lcsp13, &lcla[0].lcsp13); - writel(lli_dst->lcsp02, &lcla[1].lcsp02); - writel(lli_dst->lcsp13, &lcla[1].lcsp13); + writel_relaxed(lli_src->lcsp02, &lcla[0].lcsp02); + writel_relaxed(lli_src->lcsp13, &lcla[0].lcsp13); + writel_relaxed(lli_dst->lcsp02, &lcla[1].lcsp02); + writel_relaxed(lli_dst->lcsp13, &lcla[1].lcsp13); } static void d40_log_fill_lli(struct d40_log_lli *lli, From b96710e5b22609aa6e4ba5c3936ea7f026a7c427 Mon Sep 17 00:00:00 2001 From: Per Forlin Date: Tue, 18 Oct 2011 18:39:47 +0200 Subject: [PATCH 03/16] dmaengine: ste_dma40: set dma max seg size Maximum DMA seg size is (0xffff x data_width). If max seg size is not set it deafults to 64k. This results in failure if transferring 64k in byte mode. Large seg sizes may be supported by splitting large transfer. Signed-off-by: Per Forlin Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index 23c5573e62dd..f5724d95ed48 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -344,6 +344,7 @@ struct d40_base { int irq; int num_phy_chans; int num_log_chans; + struct device_dma_parameters dma_parms; struct dma_device dma_both; struct dma_device dma_slave; struct dma_device dma_memcpy; @@ -3362,6 +3363,13 @@ static int __init d40_probe(struct platform_device *pdev) if (err) goto failure; + base->dev->dma_parms = &base->dma_parms; + err = dma_set_max_seg_size(base->dev, STEDMA40_MAX_SEG_SIZE); + if (err) { + d40_err(&pdev->dev, "Failed to set dma max seg size\n"); + goto failure; + } + d40_hw_init(base); dev_info(base->dev, "initialized\n"); From 92bb6cdb5302a4b0b3c6b6cfc0854aaed882c4bc Mon Sep 17 00:00:00 2001 From: Per Forlin Date: Thu, 13 Oct 2011 12:11:36 +0200 Subject: [PATCH 04/16] dmaengine: ste_dma40: limit burst size to 16 The client is not aware of the maximum burst size in the dma driver. If the size exceeds 16 set max to 16. Signed-off-by: Per Forlin Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index f5724d95ed48..2d0c63dcd84c 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -2578,6 +2578,14 @@ static int d40_set_runtime_config(struct dma_chan *chan, return -EINVAL; } + if (src_maxburst > 16) { + src_maxburst = 16; + dst_maxburst = src_maxburst * src_addr_width / dst_addr_width; + } else if (dst_maxburst > 16) { + dst_maxburst = 16; + src_maxburst = dst_maxburst * dst_addr_width / src_addr_width; + } + ret = dma40_config_to_halfchannel(d40c, &cfg->src_info, src_addr_width, src_maxburst); From 42365cf0fa19473dde5fe226b0e7e9ab8ea18af8 Mon Sep 17 00:00:00 2001 From: Narayanan G Date: Fri, 20 Jan 2012 13:56:14 +0530 Subject: [PATCH 05/16] dmaengine: ste_dma40: don't check for pm_runtime_suspended() The check for runtime suspend is not needed during a regular suspend, as the framework takes care of this. This fixes the issue of DMA driver not letting the system to go to deepsleep in the first attempt. Signed-off-by: Narayanan G Reviewed-by: Rabin Vincent Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index 2d0c63dcd84c..760576b85641 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -2782,8 +2782,6 @@ static int dma40_pm_suspend(struct device *dev) struct platform_device *pdev = to_platform_device(dev); struct d40_base *base = platform_get_drvdata(pdev); int ret = 0; - if (!pm_runtime_suspended(dev)) - return -EBUSY; if (base->lcpa_regulator) ret = regulator_disable(base->lcpa_regulator); From ccc3d6976433aa67131117fccd2b5143d82a6f48 Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Thu, 17 May 2012 13:47:38 +0530 Subject: [PATCH 06/16] dmaengine: ste_dma40: don't allow high priority dest event lines Hardware bug: when a logical channel is triggerred by a high priority destination event line, an extra packet transaction is generated in case of important data write response latency on previous logical channel A and if the source transfer of current logical channel B is already completed and if no other channel with a higher priority than B is waiting for execution. Software workaround: do not set the high priority level for the destination event lines that trigger logical channels. Signed-off-by: Rabin Vincent Reviewed-by: Shreshtha Kumar Sahu Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index 760576b85641..9f8964a0a287 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -2180,11 +2180,24 @@ static void __d40_set_prio_rt(struct d40_chan *d40c, int dev_type, bool src) { bool realtime = d40c->dma_cfg.realtime; bool highprio = d40c->dma_cfg.high_priority; - u32 prioreg = highprio ? D40_DREG_PSEG1 : D40_DREG_PCEG1; u32 rtreg = realtime ? D40_DREG_RSEG1 : D40_DREG_RCEG1; u32 event = D40_TYPE_TO_EVENT(dev_type); u32 group = D40_TYPE_TO_GROUP(dev_type); u32 bit = 1 << event; + u32 prioreg; + + /* + * Due to a hardware bug, in some cases a logical channel triggered by + * a high priority destination event line can generate extra packet + * transactions. + * + * The workaround is to not set the high priority level for the + * destination event lines that trigger logical channels. + */ + if (!src && chan_is_logical(d40c)) + highprio = false; + + prioreg = highprio ? D40_DREG_PSEG1 : D40_DREG_PCEG1; /* Destination event lines are stored in the upper halfword */ if (!src) From f000df8c5a0e2002acc5989aad99a97d32a24718 Mon Sep 17 00:00:00 2001 From: Gerald Baeza Date: Thu, 8 Nov 2012 14:39:07 +0100 Subject: [PATCH 07/16] dmaengine: ste_dma40: support fixed physical channel allocation This patch makes existing use_fixed_channel field (of stedma40_chan_cfg structure) applicable to physical channels. Signed-off-by: Gerald Baeza Tested-by: Yannick Fertre Reviewed-by: Per Forlin Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index 9f8964a0a287..5feab7db9449 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -1711,10 +1711,12 @@ static int d40_allocate_channel(struct d40_chan *d40c, bool *first_phy_user) int i; int j; int log_num; + int num_phy_chans; bool is_src; bool is_log = d40c->dma_cfg.mode == STEDMA40_MODE_LOGICAL; phys = d40c->base->phy_res; + num_phy_chans = d40c->base->num_phy_chans; if (d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_MEM) { dev_type = d40c->dma_cfg.src_dev_type; @@ -1735,12 +1737,19 @@ static int d40_allocate_channel(struct d40_chan *d40c, bool *first_phy_user) if (!is_log) { if (d40c->dma_cfg.dir == STEDMA40_MEM_TO_MEM) { /* Find physical half channel */ - for (i = 0; i < d40c->base->num_phy_chans; i++) { - + if (d40c->dma_cfg.use_fixed_channel) { + i = d40c->dma_cfg.phy_channel; if (d40_alloc_mask_set(&phys[i], is_src, 0, is_log, first_phy_user)) goto found_phy; + } else { + for (i = 0; i < num_phy_chans; i++) { + if (d40_alloc_mask_set(&phys[i], is_src, + 0, is_log, + first_phy_user)) + goto found_phy; + } } } else for (j = 0; j < d40c->base->num_phy_chans; j += 8) { From 47db92f4a63499b1605b4c66f9347ba5479e7b19 Mon Sep 17 00:00:00 2001 From: Gerald Baeza Date: Fri, 21 Sep 2012 21:21:37 +0200 Subject: [PATCH 08/16] dmaengine: ste_dma40: physical channels number correction DMAC_ICFG[0:2]=SCHNB only allows to count 'multiple of 4' physical channels so it was ok with platforms having 8 channels but cannot be used for next versions (with 10 or 14 channels). This patch allows to provide the number of physical channels for a DMA device via platform_data, or still rely on SCHNB if platform_data announces 0 channel. Signed-off-by: Gerald Baeza Reviewed-by: Per Forlin Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40.c | 17 +++++++++++------ include/linux/platform_data/dma-ste-dma40.h | 4 ++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index 5feab7db9449..ca18117def0a 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -3004,14 +3004,21 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) * ? has revision 1 * DB8500v1 has revision 2 * DB8500v2 has revision 3 + * AP9540v1 has revision 4 + * DB8540v1 has revision 4 */ rev = AMBA_REV_BITS(pid); - /* The number of physical channels on this HW */ - num_phy_chans = 4 * (readl(virtbase + D40_DREG_ICFG) & 0x7) + 4; + plat_data = pdev->dev.platform_data; - dev_info(&pdev->dev, "hardware revision: %d @ 0x%x\n", - rev, res->start); + /* The number of physical channels on this HW */ + if (plat_data->num_of_phy_chans) + num_phy_chans = plat_data->num_of_phy_chans; + else + num_phy_chans = 4 * (readl(virtbase + D40_DREG_ICFG) & 0x7) + 4; + + dev_info(&pdev->dev, "hardware revision: %d @ 0x%x with %d physical channels\n", + rev, res->start, num_phy_chans); if (rev < 2) { d40_err(&pdev->dev, "hardware revision: %d is not supported", @@ -3019,8 +3026,6 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) goto failure; } - plat_data = pdev->dev.platform_data; - /* Count the number of logical channels in use */ for (i = 0; i < plat_data->dev_len; i++) if (plat_data->dev_rx[i] != 0) diff --git a/include/linux/platform_data/dma-ste-dma40.h b/include/linux/platform_data/dma-ste-dma40.h index 9ff93b065686..833cb959f3df 100644 --- a/include/linux/platform_data/dma-ste-dma40.h +++ b/include/linux/platform_data/dma-ste-dma40.h @@ -147,6 +147,9 @@ struct stedma40_chan_cfg { * @memcpy_conf_log: default configuration of logical channel memcpy * @disabled_channels: A vector, ending with -1, that marks physical channels * that are for different reasons not available for the driver. + * @num_of_phy_chans: The number of physical channels implemented in HW. + * 0 means reading the number of channels from DMA HW but this is only valid + * for 'multiple of 4' channels, like 8. */ struct stedma40_platform_data { u32 dev_len; @@ -158,6 +161,7 @@ struct stedma40_platform_data { struct stedma40_chan_cfg *memcpy_conf_log; int disabled_channels[STEDMA40_MAX_PHYS]; bool use_esram_lcla; + int num_of_phy_chans; }; #ifdef CONFIG_STE_DMA40 From 3cb645dc85a050c8a6b5c2cbdcbe4b8f39dba1b8 Mon Sep 17 00:00:00 2001 From: Tong Liu Date: Wed, 26 Sep 2012 10:07:30 +0000 Subject: [PATCH 09/16] dmaengine: ste_dma40: support more than 128 event lines U8540 DMA controller is different from u9540 we need define new registers and use them to support handling more than 128 event lines. Signed-off-by: Tong Liu Reviewed-by: Per Forlin Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40.c | 306 +++++++++++++++++++++++++++---------- drivers/dma/ste_dma40_ll.h | 130 +++++++++++++++- 2 files changed, 355 insertions(+), 81 deletions(-) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index ca18117def0a..67e565bffe16 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -53,6 +53,8 @@ #define D40_ALLOC_PHY (1 << 30) #define D40_ALLOC_LOG_FREE 0 +#define MAX(a, b) (((a) < (b)) ? (b) : (a)) + /** * enum 40_command - The different commands and/or statuses. * @@ -100,8 +102,19 @@ static u32 d40_backup_regs[] = { #define BACKUP_REGS_SZ ARRAY_SIZE(d40_backup_regs) -/* TODO: Check if all these registers have to be saved/restored on dma40 v3 */ -static u32 d40_backup_regs_v3[] = { +/* + * since 9540 and 8540 has the same HW revision + * use v4a for 9540 or ealier + * use v4b for 8540 or later + * HW revision: + * DB8500ed has revision 0 + * DB8500v1 has revision 2 + * DB8500v2 has revision 3 + * AP9540v1 has revision 4 + * DB8540v1 has revision 4 + * TODO: Check if all these registers have to be saved/restored on dma40 v4a + */ +static u32 d40_backup_regs_v4a[] = { D40_DREG_PSEG1, D40_DREG_PSEG2, D40_DREG_PSEG3, @@ -120,7 +133,32 @@ static u32 d40_backup_regs_v3[] = { D40_DREG_RCEG4, }; -#define BACKUP_REGS_SZ_V3 ARRAY_SIZE(d40_backup_regs_v3) +#define BACKUP_REGS_SZ_V4A ARRAY_SIZE(d40_backup_regs_v4a) + +static u32 d40_backup_regs_v4b[] = { + D40_DREG_CPSEG1, + D40_DREG_CPSEG2, + D40_DREG_CPSEG3, + D40_DREG_CPSEG4, + D40_DREG_CPSEG5, + D40_DREG_CPCEG1, + D40_DREG_CPCEG2, + D40_DREG_CPCEG3, + D40_DREG_CPCEG4, + D40_DREG_CPCEG5, + D40_DREG_CRSEG1, + D40_DREG_CRSEG2, + D40_DREG_CRSEG3, + D40_DREG_CRSEG4, + D40_DREG_CRSEG5, + D40_DREG_CRCEG1, + D40_DREG_CRCEG2, + D40_DREG_CRCEG3, + D40_DREG_CRCEG4, + D40_DREG_CRCEG5, +}; + +#define BACKUP_REGS_SZ_V4B ARRAY_SIZE(d40_backup_regs_v4b) static u32 d40_backup_regs_chan[] = { D40_CHAN_REG_SSCFG, @@ -133,6 +171,102 @@ static u32 d40_backup_regs_chan[] = { D40_CHAN_REG_SDLNK, }; +/** + * struct d40_interrupt_lookup - lookup table for interrupt handler + * + * @src: Interrupt mask register. + * @clr: Interrupt clear register. + * @is_error: true if this is an error interrupt. + * @offset: start delta in the lookup_log_chans in d40_base. If equals to + * D40_PHY_CHAN, the lookup_phy_chans shall be used instead. + */ +struct d40_interrupt_lookup { + u32 src; + u32 clr; + bool is_error; + int offset; +}; + + +static struct d40_interrupt_lookup il_v4a[] = { + {D40_DREG_LCTIS0, D40_DREG_LCICR0, false, 0}, + {D40_DREG_LCTIS1, D40_DREG_LCICR1, false, 32}, + {D40_DREG_LCTIS2, D40_DREG_LCICR2, false, 64}, + {D40_DREG_LCTIS3, D40_DREG_LCICR3, false, 96}, + {D40_DREG_LCEIS0, D40_DREG_LCICR0, true, 0}, + {D40_DREG_LCEIS1, D40_DREG_LCICR1, true, 32}, + {D40_DREG_LCEIS2, D40_DREG_LCICR2, true, 64}, + {D40_DREG_LCEIS3, D40_DREG_LCICR3, true, 96}, + {D40_DREG_PCTIS, D40_DREG_PCICR, false, D40_PHY_CHAN}, + {D40_DREG_PCEIS, D40_DREG_PCICR, true, D40_PHY_CHAN}, +}; + +static struct d40_interrupt_lookup il_v4b[] = { + {D40_DREG_CLCTIS1, D40_DREG_CLCICR1, false, 0}, + {D40_DREG_CLCTIS2, D40_DREG_CLCICR2, false, 32}, + {D40_DREG_CLCTIS3, D40_DREG_CLCICR3, false, 64}, + {D40_DREG_CLCTIS4, D40_DREG_CLCICR4, false, 96}, + {D40_DREG_CLCTIS5, D40_DREG_CLCICR5, false, 128}, + {D40_DREG_CLCEIS1, D40_DREG_CLCICR1, true, 0}, + {D40_DREG_CLCEIS2, D40_DREG_CLCICR2, true, 32}, + {D40_DREG_CLCEIS3, D40_DREG_CLCICR3, true, 64}, + {D40_DREG_CLCEIS4, D40_DREG_CLCICR4, true, 96}, + {D40_DREG_CLCEIS5, D40_DREG_CLCICR5, true, 128}, + {D40_DREG_CPCTIS, D40_DREG_CPCICR, false, D40_PHY_CHAN}, + {D40_DREG_CPCEIS, D40_DREG_CPCICR, true, D40_PHY_CHAN}, +}; + +/** + * struct d40_reg_val - simple lookup struct + * + * @reg: The register. + * @val: The value that belongs to the register in reg. + */ +struct d40_reg_val { + unsigned int reg; + unsigned int val; +}; + +static __initdata struct d40_reg_val dma_init_reg_v4a[] = { + /* Clock every part of the DMA block from start */ + { .reg = D40_DREG_GCC, .val = D40_DREG_GCC_ENABLE_ALL}, + + /* Interrupts on all logical channels */ + { .reg = D40_DREG_LCMIS0, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_LCMIS1, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_LCMIS2, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_LCMIS3, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_LCICR0, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_LCICR1, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_LCICR2, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_LCICR3, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_LCTIS0, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_LCTIS1, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_LCTIS2, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_LCTIS3, .val = 0xFFFFFFFF} +}; +static __initdata struct d40_reg_val dma_init_reg_v4b[] = { + /* Clock every part of the DMA block from start */ + { .reg = D40_DREG_GCC, .val = D40_DREG_GCC_ENABLE_ALL}, + + /* Interrupts on all logical channels */ + { .reg = D40_DREG_CLCMIS1, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCMIS2, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCMIS3, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCMIS4, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCMIS5, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCICR1, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCICR2, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCICR3, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCICR4, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCICR5, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCTIS1, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCTIS2, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCTIS3, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCTIS4, .val = 0xFFFFFFFF}, + { .reg = D40_DREG_CLCTIS5, .val = 0xFFFFFFFF} +}; + /** * struct d40_lli_pool - Structure for keeping LLIs in memory * @@ -288,6 +422,38 @@ struct d40_chan { enum dma_transfer_direction runtime_direction; }; +/** + * struct d40_gen_dmac - generic values to represent u8500/u8540 DMA + * controller + * + * @backup: the pointer to the registers address array for backup + * @backup_size: the size of the registers address array for backup + * @realtime_en: the realtime enable register + * @realtime_clear: the realtime clear register + * @high_prio_en: the high priority enable register + * @high_prio_clear: the high priority clear register + * @interrupt_en: the interrupt enable register + * @interrupt_clear: the interrupt clear register + * @il: the pointer to struct d40_interrupt_lookup + * @il_size: the size of d40_interrupt_lookup array + * @init_reg: the pointer to the struct d40_reg_val + * @init_reg_size: the size of d40_reg_val array + */ +struct d40_gen_dmac { + u32 *backup; + u32 backup_size; + u32 realtime_en; + u32 realtime_clear; + u32 high_prio_en; + u32 high_prio_clear; + u32 interrupt_en; + u32 interrupt_clear; + struct d40_interrupt_lookup *il; + u32 il_size; + struct d40_reg_val *init_reg; + u32 init_reg_size; +}; + /** * struct d40_base - The big global struct, one for each probe'd instance. * @@ -326,11 +492,13 @@ struct d40_chan { * @desc_slab: cache for descriptors. * @reg_val_backup: Here the values of some hardware registers are stored * before the DMA is powered off. They are restored when the power is back on. - * @reg_val_backup_v3: Backup of registers that only exits on dma40 v3 and - * later. + * @reg_val_backup_v4: Backup of registers that only exits on dma40 v3 and + * later * @reg_val_backup_chan: Backup data for standard channel parameter registers. * @gcc_pwr_off_mask: Mask to maintain the channels that can be turned off. * @initialized: true if the dma has been initialized + * @gen_dmac: the struct for generic registers values to represent u8500/8540 + * DMA controller */ struct d40_base { spinlock_t interrupt_lock; @@ -362,37 +530,11 @@ struct d40_base { resource_size_t lcpa_size; struct kmem_cache *desc_slab; u32 reg_val_backup[BACKUP_REGS_SZ]; - u32 reg_val_backup_v3[BACKUP_REGS_SZ_V3]; + u32 reg_val_backup_v4[MAX(BACKUP_REGS_SZ_V4A, BACKUP_REGS_SZ_V4B)]; u32 *reg_val_backup_chan; u16 gcc_pwr_off_mask; bool initialized; -}; - -/** - * struct d40_interrupt_lookup - lookup table for interrupt handler - * - * @src: Interrupt mask register. - * @clr: Interrupt clear register. - * @is_error: true if this is an error interrupt. - * @offset: start delta in the lookup_log_chans in d40_base. If equals to - * D40_PHY_CHAN, the lookup_phy_chans shall be used instead. - */ -struct d40_interrupt_lookup { - u32 src; - u32 clr; - bool is_error; - int offset; -}; - -/** - * struct d40_reg_val - simple lookup struct - * - * @reg: The register. - * @val: The value that belongs to the register in reg. - */ -struct d40_reg_val { - unsigned int reg; - unsigned int val; + struct d40_gen_dmac gen_dmac; }; static struct device *chan2dev(struct d40_chan *d40c) @@ -875,11 +1017,11 @@ static void d40_save_restore_registers(struct d40_base *base, bool save) save); /* Save/Restore registers only existing on dma40 v3 and later */ - if (base->rev >= 3) - dma40_backup(base->virtbase, base->reg_val_backup_v3, - d40_backup_regs_v3, - ARRAY_SIZE(d40_backup_regs_v3), - save); + if (base->gen_dmac.backup) + dma40_backup(base->virtbase, base->reg_val_backup_v4, + base->gen_dmac.backup, + base->gen_dmac.backup_size, + save); } #else static void d40_save_restore_registers(struct d40_base *base, bool save) @@ -1470,41 +1612,30 @@ err: static irqreturn_t d40_handle_interrupt(int irq, void *data) { - static const struct d40_interrupt_lookup il[] = { - {D40_DREG_LCTIS0, D40_DREG_LCICR0, false, 0}, - {D40_DREG_LCTIS1, D40_DREG_LCICR1, false, 32}, - {D40_DREG_LCTIS2, D40_DREG_LCICR2, false, 64}, - {D40_DREG_LCTIS3, D40_DREG_LCICR3, false, 96}, - {D40_DREG_LCEIS0, D40_DREG_LCICR0, true, 0}, - {D40_DREG_LCEIS1, D40_DREG_LCICR1, true, 32}, - {D40_DREG_LCEIS2, D40_DREG_LCICR2, true, 64}, - {D40_DREG_LCEIS3, D40_DREG_LCICR3, true, 96}, - {D40_DREG_PCTIS, D40_DREG_PCICR, false, D40_PHY_CHAN}, - {D40_DREG_PCEIS, D40_DREG_PCICR, true, D40_PHY_CHAN}, - }; - int i; - u32 regs[ARRAY_SIZE(il)]; u32 idx; u32 row; long chan = -1; struct d40_chan *d40c; unsigned long flags; struct d40_base *base = data; + u32 regs[base->gen_dmac.il_size]; + struct d40_interrupt_lookup *il = base->gen_dmac.il; + u32 il_size = base->gen_dmac.il_size; spin_lock_irqsave(&base->interrupt_lock, flags); /* Read interrupt status of both logical and physical channels */ - for (i = 0; i < ARRAY_SIZE(il); i++) + for (i = 0; i < il_size; i++) regs[i] = readl(base->virtbase + il[i].src); for (;;) { chan = find_next_bit((unsigned long *)regs, - BITS_PER_LONG * ARRAY_SIZE(il), chan + 1); + BITS_PER_LONG * il_size, chan + 1); /* No more set bits found? */ - if (chan == BITS_PER_LONG * ARRAY_SIZE(il)) + if (chan == BITS_PER_LONG * il_size) break; row = chan / BITS_PER_LONG; @@ -2189,12 +2320,14 @@ static void __d40_set_prio_rt(struct d40_chan *d40c, int dev_type, bool src) { bool realtime = d40c->dma_cfg.realtime; bool highprio = d40c->dma_cfg.high_priority; - u32 rtreg = realtime ? D40_DREG_RSEG1 : D40_DREG_RCEG1; + u32 rtreg; u32 event = D40_TYPE_TO_EVENT(dev_type); u32 group = D40_TYPE_TO_GROUP(dev_type); u32 bit = 1 << event; u32 prioreg; + struct d40_gen_dmac *dmac = &d40c->base->gen_dmac; + rtreg = realtime ? dmac->realtime_en : dmac->realtime_clear; /* * Due to a hardware bug, in some cases a logical channel triggered by * a high priority destination event line can generate extra packet @@ -2206,7 +2339,7 @@ static void __d40_set_prio_rt(struct d40_chan *d40c, int dev_type, bool src) if (!src && chan_is_logical(d40c)) highprio = false; - prioreg = highprio ? D40_DREG_PSEG1 : D40_DREG_PCEG1; + prioreg = highprio ? dmac->high_prio_en : dmac->high_prio_clear; /* Destination event lines are stored in the upper halfword */ if (!src) @@ -3056,6 +3189,36 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) base->phy_chans = ((void *)base) + ALIGN(sizeof(struct d40_base), 4); base->log_chans = &base->phy_chans[num_phy_chans]; + if (base->plat_data->num_of_phy_chans == 14) { + base->gen_dmac.backup = d40_backup_regs_v4b; + base->gen_dmac.backup_size = BACKUP_REGS_SZ_V4B; + base->gen_dmac.interrupt_en = D40_DREG_CPCMIS; + base->gen_dmac.interrupt_clear = D40_DREG_CPCICR; + base->gen_dmac.realtime_en = D40_DREG_CRSEG1; + base->gen_dmac.realtime_clear = D40_DREG_CRCEG1; + base->gen_dmac.high_prio_en = D40_DREG_CPSEG1; + base->gen_dmac.high_prio_clear = D40_DREG_CPCEG1; + base->gen_dmac.il = il_v4b; + base->gen_dmac.il_size = ARRAY_SIZE(il_v4b); + base->gen_dmac.init_reg = dma_init_reg_v4b; + base->gen_dmac.init_reg_size = ARRAY_SIZE(dma_init_reg_v4b); + } else { + if (base->rev >= 3) { + base->gen_dmac.backup = d40_backup_regs_v4a; + base->gen_dmac.backup_size = BACKUP_REGS_SZ_V4A; + } + base->gen_dmac.interrupt_en = D40_DREG_PCMIS; + base->gen_dmac.interrupt_clear = D40_DREG_PCICR; + base->gen_dmac.realtime_en = D40_DREG_RSEG1; + base->gen_dmac.realtime_clear = D40_DREG_RCEG1; + base->gen_dmac.high_prio_en = D40_DREG_PSEG1; + base->gen_dmac.high_prio_clear = D40_DREG_PCEG1; + base->gen_dmac.il = il_v4a; + base->gen_dmac.il_size = ARRAY_SIZE(il_v4a); + base->gen_dmac.init_reg = dma_init_reg_v4a; + base->gen_dmac.init_reg_size = ARRAY_SIZE(dma_init_reg_v4a); + } + base->phy_res = kzalloc(num_phy_chans * sizeof(struct d40_phy_res), GFP_KERNEL); if (!base->phy_res) @@ -3127,31 +3290,15 @@ failure: static void __init d40_hw_init(struct d40_base *base) { - static struct d40_reg_val dma_init_reg[] = { - /* Clock every part of the DMA block from start */ - { .reg = D40_DREG_GCC, .val = D40_DREG_GCC_ENABLE_ALL}, - - /* Interrupts on all logical channels */ - { .reg = D40_DREG_LCMIS0, .val = 0xFFFFFFFF}, - { .reg = D40_DREG_LCMIS1, .val = 0xFFFFFFFF}, - { .reg = D40_DREG_LCMIS2, .val = 0xFFFFFFFF}, - { .reg = D40_DREG_LCMIS3, .val = 0xFFFFFFFF}, - { .reg = D40_DREG_LCICR0, .val = 0xFFFFFFFF}, - { .reg = D40_DREG_LCICR1, .val = 0xFFFFFFFF}, - { .reg = D40_DREG_LCICR2, .val = 0xFFFFFFFF}, - { .reg = D40_DREG_LCICR3, .val = 0xFFFFFFFF}, - { .reg = D40_DREG_LCTIS0, .val = 0xFFFFFFFF}, - { .reg = D40_DREG_LCTIS1, .val = 0xFFFFFFFF}, - { .reg = D40_DREG_LCTIS2, .val = 0xFFFFFFFF}, - { .reg = D40_DREG_LCTIS3, .val = 0xFFFFFFFF} - }; int i; u32 prmseo[2] = {0, 0}; u32 activeo[2] = {0xFFFFFFFF, 0xFFFFFFFF}; u32 pcmis = 0; u32 pcicr = 0; + struct d40_reg_val *dma_init_reg = base->gen_dmac.init_reg; + u32 reg_size = base->gen_dmac.init_reg_size; - for (i = 0; i < ARRAY_SIZE(dma_init_reg); i++) + for (i = 0; i < reg_size; i++) writel(dma_init_reg[i].val, base->virtbase + dma_init_reg[i].reg); @@ -3184,11 +3331,14 @@ static void __init d40_hw_init(struct d40_base *base) writel(activeo[0], base->virtbase + D40_DREG_ACTIVO); /* Write which interrupt to enable */ - writel(pcmis, base->virtbase + D40_DREG_PCMIS); + writel(pcmis, base->virtbase + base->gen_dmac.interrupt_en); /* Write which interrupt to clear */ - writel(pcicr, base->virtbase + D40_DREG_PCICR); + writel(pcicr, base->virtbase + base->gen_dmac.interrupt_clear); + /* These are __initdata and cannot be accessed after init */ + base->gen_dmac.init_reg = NULL; + base->gen_dmac.init_reg_size = 0; } static int __init d40_lcla_allocate(struct d40_base *base) diff --git a/drivers/dma/ste_dma40_ll.h b/drivers/dma/ste_dma40_ll.h index 6d47373f3f58..fdde8ef77542 100644 --- a/drivers/dma/ste_dma40_ll.h +++ b/drivers/dma/ste_dma40_ll.h @@ -125,7 +125,7 @@ #define D40_DREG_GCC 0x000 #define D40_DREG_GCC_ENA 0x1 /* This assumes that there are only 4 event groups */ -#define D40_DREG_GCC_ENABLE_ALL 0xff01 +#define D40_DREG_GCC_ENABLE_ALL 0x3ff01 #define D40_DREG_GCC_EVTGRP_POS 8 #define D40_DREG_GCC_SRC 0 #define D40_DREG_GCC_DST 1 @@ -148,14 +148,31 @@ #define D40_DREG_LCPA 0x020 #define D40_DREG_LCLA 0x024 + +#define D40_DREG_SSEG1 0x030 +#define D40_DREG_SSEG2 0x034 +#define D40_DREG_SSEG3 0x038 +#define D40_DREG_SSEG4 0x03C + +#define D40_DREG_SCEG1 0x040 +#define D40_DREG_SCEG2 0x044 +#define D40_DREG_SCEG3 0x048 +#define D40_DREG_SCEG4 0x04C + #define D40_DREG_ACTIVE 0x050 #define D40_DREG_ACTIVO 0x054 -#define D40_DREG_FSEB1 0x058 -#define D40_DREG_FSEB2 0x05C +#define D40_DREG_CIDMOD 0x058 +#define D40_DREG_TCIDV 0x05C #define D40_DREG_PCMIS 0x060 #define D40_DREG_PCICR 0x064 #define D40_DREG_PCTIS 0x068 #define D40_DREG_PCEIS 0x06C + +#define D40_DREG_SPCMIS 0x070 +#define D40_DREG_SPCICR 0x074 +#define D40_DREG_SPCTIS 0x078 +#define D40_DREG_SPCEIS 0x07C + #define D40_DREG_LCMIS0 0x080 #define D40_DREG_LCMIS1 0x084 #define D40_DREG_LCMIS2 0x088 @@ -172,6 +189,33 @@ #define D40_DREG_LCEIS1 0x0B4 #define D40_DREG_LCEIS2 0x0B8 #define D40_DREG_LCEIS3 0x0BC + +#define D40_DREG_SLCMIS1 0x0C0 +#define D40_DREG_SLCMIS2 0x0C4 +#define D40_DREG_SLCMIS3 0x0C8 +#define D40_DREG_SLCMIS4 0x0CC + +#define D40_DREG_SLCICR1 0x0D0 +#define D40_DREG_SLCICR2 0x0D4 +#define D40_DREG_SLCICR3 0x0D8 +#define D40_DREG_SLCICR4 0x0DC + +#define D40_DREG_SLCTIS1 0x0E0 +#define D40_DREG_SLCTIS2 0x0E4 +#define D40_DREG_SLCTIS3 0x0E8 +#define D40_DREG_SLCTIS4 0x0EC + +#define D40_DREG_SLCEIS1 0x0F0 +#define D40_DREG_SLCEIS2 0x0F4 +#define D40_DREG_SLCEIS3 0x0F8 +#define D40_DREG_SLCEIS4 0x0FC + +#define D40_DREG_FSESS1 0x100 +#define D40_DREG_FSESS2 0x104 + +#define D40_DREG_FSEBS1 0x108 +#define D40_DREG_FSEBS2 0x10C + #define D40_DREG_PSEG1 0x110 #define D40_DREG_PSEG2 0x114 #define D40_DREG_PSEG3 0x118 @@ -188,6 +232,86 @@ #define D40_DREG_RCEG2 0x144 #define D40_DREG_RCEG3 0x148 #define D40_DREG_RCEG4 0x14C + +#define D40_DREG_PREFOT 0x15C +#define D40_DREG_EXTCFG 0x160 + +#define D40_DREG_CPSEG1 0x200 +#define D40_DREG_CPSEG2 0x204 +#define D40_DREG_CPSEG3 0x208 +#define D40_DREG_CPSEG4 0x20C +#define D40_DREG_CPSEG5 0x210 + +#define D40_DREG_CPCEG1 0x220 +#define D40_DREG_CPCEG2 0x224 +#define D40_DREG_CPCEG3 0x228 +#define D40_DREG_CPCEG4 0x22C +#define D40_DREG_CPCEG5 0x230 + +#define D40_DREG_CRSEG1 0x240 +#define D40_DREG_CRSEG2 0x244 +#define D40_DREG_CRSEG3 0x248 +#define D40_DREG_CRSEG4 0x24C +#define D40_DREG_CRSEG5 0x250 + +#define D40_DREG_CRCEG1 0x260 +#define D40_DREG_CRCEG2 0x264 +#define D40_DREG_CRCEG3 0x268 +#define D40_DREG_CRCEG4 0x26C +#define D40_DREG_CRCEG5 0x270 + +#define D40_DREG_CFSESS1 0x280 +#define D40_DREG_CFSESS2 0x284 +#define D40_DREG_CFSESS3 0x288 + +#define D40_DREG_CFSEBS1 0x290 +#define D40_DREG_CFSEBS2 0x294 +#define D40_DREG_CFSEBS3 0x298 + +#define D40_DREG_CLCMIS1 0x300 +#define D40_DREG_CLCMIS2 0x304 +#define D40_DREG_CLCMIS3 0x308 +#define D40_DREG_CLCMIS4 0x30C +#define D40_DREG_CLCMIS5 0x310 + +#define D40_DREG_CLCICR1 0x320 +#define D40_DREG_CLCICR2 0x324 +#define D40_DREG_CLCICR3 0x328 +#define D40_DREG_CLCICR4 0x32C +#define D40_DREG_CLCICR5 0x330 + +#define D40_DREG_CLCTIS1 0x340 +#define D40_DREG_CLCTIS2 0x344 +#define D40_DREG_CLCTIS3 0x348 +#define D40_DREG_CLCTIS4 0x34C +#define D40_DREG_CLCTIS5 0x350 + +#define D40_DREG_CLCEIS1 0x360 +#define D40_DREG_CLCEIS2 0x364 +#define D40_DREG_CLCEIS3 0x368 +#define D40_DREG_CLCEIS4 0x36C +#define D40_DREG_CLCEIS5 0x370 + +#define D40_DREG_CPCMIS 0x380 +#define D40_DREG_CPCICR 0x384 +#define D40_DREG_CPCTIS 0x388 +#define D40_DREG_CPCEIS 0x38C + +#define D40_DREG_SCCIDA1 0xE80 +#define D40_DREG_SCCIDA2 0xE90 +#define D40_DREG_SCCIDA3 0xEA0 +#define D40_DREG_SCCIDA4 0xEB0 +#define D40_DREG_SCCIDA5 0xEC0 + +#define D40_DREG_SCCIDB1 0xE84 +#define D40_DREG_SCCIDB2 0xE94 +#define D40_DREG_SCCIDB3 0xEA4 +#define D40_DREG_SCCIDB4 0xEB4 +#define D40_DREG_SCCIDB5 0xEC4 + +#define D40_DREG_PRSCCIDA 0xF80 +#define D40_DREG_PRSCCIDB 0xF84 + #define D40_DREG_STFU 0xFC8 #define D40_DREG_ICFG 0xFCC #define D40_DREG_PERIPHID0 0xFE0 From 4226dd86b10ac44f8e98599f6a73e3a1b929f8eb Mon Sep 17 00:00:00 2001 From: Fabio Baltieri Date: Thu, 13 Dec 2012 13:46:16 +0100 Subject: [PATCH 10/16] dmaengine: ste_dma40: add a done queue for completed descriptors This is to keep the active queue for only those transfers which are actually active in the hardware. Descriptors will be moved to the done queue after they are completed in the hardware (interrupt handler) but before all the cleanup work has been completed (tasklet). Mostly based on a previous patch by Rabin Vincent. Cc: Rabin Vincent Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40.c | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index 67e565bffe16..ff21dcbd8208 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -382,6 +382,7 @@ struct d40_base; * @client: Cliented owned descriptor list. * @pending_queue: Submitted jobs, to be issued by issue_pending() * @active: Active descriptor. + * @done: Completed jobs * @queue: Queued jobs. * @prepare_queue: Prepared jobs. * @dma_cfg: The client configuration of this dma channel. @@ -407,6 +408,7 @@ struct d40_chan { struct list_head client; struct list_head pending_queue; struct list_head active; + struct list_head done; struct list_head queue; struct list_head prepare_queue; struct stedma40_chan_cfg dma_cfg; @@ -754,6 +756,11 @@ static void d40_phy_lli_load(struct d40_chan *chan, struct d40_desc *desc) writel(lli_dst->reg_lnk, base + D40_CHAN_REG_SDLNK); } +static void d40_desc_done(struct d40_chan *d40c, struct d40_desc *desc) +{ + list_add_tail(&desc->node, &d40c->done); +} + static void d40_log_lli_to_lcxa(struct d40_chan *chan, struct d40_desc *desc) { struct d40_lcla_pool *pool = &chan->base->lcla_pool; @@ -914,6 +921,14 @@ static struct d40_desc *d40_first_queued(struct d40_chan *d40c) return d; } +static struct d40_desc *d40_first_done(struct d40_chan *d40c) +{ + if (list_empty(&d40c->done)) + return NULL; + + return list_first_entry(&d40c->done, struct d40_desc, node); +} + static int d40_psize_2_burst_size(bool is_log, int psize) { if (is_log) { @@ -1104,6 +1119,12 @@ static void d40_term_all(struct d40_chan *d40c) struct d40_desc *d40d; struct d40_desc *_d; + /* Release completed descriptors */ + while ((d40d = d40_first_done(d40c))) { + d40_desc_remove(d40d); + d40_desc_free(d40c, d40d); + } + /* Release active descriptors */ while ((d40d = d40_first_active_get(d40c))) { d40_desc_remove(d40d); @@ -1541,6 +1562,9 @@ static void dma_tc_handle(struct d40_chan *d40c) pm_runtime_put_autosuspend(d40c->base->dev); } + d40_desc_remove(d40d); + d40_desc_done(d40c, d40d); + d40c->pending_tx++; tasklet_schedule(&d40c->tasklet); @@ -1556,10 +1580,14 @@ static void dma_tasklet(unsigned long data) spin_lock_irqsave(&d40c->lock, flags); - /* Get first active entry from list */ - d40d = d40_first_active_get(d40c); - if (d40d == NULL) - goto err; + /* Get first entry from the done list */ + d40d = d40_first_done(d40c); + if (d40d == NULL) { + /* Check if we have reached here for cyclic job */ + d40d = d40_first_active_get(d40c); + if (d40d == NULL || !d40d->cyclic) + goto err; + } if (!d40d->cyclic) dma_cookie_complete(&d40d->txd); @@ -2823,6 +2851,7 @@ static void __init d40_chan_init(struct d40_base *base, struct dma_device *dma, d40c->log_num = D40_PHY_CHAN; + INIT_LIST_HEAD(&d40c->done); INIT_LIST_HEAD(&d40c->active); INIT_LIST_HEAD(&d40c->queue); INIT_LIST_HEAD(&d40c->pending_queue); From 762eb33fdebed34d98943d85ee1425663d7cceaa Mon Sep 17 00:00:00 2001 From: Fabio Baltieri Date: Thu, 13 Dec 2012 11:38:39 +0100 Subject: [PATCH 11/16] dmaengine: ste_dma40: add missing kernel-doc entry Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- include/linux/platform_data/dma-ste-dma40.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/platform_data/dma-ste-dma40.h b/include/linux/platform_data/dma-ste-dma40.h index 833cb959f3df..b99024ba94ec 100644 --- a/include/linux/platform_data/dma-ste-dma40.h +++ b/include/linux/platform_data/dma-ste-dma40.h @@ -147,6 +147,7 @@ struct stedma40_chan_cfg { * @memcpy_conf_log: default configuration of logical channel memcpy * @disabled_channels: A vector, ending with -1, that marks physical channels * that are for different reasons not available for the driver. + * @use_esram_lcla: flag for mapping the lcla into esram region * @num_of_phy_chans: The number of physical channels implemented in HW. * 0 means reading the number of channels from DMA HW but this is only valid * for 'multiple of 4' channels, like 8. From f26e03ad2b50be50c98f8ecb1fd9dbdf94db91ab Mon Sep 17 00:00:00 2001 From: Fabio Baltieri Date: Thu, 13 Dec 2012 17:12:37 +0100 Subject: [PATCH 12/16] dmaengine: ste_dma40: minor cosmetic fixes This patch contains various non functional cosmetic fixes. Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40.c | 33 ++++++++++++--------------------- drivers/dma/ste_dma40_ll.c | 2 +- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index ff21dcbd8208..623779e580c3 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -1609,13 +1609,11 @@ static void dma_tasklet(unsigned long data) if (async_tx_test_ack(&d40d->txd)) { d40_desc_remove(d40d); d40_desc_free(d40c, d40d); - } else { - if (!d40d->is_in_client_list) { - d40_desc_remove(d40d); - d40_lcla_free_all(d40c, d40d); - list_add_tail(&d40d->node, &d40c->client); - d40d->is_in_client_list = true; - } + } else if (!d40d->is_in_client_list) { + d40_desc_remove(d40d); + d40_lcla_free_all(d40c, d40d); + list_add_tail(&d40d->node, &d40c->client); + d40d->is_in_client_list = true; } } @@ -2123,7 +2121,6 @@ _exit: } - static u32 stedma40_residue(struct dma_chan *chan) { struct d40_chan *d40c = @@ -2199,7 +2196,6 @@ d40_prep_sg_phy(struct d40_chan *chan, struct d40_desc *desc, return ret < 0 ? ret : 0; } - static struct d40_desc * d40_prep_desc(struct d40_chan *chan, struct scatterlist *sg, unsigned int sg_len, unsigned long dma_flags) @@ -2225,7 +2221,6 @@ d40_prep_desc(struct d40_chan *chan, struct scatterlist *sg, goto err; } - desc->lli_current = 0; desc->txd.flags = dma_flags; desc->txd.tx_submit = d40_tx_submit; @@ -2274,7 +2269,6 @@ d40_prep_sg(struct dma_chan *dchan, struct scatterlist *sg_src, return NULL; } - spin_lock_irqsave(&chan->lock, flags); desc = d40_prep_desc(chan, sg_src, sg_len, dma_flags); @@ -2432,11 +2426,11 @@ static int d40_alloc_chan_resources(struct dma_chan *chan) if (d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_MEM) d40c->lcpa = d40c->base->lcpa_base + - d40c->dma_cfg.src_dev_type * D40_LCPA_CHAN_SIZE; + d40c->dma_cfg.src_dev_type * D40_LCPA_CHAN_SIZE; else d40c->lcpa = d40c->base->lcpa_base + - d40c->dma_cfg.dst_dev_type * - D40_LCPA_CHAN_SIZE + D40_LCPA_CHAN_DST_DELTA; + d40c->dma_cfg.dst_dev_type * + D40_LCPA_CHAN_SIZE + D40_LCPA_CHAN_DST_DELTA; } dev_dbg(chan2dev(d40c), "allocated %s channel (phy %d%s)\n", @@ -2471,7 +2465,6 @@ static void d40_free_chan_resources(struct dma_chan *chan) return; } - spin_lock_irqsave(&d40c->lock, flags); err = d40_free_dma(d40c); @@ -2514,12 +2507,10 @@ d40_prep_memcpy_sg(struct dma_chan *chan, return d40_prep_sg(chan, src_sg, dst_sg, src_nents, DMA_NONE, dma_flags); } -static struct dma_async_tx_descriptor *d40_prep_slave_sg(struct dma_chan *chan, - struct scatterlist *sgl, - unsigned int sg_len, - enum dma_transfer_direction direction, - unsigned long dma_flags, - void *context) +static struct dma_async_tx_descriptor * +d40_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, + unsigned int sg_len, enum dma_transfer_direction direction, + unsigned long dma_flags, void *context) { if (direction != DMA_DEV_TO_MEM && direction != DMA_MEM_TO_DEV) return NULL; diff --git a/drivers/dma/ste_dma40_ll.c b/drivers/dma/ste_dma40_ll.c index 1cfe7ab50c6b..7180e0d41722 100644 --- a/drivers/dma/ste_dma40_ll.c +++ b/drivers/dma/ste_dma40_ll.c @@ -251,7 +251,7 @@ d40_phy_buf_to_lli(struct d40_phy_lli *lli, dma_addr_t addr, u32 size, return lli; - err: +err: return NULL; } From 7ce529efbcf6fdcb1854e4634adf7f6a18216e81 Mon Sep 17 00:00:00 2001 From: Fabio Baltieri Date: Tue, 18 Dec 2012 16:59:09 +0100 Subject: [PATCH 13/16] dmaengine: ste_dma40: minor code readability fixes Use internal variables to the cycles to improve code readability, no functional changes. Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index 623779e580c3..e317debdbe5a 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -639,19 +639,18 @@ static int d40_lcla_alloc_one(struct d40_chan *d40c, unsigned long flags; int i; int ret = -EINVAL; - int p; spin_lock_irqsave(&d40c->base->lcla_pool.lock, flags); - p = d40c->phy_chan->num * D40_LCLA_LINK_PER_EVENT_GRP; - /* * Allocate both src and dst at the same time, therefore the half * start on 1 since 0 can't be used since zero is used as end marker. */ for (i = 1 ; i < D40_LCLA_LINK_PER_EVENT_GRP / 2; i++) { - if (!d40c->base->lcla_pool.alloc_map[p + i]) { - d40c->base->lcla_pool.alloc_map[p + i] = d40d; + int idx = d40c->phy_chan->num * D40_LCLA_LINK_PER_EVENT_GRP + i; + + if (!d40c->base->lcla_pool.alloc_map[idx]) { + d40c->base->lcla_pool.alloc_map[idx] = d40d; d40d->lcla_alloc++; ret = i; break; @@ -676,10 +675,10 @@ static int d40_lcla_free_all(struct d40_chan *d40c, spin_lock_irqsave(&d40c->base->lcla_pool.lock, flags); for (i = 1 ; i < D40_LCLA_LINK_PER_EVENT_GRP / 2; i++) { - if (d40c->base->lcla_pool.alloc_map[d40c->phy_chan->num * - D40_LCLA_LINK_PER_EVENT_GRP + i] == d40d) { - d40c->base->lcla_pool.alloc_map[d40c->phy_chan->num * - D40_LCLA_LINK_PER_EVENT_GRP + i] = NULL; + int idx = d40c->phy_chan->num * D40_LCLA_LINK_PER_EVENT_GRP + i; + + if (d40c->base->lcla_pool.alloc_map[idx] == d40d) { + d40c->base->lcla_pool.alloc_map[idx] = NULL; d40d->lcla_alloc--; if (d40d->lcla_alloc == 0) { ret = 0; From 7407048bec896268b50e3c43c1d012a4764dc210 Mon Sep 17 00:00:00 2001 From: Fabio Baltieri Date: Tue, 18 Dec 2012 12:25:14 +0100 Subject: [PATCH 14/16] dmaengine: ste_dma40: add software lli support This patch add support to manage LLI by SW for select phy channels. There is a HW issue in certain controllers due to which on certain occassions HW LLI cannot be used on some physical channels. To avoid the HW issue on a specific phy channel, the phy channel number can be added to the list of soft_lli_channels and there after all the transfers on that channel will use software LLI, for peripheral to memory transfers. SoftLLI introduces relink overhead, that could impact performace for certain use cases. This is based on a previous patch of Narayanan Gopalakrishnan. Cc: Shreshtha Kumar Sahu Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40.c | 20 +++++++++++++++++++- include/linux/platform_data/dma-ste-dma40.h | 8 ++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index e317debdbe5a..2ecefb7113b9 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -355,6 +355,7 @@ struct d40_lcla_pool { * @allocated_dst: Same as for src but is dst. * allocated_dst and allocated_src uses the D40_ALLOC* defines as well as * event line number. + * @use_soft_lli: To mark if the linked lists of channel are managed by SW. */ struct d40_phy_res { spinlock_t lock; @@ -362,6 +363,7 @@ struct d40_phy_res { int num; u32 allocated_src; u32 allocated_dst; + bool use_soft_lli; }; struct d40_base; @@ -783,7 +785,16 @@ static void d40_log_lli_to_lcxa(struct d40_chan *chan, struct d40_desc *desc) * can't link back to the one in LCPA space */ if (linkback || (lli_len - lli_current > 1)) { - curr_lcla = d40_lcla_alloc_one(chan, desc); + /* + * If the channel is expected to use only soft_lli don't + * allocate a lcla. This is to avoid a HW issue that exists + * in some controller during a peripheral to memory transfer + * that uses linked lists. + */ + if (!(chan->phy_chan->use_soft_lli && + chan->dma_cfg.dir == STEDMA40_PERIPH_TO_MEM)) + curr_lcla = d40_lcla_alloc_one(chan, desc); + first_lcla = curr_lcla; } @@ -3063,6 +3074,13 @@ static int __init d40_phy_res_init(struct d40_base *base) num_phy_chans_avail--; } + /* Mark soft_lli channels */ + for (i = 0; i < base->plat_data->num_of_soft_lli_chans; i++) { + int chan = base->plat_data->soft_lli_chans[i]; + + base->phy_res[chan].use_soft_lli = true; + } + dev_info(base->dev, "%d of %d physical DMA channels available\n", num_phy_chans_avail, base->num_phy_chans); diff --git a/include/linux/platform_data/dma-ste-dma40.h b/include/linux/platform_data/dma-ste-dma40.h index b99024ba94ec..4b781014b0a0 100644 --- a/include/linux/platform_data/dma-ste-dma40.h +++ b/include/linux/platform_data/dma-ste-dma40.h @@ -147,6 +147,12 @@ struct stedma40_chan_cfg { * @memcpy_conf_log: default configuration of logical channel memcpy * @disabled_channels: A vector, ending with -1, that marks physical channels * that are for different reasons not available for the driver. + * @soft_lli_chans: A vector, that marks physical channels will use LLI by SW + * which avoids HW bug that exists in some versions of the controller. + * SoftLLI introduces relink overhead that could impact performace for + * certain use cases. + * @num_of_soft_lli_chans: The number of channels that needs to be configured + * to use SoftLLI. * @use_esram_lcla: flag for mapping the lcla into esram region * @num_of_phy_chans: The number of physical channels implemented in HW. * 0 means reading the number of channels from DMA HW but this is only valid @@ -161,6 +167,8 @@ struct stedma40_platform_data { struct stedma40_chan_cfg *memcpy_conf_phy; struct stedma40_chan_cfg *memcpy_conf_log; int disabled_channels[STEDMA40_MAX_PHYS]; + int *soft_lli_chans; + int num_of_soft_lli_chans; bool use_esram_lcla; int num_of_phy_chans; }; From 53d6d68f3c1792bce0144d8499435468e425a995 Mon Sep 17 00:00:00 2001 From: Fabio Baltieri Date: Wed, 19 Dec 2012 14:41:56 +0100 Subject: [PATCH 15/16] dmaengine: set_dma40: ignore spurious interrupts Some DMA channels may be used by other cores in the SoC. This patch modifies the dma interrupt handler to ignore interrupts from unknown channels. Cc: Rabin Vincent Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index 2ecefb7113b9..a97bbd3a4a47 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -1677,13 +1677,22 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data) row = chan / BITS_PER_LONG; idx = chan & (BITS_PER_LONG - 1); - /* ACK interrupt */ - writel(1 << idx, base->virtbase + il[row].clr); - if (il[row].offset == D40_PHY_CHAN) d40c = base->lookup_phy_chans[idx]; else d40c = base->lookup_log_chans[il[row].offset + idx]; + + if (!d40c) { + /* + * No error because this can happen if something else + * in the system is using the channel. + */ + continue; + } + + /* ACK interrupt */ + writel(1 << idx, base->virtbase + il[row].clr); + spin_lock(&d40c->lock); if (!il[row].is_error) From da2ac56a1bc9c6c56244aa9ca990d5c5c7574b5f Mon Sep 17 00:00:00 2001 From: Fabio Baltieri Date: Mon, 7 Jan 2013 10:58:35 +0100 Subject: [PATCH 16/16] dmaengine: set_dma40: balance clock in probe fail code Clock code was changed to use clk_prepare_enable in: b707c65 dma/ste_dma40: Fixup clock usage during probe but clk_disable on probe fail path was not updated. This patch fix this by using clk_disable_unprepare in place of clk_disable. Acked-by: Ulf Hansson Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Fabio Baltieri --- drivers/dma/ste_dma40.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index a97bbd3a4a47..d77d41d7c463 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -3634,7 +3634,7 @@ failure: release_mem_region(base->phy_start, base->phy_size); if (base->clk) { - clk_disable(base->clk); + clk_disable_unprepare(base->clk); clk_put(base->clk); }