* [PATCH 30/48] ARM: PL08x: move default cctl into txd structure
From: Russell King - ARM Linux @ 2011-01-03 22:40 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
Rather than modifying platform data while preparing a transfer, copy
the cctl value into the txd structure and modify the value there.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 21 ++++++---------------
include/linux/amba/pl08x.h | 3 ++-
2 files changed, 8 insertions(+), 16 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index f0a2988..6d224d4 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -577,7 +577,6 @@ static inline size_t pl08x_pre_boundary(u32 addr, size_t len)
static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
struct pl08x_txd *txd)
{
- struct pl08x_channel_data *cd = txd->cd;
struct pl08x_bus_data *mbus, *sbus;
size_t remainder;
int num_llis = 0;
@@ -595,17 +594,8 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
pl08x->pool_ctr++;
- /*
- * Initialize bus values for this transfer
- * from the passed optimal values
- */
- if (!cd) {
- dev_err(&pl08x->adev->dev, "%s no channel data\n", __func__);
- return 0;
- }
-
- /* Get the default CCTL from the platform data */
- cctl = cd->cctl;
+ /* Get the default CCTL */
+ cctl = txd->cctl;
/*
* On the PL080 we have two bus masters and we
@@ -1358,11 +1348,11 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy(
txd->dstbus.addr = dest;
/* Set platform data for m2m */
- txd->cd = &pl08x->pd->memcpy_channel;
txd->ccfg |= PL080_FLOW_MEM2MEM << PL080_CONFIG_FLOW_CONTROL_SHIFT;
+ txd->cctl = pl08x->pd->memcpy_channel.cctl;
/* Both to be incremented or the code will break */
- txd->cd->cctl |= PL080_CONTROL_SRC_INCR | PL080_CONTROL_DST_INCR;
+ txd->cctl |= PL080_CONTROL_SRC_INCR | PL080_CONTROL_DST_INCR;
txd->len = len;
ret = pl08x_prep_channel_resources(plchan, txd);
@@ -1415,6 +1405,8 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
* channel target address dynamically at runtime.
*/
txd->direction = direction;
+ txd->cctl = plchan->cd->cctl;
+
if (direction == DMA_TO_DEVICE) {
txd->ccfg |= PL080_FLOW_MEM2PER << PL080_CONFIG_FLOW_CONTROL_SHIFT;
txd->srcbus.addr = sgl->dma_address;
@@ -1434,7 +1426,6 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
"%s direction unsupported\n", __func__);
return NULL;
}
- txd->cd = plchan->cd;
txd->len = sgl->length;
ret = pl08x_prep_channel_resources(plchan, txd);
diff --git a/include/linux/amba/pl08x.h b/include/linux/amba/pl08x.h
index 8e74cb1..8d90830 100644
--- a/include/linux/amba/pl08x.h
+++ b/include/linux/amba/pl08x.h
@@ -110,8 +110,9 @@ struct pl08x_txd {
size_t len;
dma_addr_t llis_bus;
void *llis_va;
- struct pl08x_channel_data *cd;
bool active;
+ /* Default cctl value for LLIs */
+ u32 cctl;
/*
* Settings to be put into the physical channel when we
* trigger this txd. Other registers are in llis_va[0].
--
1.6.2.5
^ permalink raw reply related
* [PATCH 31/48] ARM: PL08x: move cctl increment and protection setup to prep_slave_sg
From: Russell King - ARM Linux @ 2011-01-03 22:40 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
We don't need to initialize the cctl increment and protection values
in the runtime_config method - we have all the inforamtion to setup
these values in prep_slave_sg(). Move their initialization there.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 15 ++++++++-------
1 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index 6d224d4..72cc971 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -1148,12 +1148,10 @@ static void dma_set_runtime_config(struct dma_chan *chan,
plchan->runtime_direction = config->direction;
if (config->direction == DMA_TO_DEVICE) {
plchan->runtime_addr = config->dst_addr;
- cctl |= PL080_CONTROL_SRC_INCR;
addr_width = config->dst_addr_width;
maxburst = config->dst_maxburst;
} else if (config->direction == DMA_FROM_DEVICE) {
plchan->runtime_addr = config->src_addr;
- cctl |= PL080_CONTROL_DST_INCR;
addr_width = config->src_addr_width;
maxburst = config->src_maxburst;
} else {
@@ -1197,10 +1195,6 @@ static void dma_set_runtime_config(struct dma_chan *chan,
cctl |= burst_sizes[i].reg;
}
- /* Access the cell in privileged mode, non-bufferable, non-cacheable */
- cctl &= ~PL080_CONTROL_PROT_MASK;
- cctl |= PL080_CONTROL_PROT_SYS;
-
/* Modify the default channel data to fit PrimeCell request */
cd->cctl = cctl;
@@ -1405,10 +1399,16 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
* channel target address dynamically at runtime.
*/
txd->direction = direction;
- txd->cctl = plchan->cd->cctl;
+ txd->cctl = plchan->cd->cctl &
+ ~(PL080_CONTROL_SRC_INCR | PL080_CONTROL_DST_INCR |
+ PL080_CONTROL_PROT_MASK);
+
+ /* Access the cell in privileged mode, non-bufferable, non-cacheable */
+ txd->cctl |= PL080_CONTROL_PROT_SYS;
if (direction == DMA_TO_DEVICE) {
txd->ccfg |= PL080_FLOW_MEM2PER << PL080_CONFIG_FLOW_CONTROL_SHIFT;
+ txd->cctl |= PL080_CONTROL_SRC_INCR;
txd->srcbus.addr = sgl->dma_address;
if (plchan->runtime_addr)
txd->dstbus.addr = plchan->runtime_addr;
@@ -1416,6 +1416,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
txd->dstbus.addr = plchan->cd->addr;
} else if (direction == DMA_FROM_DEVICE) {
txd->ccfg |= PL080_FLOW_PER2MEM << PL080_CONFIG_FLOW_CONTROL_SHIFT;
+ txd->cctl |= PL080_CONTROL_DST_INCR;
if (plchan->runtime_addr)
txd->srcbus.addr = plchan->runtime_addr;
else
--
1.6.2.5
^ permalink raw reply related
* [PATCH 32/48] ARM: PL08x: move AHB master port selection into prep_* functions
From: Russell King - ARM Linux @ 2011-01-03 22:40 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
As we initialize the default cctl value in the prep_* functions along
with the increment settings, we don't need to repeat the selection of
the AHB ports each time we create a LLI entry. Do this in the prep_*
functions once per transfer.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 47 ++++++++++++++++++++++-----------------------
1 files changed, 23 insertions(+), 24 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index 72cc971..4ee0ab1 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -597,26 +597,6 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
/* Get the default CCTL */
cctl = txd->cctl;
- /*
- * On the PL080 we have two bus masters and we
- * should select one for source and one for
- * destination. We try to use AHB2 for the
- * bus which does not increment (typically the
- * peripheral) else we just choose something.
- */
- cctl &= ~(PL080_CONTROL_DST_AHB2 | PL080_CONTROL_SRC_AHB2);
- if (pl08x->vd->dualmaster) {
- if (cctl & PL080_CONTROL_SRC_INCR)
- /* Source increments, use AHB2 for destination */
- cctl |= PL080_CONTROL_DST_AHB2;
- else if (cctl & PL080_CONTROL_DST_INCR)
- /* Destination increments, use AHB2 for source */
- cctl |= PL080_CONTROL_SRC_AHB2;
- else
- /* Just pick something, source AHB1 dest AHB2 */
- cctl |= PL080_CONTROL_DST_AHB2;
- }
-
/* Find maximum width of the source bus */
txd->srcbus.maxwidth =
pl08x_get_bytes_for_cctl((cctl & PL080_CONTROL_SWIDTH_MASK) >>
@@ -1340,14 +1320,25 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy(
txd->direction = DMA_NONE;
txd->srcbus.addr = src;
txd->dstbus.addr = dest;
+ txd->len = len;
/* Set platform data for m2m */
txd->ccfg |= PL080_FLOW_MEM2MEM << PL080_CONFIG_FLOW_CONTROL_SHIFT;
- txd->cctl = pl08x->pd->memcpy_channel.cctl;
+ txd->cctl = pl08x->pd->memcpy_channel.cctl &
+ ~(PL080_CONTROL_DST_AHB2 | PL080_CONTROL_SRC_AHB2);
/* Both to be incremented or the code will break */
txd->cctl |= PL080_CONTROL_SRC_INCR | PL080_CONTROL_DST_INCR;
- txd->len = len;
+
+ /*
+ * On the PL080 we have two bus masters and we should select one for
+ * source and one for destination. We try to use AHB2 for the bus
+ * which does not increment (typically the peripheral) else we just
+ * choose something.
+ */
+ if (pl08x->vd->dualmaster)
+ /* Source increments, use AHB2 for destination */
+ txd->cctl |= PL080_CONTROL_DST_AHB2;
ret = pl08x_prep_channel_resources(plchan, txd);
if (ret)
@@ -1399,8 +1390,11 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
* channel target address dynamically at runtime.
*/
txd->direction = direction;
+ txd->len = sgl->length;
+
txd->cctl = plchan->cd->cctl &
- ~(PL080_CONTROL_SRC_INCR | PL080_CONTROL_DST_INCR |
+ ~(PL080_CONTROL_SRC_AHB2 | PL080_CONTROL_DST_AHB2 |
+ PL080_CONTROL_SRC_INCR | PL080_CONTROL_DST_INCR |
PL080_CONTROL_PROT_MASK);
/* Access the cell in privileged mode, non-bufferable, non-cacheable */
@@ -1409,6 +1403,9 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
if (direction == DMA_TO_DEVICE) {
txd->ccfg |= PL080_FLOW_MEM2PER << PL080_CONFIG_FLOW_CONTROL_SHIFT;
txd->cctl |= PL080_CONTROL_SRC_INCR;
+ if (pl08x->vd->dualmaster)
+ /* Source increments, use AHB2 for destination */
+ txd->cctl |= PL080_CONTROL_DST_AHB2;
txd->srcbus.addr = sgl->dma_address;
if (plchan->runtime_addr)
txd->dstbus.addr = plchan->runtime_addr;
@@ -1417,6 +1414,9 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
} else if (direction == DMA_FROM_DEVICE) {
txd->ccfg |= PL080_FLOW_PER2MEM << PL080_CONFIG_FLOW_CONTROL_SHIFT;
txd->cctl |= PL080_CONTROL_DST_INCR;
+ if (pl08x->vd->dualmaster)
+ /* Destination increments, use AHB2 for source */
+ txd->cctl |= PL080_CONTROL_SRC_AHB2;
if (plchan->runtime_addr)
txd->srcbus.addr = plchan->runtime_addr;
else
@@ -1427,7 +1427,6 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
"%s direction unsupported\n", __func__);
return NULL;
}
- txd->len = sgl->length;
ret = pl08x_prep_channel_resources(plchan, txd);
if (ret)
--
1.6.2.5
^ permalink raw reply related
* [PATCH 33/48] ARM: PL08x: allow AHB master port selection to be configured
From: Russell King - ARM Linux @ 2011-01-03 22:41 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
Platforms need to be able to control which AHB master interface is used,
as each AHB master interface may be asymetric. Allow the interfaces
used for fetching LLIs, memory, and each peripheral to be configured
individually.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 77 ++++++++++++++++++++++++-------------------
include/linux/amba/pl08x.h | 15 +++++++-
2 files changed, 56 insertions(+), 36 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index 4ee0ab1..986c127 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -126,6 +126,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
+ * @mem_buses: set to indicate memory transfers on AHB2.
* @lock: a spinlock for this struct
*/
struct pl08x_driver_data {
@@ -138,6 +140,8 @@ struct pl08x_driver_data {
struct pl08x_phy_chan *phy_chans;
struct dma_pool *pool;
int pool_ctr;
+ u8 lli_buses;
+ u8 mem_buses;
spinlock_t lock;
};
@@ -526,20 +530,12 @@ static int pl08x_fill_lli_for_desc(struct pl08x_driver_data *pl08x,
BUG_ON(num_llis >= MAX_NUM_TSFR_LLIS);
- llis_va[num_llis].cctl = cctl;
- llis_va[num_llis].src = txd->srcbus.addr;
- llis_va[num_llis].dst = txd->dstbus.addr;
-
- /*
- * On versions with dual masters, you can optionally AND on
- * PL080_LLI_LM_AHB2 to the LLI to tell the hardware to read
- * in new LLIs with that controller, but we always try to
- * choose AHB1 to point into memory. The idea is to have AHB2
- * fixed on the peripheral and AHB1 messing around in the
- * memory. So we don't manipulate this bit currently.
- */
-
+ llis_va[num_llis].cctl = cctl;
+ llis_va[num_llis].src = txd->srcbus.addr;
+ llis_va[num_llis].dst = txd->dstbus.addr;
llis_va[num_llis].lli = llis_bus + (num_llis + 1) * sizeof(struct pl08x_lli);
+ if (pl08x->lli_buses & PL08X_AHB2)
+ llis_va[num_llis].lli |= PL080_LLI_LM_AHB2;
if (cctl & PL080_CONTROL_SRC_INCR)
txd->srcbus.addr += len;
@@ -639,13 +635,6 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
pl08x_choose_master_bus(&txd->srcbus, &txd->dstbus,
&mbus, &sbus, cctl);
-
- /*
- * The lowest bit of the LLI register
- * is also used to indicate which master to
- * use for reading the LLIs.
- */
-
if (txd->len < mbus->buswidth) {
/*
* Less than a bus width available
@@ -1282,6 +1271,23 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan,
return 0;
}
+/*
+ * Given the source and destination available bus masks, select which
+ * will be routed to each port. We try to have source and destination
+ * on separate ports, but always respect the allowable settings.
+ */
+static u32 pl08x_select_bus(struct pl08x_driver_data *pl08x, u8 src, u8 dst)
+{
+ u32 cctl = 0;
+
+ if (!(dst & PL08X_AHB1) || ((dst & PL08X_AHB2) && (src & PL08X_AHB1)))
+ cctl |= PL080_CONTROL_DST_AHB2;
+ if (!(src & PL08X_AHB1) || ((src & PL08X_AHB2) && !(dst & PL08X_AHB2)))
+ cctl |= PL080_CONTROL_SRC_AHB2;
+
+ return cctl;
+}
+
static struct pl08x_txd *pl08x_get_txd(struct pl08x_dma_chan *plchan)
{
struct pl08x_txd *txd = kzalloc(sizeof(struct pl08x_txd), GFP_NOWAIT);
@@ -1330,15 +1336,9 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy(
/* Both to be incremented or the code will break */
txd->cctl |= PL080_CONTROL_SRC_INCR | PL080_CONTROL_DST_INCR;
- /*
- * On the PL080 we have two bus masters and we should select one for
- * source and one for destination. We try to use AHB2 for the bus
- * which does not increment (typically the peripheral) else we just
- * choose something.
- */
if (pl08x->vd->dualmaster)
- /* Source increments, use AHB2 for destination */
- txd->cctl |= PL080_CONTROL_DST_AHB2;
+ txd->cctl |= pl08x_select_bus(pl08x,
+ pl08x->mem_buses, pl08x->mem_buses);
ret = pl08x_prep_channel_resources(plchan, txd);
if (ret)
@@ -1359,6 +1359,7 @@ 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;
+ u8 src_buses, dst_buses;
int ret;
/*
@@ -1403,31 +1404,31 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
if (direction == DMA_TO_DEVICE) {
txd->ccfg |= PL080_FLOW_MEM2PER << PL080_CONFIG_FLOW_CONTROL_SHIFT;
txd->cctl |= PL080_CONTROL_SRC_INCR;
- if (pl08x->vd->dualmaster)
- /* Source increments, use AHB2 for destination */
- txd->cctl |= PL080_CONTROL_DST_AHB2;
txd->srcbus.addr = sgl->dma_address;
if (plchan->runtime_addr)
txd->dstbus.addr = plchan->runtime_addr;
else
txd->dstbus.addr = plchan->cd->addr;
+ src_buses = pl08x->mem_buses;
+ dst_buses = plchan->cd->periph_buses;
} else if (direction == DMA_FROM_DEVICE) {
txd->ccfg |= PL080_FLOW_PER2MEM << PL080_CONFIG_FLOW_CONTROL_SHIFT;
txd->cctl |= PL080_CONTROL_DST_INCR;
- if (pl08x->vd->dualmaster)
- /* Destination increments, use AHB2 for source */
- txd->cctl |= PL080_CONTROL_SRC_AHB2;
if (plchan->runtime_addr)
txd->srcbus.addr = plchan->runtime_addr;
else
txd->srcbus.addr = plchan->cd->addr;
txd->dstbus.addr = sgl->dma_address;
+ src_buses = plchan->cd->periph_buses;
+ dst_buses = pl08x->mem_buses;
} else {
dev_err(&pl08x->adev->dev,
"%s direction unsupported\n", __func__);
return NULL;
}
+ txd->cctl |= pl08x_select_bus(pl08x, src_buses, dst_buses);
+
ret = pl08x_prep_channel_resources(plchan, txd);
if (ret)
return NULL;
@@ -1879,6 +1880,14 @@ static int pl08x_probe(struct amba_device *adev, struct amba_id *id)
pl08x->adev = adev;
pl08x->vd = vd;
+ /* By default, AHB1 only. If dualmaster, from platform */
+ pl08x->lli_buses = PL08X_AHB1;
+ pl08x->mem_buses = PL08X_AHB1;
+ if (pl08x->vd->dualmaster) {
+ pl08x->lli_buses = pl08x->pd->lli_buses;
+ pl08x->mem_buses = pl08x->pd->mem_buses;
+ }
+
/* A DMA memory pool for LLIs, align on 1-byte boundary */
pl08x->pool = dma_pool_create(DRIVER_NAME, &pl08x->adev->dev,
PL08X_LLI_TSFR_SIZE, PL08X_ALIGN, 0);
diff --git a/include/linux/amba/pl08x.h b/include/linux/amba/pl08x.h
index 8d90830..f858651 100644
--- a/include/linux/amba/pl08x.h
+++ b/include/linux/amba/pl08x.h
@@ -25,6 +25,12 @@
struct pl08x_lli;
struct pl08x_driver_data;
+/* Bitmasks for selecting AHB ports for DMA transfers */
+enum {
+ PL08X_AHB1 = (1 << 0),
+ PL08X_AHB2 = (1 << 1)
+};
+
/**
* struct pl08x_channel_data - data structure to pass info between
* platform and PL08x driver regarding channel configuration
@@ -51,6 +57,8 @@ struct pl08x_driver_data;
* round round round)
* @single: the device connected to this channel will request single
* DMA transfers, not bursts. (Bursts are default.)
+ * @periph_buses: the device connected to this channel is accessible via
+ * these buses (use PL08X_AHB1 | PL08X_AHB2).
*/
struct pl08x_channel_data {
char *bus_id;
@@ -61,6 +69,7 @@ struct pl08x_channel_data {
dma_addr_t addr;
bool circular_buffer;
bool single;
+ u8 periph_buses;
};
/**
@@ -193,8 +202,8 @@ struct pl08x_dma_chan {
* less than zero, else it returns the allocated signal number
* @put_signal: indicate to the platform that this physical signal is not
* running any DMA transfer and multiplexing can be recycled
- * @bus_bit_lli: Bit[0] of the address indicated which AHB bus master the
- * LLI addresses are on 0/1 Master 1/2.
+ * @lli_buses: buses which LLIs can be fetched from: PL08X_AHB1 | PL08X_AHB2
+ * @mem_buses: buses which memory can be accessed from: PL08X_AHB1 | PL08X_AHB2
*/
struct pl08x_platform_data {
struct pl08x_channel_data *slave_channels;
@@ -202,6 +211,8 @@ struct pl08x_platform_data {
struct pl08x_channel_data memcpy_channel;
int (*get_signal)(struct pl08x_dma_chan *);
void (*put_signal)(struct pl08x_dma_chan *);
+ u8 lli_buses;
+ u8 mem_buses;
};
#ifdef CONFIG_AMBA_PL08X
--
1.6.2.5
^ permalink raw reply related
* [PATCH 34/48] ARM: PL08x: move callback outside spinlock'd region
From: Russell King - ARM Linux @ 2011-01-03 22:41 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
Calling the callback handler with spinlocks in the tasklet held leads
to deadlock when dmaengine functions are called:
BUG: spinlock lockup on CPU#0, sh/417, c1870a08
Backtrace:
...
[<c017b408>] (do_raw_spin_lock+0x0/0x154) from [<c02c4b98>] (_raw_spin_lock_irqsave+0x54/0x60)
[<c02c4b44>] (_raw_spin_lock_irqsave+0x0/0x60) from [<c01f5828>] (pl08x_prep_channel_resources+0x718/0x8b4)
[<c01f5110>] (pl08x_prep_channel_resources+0x0/0x8b4) from [<c01f5bb4>] (pl08x_prep_slave_sg+0x120/0x19c)
[<c01f5a94>] (pl08x_prep_slave_sg+0x0/0x19c) from [<c01be7a0>] (pl011_dma_tx_refill+0x164/0x224)
[<c01be63c>] (pl011_dma_tx_refill+0x0/0x224) from [<c01bf1c8>] (pl011_dma_tx_callback+0x7c/0xc4)
[<c01bf14c>] (pl011_dma_tx_callback+0x0/0xc4) from [<c01f4d34>] (pl08x_tasklet+0x60/0x368)
[<c01f4cd4>] (pl08x_tasklet+0x0/0x368) from [<c004d978>] (tasklet_action+0xa0/0x100)
Dan quoted the documentation:
> 2/ Completion callback routines cannot submit new operations. ?This
> ? ?results in recursion in the synchronous case and spin_locks being
> ? ?acquired twice in the asynchronous case.
but then followed up to say:
> I should clarify, this is the async_memcpy() api requirement which is
> not used outside of md/raid5. DMA drivers can and do allow new
> submissions from callbacks, and the ones that do so properly move the
> callback outside of the driver lock.
So let's fix it by moving the callback out of the spinlocked region.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 29 +++++++++++++++--------------
1 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index 986c127..660165d 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -1540,32 +1540,29 @@ static void pl08x_tasklet(unsigned long data)
{
struct pl08x_dma_chan *plchan = (struct pl08x_dma_chan *) data;
struct pl08x_driver_data *pl08x = plchan->host;
+ struct pl08x_txd *txd;
+ dma_async_tx_callback callback = NULL;
+ void *callback_param = NULL;
unsigned long flags;
spin_lock_irqsave(&plchan->lock, flags);
- if (plchan->at) {
- dma_async_tx_callback callback =
- plchan->at->tx.callback;
- void *callback_param =
- plchan->at->tx.callback_param;
+ txd = plchan->at;
+ plchan->at = NULL;
- /*
- * Update last completed
- */
- plchan->lc = plchan->at->tx.cookie;
+ if (txd) {
+ callback = txd->tx.callback;
+ callback_param = txd->tx.callback_param;
/*
- * Callback to signal completion
+ * Update last completed
*/
- if (callback)
- callback(callback_param);
+ plchan->lc = txd->tx.cookie;
/*
* Free the descriptor
*/
- pl08x_free_txd(pl08x, plchan->at);
- plchan->at = NULL;
+ pl08x_free_txd(pl08x, txd);
}
/*
* If a new descriptor is queued, set it up
@@ -1616,6 +1613,10 @@ static void pl08x_tasklet(unsigned long data)
}
spin_unlock_irqrestore(&plchan->lock, flags);
+
+ /* Callback to signal completion */
+ if (callback)
+ callback(callback_param);
}
static irqreturn_t pl08x_irq(int irq, void *dev)
--
1.6.2.5
^ permalink raw reply related
* [PATCH 35/48] ARM: PL08x: make pl08x_fill_lli_for_desc() return void
From: Russell King - ARM Linux @ 2011-01-03 22:41 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
We don't need pl08x_fill_lli_for_desc() to return num_llis + 1 as
we know that's what it always does. We can just pass in num_llis
and use post-increment in the caller.
This makes the code slightly easier to read.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 30 ++++++++++++------------------
1 files changed, 12 insertions(+), 18 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index 660165d..f619404 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -521,9 +521,8 @@ static void pl08x_choose_master_bus(struct pl08x_bus_data *src_bus,
* Fills in one LLI for a certain transfer descriptor
* and advance the counter
*/
-static int pl08x_fill_lli_for_desc(struct pl08x_driver_data *pl08x,
- struct pl08x_txd *txd, int num_llis, int len,
- u32 cctl, u32 *remainder)
+static void pl08x_fill_lli_for_desc(struct pl08x_driver_data *pl08x,
+ struct pl08x_txd *txd, int num_llis, int len, u32 cctl, u32 *remainder)
{
struct pl08x_lli *llis_va = txd->llis_va;
dma_addr_t llis_bus = txd->llis_bus;
@@ -545,8 +544,6 @@ static int pl08x_fill_lli_for_desc(struct pl08x_driver_data *pl08x,
BUG_ON(*remainder < len);
*remainder -= len;
-
- return num_llis + 1;
}
/*
@@ -646,8 +643,7 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
"less than a bus width (remain 0x%08x)\n",
__func__, remainder);
cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
- num_llis =
- pl08x_fill_lli_for_desc(pl08x, txd, num_llis, 1,
+ pl08x_fill_lli_for_desc(pl08x, txd, num_llis++, 1,
cctl, &remainder);
total_bytes++;
}
@@ -662,8 +658,8 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
"(remain 0x%08x)\n",
__func__, remainder);
cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
- num_llis = pl08x_fill_lli_for_desc
- (pl08x, txd, num_llis, 1, cctl, &remainder);
+ pl08x_fill_lli_for_desc(pl08x, txd, num_llis++, 1,
+ cctl, &remainder);
total_bytes++;
}
@@ -787,9 +783,8 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
dev_vdbg(&pl08x->adev->dev,
"%s fill lli with single lli chunk of size 0x%08zx (remainder 0x%08zx)\n",
__func__, lli_len, remainder);
- num_llis = pl08x_fill_lli_for_desc(pl08x, txd,
- num_llis, lli_len, cctl,
- &remainder);
+ pl08x_fill_lli_for_desc(pl08x, txd, num_llis++,
+ lli_len, cctl, &remainder);
total_bytes += lli_len;
}
@@ -806,10 +801,9 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
dev_vdbg(&pl08x->adev->dev,
"%s align with boundary, single byte (remain 0x%08zx)\n",
__func__, remainder);
- num_llis =
- pl08x_fill_lli_for_desc(pl08x,
- txd, num_llis, 1,
- cctl, &remainder);
+ pl08x_fill_lli_for_desc(pl08x, txd,
+ num_llis++, 1, cctl,
+ &remainder);
total_bytes++;
}
}
@@ -823,8 +817,8 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
dev_vdbg(&pl08x->adev->dev,
"%s align with boundary, single odd byte (remain %zu)\n",
__func__, remainder);
- num_llis = pl08x_fill_lli_for_desc(pl08x, txd, num_llis,
- 1, cctl, &remainder);
+ pl08x_fill_lli_for_desc(pl08x, txd, num_llis++, 1,
+ cctl, &remainder);
total_bytes++;
}
}
--
1.6.2.5
^ permalink raw reply related
* [PATCH 36/48] ARM: PL08x: ensure pl08x_pre_boundary() works for any value of addr
From: Russell King - ARM Linux @ 2011-01-03 22:42 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
pl08x_pre_boundary() was unsafe with addresses towards the top of
memory space:
boundary = ((addr >> PL08X_BOUNDARY_SHIFT) + 1)
<< PL08X_BOUNDARY_SHIFT;
This can overflow a 32-bit number, producing zero. When it does:
if (boundary < addr + len)
return boundary - addr;
else
return len;
results in (boundary - addr) returning either a large positive value.
Also if addr + len overflows, this calculation also fails.
We can fix this trivially as the only thing we're actually interested
in is the value of the least significant PL08X_BOUNDARY_SHIFT bits:
boundary_len = PL08X_BOUNDARY_SIZE -
(addr & (PL08X_BOUNDARY_SIZE - 1));
gives us the number of bytes before 'addr' becomes a multiple of
PL08X_BOUNDARY_SIZE. We can then just take the min() of the two
calculated lengths.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 14 +++++---------
1 files changed, 5 insertions(+), 9 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index f619404..7c327c3 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -547,19 +547,15 @@ static void pl08x_fill_lli_for_desc(struct pl08x_driver_data *pl08x,
}
/*
- * Return number of bytes to fill to boundary, or 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)
{
- u32 boundary;
+ size_t boundary_len = PL08X_BOUNDARY_SIZE -
+ (addr & (PL08X_BOUNDARY_SIZE - 1));
- boundary = ((addr >> PL08X_BOUNDARY_SHIFT) + 1)
- << PL08X_BOUNDARY_SHIFT;
-
- if (boundary < addr + len)
- return boundary - addr;
- else
- return len;
+ return min(boundary_len, len);
}
/*
--
1.6.2.5
^ permalink raw reply related
* [PATCH 37/48] ARM: PL08x: use min() to calculate target_len
From: Russell King - ARM Linux @ 2011-01-03 22:42 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 4 +---
1 files changed, 1 insertions(+), 3 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index 7c327c3..91dd6bf 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -682,9 +682,7 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
* If enough left try to send max possible,
* otherwise try to send the remainder
*/
- target_len = remainder;
- if (remainder > max_bytes_per_lli)
- target_len = max_bytes_per_lli;
+ target_len = min(remainder, max_bytes_per_lli);
/*
* Set bus lengths for incrementing buses
--
1.6.2.5
^ permalink raw reply related
* [PATCH 38/48] ARM: PL08x: fix fill_bytes calculation
From: Russell King - ARM Linux @ 2011-01-03 22:42 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
The number of bytes we want to fill into any LLI is the minimum of:
- number of bytes remaining in the transfer
- number of bytes we can transfer in a single LLI
- number of bytes we can transfer without overflowing the source boundary
- number of bytes we can transfer without overflowing the destination boundary
The minimum of the first two is already calculated (target_len). We
limit the boundary calculations to this number of bytes, which will
then give us the number of bytes we can place into this LLI.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 26 ++++++++++----------------
1 files changed, 10 insertions(+), 16 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index 91dd6bf..be7fa17 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -685,31 +685,25 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
target_len = min(remainder, max_bytes_per_lli);
/*
- * Set bus lengths for incrementing buses
- * to number of bytes which fill to next memory
- * boundary
+ * 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)
txd->srcbus.fill_bytes =
- pl08x_pre_boundary(
- txd->srcbus.addr,
- remainder);
+ pl08x_pre_boundary(txd->srcbus.addr,
+ target_len);
else
- txd->srcbus.fill_bytes =
- max_bytes_per_lli;
+ txd->srcbus.fill_bytes = target_len;
if (cctl & PL080_CONTROL_DST_INCR)
txd->dstbus.fill_bytes =
- pl08x_pre_boundary(
- txd->dstbus.addr,
- remainder);
+ pl08x_pre_boundary(txd->dstbus.addr,
+ target_len);
else
- txd->dstbus.fill_bytes =
- max_bytes_per_lli;
+ txd->dstbus.fill_bytes = target_len;
- /*
- * Find the nearest
- */
+ /* Find the nearest */
lli_len = min(txd->srcbus.fill_bytes,
txd->dstbus.fill_bytes);
--
1.6.2.5
^ permalink raw reply related
* [PATCH 39/48] ARM: PL08x: don't manipulate txd->srcbus or txd->dstbus during LLI fill
From: Russell King - ARM Linux @ 2011-01-03 22:43 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
Don't alter any txd->srcbus or txd->dstbus values while building the
LLI list. This allows us to see the original dma_addr_t values passed
in via the prep_memcpy() method.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 159 ++++++++++++++++++++++++----------------------
1 files changed, 83 insertions(+), 76 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index be7fa17..39970c5 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -481,38 +481,45 @@ static inline u32 pl08x_cctl_bits(u32 cctl, u8 srcwidth, u8 dstwidth,
return retbits;
}
+struct pl08x_lli_build_data {
+ struct pl08x_txd *txd;
+ struct pl08x_driver_data *pl08x;
+ struct pl08x_bus_data srcbus;
+ struct pl08x_bus_data dstbus;
+ size_t remainder;
+};
+
/*
* 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
*/
-static void pl08x_choose_master_bus(struct pl08x_bus_data *src_bus,
- struct pl08x_bus_data *dst_bus, struct pl08x_bus_data **mbus,
- struct pl08x_bus_data **sbus, u32 cctl)
+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 = src_bus;
- *sbus = dst_bus;
+ *mbus = &bd->srcbus;
+ *sbus = &bd->dstbus;
} else if (!(cctl & PL080_CONTROL_SRC_INCR)) {
- *mbus = dst_bus;
- *sbus = src_bus;
+ *mbus = &bd->dstbus;
+ *sbus = &bd->srcbus;
} else {
- if (dst_bus->buswidth == 4) {
- *mbus = dst_bus;
- *sbus = src_bus;
- } else if (src_bus->buswidth == 4) {
- *mbus = src_bus;
- *sbus = dst_bus;
- } else if (dst_bus->buswidth == 2) {
- *mbus = dst_bus;
- *sbus = src_bus;
- } else if (src_bus->buswidth == 2) {
- *mbus = src_bus;
- *sbus = dst_bus;
+ if (bd->dstbus.buswidth == 4) {
+ *mbus = &bd->dstbus;
+ *sbus = &bd->srcbus;
+ } else if (bd->srcbus.buswidth == 4) {
+ *mbus = &bd->srcbus;
+ *sbus = &bd->dstbus;
+ } else if (bd->dstbus.buswidth == 2) {
+ *mbus = &bd->dstbus;
+ *sbus = &bd->srcbus;
+ } else if (bd->srcbus.buswidth == 2) {
+ *mbus = &bd->srcbus;
+ *sbus = &bd->dstbus;
} else {
- /* src_bus->buswidth == 1 */
- *mbus = dst_bus;
- *sbus = src_bus;
+ /* bd->srcbus.buswidth == 1 */
+ *mbus = &bd->dstbus;
+ *sbus = &bd->srcbus;
}
}
}
@@ -521,29 +528,29 @@ static void pl08x_choose_master_bus(struct pl08x_bus_data *src_bus,
* Fills in one LLI for a certain transfer descriptor
* and advance the counter
*/
-static void pl08x_fill_lli_for_desc(struct pl08x_driver_data *pl08x,
- struct pl08x_txd *txd, int num_llis, int len, u32 cctl, u32 *remainder)
+static void pl08x_fill_lli_for_desc(struct pl08x_lli_build_data *bd,
+ int num_llis, int len, u32 cctl)
{
- struct pl08x_lli *llis_va = txd->llis_va;
- dma_addr_t llis_bus = txd->llis_bus;
+ struct pl08x_lli *llis_va = bd->txd->llis_va;
+ dma_addr_t llis_bus = bd->txd->llis_bus;
BUG_ON(num_llis >= MAX_NUM_TSFR_LLIS);
llis_va[num_llis].cctl = cctl;
- llis_va[num_llis].src = txd->srcbus.addr;
- llis_va[num_llis].dst = txd->dstbus.addr;
+ 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);
- if (pl08x->lli_buses & PL08X_AHB2)
+ if (bd->pl08x->lli_buses & PL08X_AHB2)
llis_va[num_llis].lli |= PL080_LLI_LM_AHB2;
if (cctl & PL080_CONTROL_SRC_INCR)
- txd->srcbus.addr += len;
+ bd->srcbus.addr += len;
if (cctl & PL080_CONTROL_DST_INCR)
- txd->dstbus.addr += len;
+ bd->dstbus.addr += len;
- BUG_ON(*remainder < len);
+ BUG_ON(bd->remainder < len);
- *remainder -= len;
+ bd->remainder -= len;
}
/*
@@ -567,7 +574,7 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
struct pl08x_txd *txd)
{
struct pl08x_bus_data *mbus, *sbus;
- size_t remainder;
+ struct pl08x_lli_build_data bd;
int num_llis = 0;
u32 cctl;
size_t max_bytes_per_lli;
@@ -586,38 +593,43 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
/* Get the default CCTL */
cctl = txd->cctl;
+ bd.txd = txd;
+ bd.pl08x = pl08x;
+ bd.srcbus.addr = txd->srcbus.addr;
+ bd.dstbus.addr = txd->dstbus.addr;
+
/* Find maximum width of the source bus */
- txd->srcbus.maxwidth =
+ bd.srcbus.maxwidth =
pl08x_get_bytes_for_cctl((cctl & PL080_CONTROL_SWIDTH_MASK) >>
PL080_CONTROL_SWIDTH_SHIFT);
/* Find maximum width of the destination bus */
- txd->dstbus.maxwidth =
+ bd.dstbus.maxwidth =
pl08x_get_bytes_for_cctl((cctl & PL080_CONTROL_DWIDTH_MASK) >>
PL080_CONTROL_DWIDTH_SHIFT);
/* Set up the bus widths to the maximum */
- txd->srcbus.buswidth = txd->srcbus.maxwidth;
- txd->dstbus.buswidth = txd->dstbus.maxwidth;
+ bd.srcbus.buswidth = bd.srcbus.maxwidth;
+ bd.dstbus.buswidth = bd.dstbus.maxwidth;
dev_vdbg(&pl08x->adev->dev,
"%s source bus is %d bytes wide, dest bus is %d bytes wide\n",
- __func__, txd->srcbus.buswidth, txd->dstbus.buswidth);
+ __func__, bd.srcbus.buswidth, bd.dstbus.buswidth);
/*
* Bytes transferred == tsize * MIN(buswidths), not max(buswidths)
*/
- max_bytes_per_lli = min(txd->srcbus.buswidth, txd->dstbus.buswidth) *
+ max_bytes_per_lli = min(bd.srcbus.buswidth, bd.dstbus.buswidth) *
PL080_CONTROL_TRANSFER_SIZE_MASK;
dev_vdbg(&pl08x->adev->dev,
"%s max bytes per lli = %zu\n",
__func__, max_bytes_per_lli);
/* We need to count this down to zero */
- remainder = txd->len;
+ bd.remainder = txd->len;
dev_vdbg(&pl08x->adev->dev,
"%s remainder = %zu\n",
- __func__, remainder);
+ __func__, bd.remainder);
/*
* Choose bus to align to
@@ -625,22 +637,20 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
* - if fixed address on one bus chooses other
* - modifies cctl to choose an appropriate master
*/
- pl08x_choose_master_bus(&txd->srcbus, &txd->dstbus,
- &mbus, &sbus, cctl);
+ pl08x_choose_master_bus(&bd, &mbus, &sbus, cctl);
if (txd->len < mbus->buswidth) {
/*
* Less than a bus width available
* - send as single bytes
*/
- while (remainder) {
+ 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__, remainder);
+ __func__, bd.remainder);
cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
- pl08x_fill_lli_for_desc(pl08x, txd, num_llis++, 1,
- cctl, &remainder);
+ pl08x_fill_lli_for_desc(&bd, num_llis++, 1, cctl);
total_bytes++;
}
} else {
@@ -652,10 +662,9 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
dev_vdbg(&pl08x->adev->dev,
"%s adjustment lli for less than bus width "
"(remain 0x%08x)\n",
- __func__, remainder);
+ __func__, bd.remainder);
cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
- pl08x_fill_lli_for_desc(pl08x, txd, num_llis++, 1,
- cctl, &remainder);
+ pl08x_fill_lli_for_desc(&bd, num_llis++, 1, cctl);
total_bytes++;
}
@@ -675,14 +684,14 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
* Make largest possible LLIs until less than one bus
* width left
*/
- while (remainder > (mbus->buswidth - 1)) {
+ while (bd.remainder > (mbus->buswidth - 1)) {
size_t lli_len, target_len, tsize, odd_bytes;
/*
* If enough left try to send max possible,
* otherwise try to send the remainder
*/
- target_len = min(remainder, max_bytes_per_lli);
+ target_len = min(bd.remainder, max_bytes_per_lli);
/*
* Set bus lengths for incrementing buses to the
@@ -690,24 +699,24 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
* limiting on the target length calculated above.
*/
if (cctl & PL080_CONTROL_SRC_INCR)
- txd->srcbus.fill_bytes =
- pl08x_pre_boundary(txd->srcbus.addr,
+ bd.srcbus.fill_bytes =
+ pl08x_pre_boundary(bd.srcbus.addr,
target_len);
else
- txd->srcbus.fill_bytes = target_len;
+ bd.srcbus.fill_bytes = target_len;
if (cctl & PL080_CONTROL_DST_INCR)
- txd->dstbus.fill_bytes =
- pl08x_pre_boundary(txd->dstbus.addr,
+ bd.dstbus.fill_bytes =
+ pl08x_pre_boundary(bd.dstbus.addr,
target_len);
else
- txd->dstbus.fill_bytes = target_len;
+ bd.dstbus.fill_bytes = target_len;
/* Find the nearest */
- lli_len = min(txd->srcbus.fill_bytes,
- txd->dstbus.fill_bytes);
+ lli_len = min(bd.srcbus.fill_bytes,
+ bd.dstbus.fill_bytes);
- BUG_ON(lli_len > remainder);
+ BUG_ON(lli_len > bd.remainder);
if (lli_len <= 0) {
dev_err(&pl08x->adev->dev,
@@ -764,15 +773,15 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
}
cctl = pl08x_cctl_bits(cctl,
- txd->srcbus.buswidth,
- txd->dstbus.buswidth,
+ 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",
- __func__, lli_len, remainder);
- pl08x_fill_lli_for_desc(pl08x, txd, num_llis++,
- lli_len, cctl, &remainder);
+ __func__, lli_len, bd.remainder);
+ pl08x_fill_lli_for_desc(&bd, num_llis++,
+ lli_len, cctl);
total_bytes += lli_len;
}
@@ -784,14 +793,13 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
*/
int j;
for (j = 0; (j < mbus->buswidth)
- && (remainder); j++) {
+ && (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__, remainder);
- pl08x_fill_lli_for_desc(pl08x, txd,
- num_llis++, 1, cctl,
- &remainder);
+ __func__, bd.remainder);
+ pl08x_fill_lli_for_desc(&bd,
+ num_llis++, 1, cctl);
total_bytes++;
}
}
@@ -800,13 +808,12 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
/*
* Send any odd bytes
*/
- while (remainder) {
+ while (bd.remainder) {
cctl = pl08x_cctl_bits(cctl, 1, 1, 1);
dev_vdbg(&pl08x->adev->dev,
"%s align with boundary, single odd byte (remain %zu)\n",
- __func__, remainder);
- pl08x_fill_lli_for_desc(pl08x, txd, num_llis++, 1,
- cctl, &remainder);
+ __func__, bd.remainder);
+ pl08x_fill_lli_for_desc(&bd, num_llis++, 1, cctl);
total_bytes++;
}
}
--
1.6.2.5
^ permalink raw reply related
* [PATCH 40/48] ARM: PL08x: shrink srcbus/dstbus in txd structure
From: Russell King - ARM Linux @ 2011-01-03 22:43 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
We only need to store the dma address.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 20 ++++++++++----------
include/linux/amba/pl08x.h | 4 ++--
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index 39970c5..8d8d33c 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -595,8 +595,8 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
bd.txd = txd;
bd.pl08x = pl08x;
- bd.srcbus.addr = txd->srcbus.addr;
- bd.dstbus.addr = txd->dstbus.addr;
+ bd.srcbus.addr = txd->src_addr;
+ bd.dstbus.addr = txd->dst_addr;
/* Find maximum width of the source bus */
bd.srcbus.maxwidth =
@@ -1313,8 +1313,8 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy(
}
txd->direction = DMA_NONE;
- txd->srcbus.addr = src;
- txd->dstbus.addr = dest;
+ txd->src_addr = src;
+ txd->dst_addr = dest;
txd->len = len;
/* Set platform data for m2m */
@@ -1393,21 +1393,21 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
if (direction == DMA_TO_DEVICE) {
txd->ccfg |= PL080_FLOW_MEM2PER << PL080_CONFIG_FLOW_CONTROL_SHIFT;
txd->cctl |= PL080_CONTROL_SRC_INCR;
- txd->srcbus.addr = sgl->dma_address;
+ txd->src_addr = sgl->dma_address;
if (plchan->runtime_addr)
- txd->dstbus.addr = plchan->runtime_addr;
+ txd->dst_addr = plchan->runtime_addr;
else
- txd->dstbus.addr = plchan->cd->addr;
+ txd->dst_addr = plchan->cd->addr;
src_buses = pl08x->mem_buses;
dst_buses = plchan->cd->periph_buses;
} else if (direction == DMA_FROM_DEVICE) {
txd->ccfg |= PL080_FLOW_PER2MEM << PL080_CONFIG_FLOW_CONTROL_SHIFT;
txd->cctl |= PL080_CONTROL_DST_INCR;
if (plchan->runtime_addr)
- txd->srcbus.addr = plchan->runtime_addr;
+ txd->src_addr = plchan->runtime_addr;
else
- txd->srcbus.addr = plchan->cd->addr;
- txd->dstbus.addr = sgl->dma_address;
+ txd->src_addr = plchan->cd->addr;
+ txd->dst_addr = sgl->dma_address;
src_buses = plchan->cd->periph_buses;
dst_buses = pl08x->mem_buses;
} else {
diff --git a/include/linux/amba/pl08x.h b/include/linux/amba/pl08x.h
index f858651..4e9e718 100644
--- a/include/linux/amba/pl08x.h
+++ b/include/linux/amba/pl08x.h
@@ -114,8 +114,8 @@ struct pl08x_txd {
struct dma_async_tx_descriptor tx;
struct list_head node;
enum dma_data_direction direction;
- struct pl08x_bus_data srcbus;
- struct pl08x_bus_data dstbus;
+ dma_addr_t src_addr;
+ dma_addr_t dst_addr;
size_t len;
dma_addr_t llis_bus;
void *llis_va;
--
1.6.2.5
^ permalink raw reply related
* [PATCH 41/48] ARM: PL08x: store prep_* flags in async_tx structure
From: Russell King - ARM Linux @ 2011-01-03 22:43 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
Like other DMA engine drivers do, store the passed flags into the
async_tx structure, so they can be checked when the operation
completes.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 8 +++++---
1 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index 8d8d33c..00058e3 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -1277,12 +1277,14 @@ static u32 pl08x_select_bus(struct pl08x_driver_data *pl08x, u8 src, u8 dst)
return cctl;
}
-static struct pl08x_txd *pl08x_get_txd(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);
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);
@@ -1305,7 +1307,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy(
struct pl08x_txd *txd;
int ret;
- txd = pl08x_get_txd(plchan);
+ txd = pl08x_get_txd(plchan, flags);
if (!txd) {
dev_err(&pl08x->adev->dev,
"%s no memory for descriptor\n", __func__);
@@ -1363,7 +1365,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
dev_dbg(&pl08x->adev->dev, "%s prepare transaction of %d bytes from %s\n",
__func__, sgl->length, plchan->name);
- txd = pl08x_get_txd(plchan);
+ txd = pl08x_get_txd(plchan, flags);
if (!txd) {
dev_err(&pl08x->adev->dev, "%s no txd\n", __func__);
return NULL;
--
1.6.2.5
^ permalink raw reply related
* [PATCH 42/48] ARM: PL08x: implement unmapping of memcpy buffers
From: Russell King - ARM Linux @ 2011-01-03 22:44 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
The DMA engine API requires DMA engine implementations to unmap buffers
passed into the non-slave DMA methods unless the relevant completion
flag is set. We aren't doing this, so implement this facility.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 52 ++++++++++++++++++++++++++++++++++-----------
1 files changed, 39 insertions(+), 13 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index 00058e3..fb469de 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -1527,13 +1527,33 @@ static void pl08x_ensure_on(struct pl08x_driver_data *pl08x)
writel(val, pl08x->base + PL080_CONFIG);
}
+static void pl08x_unmap_buffers(struct pl08x_txd *txd)
+{
+ struct device *dev = txd->tx.chan->device->dev;
+
+ 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,
+ DMA_TO_DEVICE);
+ else
+ dma_unmap_page(dev, txd->src_addr, txd->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,
+ DMA_FROM_DEVICE);
+ else
+ dma_unmap_page(dev, txd->dst_addr, txd->len,
+ DMA_FROM_DEVICE);
+ }
+}
+
static void pl08x_tasklet(unsigned long data)
{
struct pl08x_dma_chan *plchan = (struct pl08x_dma_chan *) data;
struct pl08x_driver_data *pl08x = plchan->host;
struct pl08x_txd *txd;
- dma_async_tx_callback callback = NULL;
- void *callback_param = NULL;
unsigned long flags;
spin_lock_irqsave(&plchan->lock, flags);
@@ -1542,18 +1562,10 @@ static void pl08x_tasklet(unsigned long data)
plchan->at = NULL;
if (txd) {
- callback = txd->tx.callback;
- callback_param = txd->tx.callback_param;
-
/*
* Update last completed
*/
plchan->lc = txd->tx.cookie;
-
- /*
- * Free the descriptor
- */
- pl08x_free_txd(pl08x, txd);
}
/*
* If a new descriptor is queued, set it up
@@ -1605,9 +1617,23 @@ static void pl08x_tasklet(unsigned long data)
spin_unlock_irqrestore(&plchan->lock, flags);
- /* Callback to signal completion */
- if (callback)
- callback(callback_param);
+ if (txd) {
+ dma_async_tx_callback callback = txd->tx.callback;
+ void *callback_param = txd->tx.callback_param;
+
+ /* Don't try to unmap buffers on slave channels */
+ if (!plchan->slave)
+ pl08x_unmap_buffers(txd);
+
+ /* Free the descriptor */
+ spin_lock_irqsave(&plchan->lock, flags);
+ pl08x_free_txd(pl08x, txd);
+ spin_unlock_irqrestore(&plchan->lock, flags);
+
+ /* Callback to signal completion */
+ if (callback)
+ callback(callback_param);
+ }
}
static irqreturn_t pl08x_irq(int irq, void *dev)
--
1.6.2.5
^ permalink raw reply related
* [PATCH 43/48] ARM: PL08x: rename 'desc_list' as 'pend_list'
From: Russell King - ARM Linux @ 2011-01-03 22:44 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
This 'desc_list' is actually a list of pending descriptors, so name
it after its function (pending list) rather than what it contains
(descriptors).
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 20 ++++++++++----------
include/linux/amba/pl08x.h | 4 ++--
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index fb469de..433b9e7 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -348,9 +348,9 @@ static u32 pl08x_getbytes_chan(struct pl08x_dma_chan *plchan)
}
/* Sum up all queued transactions */
- if (!list_empty(&plchan->desc_list)) {
+ if (!list_empty(&plchan->pend_list)) {
struct pl08x_txd *txdi;
- list_for_each_entry(txdi, &plchan->desc_list, node) {
+ list_for_each_entry(txdi, &plchan->pend_list, node) {
bytes += txdi->len;
}
}
@@ -880,9 +880,9 @@ static void pl08x_free_txd_list(struct pl08x_driver_data *pl08x,
struct pl08x_txd *txdi = NULL;
struct pl08x_txd *next;
- if (!list_empty(&plchan->desc_list)) {
+ if (!list_empty(&plchan->pend_list)) {
list_for_each_entry_safe(txdi,
- next, &plchan->desc_list, node) {
+ next, &plchan->pend_list, node) {
list_del(&txdi->node);
pl08x_free_txd(pl08x, txdi);
}
@@ -1183,10 +1183,10 @@ static void pl08x_issue_pending(struct dma_chan *chan)
}
/* Take the first element in the queue and execute it */
- if (!list_empty(&plchan->desc_list)) {
+ if (!list_empty(&plchan->pend_list)) {
struct pl08x_txd *next;
- next = list_first_entry(&plchan->desc_list,
+ next = list_first_entry(&plchan->pend_list,
struct pl08x_txd,
node);
list_del(&next->node);
@@ -1213,7 +1213,7 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan,
spin_lock_irqsave(&plchan->lock, plchan->lockflags);
- list_add_tail(&txd->node, &plchan->desc_list);
+ list_add_tail(&txd->node, &plchan->pend_list);
/*
* See if we already have a physical channel allocated,
@@ -1571,10 +1571,10 @@ static void pl08x_tasklet(unsigned long data)
* If a new descriptor is queued, set it up
* plchan->at is NULL here
*/
- if (!list_empty(&plchan->desc_list)) {
+ if (!list_empty(&plchan->pend_list)) {
struct pl08x_txd *next;
- next = list_first_entry(&plchan->desc_list,
+ next = list_first_entry(&plchan->pend_list,
struct pl08x_txd,
node);
list_del(&next->node);
@@ -1736,7 +1736,7 @@ static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x,
chan->lc = 0;
spin_lock_init(&chan->lock);
- INIT_LIST_HEAD(&chan->desc_list);
+ INIT_LIST_HEAD(&chan->pend_list);
tasklet_init(&chan->tasklet, pl08x_tasklet,
(unsigned long) chan);
diff --git a/include/linux/amba/pl08x.h b/include/linux/amba/pl08x.h
index 4e9e718..08a9024 100644
--- a/include/linux/amba/pl08x.h
+++ b/include/linux/amba/pl08x.h
@@ -158,7 +158,7 @@ enum pl08x_dma_chan_state {
* @runtime_direction: current direction of this channel according to
* runtime config
* @lc: last completed transaction on this channel
- * @desc_list: queued transactions pending on this channel
+ * @pend_list: queued transactions pending on this channel
* @at: active transaction on this channel
* @lockflags: sometimes we let a lock last between two function calls,
* especially prep/submit, and then we need to store the IRQ flags
@@ -179,7 +179,7 @@ struct pl08x_dma_chan {
dma_addr_t runtime_addr;
enum dma_data_direction runtime_direction;
dma_cookie_t lc;
- struct list_head desc_list;
+ struct list_head pend_list;
struct pl08x_txd *at;
unsigned long lockflags;
spinlock_t lock;
--
1.6.2.5
^ permalink raw reply related
* [PATCH 44/48] ARM: PL08x: put txd's on the pending list in pl08x_tx_submit()
From: Russell King - ARM Linux @ 2011-01-03 22:44 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
Don't place TXDs on the pending list when they're prepared - place
them on the list when they're ready to be submitted. Also, only
place memcpy requests in the wait state when they're submitted and
don't have a physical channel associated.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 46 ++++++++++++++++++++++++++++++++--------------
1 files changed, 32 insertions(+), 14 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index 433b9e7..650e2bb 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -173,6 +173,11 @@ static inline struct pl08x_dma_chan *to_pl08x_chan(struct dma_chan *chan)
return container_of(chan, struct pl08x_dma_chan, chan);
}
+static inline struct pl08x_txd *to_pl08x_txd(struct dma_async_tx_descriptor *tx)
+{
+ return container_of(tx, struct pl08x_txd, tx);
+}
+
/*
* Physical channel handling
*/
@@ -974,11 +979,27 @@ static void release_phy_channel(struct pl08x_dma_chan *plchan)
static dma_cookie_t pl08x_tx_submit(struct dma_async_tx_descriptor *tx)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(tx->chan);
+ struct pl08x_txd *txd = to_pl08x_txd(tx);
plchan->chan.cookie += 1;
if (plchan->chan.cookie < 0)
plchan->chan.cookie = 1;
tx->cookie = plchan->chan.cookie;
+
+ /* Put this onto the pending list */
+ list_add_tail(&txd->node, &plchan->pend_list);
+
+ /*
+ * If there was no physical channel available for this memcpy,
+ * stack the request up and indicate that the channel is waiting
+ * for a free physical channel.
+ */
+ if (!plchan->slave && !plchan->phychan) {
+ /* Do this memcpy whenever there is a channel ready */
+ plchan->state = PL08X_CHAN_WAITING;
+ plchan->waiting = txd;
+ }
+
/* This unlock follows the lock in the prep() function */
spin_unlock_irqrestore(&plchan->lock, plchan->lockflags);
@@ -1213,8 +1234,6 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan,
spin_lock_irqsave(&plchan->lock, plchan->lockflags);
- list_add_tail(&txd->node, &plchan->pend_list);
-
/*
* See if we already have a physical channel allocated,
* else this is the time to try to get one.
@@ -1222,24 +1241,23 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan,
ret = prep_phy_channel(plchan, txd);
if (ret) {
/*
- * No physical channel available, we will
- * stack up the memcpy channels until there is a channel
- * available to handle it whereas slave transfers may
- * have been denied due to platform channel muxing restrictions
- * and since there is no guarantee that this will ever be
- * resolved, and since the signal must be acquired AFTER
- * acquiring the physical channel, we will let them be NACK:ed
- * with -EBUSY here. The drivers can alway retry the prep()
- * call if they are eager on doing this using DMA.
+ * No physical channel was available.
+ *
+ * memcpy transfers can be sorted out at submission time.
+ *
+ * Slave transfers may have been denied due to platform
+ * channel muxing restrictions. Since there is no guarantee
+ * that this will ever be resolved, and the signal must be
+ * acquired AFTER acquiring the physical channel, we will let
+ * them be NACK:ed with -EBUSY here. The drivers can retry
+ * the prep() call if they are eager on doing this using DMA.
*/
if (plchan->slave) {
pl08x_free_txd_list(pl08x, plchan);
+ pl08x_free_txd(pl08x, txd);
spin_unlock_irqrestore(&plchan->lock, plchan->lockflags);
return -EBUSY;
}
- /* Do this memcpy whenever there is a channel ready */
- plchan->state = PL08X_CHAN_WAITING;
- plchan->waiting = txd;
} else
/*
* Else we're all set, paused and ready to roll,
--
1.6.2.5
^ permalink raw reply related
* [PATCH 45/48] ARM: PL08x: introduce 'phychan_hold' to hold on to physical channels
From: Russell King - ARM Linux @ 2011-01-03 22:45 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
Introduce 'phychan_hold' to hold on to physical DMA channels while we're
preparing a new descriptor for it. This will be incremented when we
allocate a physical channel and set the MUX registers during the
preparation of the TXD, and will only be decremented when the TXD is
submitted.
This prevents the physical channel being given up before the new TXD
is placed on the queue.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 11 +++++++++++
include/linux/amba/pl08x.h | 3 +++
2 files changed, 14 insertions(+), 0 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index 650e2bb..bf6f7d0 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -959,6 +959,7 @@ static int prep_phy_channel(struct pl08x_dma_chan *plchan,
ch->signal,
plchan->name);
+ plchan->phychan_hold++;
plchan->phychan = ch;
return 0;
@@ -998,6 +999,8 @@ static dma_cookie_t pl08x_tx_submit(struct dma_async_tx_descriptor *tx)
/* Do this memcpy whenever there is a channel ready */
plchan->state = PL08X_CHAN_WAITING;
plchan->waiting = txd;
+ } else {
+ plchan->phychan_hold--;
}
/* This unlock follows the lock in the prep() function */
@@ -1585,6 +1588,7 @@ static void pl08x_tasklet(unsigned long data)
*/
plchan->lc = txd->tx.cookie;
}
+
/*
* If a new descriptor is queued, set it up
* plchan->at is NULL here
@@ -1598,6 +1602,12 @@ static void pl08x_tasklet(unsigned long data)
list_del(&next->node);
pl08x_start_txd(plchan, next);
+ } else if (plchan->phychan_hold) {
+ /*
+ * This channel is still in use - we have a new txd being
+ * prepared and will soon be queued. Don't give up the
+ * physical channel.
+ */
} else {
struct pl08x_dma_chan *waiting = NULL;
@@ -1625,6 +1635,7 @@ static void pl08x_tasklet(unsigned long data)
ret = prep_phy_channel(waiting,
waiting->waiting);
BUG_ON(ret);
+ waiting->phychan_hold--;
waiting->state = PL08X_CHAN_RUNNING;
waiting->waiting = NULL;
pl08x_issue_pending(&waiting->chan);
diff --git a/include/linux/amba/pl08x.h b/include/linux/amba/pl08x.h
index 08a9024..95b76ea 100644
--- a/include/linux/amba/pl08x.h
+++ b/include/linux/amba/pl08x.h
@@ -151,6 +151,8 @@ enum pl08x_dma_chan_state {
* struct pl08x_dma_chan - this structure wraps a DMA ENGINE channel
* @chan: wrappped abstract channel
* @phychan: the physical channel utilized by this channel, if there is one
+ * @phychan_hold: if non-zero, hold on to the physical channel even if we
+ * have no pending entries
* @tasklet: tasklet scheduled by the IRQ to handle actual work etc
* @name: name of channel
* @cd: channel platform data
@@ -173,6 +175,7 @@ enum pl08x_dma_chan_state {
struct pl08x_dma_chan {
struct dma_chan chan;
struct pl08x_phy_chan *phychan;
+ int phychan_hold;
struct tasklet_struct tasklet;
char *name;
struct pl08x_channel_data *cd;
--
1.6.2.5
^ permalink raw reply related
* [PATCH 46/48] ARM: PL08x: fix locking between prepare function and submit function
From: Russell King - ARM Linux @ 2011-01-03 22:45 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
The PL08x driver holds on to the channel lock with interrupts disabled
between the prepare and the subsequent submit API functions. This
means that the locking state when the prepare function returns is
dependent on whether it suceeeds or not.
It did this to ensure that the physical channel wasn't released, and
as it used to add the descriptor onto the pending list at prepare time
rather than submit time.
Now that we have reorganized the code to remove those reasons, we can
now safely release the spinlock at the end of preparation and reacquire
it in our submit function.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 28 +++++++++-------------------
include/linux/amba/pl08x.h | 4 ----
2 files changed, 9 insertions(+), 23 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index bf6f7d0..1c9f712 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -981,6 +981,9 @@ static dma_cookie_t pl08x_tx_submit(struct dma_async_tx_descriptor *tx)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(tx->chan);
struct pl08x_txd *txd = to_pl08x_txd(tx);
+ unsigned long flags;
+
+ spin_lock_irqsave(&plchan->lock, flags);
plchan->chan.cookie += 1;
if (plchan->chan.cookie < 0)
@@ -1003,8 +1006,7 @@ static dma_cookie_t pl08x_tx_submit(struct dma_async_tx_descriptor *tx)
plchan->phychan_hold--;
}
- /* This unlock follows the lock in the prep() function */
- spin_unlock_irqrestore(&plchan->lock, plchan->lockflags);
+ spin_unlock_irqrestore(&plchan->lock, flags);
return tx->cookie;
}
@@ -1225,9 +1227,9 @@ static void pl08x_issue_pending(struct dma_chan *chan)
static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan,
struct pl08x_txd *txd)
{
- int num_llis;
struct pl08x_driver_data *pl08x = plchan->host;
- int ret;
+ unsigned long flags;
+ int num_llis, ret;
num_llis = pl08x_fill_llis_for_desc(pl08x, txd);
if (!num_llis) {
@@ -1235,7 +1237,7 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan,
return -EINVAL;
}
- spin_lock_irqsave(&plchan->lock, plchan->lockflags);
+ spin_lock_irqsave(&plchan->lock, flags);
/*
* See if we already have a physical channel allocated,
@@ -1258,7 +1260,7 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan,
if (plchan->slave) {
pl08x_free_txd_list(pl08x, plchan);
pl08x_free_txd(pl08x, txd);
- spin_unlock_irqrestore(&plchan->lock, plchan->lockflags);
+ spin_unlock_irqrestore(&plchan->lock, flags);
return -EBUSY;
}
} else
@@ -1272,11 +1274,7 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan,
if (plchan->state == PL08X_CHAN_IDLE)
plchan->state = PL08X_CHAN_PAUSED;
- /*
- * Notice that we leave plchan->lock locked on purpose:
- * it will be unlocked in the subsequent tx_submit()
- * call. This is a consequence of the current API.
- */
+ spin_unlock_irqrestore(&plchan->lock, flags);
return 0;
}
@@ -1355,10 +1353,6 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy(
ret = pl08x_prep_channel_resources(plchan, txd);
if (ret)
return NULL;
- /*
- * NB: the channel lock is held at this point so tx_submit()
- * must be called in direct succession.
- */
return &txd->tx;
}
@@ -1444,10 +1438,6 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
ret = pl08x_prep_channel_resources(plchan, txd);
if (ret)
return NULL;
- /*
- * NB: the channel lock is held at this point so tx_submit()
- * must be called in direct succession.
- */
return &txd->tx;
}
diff --git a/include/linux/amba/pl08x.h b/include/linux/amba/pl08x.h
index 95b76ea..933b4ed 100644
--- a/include/linux/amba/pl08x.h
+++ b/include/linux/amba/pl08x.h
@@ -162,9 +162,6 @@ enum pl08x_dma_chan_state {
* @lc: last completed transaction on this channel
* @pend_list: queued transactions pending on this channel
* @at: active transaction on this channel
- * @lockflags: sometimes we let a lock last between two function calls,
- * especially prep/submit, and then we need to store the IRQ flags
- * in the channel state, here
* @lock: a lock for this channel data
* @host: a pointer to the host (internal use)
* @state: whether the channel is idle, paused, running etc
@@ -184,7 +181,6 @@ struct pl08x_dma_chan {
dma_cookie_t lc;
struct list_head pend_list;
struct pl08x_txd *at;
- unsigned long lockflags;
spinlock_t lock;
struct pl08x_driver_data *host;
enum pl08x_dma_chan_state state;
--
1.6.2.5
^ permalink raw reply related
* [PATCH 47/48] ARM: PL08x: allow dma_set_runtime_config() to return errors
From: Russell King - ARM Linux @ 2011-01-03 22:45 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
There are cases in dma_set_runtime_config() where we fail to perform
the requested action - and we just issue a KERN_ERR message in that
case. We have the facility to return an error to the caller, so that
is what we should do.
When we encounter an error due to invalid parameters, we should not
modify driver state.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 23 +++++++++++++----------
1 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index 1c9f712..c7f7b82 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -1117,13 +1117,14 @@ static const struct burst_table burst_sizes[] = {
},
};
-static void dma_set_runtime_config(struct dma_chan *chan,
- struct dma_slave_config *config)
+static int dma_set_runtime_config(struct dma_chan *chan,
+ struct dma_slave_config *config)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
struct pl08x_driver_data *pl08x = plchan->host;
struct pl08x_channel_data *cd = plchan->cd;
enum dma_slave_buswidth addr_width;
+ dma_addr_t addr;
u32 maxburst;
u32 cctl = 0;
int i;
@@ -1131,17 +1132,17 @@ static void dma_set_runtime_config(struct dma_chan *chan,
/* Transfer direction */
plchan->runtime_direction = config->direction;
if (config->direction == DMA_TO_DEVICE) {
- plchan->runtime_addr = config->dst_addr;
+ addr = config->dst_addr;
addr_width = config->dst_addr_width;
maxburst = config->dst_maxburst;
} else if (config->direction == DMA_FROM_DEVICE) {
- plchan->runtime_addr = config->src_addr;
+ addr = config->src_addr;
addr_width = config->src_addr_width;
maxburst = config->src_maxburst;
} else {
dev_err(&pl08x->adev->dev,
"bad runtime_config: alien transfer direction\n");
- return;
+ return -EINVAL;
}
switch (addr_width) {
@@ -1160,7 +1161,7 @@ static void dma_set_runtime_config(struct dma_chan *chan,
default:
dev_err(&pl08x->adev->dev,
"bad runtime_config: alien address width\n");
- return;
+ return -EINVAL;
}
/*
@@ -1179,6 +1180,8 @@ static void dma_set_runtime_config(struct dma_chan *chan,
cctl |= burst_sizes[i].reg;
}
+ plchan->runtime_addr = addr;
+
/* Modify the default channel data to fit PrimeCell request */
cd->cctl = cctl;
@@ -1190,6 +1193,8 @@ static void dma_set_runtime_config(struct dma_chan *chan,
addr_width,
maxburst,
cctl);
+
+ return 0;
}
/*
@@ -1452,10 +1457,8 @@ static int pl08x_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
/* Controls applicable to inactive channels */
if (cmd == DMA_SLAVE_CONFIG) {
- dma_set_runtime_config(chan,
- (struct dma_slave_config *)
- arg);
- return 0;
+ return dma_set_runtime_config(chan,
+ (struct dma_slave_config *)arg);
}
/*
--
1.6.2.5
^ permalink raw reply related
* [PATCH 48/48] ARM: PL08x: prevent dma_set_runtime_config() reconfiguring memcpy channels
From: Russell King - ARM Linux @ 2011-01-03 22:46 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103222943.GB4572@n2100.arm.linux.org.uk>
Prevent dma_set_runtime_config() being used to alter the configuration
supplied by the platform for memcpy channel configuration. No one
should be trying to change this configuration.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
drivers/dma/amba-pl08x.c | 3 +++
1 files changed, 3 insertions(+), 0 deletions(-)
diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index c7f7b82..bebc678 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -1129,6 +1129,9 @@ static int dma_set_runtime_config(struct dma_chan *chan,
u32 cctl = 0;
int i;
+ if (!plchan->slave)
+ return -EINVAL;
+
/* Transfer direction */
plchan->runtime_direction = config->direction;
if (config->direction == DMA_TO_DEVICE) {
--
1.6.2.5
^ permalink raw reply related
* [PATCH v5] davinci: Add additional JTAG code for AM-1808 and OMAP-L138 Rev 2.0 SoCs From: Sudhakar Rajashekhara <sudhakar.raj@ti.com>
From: Kevin Hilman @ 2011-01-03 23:50 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <4D22503A.7010709@criticallink.com>
Michael Williamson <michael.williamson@criticallink.com> writes:
> On 01/03/2011 05:33 PM, Kevin Hilman wrote:
>> Michael Williamson <michael.williamson@criticallink.com> writes:
>>
>> I'm assuming the 'From:' line currently in the subject was supposed to
>> go here?
>>
>> Kevin
>
> Arg. Yes. Sorry about that; not sure how I lost the newline. Do you need me
> to resend?
No biggie, I can take care of it. Just wanted to let you know something
got messed up on the sending side.
Kevin
>>
>>> The JTAG variant code for Rev-2.0 silicon of the OMAP-L138 has changed.
>>> In addition, the variant code for the AM-1808 SoC appears to match
>>> the Rev-2.0 code for the OMAP-L138. Add an additional entry to support
>>> these chips.
>>>
>>> This patch is originally from a patch on the arago project, here:
>>> http://arago-project.org/git/projects/?p=linux-omapl1.git;a=commit;h=6157618435e313a444cdf059702bd34036a6e2b7
>>>
>>> Further information related to the need for this patch can be located at
>>> http://e2e.ti.com/support/embedded/f/354/p/67290/248486.aspx
>>> http://linux.davincidsp.com/pipermail/davinci-linux-open-source/2010-November/021224.html
>>>
>>> This patch was tested using an AM-1808 SoC on a MityARM-1808 SoM card. It
>>> was also tested using a Rev 1.0 silicon OMAP-L138 on a MityDSP-L138F card.
>>>
>>> Signed-off-by: Sudhakar Rajashekhara <sudhakar.raj@ti.com>
>>> Signed-off-by: Michael Williamson <michael.williamson@criticallink.com>
>>> Tested-by: Michael Williamson <michael.williamson@criticallink.com>
>>> Reported-by: Nicolas Luna <luna.id@gmail.com>
>>> ---
>>> Built against linux-davinci tree.
>>>
>>> Changes since v4.
>>>
>>> - removed am18x code from 0 variant per Sekhar's request.
>>>
>>> arch/arm/mach-davinci/da850.c | 7 +++++++
>>> 1 files changed, 7 insertions(+), 0 deletions(-)
>>>
>>> diff --git a/arch/arm/mach-davinci/da850.c b/arch/arm/mach-davinci/da850.c
>>> index 78b5ae2..1550ac3 100644
>>> --- a/arch/arm/mach-davinci/da850.c
>>> +++ b/arch/arm/mach-davinci/da850.c
>>> @@ -764,6 +764,13 @@ static struct davinci_id da850_ids[] = {
>>> .cpu_id = DAVINCI_CPU_ID_DA850,
>>> .name = "da850/omap-l138",
>>> },
>>> + {
>>> + .variant = 0x1,
>>> + .part_no = 0xb7d1,
>>> + .manufacturer = 0x017, /* 0x02f >> 1 */
>>> + .cpu_id = DAVINCI_CPU_ID_DA850,
>>> + .name = "da850/omap-l138/am18x",
>>> + },
>>> };
>>>
>>> static struct davinci_timer_instance da850_timer_instance[4] = {
^ permalink raw reply
* [PATCH v5] davinci: Add additional JTAG code for AM-1808 and OMAP-L138 Rev 2.0 SoCs From: Sudhakar Rajashekhara <sudhakar.raj@ti.com>
From: Kevin Hilman @ 2011-01-03 23:53 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1294059807-2723-1-git-send-email-michael.williamson@criticallink.com>
Michael Williamson <michael.williamson@criticallink.com> writes:
> The JTAG variant code for Rev-2.0 silicon of the OMAP-L138 has changed.
> In addition, the variant code for the AM-1808 SoC appears to match
> the Rev-2.0 code for the OMAP-L138. Add an additional entry to support
> these chips.
>
> This patch is originally from a patch on the arago project, here:
> http://arago-project.org/git/projects/?p=linux-omapl1.git;a=commit;h=6157618435e313a444cdf059702bd34036a6e2b7
>
> Further information related to the need for this patch can be located at
> http://e2e.ti.com/support/embedded/f/354/p/67290/248486.aspx
> http://linux.davincidsp.com/pipermail/davinci-linux-open-source/2010-November/021224.html
>
> This patch was tested using an AM-1808 SoC on a MityARM-1808 SoM card. It
> was also tested using a Rev 1.0 silicon OMAP-L138 on a MityDSP-L138F card.
>
> Signed-off-by: Sudhakar Rajashekhara <sudhakar.raj@ti.com>
> Signed-off-by: Michael Williamson <michael.williamson@criticallink.com>
> Tested-by: Michael Williamson <michael.williamson@criticallink.com>
> Reported-by: Nicolas Luna <luna.id@gmail.com>
Thanks, queueing this for 2.6.39.
Also added a reviewed-by tag for Sekhar.
Kevin
> ---
> Built against linux-davinci tree.
>
> Changes since v4.
>
> - removed am18x code from 0 variant per Sekhar's request.
>
> arch/arm/mach-davinci/da850.c | 7 +++++++
> 1 files changed, 7 insertions(+), 0 deletions(-)
>
> diff --git a/arch/arm/mach-davinci/da850.c b/arch/arm/mach-davinci/da850.c
> index 78b5ae2..1550ac3 100644
> --- a/arch/arm/mach-davinci/da850.c
> +++ b/arch/arm/mach-davinci/da850.c
> @@ -764,6 +764,13 @@ static struct davinci_id da850_ids[] = {
> .cpu_id = DAVINCI_CPU_ID_DA850,
> .name = "da850/omap-l138",
> },
> + {
> + .variant = 0x1,
> + .part_no = 0xb7d1,
> + .manufacturer = 0x017, /* 0x02f >> 1 */
> + .cpu_id = DAVINCI_CPU_ID_DA850,
> + .name = "da850/omap-l138/am18x",
> + },
> };
>
> static struct davinci_timer_instance da850_timer_instance[4] = {
^ permalink raw reply
* still nfs problems [Was: Linux 2.6.37-rc8]
From: Trond Myklebust @ 2011-01-04 0:22 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103213850.GC25121@pengutronix.de>
On Mon, 2011-01-03 at 22:38 +0100, Uwe Kleine-K?nig wrote:
> On Thu, Dec 30, 2010 at 08:18:46PM +0100, Uwe Kleine-K?nig wrote:
> > On Thu, Dec 30, 2010 at 12:59:52PM -0500, Trond Myklebust wrote:
> > > What filesystem are you exporting on the server? What is the NFS
> > > version? Is this nfsroot, autofs or an ordinary nfs mount?
> > This is an nfsroot of /home/ukl/nfsroot/tx28 which is a symlink to a
> > directory on a different partition. I don't know the filesystem of my
> > homedir as it resides on a server I have no access to, but I asked the
> > admin, so I can follow up with this info later (I'd suspect ext3, too).
> Yes, it is ext3.
>
> > The real root directory is on ext3 (rw,noatime).
> >
> > The serving nfs-server is Debian's nfs-kernel-server 1:1.2.2-1.
> If that matters, kernel is linux-image-2.6.32-5-amd64 (2.6.32-29)
> provided by Debian.
>
> > I don't
> > know if testing that further would help or just waste of my time, so
> > please let me know if I can help you and how.
> This still applies
I'm having trouble reproducing this with my own nfsroot setup (which is
just a 'fedora 13 live' disk with NetworkManager turned firmly off).
However looking back at your report, you said that when you remove the
symlink, you get an error message of the form:
"starting splashutils daemon.../etc/rc.d/S00splashutils: line
50: //sbin/fbsplashd.static: Unknown error 521"
Error 521 is EBADHANDLE, which basically means your client got a
corrupted filehandle. The 'fileid changed' thing also indicates some
form of corruption.
The question is whether this is something happening on the server or the
client. Does an older client kernel boot without any trouble?
--
Trond Myklebust
Linux NFS client maintainer
NetApp
Trond.Myklebust at netapp.com
www.netapp.com
^ permalink raw reply
* [PATCH 06/13] DMAENGINE: driver for the ARM PL080/PL081 PrimeCells
From: Jassi Brar @ 2011-01-04 0:41 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20110103151944.GA32508@n2100.arm.linux.org.uk>
On Tue, Jan 4, 2011 at 12:19 AM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> I've not decided whether it should be possible to resume an ETIMEOUT'd
> pause request (in theory with pl08x, that's just a matter of clearing
> the HALT bit) or whether an ETIMEOUT'd pause request should restore
> the previous HALT bit setting (possibly re-enabling transfers on the
> channel.) ?Allowing it to be re-enabled in some way would be the safest
> thing in terms of data integrity for the reason mentioned in the
> "important note" above.
Not sure if every DMAC could resume cleanly from exact pause point.
Perhaps the upper layer of client driver should take care of that, just
like ALSA which emulates pause/resume if the 'optional' capability is
not advertised by the audio dma driver.
^ permalink raw reply
* mc13xxx core support for i2c
From: Ben Dooks @ 2011-01-04 1:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1292817055-17715-1-git-send-email-marc@cpdesign.com.au>
On 20/12/10 03:50, Marc Reilly wrote:
> These patches add i2c support for the mc13xxx-core driver. This v3 improves the v2
> (http://lists.arm.linux.org.uk/lurker/message/20101215.214755.b6bae366.en.html) by splitting
> the driver up into core, spi and i2c files so that it can support one subsystem in the absence
> of the other.
As noted by others, this needs to be re-worked so that the driver can
always been built and used across bisections.
A suggested approach would be to move items out into the header file
first, and then maybe split the spi driver out of the core, and then
add the I2c driver. It may also make the patch set easier to follow.
^ permalink raw reply
* [PATCH V2] ARM: S5PV310: Implement kernel timers using MCT
From: Kukjin Kim @ 2011-01-04 2:18 UTC (permalink / raw)
To: linux-arm-kernel
From: Changhwan Youn <chaos.youn@samsung.com>
The Multi-Core Timer(MCT) of S5PV310 is designed for implementing
clock source timer and clock event timers. This patch implements
1 clock source timer with 64 bit free running counter of MCT and
2 clock event timers with two of 31-bit tick counters.
Signed-off-by: Changhwan Youn <chaos.youn@samsung.com>
Cc: Ben Dooks <ben-linux@fluff.org>
Cc: Russell King <rmk+kernel@arm.linux.org.uk>
Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
---
Changes since v1:
- Addressed comments from Russell King
(implemented global timer, used local timer and so on)
- Fixed small things
arch/arm/Kconfig | 2 +-
arch/arm/mach-s5pv310/Kconfig | 5 +
arch/arm/mach-s5pv310/Makefile | 10 +-
arch/arm/mach-s5pv310/include/mach/regs-mct.h | 58 +++
arch/arm/mach-s5pv310/mct.c | 492 +++++++++++++++++++++++++
5 files changed, 564 insertions(+), 3 deletions(-)
create mode 100644 arch/arm/mach-s5pv310/include/mach/regs-mct.h
create mode 100644 arch/arm/mach-s5pv310/mct.c
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index d56d21c..d9ee300 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -1291,7 +1291,7 @@ config LOCAL_TIMERS
bool "Use local timer interrupts"
depends on SMP
default y
- select HAVE_ARM_TWD
+ select HAVE_ARM_TWD if !S5PV310_MCT
help
Enable support for local timers on SMP platforms, rather then the
legacy IPI broadcast method. Local timers allows the system
diff --git a/arch/arm/mach-s5pv310/Kconfig b/arch/arm/mach-s5pv310/Kconfig
index 1150b36..5fecf1b 100644
--- a/arch/arm/mach-s5pv310/Kconfig
+++ b/arch/arm/mach-s5pv310/Kconfig
@@ -14,6 +14,11 @@ config CPU_S5PV310
help
Enable S5PV310 CPU support
+config S5PV310_MCT
+ bool "Kernel timer support by MCT"
+ help
+ Use MCT (Multi Core Timer) as kernel timers
+
config S5PV310_SETUP_I2C1
bool
help
diff --git a/arch/arm/mach-s5pv310/Makefile b/arch/arm/mach-s5pv310/Makefile
index 84afc64..5ca7645 100644
--- a/arch/arm/mach-s5pv310/Makefile
+++ b/arch/arm/mach-s5pv310/Makefile
@@ -13,10 +13,16 @@ obj- :=
# Core support for S5PV310 system
obj-$(CONFIG_CPU_S5PV310) += cpu.o init.o clock.o irq-combiner.o
-obj-$(CONFIG_CPU_S5PV310) += setup-i2c0.o time.o gpiolib.o irq-eint.o
+obj-$(CONFIG_CPU_S5PV310) += setup-i2c0.o gpiolib.o irq-eint.o
-obj-$(CONFIG_SMP) += platsmp.o headsmp.o
+ifeq ($(CONFIG_S5PV310_MCT),y)
+obj-y += mct.o
+else
+obj-y += time.o
obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o
+endif
+
+obj-$(CONFIG_SMP) += platsmp.o headsmp.o
obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o
# machine support
diff --git a/arch/arm/mach-s5pv310/include/mach/regs-mct.h b/arch/arm/mach-s5pv310/include/mach/regs-mct.h
new file mode 100644
index 0000000..1ed14f0
--- /dev/null
+++ b/arch/arm/mach-s5pv310/include/mach/regs-mct.h
@@ -0,0 +1,58 @@
+/* arch/arm/mach-s5pv310/include/mach/regs-mct.h
+ *
+ * Copyright (c) 2010 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * S5PV310 MCT configutation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#ifndef __ASM_ARCH_REGS_MCT_H
+#define __ASM_ARCH_REGS_MCT_H __FILE__
+
+#include <mach/map.h>
+
+#define S5PV310_MCTREG(x) (S5P_VA_SYSTIMER + (x))
+
+#define S5PV310_MCT_G_CNT_L S5PV310_MCTREG(0x100)
+#define S5PV310_MCT_G_CNT_U S5PV310_MCTREG(0x104)
+#define S5PV310_MCT_G_CNT_WSTAT S5PV310_MCTREG(0x110)
+
+#define S5PV310_MCT_G_COMP0_L S5PV310_MCTREG(0x200)
+#define S5PV310_MCT_G_COMP0_U S5PV310_MCTREG(0x204)
+#define S5PV310_MCT_G_COMP0_ADD_INCR S5PV310_MCTREG(0x208)
+
+#define S5PV310_MCT_G_TCON S5PV310_MCTREG(0x240)
+
+#define S5PV310_MCT_G_INT_CSTAT S5PV310_MCTREG(0x244)
+#define S5PV310_MCT_G_INT_ENB S5PV310_MCTREG(0x248)
+#define S5PV310_MCT_G_WSTAT S5PV310_MCTREG(0x24C)
+
+#define S5PV310_MCT_L0_TCNTB S5PV310_MCTREG(0x300)
+#define S5PV310_MCT_L0_ICNTB S5PV310_MCTREG(0x308)
+#define S5PV310_MCT_L0_TCON S5PV310_MCTREG(0x320)
+
+#define S5PV310_MCT_L0_INT_CSTAT S5PV310_MCTREG(0x330)
+#define S5PV310_MCT_L0_INT_ENB S5PV310_MCTREG(0x334)
+#define S5PV310_MCT_L0_WSTAT S5PV310_MCTREG(0x340)
+
+#define S5PV310_MCT_L1_TCNTB S5PV310_MCTREG(0x400)
+#define S5PV310_MCT_L1_ICNTB S5PV310_MCTREG(0x408)
+#define S5PV310_MCT_L1_TCON S5PV310_MCTREG(0x420)
+
+#define S5PV310_MCT_L1_INT_CSTAT S5PV310_MCTREG(0x430)
+#define S5PV310_MCT_L1_INT_ENB S5PV310_MCTREG(0x434)
+#define S5PV310_MCT_L1_WSTAT S5PV310_MCTREG(0x440)
+
+#define MCT_G_TCON_START (1 << 8)
+#define MCT_G_TCON_COMP0_AUTO_INC (1 << 1)
+#define MCT_G_TCON_COMP0_ENABLE (1 << 0)
+
+#define MCT_L_TCON_INTERVAL_MODE (1 << 2)
+#define MCT_L_TCON_INT_START (1 << 1)
+#define MCT_L_TCON_TIMER_START (1 << 0)
+
+#endif /* __ASM_ARCH_REGS_MCT_H */
diff --git a/arch/arm/mach-s5pv310/mct.c b/arch/arm/mach-s5pv310/mct.c
new file mode 100644
index 0000000..05c2280
--- /dev/null
+++ b/arch/arm/mach-s5pv310/mct.c
@@ -0,0 +1,492 @@
+/* linux/arch/arm/mach-s5pv310/mct.c
+ *
+ * Copyright (c) 2010 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * S5PV310 MCT(Multi-Core Timer) 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.
+*/
+
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/percpu.h>
+
+#include <mach/map.h>
+#include <mach/regs-mct.h>
+#include <asm/mach/time.h>
+
+static unsigned long clk_cnt_per_tick;
+static unsigned long clk_rate;
+static cycle_t time_suspended;
+
+enum mct_tick_type {
+ MCT_TICK0,
+ MCT_TICK1,
+};
+
+static void s5pv310_mct_write(unsigned int value, void *addr)
+{
+ void __iomem *stat_addr;
+ u32 mask;
+ u32 i;
+
+ __raw_writel(value, addr);
+
+ switch ((u32) addr) {
+ case (u32) S5PV310_MCT_G_TCON:
+ stat_addr = S5PV310_MCT_G_WSTAT;
+ mask = 1 << 16; /* G_TCON write status */
+ break;
+ case (u32) S5PV310_MCT_G_COMP0_L:
+ stat_addr = S5PV310_MCT_G_WSTAT;
+ mask = 1 << 0; /* G_COMP0_L write status */
+ break;
+ case (u32) S5PV310_MCT_G_COMP0_U:
+ stat_addr = S5PV310_MCT_G_WSTAT;
+ mask = 1 << 1; /* G_COMP0_U write status */
+ break;
+ case (u32) S5PV310_MCT_G_COMP0_ADD_INCR:
+ stat_addr = S5PV310_MCT_G_WSTAT;
+ mask = 1 << 2; /* G_COMP0_ADD_INCR write status */
+ break;
+ case (u32) S5PV310_MCT_G_CNT_L:
+ stat_addr = S5PV310_MCT_G_CNT_WSTAT;
+ mask = 1 << 0; /* G_CNT_L write status */
+ break;
+ case (u32) S5PV310_MCT_G_CNT_U:
+ stat_addr = S5PV310_MCT_G_CNT_WSTAT;
+ mask = 1 << 1; /* G_CNT_U write status */
+ break;
+ case (u32) S5PV310_MCT_L0_TCON:
+ stat_addr = S5PV310_MCT_L0_WSTAT;
+ mask = 1 << 3; /* L0_TCON write status */
+ break;
+ case (u32) S5PV310_MCT_L1_TCON:
+ stat_addr = S5PV310_MCT_L1_WSTAT;
+ mask = 1 << 3; /* L1_TCON write status */
+ break;
+ case (u32) S5PV310_MCT_L0_TCNTB:
+ stat_addr = S5PV310_MCT_L0_WSTAT;
+ mask = 1 << 0; /* L0_TCNTB write status */
+ break;
+ case (u32) S5PV310_MCT_L1_TCNTB:
+ stat_addr = S5PV310_MCT_L1_WSTAT;
+ mask = 1 << 0; /* L1_TCNTB write status */
+ break;
+ case (u32) S5PV310_MCT_L0_ICNTB:
+ stat_addr = S5PV310_MCT_L0_WSTAT;
+ mask = 1 << 1; /* L0_ICNTB write status */
+ break;
+ case (u32) S5PV310_MCT_L1_ICNTB:
+ stat_addr = S5PV310_MCT_L1_WSTAT;
+ mask = 1 << 1; /* L1_ICNTB write status */
+ break;
+ default:
+ return;
+ }
+
+ /* Wait maximum 1 ms until written values are applied */
+ for (i = 0; i < loops_per_jiffy / 1000 * HZ; i++)
+ if (__raw_readl(stat_addr) & mask) {
+ __raw_writel(mask, stat_addr);
+ return;
+ }
+
+ panic("MCT hangs after writing %d (addr:0x%08x)\n", value, (u32)addr);
+}
+
+/* Clocksource handling */
+static void s5pv310_mct_frc_start(u32 hi, u32 lo)
+{
+ u32 reg;
+
+ s5pv310_mct_write(lo, S5PV310_MCT_G_CNT_L);
+ s5pv310_mct_write(hi, S5PV310_MCT_G_CNT_U);
+
+ reg = __raw_readl(S5PV310_MCT_G_TCON);
+ reg |= MCT_G_TCON_START;
+ s5pv310_mct_write(reg, S5PV310_MCT_G_TCON);
+}
+
+static cycle_t s5pv310_frc_read(struct clocksource *cs)
+{
+ unsigned int lo, hi;
+ u32 hi2 = __raw_readl(S5PV310_MCT_G_CNT_U);
+
+ do {
+ hi = hi2;
+ lo = __raw_readl(S5PV310_MCT_G_CNT_L);
+ hi2 = __raw_readl(S5PV310_MCT_G_CNT_U);
+ } while (hi != hi2);
+
+ return ((cycle_t)hi << 32) | lo;
+}
+
+static void s5pv310_frc_suspend(struct clocksource *cs)
+{
+ time_suspended = s5pv310_frc_read(cs);
+};
+
+static void s5pv310_frc_resume(struct clocksource *cs)
+{
+ s5pv310_mct_frc_start((u32)(time_suspended >> 32), (u32)time_suspended);
+};
+
+struct clocksource mct_frc = {
+ .name = "mct-frc",
+ .rating = 400,
+ .read = s5pv310_frc_read,
+ .mask = CLOCKSOURCE_MASK(64),
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+ .suspend = s5pv310_frc_suspend,
+ .resume = s5pv310_frc_resume,
+};
+
+static void __init s5pv310_clocksource_init(void)
+{
+ s5pv310_mct_frc_start(0, 0);
+
+ if (clocksource_register_hz(&mct_frc, clk_rate))
+ panic("%s: can't register clocksource\n", mct_frc.name);
+}
+
+static void s5pv310_mct_comp0_stop(void)
+{
+ unsigned int tcon;
+
+ tcon = __raw_readl(S5PV310_MCT_G_TCON);
+ tcon &= ~(MCT_G_TCON_COMP0_ENABLE | MCT_G_TCON_COMP0_AUTO_INC);
+
+ s5pv310_mct_write(tcon, S5PV310_MCT_G_TCON);
+ s5pv310_mct_write(0, S5PV310_MCT_G_INT_ENB);
+}
+
+static void s5pv310_mct_comp0_start(enum clock_event_mode mode,
+ unsigned long cycles)
+{
+ unsigned int tcon;
+ cycle_t comp_cycle;
+
+ tcon = __raw_readl(S5PV310_MCT_G_TCON);
+
+ if (mode == CLOCK_EVT_MODE_PERIODIC) {
+ tcon |= MCT_G_TCON_COMP0_AUTO_INC;
+ s5pv310_mct_write(cycles, S5PV310_MCT_G_COMP0_ADD_INCR);
+ }
+
+ comp_cycle = s5pv310_frc_read(&mct_frc) + cycles;
+ s5pv310_mct_write((u32)comp_cycle, S5PV310_MCT_G_COMP0_L);
+ s5pv310_mct_write((u32)(comp_cycle >> 32), S5PV310_MCT_G_COMP0_U);
+
+ s5pv310_mct_write(0x1, S5PV310_MCT_G_INT_ENB);
+
+ tcon |= MCT_G_TCON_COMP0_ENABLE;
+ s5pv310_mct_write(tcon , S5PV310_MCT_G_TCON);
+}
+
+static int s5pv310_comp_set_next_event(unsigned long cycles,
+ struct clock_event_device *evt)
+{
+ s5pv310_mct_comp0_start(evt->mode, cycles);
+
+ return 0;
+}
+
+static void s5pv310_comp_set_mode(enum clock_event_mode mode,
+ struct clock_event_device *evt)
+{
+ s5pv310_mct_comp0_stop();
+
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ s5pv310_mct_comp0_start(mode, clk_cnt_per_tick);
+ break;
+
+ case CLOCK_EVT_MODE_ONESHOT:
+ case CLOCK_EVT_MODE_UNUSED:
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ case CLOCK_EVT_MODE_RESUME:
+ break;
+ }
+}
+
+static struct clock_event_device mct_comp_device = {
+ .name = "mct-comp",
+ .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
+ .rating = 250,
+ .set_next_event = s5pv310_comp_set_next_event,
+ .set_mode = s5pv310_comp_set_mode,
+};
+
+irqreturn_t s5pv310_mct_comp_isr(int irq, void *dev_id)
+{
+ struct clock_event_device *evt;
+
+ s5pv310_mct_write(0x1, S5PV310_MCT_G_INT_CSTAT);
+
+ evt = &mct_comp_device;
+ evt->event_handler(evt);
+
+ return IRQ_HANDLED;
+}
+
+static struct irqaction mct_comp_event_irq = {
+ .name = "mct_comp_irq",
+ .flags = IRQF_TIMER | IRQF_IRQPOLL,
+ .handler = s5pv310_mct_comp_isr,
+};
+
+static void s5pv310_clockevent_init(void)
+{
+ clk_cnt_per_tick = clk_rate / 2 / HZ;
+
+ clockevents_calc_mult_shift(&mct_comp_device, clk_rate / 2, 5);
+ mct_comp_device.max_delta_ns =
+ clockevent_delta2ns(0xffffffff, &mct_comp_device);
+ mct_comp_device.min_delta_ns =
+ clockevent_delta2ns(0xf, &mct_comp_device);
+ mct_comp_device.cpumask = cpumask_of(0);
+ clockevents_register_device(&mct_comp_device);
+
+ setup_irq(IRQ_MCT_G0, &mct_comp_event_irq);
+}
+
+#ifdef CONFIG_LOCAL_TIMERS
+/* Clock event handling */
+static void s5pv310_mct_tick_stop(enum mct_tick_type type)
+{
+ unsigned long tmp;
+ void __iomem *addr;
+
+ if (type == MCT_TICK0)
+ addr = S5PV310_MCT_L0_TCON;
+ else
+ addr = S5PV310_MCT_L1_TCON;
+
+ tmp = __raw_readl(addr);
+ tmp &= ~(MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START);
+ s5pv310_mct_write(tmp, addr);
+}
+
+static void s5pv310_mct_tick_start(enum mct_tick_type type,
+ unsigned long cycles)
+{
+ unsigned long tmp;
+ unsigned int tmp1, tmp2;
+ void __iomem *addr;
+
+ s5pv310_mct_tick_stop(type);
+
+ tmp1 = (1 << 31) | cycles; /* MCT_L_UPDATE_ICNTB */
+ tmp2 = 1 << 0; /* L_INT_ENB_CNTIE */
+
+ if (type == MCT_TICK0) {
+ /* update interrupt count buffer */
+ s5pv310_mct_write(tmp1, S5PV310_MCT_L0_ICNTB);
+
+ /* enable MCT tick0 interupt */
+ s5pv310_mct_write(tmp2, S5PV310_MCT_L0_INT_ENB);
+
+ addr = S5PV310_MCT_L0_TCON;
+ } else {
+ /* update interrupt count buffer */
+ s5pv310_mct_write(tmp1, S5PV310_MCT_L1_ICNTB);
+
+ /* enable MCT tick1 interupt */
+ s5pv310_mct_write(tmp2, S5PV310_MCT_L1_INT_ENB);
+
+ addr = S5PV310_MCT_L1_TCON;
+ }
+
+ tmp = __raw_readl(addr);
+ tmp |= MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START |
+ MCT_L_TCON_INTERVAL_MODE;
+ s5pv310_mct_write(tmp, addr);
+}
+
+static inline int s5pv310_tick_set_next_event(enum mct_tick_type type,
+ unsigned long cycles)
+{
+ s5pv310_mct_tick_start(type, cycles);
+
+ return 0;
+}
+
+static inline void s5pv310_tick_set_mode(enum mct_tick_type type,
+ enum clock_event_mode mode)
+{
+ s5pv310_mct_tick_stop(type);
+
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ s5pv310_mct_tick_start(type, clk_cnt_per_tick);
+ break;
+
+ case CLOCK_EVT_MODE_ONESHOT:
+ case CLOCK_EVT_MODE_UNUSED:
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ case CLOCK_EVT_MODE_RESUME:
+ break;
+ }
+}
+
+static int s5pv310_tick0_set_next_event(unsigned long cycles,
+ struct clock_event_device *evt)
+{
+ return s5pv310_tick_set_next_event(MCT_TICK0, cycles);
+}
+
+static int s5pv310_tick1_set_next_event(unsigned long cycles,
+ struct clock_event_device *evt)
+{
+ return s5pv310_tick_set_next_event(MCT_TICK1, cycles);
+}
+
+static void s5pv310_tick0_set_mode(enum clock_event_mode mode,
+ struct clock_event_device *evt)
+{
+ s5pv310_tick_set_mode(MCT_TICK0, mode);
+}
+
+static void s5pv310_tick1_set_mode(enum clock_event_mode mode,
+ struct clock_event_device *evt)
+{
+ s5pv310_tick_set_mode(MCT_TICK1, mode);
+}
+
+irqreturn_t s5pv310_mct_tick0_isr(int irq, void *dev_id)
+{
+ struct clock_event_device *evt = dev_id;
+
+ /*
+ * This is for supporting oneshot mode.
+ * Mct would generate interrupt periodically
+ * without explicit stopping.
+ */
+ if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
+ s5pv310_mct_tick_stop(MCT_TICK0);
+
+ /* Clear the MCT tick0 interrupt */
+ s5pv310_mct_write(0x1, S5PV310_MCT_L0_INT_CSTAT);
+
+ evt->event_handler(evt);
+
+ return IRQ_HANDLED;
+}
+
+irqreturn_t s5pv310_mct_tick1_isr(int irq, void *dev_id)
+{
+ struct clock_event_device *evt = dev_id;
+
+ /*
+ * This is for supporting oneshot mode.
+ * Mct would generate interrupt periodically
+ * without explicit stopping.
+ */
+ if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
+ s5pv310_mct_tick_stop(MCT_TICK1);
+
+ /* Clear the MCT tick1 interrupt */
+ s5pv310_mct_write(0x1, S5PV310_MCT_L1_INT_CSTAT);
+
+ evt->event_handler(evt);
+
+ return IRQ_HANDLED;
+}
+
+static struct irqaction mct_tick0_event_irq = {
+ .name = "mct_tick0_irq",
+ .flags = IRQF_TIMER | IRQF_NOBALANCING,
+ .handler = s5pv310_mct_tick0_isr,
+};
+
+static struct irqaction mct_tick1_event_irq = {
+ .name = "mct_tick1_irq",
+ .flags = IRQF_TIMER | IRQF_NOBALANCING,
+ .handler = s5pv310_mct_tick1_isr,
+};
+
+static void s5pv310_mct_tick_init(struct clock_event_device *evt)
+{
+ unsigned int cpu = smp_processor_id();
+
+ if (cpu == 0) {
+ s5pv310_mct_write(0x1, S5PV310_MCT_L0_TCNTB);
+ evt->set_next_event = s5pv310_tick0_set_next_event;
+ evt->set_mode = s5pv310_tick0_set_mode;
+ evt->name = "mct_tick0";
+ } else {
+ s5pv310_mct_write(0x1, S5PV310_MCT_L1_TCNTB);
+ evt->set_next_event = s5pv310_tick1_set_next_event;
+ evt->set_mode = s5pv310_tick1_set_mode;
+ evt->name = "mct_tick1";
+ }
+
+ evt->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
+ evt->rating = 450;
+
+ clockevents_calc_mult_shift(evt, clk_rate / 2, 5);
+ evt->max_delta_ns =
+ clockevent_delta2ns(0x7fffffff, evt);
+ evt->min_delta_ns =
+ clockevent_delta2ns(0xf, evt);
+
+ clockevents_register_device(evt);
+
+ if (cpu == 0) {
+ mct_tick0_event_irq.dev_id = evt;
+ setup_irq(IRQ_MCT_L0, &mct_tick0_event_irq);
+ } else {
+ mct_tick1_event_irq.dev_id = evt;
+ irq_set_affinity(IRQ_MCT1, cpumask_of(1));
+ setup_irq(IRQ_MCT_L1, &mct_tick1_event_irq);
+ }
+}
+
+/* Setup the local clock events for a CPU */
+void __cpuinit local_timer_setup(struct clock_event_device *evt)
+{
+ s5pv310_mct_tick_init(evt);
+}
+
+int local_timer_ack(void)
+{
+ return 0;
+}
+
+#ifdef CONFIG_HOTPLUG_CPU
+void local_timer_stop(void)
+{
+ s5pv310_mct_tick_stop(smp_processor_id());
+}
+#endif /* CONFIG_HOTPLUG_CPU */
+
+#endif /* CONFIG_LOCAL_TIMERS */
+
+static void __init s5pv310_timer_resources(void)
+{
+ struct clk *mct_clk;
+ mct_clk = clk_get(NULL, "xtal");
+
+ clk_rate = clk_get_rate(mct_clk);
+}
+
+static void __init s5pv310_timer_init(void)
+{
+ s5pv310_timer_resources();
+ s5pv310_clocksource_init();
+ s5pv310_clockevent_init();
+}
+
+struct sys_timer s5pv310_timer = {
+ .init = s5pv310_timer_init,
+};
--
1.6.2.5
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox