diff options
-rw-r--r-- | arch/arm/mach-sa1100/generic.c | 25 | ||||
-rw-r--r-- | drivers/dma/Kconfig | 9 | ||||
-rw-r--r-- | drivers/dma/Makefile | 1 | ||||
-rw-r--r-- | drivers/dma/sa11x0-dma.c | 1109 | ||||
-rw-r--r-- | drivers/net/irda/Kconfig | 2 | ||||
-rw-r--r-- | drivers/net/irda/sa1100_ir.c | 953 | ||||
-rw-r--r-- | include/linux/sa11x0-dma.h | 24 |
7 files changed, 1700 insertions, 423 deletions
diff --git a/arch/arm/mach-sa1100/generic.c b/arch/arm/mach-sa1100/generic.c index 2b33b45..1752686 100644 --- a/arch/arm/mach-sa1100/generic.c +++ b/arch/arm/mach-sa1100/generic.c @@ -14,6 +14,7 @@ #include <linux/kernel.h> #include <linux/init.h> #include <linux/delay.h> +#include <linux/dma-mapping.h> #include <linux/pm.h> #include <linux/cpufreq.h> #include <linux/ioport.h> @@ -289,6 +290,29 @@ static struct platform_device sa11x0rtc_device = { .id = -1, }; +static struct resource sa11x0dma_resources[] = { + DEFINE_RES_MEM(__PREG(DDAR(0)), 6 * DMASp), + DEFINE_RES_IRQ(IRQ_DMA0), + DEFINE_RES_IRQ(IRQ_DMA1), + DEFINE_RES_IRQ(IRQ_DMA2), + DEFINE_RES_IRQ(IRQ_DMA3), + DEFINE_RES_IRQ(IRQ_DMA4), + DEFINE_RES_IRQ(IRQ_DMA5), +}; + +static u64 sa11x0dma_dma_mask = DMA_BIT_MASK(32); + +static struct platform_device sa11x0dma_device = { + .name = "sa11x0-dma", + .id = -1, + .dev = { + .dma_mask = &sa11x0dma_dma_mask, + .coherent_dma_mask = 0xffffffff, + }, + .num_resources = ARRAY_SIZE(sa11x0dma_resources), + .resource = sa11x0dma_resources, +}; + static struct platform_device *sa11x0_devices[] __initdata = { &sa11x0udc_device, &sa11x0uart1_device, @@ -297,6 +321,7 @@ static struct platform_device *sa11x0_devices[] __initdata = { &sa11x0pcmcia_device, &sa11x0fb_device, &sa11x0rtc_device, + &sa11x0dma_device, }; static int __init sa1100_init(void) diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index f1a2749..4a6c46d 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -252,6 +252,15 @@ config EP93XX_DMA help Enable support for the Cirrus Logic EP93xx M2P/M2M DMA controller. +config DMA_SA11X0 + tristate "SA-11x0 DMA support" + depends on ARCH_SA1100 + select DMA_ENGINE + help + Support the DMA engine found on Intel StrongARM SA-1100 and + SA-1110 SoCs. This DMA engine can only be used with on-chip + devices. + config DMA_ENGINE bool diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 009a222..86b795b 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -27,3 +27,4 @@ obj-$(CONFIG_PL330_DMA) += pl330.o obj-$(CONFIG_PCH_DMA) += pch_dma.o obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o obj-$(CONFIG_EP93XX_DMA) += ep93xx_dma.o +obj-$(CONFIG_DMA_SA11X0) += sa11x0-dma.o diff --git a/drivers/dma/sa11x0-dma.c b/drivers/dma/sa11x0-dma.c new file mode 100644 index 0000000..16a6b48 --- /dev/null +++ b/drivers/dma/sa11x0-dma.c @@ -0,0 +1,1109 @@ +/* + * SA11x0 DMAengine support + * + * Copyright (C) 2012 Russell King + * Derived in part from arch/arm/mach-sa1100/dma.c, + * Copyright (C) 2000, 2001 by Nicolas Pitre + * + * 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/device.h> +#include <linux/dmaengine.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sa11x0-dma.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#define NR_PHY_CHAN 6 +#define DMA_ALIGN 3 +#define DMA_MAX_SIZE 0x1fff +#define DMA_CHUNK_SIZE 0x1000 + +#define DMA_DDAR 0x00 +#define DMA_DCSR_S 0x04 +#define DMA_DCSR_C 0x08 +#define DMA_DCSR_R 0x0c +#define DMA_DBSA 0x10 +#define DMA_DBTA 0x14 +#define DMA_DBSB 0x18 +#define DMA_DBTB 0x1c +#define DMA_SIZE 0x20 + +#define DCSR_RUN (1 << 0) +#define DCSR_IE (1 << 1) +#define DCSR_ERROR (1 << 2) +#define DCSR_DONEA (1 << 3) +#define DCSR_STRTA (1 << 4) +#define DCSR_DONEB (1 << 5) +#define DCSR_STRTB (1 << 6) +#define DCSR_BIU (1 << 7) + +#define DDAR_RW (1 << 0) /* 0 = W, 1 = R */ +#define DDAR_E (1 << 1) /* 0 = LE, 1 = BE */ +#define DDAR_BS (1 << 2) /* 0 = BS4, 1 = BS8 */ +#define DDAR_DW (1 << 3) /* 0 = 8b, 1 = 16b */ +#define DDAR_Ser0UDCTr (0x0 << 4) +#define DDAR_Ser0UDCRc (0x1 << 4) +#define DDAR_Ser1SDLCTr (0x2 << 4) +#define DDAR_Ser1SDLCRc (0x3 << 4) +#define DDAR_Ser1UARTTr (0x4 << 4) +#define DDAR_Ser1UARTRc (0x5 << 4) +#define DDAR_Ser2ICPTr (0x6 << 4) +#define DDAR_Ser2ICPRc (0x7 << 4) +#define DDAR_Ser3UARTTr (0x8 << 4) +#define DDAR_Ser3UARTRc (0x9 << 4) +#define DDAR_Ser4MCP0Tr (0xa << 4) +#define DDAR_Ser4MCP0Rc (0xb << 4) +#define DDAR_Ser4MCP1Tr (0xc << 4) +#define DDAR_Ser4MCP1Rc (0xd << 4) +#define DDAR_Ser4SSPTr (0xe << 4) +#define DDAR_Ser4SSPRc (0xf << 4) + +struct sa11x0_dma_sg { + u32 addr; + u32 len; +}; + +struct sa11x0_dma_desc { + struct dma_async_tx_descriptor tx; + u32 ddar; + size_t size; + + /* maybe protected by c->lock */ + struct list_head node; + unsigned sglen; + struct sa11x0_dma_sg sg[0]; +}; + +struct sa11x0_dma_phy; + +struct sa11x0_dma_chan { + struct dma_chan chan; + spinlock_t lock; + dma_cookie_t lc; + + /* protected by c->lock */ + struct sa11x0_dma_phy *phy; + enum dma_status status; + struct list_head desc_submitted; + struct list_head desc_issued; + + /* protected by d->lock */ + struct list_head node; + + u32 ddar; + const char *name; +}; + +struct sa11x0_dma_phy { + void __iomem *base; + struct sa11x0_dma_dev *dev; + unsigned num; + + struct sa11x0_dma_chan *vchan; + + /* Protected by c->lock */ + unsigned sg_load; + struct sa11x0_dma_desc *txd_load; + unsigned sg_done; + struct sa11x0_dma_desc *txd_done; +#ifdef CONFIG_PM_SLEEP + u32 dbs[2]; + u32 dbt[2]; + u32 dcsr; +#endif +}; + +struct sa11x0_dma_dev { + struct dma_device slave; + void __iomem *base; + spinlock_t lock; + struct tasklet_struct task; + struct list_head chan_pending; + struct list_head desc_complete; + struct sa11x0_dma_phy phy[NR_PHY_CHAN]; +}; + +static struct sa11x0_dma_chan *to_sa11x0_dma_chan(struct dma_chan *chan) +{ + return container_of(chan, struct sa11x0_dma_chan, chan); +} + +static struct sa11x0_dma_dev *to_sa11x0_dma(struct dma_device *dmadev) +{ + return container_of(dmadev, struct sa11x0_dma_dev, slave); +} + +static struct sa11x0_dma_desc *to_sa11x0_dma_tx(struct dma_async_tx_descriptor *tx) +{ + return container_of(tx, struct sa11x0_dma_desc, tx); +} + +static struct sa11x0_dma_desc *sa11x0_dma_next_desc(struct sa11x0_dma_chan *c) +{ + if (list_empty(&c->desc_issued)) + return NULL; + + return list_first_entry(&c->desc_issued, struct sa11x0_dma_desc, node); +} + +static void sa11x0_dma_start_desc(struct sa11x0_dma_phy *p, struct sa11x0_dma_desc *txd) +{ + list_del(&txd->node); + p->txd_load = txd; + p->sg_load = 0; + + dev_vdbg(p->dev->slave.dev, "pchan %u: txd %p[%x]: starting: DDAR:%x\n", + p->num, txd, txd->tx.cookie, txd->ddar); +} + +static void noinline sa11x0_dma_start_sg(struct sa11x0_dma_phy *p, + struct sa11x0_dma_chan *c) +{ + struct sa11x0_dma_desc *txd = p->txd_load; + struct sa11x0_dma_sg *sg; + void __iomem *base = p->base; + unsigned dbsx, dbtx; + u32 dcsr; + + if (!txd) + return; + + dcsr = readl_relaxed(base + DMA_DCSR_R); + + /* Don't try to load the next transfer if both buffers are started */ + if ((dcsr & (DCSR_STRTA | DCSR_STRTB)) == (DCSR_STRTA | DCSR_STRTB)) + return; + + if (p->sg_load == txd->sglen) { + struct sa11x0_dma_desc *txn = sa11x0_dma_next_desc(c); + + /* + * We have reached the end of the current descriptor. + * Peek at the next descriptor, and if compatible with + * the current, start processing it. + */ + if (txn && txn->ddar == txd->ddar) { + txd = txn; + sa11x0_dma_start_desc(p, txn); + } else { + p->txd_load = NULL; + return; + } + } + + sg = &txd->sg[p->sg_load++]; + + /* Select buffer to load according to channel status */ + if (((dcsr & (DCSR_BIU | DCSR_STRTB)) == (DCSR_BIU | DCSR_STRTB)) || + ((dcsr & (DCSR_BIU | DCSR_STRTA)) == 0)) { + dbsx = DMA_DBSA; + dbtx = DMA_DBTA; + dcsr = DCSR_STRTA | DCSR_IE | DCSR_RUN; + } else { + dbsx = DMA_DBSB; + dbtx = DMA_DBTB; + dcsr = DCSR_STRTB | DCSR_IE | DCSR_RUN; + } + + writel_relaxed(sg->addr, base + dbsx); + writel_relaxed(sg->len, base + dbtx); + writel(dcsr, base + DMA_DCSR_S); + + dev_dbg(p->dev->slave.dev, "pchan %u: load: DCSR:%02x DBS%c:%08x DBT%c:%08x\n", + p->num, dcsr, + 'A' + (dbsx == DMA_DBSB), sg->addr, + 'A' + (dbtx == DMA_DBTB), sg->len); +} + +static void noinline sa11x0_dma_complete(struct sa11x0_dma_phy *p, + struct sa11x0_dma_chan *c) +{ + struct sa11x0_dma_desc *txd = p->txd_done; + + if (++p->sg_done == txd->sglen) { + struct sa11x0_dma_dev *d = p->dev; + + dev_vdbg(d->slave.dev, "pchan %u: txd %p[%x]: completed\n", + p->num, p->txd_done, p->txd_done->tx.cookie); + + c->lc = txd->tx.cookie; + + spin_lock(&d->lock); + list_add_tail(&txd->node, &d->desc_complete); + spin_unlock(&d->lock); + + p->sg_done = 0; + p->txd_done = p->txd_load; + + tasklet_schedule(&d->task); + } + + sa11x0_dma_start_sg(p, c); +} + +static irqreturn_t sa11x0_dma_irq(int irq, void *dev_id) +{ + struct sa11x0_dma_phy *p = dev_id; + struct sa11x0_dma_dev *d = p->dev; + struct sa11x0_dma_chan *c; + u32 dcsr; + + dcsr = readl_relaxed(p->base + DMA_DCSR_R); + if (!(dcsr & (DCSR_ERROR | DCSR_DONEA | DCSR_DONEB))) + return IRQ_NONE; + + /* Clear reported status bits */ + writel_relaxed(dcsr & (DCSR_ERROR | DCSR_DONEA | DCSR_DONEB), + p->base + DMA_DCSR_C); + + dev_dbg(d->slave.dev, "pchan %u: irq: DCSR:%02x\n", p->num, dcsr); + + if (dcsr & DCSR_ERROR) { + dev_err(d->slave.dev, "pchan %u: error. DCSR:%02x DDAR:%08x DBSA:%08x DBTA:%08x DBSB:%08x DBTB:%08x\n", + p->num, dcsr, + readl_relaxed(p->base + DMA_DDAR), + readl_relaxed(p->base + DMA_DBSA), + readl_relaxed(p->base + DMA_DBTA), + readl_relaxed(p->base + DMA_DBSB), + readl_relaxed(p->base + DMA_DBTB)); + } + + c = p->vchan; + if (c) { + unsigned long flags; + + spin_lock_irqsave(&c->lock, flags); + /* + * Now that we're holding the lock, check that the vchan + * really is associated with this pchan before touching the + * hardware. This should always succeed, because we won't + * change p->vchan or c->phy while the channel is actively + * transferring. + */ + if (c->phy == p) { + if (dcsr & DCSR_DONEA) + sa11x0_dma_complete(p, c); + if (dcsr & DCSR_DONEB) + sa11x0_dma_complete(p, c); + } + spin_unlock_irqrestore(&c->lock, flags); + } + + return IRQ_HANDLED; +} + +static void sa11x0_dma_start_txd(struct sa11x0_dma_chan *c) +{ + struct sa11x0_dma_desc *txd = sa11x0_dma_next_desc(c); + + /* If the issued list is empty, we have no further txds to process */ + if (txd) { + struct sa11x0_dma_phy *p = c->phy; + + sa11x0_dma_start_desc(p, txd); + p->txd_done = txd; + p->sg_done = 0; + + /* The channel should not have any transfers started */ + WARN_ON(readl_relaxed(p->base + DMA_DCSR_R) & + (DCSR_STRTA | DCSR_STRTB)); + + /* Clear the run and start bits before changing DDAR */ + writel_relaxed(DCSR_RUN | DCSR_STRTA | DCSR_STRTB, + p->base + DMA_DCSR_C); + writel_relaxed(txd->ddar, p->base + DMA_DDAR); + + /* Try to start both buffers */ + sa11x0_dma_start_sg(p, c); + sa11x0_dma_start_sg(p, c); + } +} + +static void sa11x0_dma_tasklet(unsigned long arg) +{ + struct sa11x0_dma_dev *d = (struct sa11x0_dma_dev *)arg; + struct sa11x0_dma_phy *p; + struct sa11x0_dma_chan *c; + struct sa11x0_dma_desc *txd, *txn; + LIST_HEAD(head); + unsigned pch, pch_alloc = 0; + + dev_dbg(d->slave.dev, "tasklet enter\n"); + + /* Get the completed tx descriptors */ + spin_lock_irq(&d->lock); + list_splice_init(&d->desc_complete, &head); + spin_unlock_irq(&d->lock); + + list_for_each_entry(txd, &head, node) { + c = to_sa11x0_dma_chan(txd->tx.chan); + + dev_dbg(d->slave.dev, "vchan %p: txd %p[%x] completed\n", + c, txd, txd->tx.cookie); + + spin_lock_irq(&c->lock); + p = c->phy; + if (p) { + if (!p->txd_done) + sa11x0_dma_start_txd(c); + if (!p->txd_done) { + /* No current txd associated with this channel */ + dev_dbg(d->slave.dev, "pchan %u: free\n", p->num); + + /* Mark this channel free */ + c->phy = NULL; + p->vchan = NULL; + } + } + spin_unlock_irq(&c->lock); + } + + spin_lock_irq(&d->lock); + for (pch = 0; pch < NR_PHY_CHAN; pch++) { + p = &d->phy[pch]; + + if (p->vchan == NULL && !list_empty(&d->chan_pending)) { + c = list_first_entry(&d->chan_pending, + struct sa11x0_dma_chan, node); + list_del_init(&c->node); + + pch_alloc |= 1 << pch; + + /* Mark this channel allocated */ + p->vchan = c; + + dev_dbg(d->slave.dev, "pchan %u: alloc vchan %p\n", pch, c); + } + } + spin_unlock_irq(&d->lock); + + for (pch = 0; pch < NR_PHY_CHAN; pch++) { + if (pch_alloc & (1 << pch)) { + p = &d->phy[pch]; + c = p->vchan; + + spin_lock_irq(&c->lock); + c->phy = p; + + sa11x0_dma_start_txd(c); + spin_unlock_irq(&c->lock); + } + } + + /* Now free the completed tx descriptor, and call their callbacks */ + list_for_each_entry_safe(txd, txn, &head, node) { + dma_async_tx_callback callback = txd->tx.callback; + void *callback_param = txd->tx.callback_param; + + dev_dbg(d->slave.dev, "txd %p[%x]: callback and free\n", + txd, txd->tx.cookie); + + kfree(txd); + + if (callback) + callback(callback_param); + } + + dev_dbg(d->slave.dev, "tasklet exit\n"); +} + + +static void sa11x0_dma_desc_free(struct sa11x0_dma_dev *d, struct list_head *head) +{ + struct sa11x0_dma_desc *txd, *txn; + + list_for_each_entry_safe(txd, txn, head, node) { + dev_dbg(d->slave.dev, "txd %p: freeing\n", txd); + kfree(txd); + } +} + +static int sa11x0_dma_alloc_chan_resources(struct dma_chan *chan) +{ + return 0; +} + +static void sa11x0_dma_free_chan_resources(struct dma_chan *chan) +{ + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); + struct sa11x0_dma_dev *d = to_sa11x0_dma(chan->device); + unsigned long flags; + LIST_HEAD(head); + + spin_lock_irqsave(&c->lock, flags); + spin_lock(&d->lock); + list_del_init(&c->node); + spin_unlock(&d->lock); + + list_splice_tail_init(&c->desc_submitted, &head); + list_splice_tail_init(&c->desc_issued, &head); + spin_unlock_irqrestore(&c->lock, flags); + + sa11x0_dma_desc_free(d, &head); +} + +static dma_addr_t sa11x0_dma_pos(struct sa11x0_dma_phy *p) +{ + unsigned reg; + u32 dcsr; + + dcsr = readl_relaxed(p->base + DMA_DCSR_R); + + if ((dcsr & (DCSR_BIU | DCSR_STRTA)) == DCSR_STRTA || + (dcsr & (DCSR_BIU | DCSR_STRTB)) == DCSR_BIU) + reg = DMA_DBSA; + else + reg = DMA_DBSB; + + return readl_relaxed(p->base + reg); +} + +static enum dma_status sa11x0_dma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, struct dma_tx_state *state) +{ + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); + struct sa11x0_dma_dev *d = to_sa11x0_dma(chan->device); + struct sa11x0_dma_phy *p; + struct sa11x0_dma_desc *txd; + dma_cookie_t last_used, last_complete; + unsigned long flags; + enum dma_status ret; + size_t bytes = 0; + + last_used = c->chan.cookie; + last_complete = c->lc; + + ret = dma_async_is_complete(cookie, last_complete, last_used); + if (ret == DMA_SUCCESS) { + dma_set_tx_state(state, last_complete, last_used, 0); + return ret; + } + + spin_lock_irqsave(&c->lock, flags); + p = c->phy; + ret = c->status; + if (p) { + dma_addr_t addr = sa11x0_dma_pos(p); + + dev_vdbg(d->slave.dev, "tx_status: addr:%x\n", addr); + + txd = p->txd_done; + if (txd) { + unsigned i; + + for (i = 0; i < txd->sglen; i++) { + dev_vdbg(d->slave.dev, "tx_status: [%u] %x+%x\n", + i, txd->sg[i].addr, txd->sg[i].len); + if (addr >= txd->sg[i].addr && + addr < txd->sg[i].addr + txd->sg[i].len) { + unsigned len; + + len = txd->sg[i].len - + (addr - txd->sg[i].addr); + dev_vdbg(d->slave.dev, "tx_status: [%u] +%x\n", + i, len); + bytes += len; + i++; + break; + } + } + for (; i < txd->sglen; i++) { + dev_vdbg(d->slave.dev, "tx_status: [%u] %x+%x ++\n", + i, txd->sg[i].addr, txd->sg[i].len); + bytes += txd->sg[i].len; + } + } + if (txd != p->txd_load && p->txd_load) + bytes += p->txd_load->size; + } + list_for_each_entry(txd, &c->desc_issued, node) { + bytes += txd->size; + } + spin_unlock_irqrestore(&c->lock, flags); + + dma_set_tx_state(state, last_complete, last_used, bytes); + + dev_vdbg(d->slave.dev, "tx_status: bytes 0x%zx\n", bytes); + + return ret; +} + +/* + * Move pending txds to the issued list, and re-init pending list. + * If not already pending, add this channel to the list of pending + * channels and trigger the tasklet to run. + */ +static void sa11x0_dma_issue_pending(struct dma_chan *chan) +{ + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); + struct sa11x0_dma_dev *d = to_sa11x0_dma(chan->device); + unsigned long flags; + + spin_lock_irqsave(&c->lock, flags); + list_splice_tail_init(&c->desc_submitted, &c->desc_issued); + if (!list_empty(&c->desc_issued)) { + spin_lock(&d->lock); + if (!c->phy && list_empty(&c->node)) { + list_add_tail(&c->node, &d->chan_pending); + tasklet_schedule(&d->task); + dev_dbg(d->slave.dev, "vchan %p: issued\n", c); + } + spin_unlock(&d->lock); + } else + dev_dbg(d->slave.dev, "vchan %p: nothing to issue\n", c); + spin_unlock_irqrestore(&c->lock, flags); +} + +static dma_cookie_t sa11x0_dma_tx_submit(struct dma_async_tx_descriptor *tx) +{ + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(tx->chan); + struct sa11x0_dma_desc *txd = to_sa11x0_dma_tx(tx); + unsigned long flags; + + spin_lock_irqsave(&c->lock, flags); + c->chan.cookie += 1; + if (c->chan.cookie < 0) + c->chan.cookie = 1; + txd->tx.cookie = c->chan.cookie; + + list_add_tail(&txd->node, &c->desc_submitted); + spin_unlock_irqrestore(&c->lock, flags); + + dev_dbg(tx->chan->device->dev, "vchan %p: txd %p[%x]: submitted\n", + c, txd, txd->tx.cookie); + + return txd->tx.cookie; +} + +static struct dma_async_tx_descriptor *sa11x0_dma_prep_slave_sg( + struct dma_chan *chan, struct scatterlist *sg, unsigned int sglen, + enum dma_transfer_direction dir, unsigned long flags) +{ + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); + struct sa11x0_dma_desc *txd; + struct scatterlist *sgent; + unsigned i, j = sglen; + size_t size = 0; + + /* SA11x0 channels can only operate in their native direction */ + if (dir != (c->ddar & DDAR_RW ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV)) { + dev_err(chan->device->dev, "vchan %p: bad DMA direction: DDAR:%08x dir:%u\n", + c, c->ddar, dir); + return NULL; + } + + /* Do not allow zero-sized txds */ + if (sglen == 0) + return NULL; + + for_each_sg(sg, sgent, sglen, i) { + dma_addr_t addr = sg_dma_address(sgent); + unsigned int len = sg_dma_len(sgent); + + if (len > DMA_MAX_SIZE) + j += DIV_ROUND_UP(len, DMA_MAX_SIZE & ~DMA_ALIGN) - 1; + if (addr & DMA_ALIGN) { + dev_dbg(chan->device->dev, "vchan %p: bad buffer alignment: %08x\n", + c, addr); + return NULL; + } + } + + txd = kzalloc(sizeof(*txd) + j * sizeof(txd->sg[0]), GFP_ATOMIC); + if (!txd) { + dev_dbg(chan->device->dev, "vchan %p: kzalloc failed\n", c); + return NULL; + } + + j = 0; + for_each_sg(sg, sgent, sglen, i) { + dma_addr_t addr = sg_dma_address(sgent); + unsigned len = sg_dma_len(sgent); + + size += len; + + do { + unsigned tlen = len; + + /* + * Check whether the transfer will fit. If not, try + * to split the transfer up such that we end up with + * equal chunks - but make sure that we preserve the + * alignment. This avoids small segments. + */ + if (tlen > DMA_MAX_SIZE) { + unsigned mult = DIV_ROUND_UP(tlen, + DMA_MAX_SIZE & ~DMA_ALIGN); + + tlen = (tlen / mult) & ~DMA_ALIGN; + } + + txd->sg[j].addr = addr; + txd->sg[j].len = tlen; + + addr += tlen; + len -= tlen; + j++; + } while (len); + } + + dma_async_tx_descriptor_init(&txd->tx, &c->chan); + txd->tx.flags = flags; + txd->tx.tx_submit = sa11x0_dma_tx_submit; + txd->ddar = c->ddar; + txd->size = size; + txd->sglen = j; + + dev_dbg(chan->device->dev, "vchan %p: txd %p: size %u nr %u\n", + c, txd, txd->size, txd->sglen); + + return &txd->tx; +} + +static int sa11x0_dma_slave_config(struct sa11x0_dma_chan *c, struct dma_slave_config *cfg) +{ + u32 ddar = c->ddar & ((0xf << 4) | DDAR_RW); + dma_addr_t addr; + enum dma_slave_buswidth width; + u32 maxburst; + + if (ddar & DDAR_RW) { + addr = cfg->src_addr; + width = cfg->src_addr_width; + maxburst = cfg->src_maxburst; + } else { + addr = cfg->dst_addr; + width = cfg->dst_addr_width; + maxburst = cfg->dst_maxburst; + } + + if ((width != DMA_SLAVE_BUSWIDTH_1_BYTE && + width != DMA_SLAVE_BUSWIDTH_2_BYTES) || + (maxburst != 4 && maxburst != 8)) + return -EINVAL; + + if (width == DMA_SLAVE_BUSWIDTH_2_BYTES) + ddar |= DDAR_DW; + if (maxburst == 8) + ddar |= DDAR_BS; + + dev_dbg(c->chan.device->dev, "vchan %p: dma_slave_config addr %x width %u burst %u\n", + c, addr, width, maxburst); + + c->ddar = ddar | (addr & 0xf0000000) | (addr & 0x003ffffc) << 6; + + return 0; +} + +static int sa11x0_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, + unsigned long arg) +{ + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); + struct sa11x0_dma_dev *d = to_sa11x0_dma(chan->device); + struct sa11x0_dma_phy *p; + LIST_HEAD(head); + unsigned long flags; + int ret; + + switch (cmd) { + case DMA_SLAVE_CONFIG: + return sa11x0_dma_slave_config(c, (struct dma_slave_config *)arg); + + case DMA_TERMINATE_ALL: + dev_dbg(d->slave.dev, "vchan %p: terminate all\n", c); + /* Clear the tx descriptor lists */ + spin_lock_irqsave(&c->lock, flags); + list_splice_tail_init(&c->desc_submitted, &head); + list_splice_tail_init(&c->desc_issued, &head); + + p = c->phy; + if (p) { + struct sa11x0_dma_desc *txd, *txn; + + dev_dbg(d->slave.dev, "pchan %u: terminating\n", p->num); + /* vchan is assigned to a pchan - stop the channel */ + writel(DCSR_RUN | DCSR_IE | + DCSR_STRTA | DCSR_DONEA | + DCSR_STRTB | DCSR_DONEB, + p->base + DMA_DCSR_C); + + list_for_each_entry_safe(txd, txn, &d->desc_complete, node) + if (txd->tx.chan == &c->chan) + list_move(&txd->node, &head); + + if (p->txd_load) { + if (p->txd_load != p->txd_done) + list_add_tail(&p->txd_load->node, &head); + p->txd_load = NULL; + } + if (p->txd_done) { + list_add_tail(&p->txd_done->node, &head); + p->txd_done = NULL; + } + c->phy = NULL; + spin_lock(&d->lock); + p->vchan = NULL; + spin_unlock(&d->lock); + tasklet_schedule(&d->task); + } + spin_unlock_irqrestore(&c->lock, flags); + sa11x0_dma_desc_free(d, &head); + ret = 0; + break; + + case DMA_PAUSE: + dev_dbg(d->slave.dev, "vchan %p: pause\n", c); + spin_lock_irqsave(&c->lock, flags); + if (c->status == DMA_IN_PROGRESS) { + c->status = DMA_PAUSED; + + p = c->phy; + if (p) { + writel(DCSR_RUN | DCSR_IE, p->base + DMA_DCSR_C); + } else { + spin_lock(&d->lock); + list_del_init(&c->node); + spin_unlock(&d->lock); + } + } + spin_unlock_irqrestore(&c->lock, flags); + ret = 0; + break; + + case DMA_RESUME: + dev_dbg(d->slave.dev, "vchan %p: resume\n", c); + spin_lock_irqsave(&c->lock, flags); + if (c->status == DMA_PAUSED) { + c->status = DMA_IN_PROGRESS; + + p = c->phy; + if (p) { + writel(DCSR_RUN | DCSR_IE, p->base + DMA_DCSR_S); + } else if (!list_empty(&c->desc_issued)) { + spin_lock(&d->lock); + list_add_tail(&c->node, &d->chan_pending); + spin_unlock(&d->lock); + } + } + spin_unlock_irqrestore(&c->lock, flags); + ret = 0; + break; + + default: + ret = -ENXIO; + break; + } + + return ret; +} + +struct sa11x0_dma_channel_desc { + u32 ddar; + const char *name; +}; + +#define CD(d1, d2) { .ddar = DDAR_##d1 | d2, .name = #d1 } +static const struct sa11x0_dma_channel_desc chan_desc[] = { + CD(Ser0UDCTr, 0), + CD(Ser0UDCRc, DDAR_RW), + CD(Ser1SDLCTr, 0), + CD(Ser1SDLCRc, DDAR_RW), + CD(Ser1UARTTr, 0), + CD(Ser1UARTRc, DDAR_RW), + CD(Ser2ICPTr, 0), + CD(Ser2ICPRc, DDAR_RW), + CD(Ser3UARTTr, 0), + CD(Ser3UARTRc, DDAR_RW), + CD(Ser4MCP0Tr, 0), + CD(Ser4MCP0Rc, DDAR_RW), + CD(Ser4MCP1Tr, 0), + CD(Ser4MCP1Rc, DDAR_RW), + CD(Ser4SSPTr, 0), + CD(Ser4SSPRc, DDAR_RW), +}; + +static int __devinit sa11x0_dma_init_dmadev(struct dma_device *dmadev, + struct device *dev) +{ + unsigned i; + + dmadev->chancnt = ARRAY_SIZE(chan_desc); + INIT_LIST_HEAD(&dmadev->channels); + dmadev->dev = dev; + dmadev->device_alloc_chan_resources = sa11x0_dma_alloc_chan_resources; + dmadev->device_free_chan_resources = sa11x0_dma_free_chan_resources; + dmadev->device_control = sa11x0_dma_control; + dmadev->device_tx_status = sa11x0_dma_tx_status; + dmadev->device_issue_pending = sa11x0_dma_issue_pending; + + for (i = 0; i < dmadev->chancnt; i++) { + struct sa11x0_dma_chan *c; + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) { + dev_err(dev, "no memory for channel %u\n", i); + return -ENOMEM; + } + + c->chan.device = dmadev; + c->status = DMA_IN_PROGRESS; + c->ddar = chan_desc[i].ddar; + c->name = chan_desc[i].name; + spin_lock_init(&c->lock); + INIT_LIST_HEAD(&c->desc_submitted); + INIT_LIST_HEAD(&c->desc_issued); + INIT_LIST_HEAD(&c->node); + list_add_tail(&c->chan.device_node, &dmadev->channels); + } + + return dma_async_device_register(dmadev); +} + +static int sa11x0_dma_request_irq(struct platform_device *pdev, int nr, + void *data) +{ + int irq = platform_get_irq(pdev, nr); + + if (irq <= 0) + return -ENXIO; + + return request_irq(irq, sa11x0_dma_irq, 0, dev_name(&pdev->dev), data); +} + +static void sa11x0_dma_free_irq(struct platform_device *pdev, int nr, + void *data) +{ + int irq = platform_get_irq(pdev, nr); + if (irq > 0) + free_irq(irq, data); +} + +static void sa11x0_dma_free_channels(struct dma_device *dmadev) +{ + struct sa11x0_dma_chan *c, *cn; + + list_for_each_entry_safe(c, cn, &dmadev->channels, chan.device_node) { + list_del(&c->chan.device_node); + kfree(c); + } +} + +static int __devinit sa11x0_dma_probe(struct platform_device *pdev) +{ + struct sa11x0_dma_dev *d; + struct resource *res; + unsigned i; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) { + ret = -ENOMEM; + goto err_alloc; + } + + spin_lock_init(&d->lock); + INIT_LIST_HEAD(&d->chan_pending); + INIT_LIST_HEAD(&d->desc_complete); + + d->base = ioremap(res->start, resource_size(res)); + if (!d->base) { + ret = -ENOMEM; + goto err_ioremap; + } + + tasklet_init(&d->task, sa11x0_dma_tasklet, (unsigned long)d); + + for (i = 0; i < NR_PHY_CHAN; i++) { + struct sa11x0_dma_phy *p = &d->phy[i]; + + p->dev = d; + p->num = i; + p->base = d->base + i * DMA_SIZE; + writel_relaxed(DCSR_RUN | DCSR_IE | DCSR_ERROR | + DCSR_DONEA | DCSR_STRTA | DCSR_DONEB | DCSR_STRTB, + p->base + DMA_DCSR_C); + writel_relaxed(0, p->base + DMA_DDAR); + + ret = sa11x0_dma_request_irq(pdev, i, p); + if (ret) { + while (i) { + i--; + sa11x0_dma_free_irq(pdev, i, &d->phy[i]); + } + goto err_irq; + } + } + + dma_cap_set(DMA_SLAVE, d->slave.cap_mask); + d->slave.device_prep_slave_sg = sa11x0_dma_prep_slave_sg; + ret = sa11x0_dma_init_dmadev(&d->slave, &pdev->dev); + if (ret) { + dev_warn(d->slave.dev, "failed to register slave async device: %d\n", + ret); + goto err_slave_reg; + } + + platform_set_drvdata(pdev, d); + return 0; + + err_slave_reg: + sa11x0_dma_free_channels(&d->slave); + for (i = 0; i < NR_PHY_CHAN; i++) + sa11x0_dma_free_irq(pdev, i, &d->phy[i]); + err_irq: + tasklet_kill(&d->task); + iounmap(d->base); + err_ioremap: + kfree(d); + err_alloc: + return ret; +} + +static int __devexit sa11x0_dma_remove(struct platform_device *pdev) +{ + struct sa11x0_dma_dev *d = platform_get_drvdata(pdev); + unsigned pch; + + dma_async_device_unregister(&d->slave); + + sa11x0_dma_free_channels(&d->slave); + for (pch = 0; pch < NR_PHY_CHAN; pch++) + sa11x0_dma_free_irq(pdev, pch, &d->phy[pch]); + tasklet_kill(&d->task); + iounmap(d->base); + kfree(d); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int sa11x0_dma_suspend(struct device *dev) +{ + struct sa11x0_dma_dev *d = dev_get_drvdata(dev); + unsigned pch; + + for (pch = 0; pch < NR_PHY_CHAN; pch++) { + struct sa11x0_dma_phy *p = &d->phy[pch]; + u32 dcsr, saved_dcsr; + + dcsr = saved_dcsr = readl_relaxed(p->base + DMA_DCSR_R); + if (dcsr & DCSR_RUN) { + writel(DCSR_RUN | DCSR_IE, p->base + DMA_DCSR_C); + dcsr = readl_relaxed(p->base + DMA_DCSR_R); + } + + saved_dcsr &= DCSR_RUN | DCSR_IE; + if (dcsr & DCSR_BIU) { + p->dbs[0] = readl_relaxed(p->base + DMA_DBSB); + p->dbt[0] = readl_relaxed(p->base + DMA_DBTB); + p->dbs[1] = readl_relaxed(p->base + DMA_DBSA); + p->dbt[1] = readl_relaxed(p->base + DMA_DBTA); + saved_dcsr |= (dcsr & DCSR_STRTA ? DCSR_STRTB : 0) | + (dcsr & DCSR_STRTB ? DCSR_STRTA : 0); + } else { + p->dbs[0] = readl_relaxed(p->base + DMA_DBSA); + p->dbt[0] = readl_relaxed(p->base + DMA_DBTA); + p->dbs[1] = readl_relaxed(p->base + DMA_DBSB); + p->dbt[1] = readl_relaxed(p->base + DMA_DBTB); + saved_dcsr |= dcsr & (DCSR_STRTA | DCSR_STRTB); + } + p->dcsr = saved_dcsr; + + writel(DCSR_STRTA | DCSR_STRTB, p->base + DMA_DCSR_C); + } + + return 0; +} + +static int sa11x0_dma_resume(struct device *dev) +{ + struct sa11x0_dma_dev *d = dev_get_drvdata(dev); + unsigned pch; + + for (pch = 0; pch < NR_PHY_CHAN; pch++) { + struct sa11x0_dma_phy *p = &d->phy[pch]; + struct sa11x0_dma_desc *txd = NULL; + u32 dcsr = readl_relaxed(p->base + DMA_DCSR_R); + + WARN_ON(dcsr & (DCSR_BIU | DCSR_STRTA | DCSR_STRTB | DCSR_RUN)); + + if (p->txd_done) + txd = p->txd_done; + else if (p->txd_load) + txd = p->txd_load; + + if (!txd) + continue; + + writel_relaxed(txd->ddar, p->base + DMA_DDAR); + + writel_relaxed(p->dbs[0], p->base + DMA_DBSA); + writel_relaxed(p->dbt[0], p->base + DMA_DBTA); + writel_relaxed(p->dbs[1], p->base + DMA_DBSB); + writel_relaxed(p->dbt[1], p->base + DMA_DBTB); + writel_relaxed(p->dcsr, p->base + DMA_DCSR_S); + } + + return 0; +} +#endif + +static const struct dev_pm_ops sa11x0_dma_pm_ops = { + .suspend_noirq = sa11x0_dma_suspend, + .resume_noirq = sa11x0_dma_resume, + .freeze_noirq = sa11x0_dma_suspend, + .thaw_noirq = sa11x0_dma_resume, + .poweroff_noirq = sa11x0_dma_suspend, + .restore_noirq = sa11x0_dma_resume, +}; + +static struct platform_driver sa11x0_dma_driver = { + .driver = { + .name = "sa11x0-dma", + .owner = THIS_MODULE, + .pm = &sa11x0_dma_pm_ops, + }, + .probe = sa11x0_dma_probe, + .remove = __devexit_p(sa11x0_dma_remove), +}; + +bool sa11x0_dma_filter_fn(struct dma_chan *chan, void *param) +{ + if (chan->device->dev->driver == &sa11x0_dma_driver.driver) { + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); + const char *p = param; + + return !strcmp(c->name, p); + } + return false; +} +EXPORT_SYMBOL(sa11x0_dma_filter_fn); + +static int __init sa11x0_dma_init(void) +{ + return platform_driver_register(&sa11x0_dma_driver); +} +subsys_initcall(sa11x0_dma_init); + +static void __exit sa11x0_dma_exit(void) +{ + platform_driver_unregister(&sa11x0_dma_driver); +} +module_exit(sa11x0_dma_exit); + +MODULE_AUTHOR("Russell King"); +MODULE_DESCRIPTION("SA-11x0 DMA driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sa11x0-dma"); diff --git a/drivers/net/irda/Kconfig b/drivers/net/irda/Kconfig index e535137..4680478 100644 --- a/drivers/net/irda/Kconfig +++ b/drivers/net/irda/Kconfig @@ -356,7 +356,7 @@ config VLSI_FIR config SA1100_FIR tristate "SA1100 Internal IR" - depends on ARCH_SA1100 && IRDA + depends on ARCH_SA1100 && IRDA && DMA_SA11X0 config VIA_FIR tristate "VIA VT8231/VT1211 SIR/MIR/FIR" diff --git a/drivers/net/irda/sa1100_ir.c b/drivers/net/irda/sa1100_ir.c index da27050..a0d1913 100644 --- a/drivers/net/irda/sa1100_ir.c +++ b/drivers/net/irda/sa1100_ir.c @@ -15,7 +15,7 @@ * This driver takes one kernel command line parameter, sa1100ir=, with * the following options: * max_rate:baudrate - set the maximum baud rate - * power_leve:level - set the transmitter power level + * power_level:level - set the transmitter power level * tx_lpm:0|1 - set transmit low power mode */ #include <linux/module.h> @@ -30,13 +30,13 @@ #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/sa11x0-dma.h> #include <net/irda/irda.h> #include <net/irda/wrapper.h> #include <net/irda/irda_device.h> -#include <asm/irq.h> -#include <mach/dma.h> #include <mach/hardware.h> #include <asm/mach/irda.h> @@ -44,8 +44,15 @@ static int power_level = 3; static int tx_lpm; static int max_rate = 4000000; +struct sa1100_buf { + struct device *dev; + struct sk_buff *skb; + struct scatterlist sg; + struct dma_chan *chan; + dma_cookie_t cookie; +}; + struct sa1100_irda { - unsigned char hscr0; unsigned char utcr4; unsigned char power; unsigned char open; @@ -53,12 +60,8 @@ struct sa1100_irda { int speed; int newspeed; - struct sk_buff *txskb; - struct sk_buff *rxskb; - dma_addr_t txbuf_dma; - dma_addr_t rxbuf_dma; - dma_regs_t *txdma; - dma_regs_t *rxdma; + struct sa1100_buf dma_rx; + struct sa1100_buf dma_tx; struct device *dev; struct irda_platform_data *pdata; @@ -67,23 +70,103 @@ struct sa1100_irda { iobuff_t tx_buff; iobuff_t rx_buff; + + int (*tx_start)(struct sk_buff *, struct net_device *, struct sa1100_irda *); + irqreturn_t (*irq)(struct net_device *, struct sa1100_irda *); }; +static int sa1100_irda_set_speed(struct sa1100_irda *, int); + #define IS_FIR(si) ((si)->speed >= 4000000) #define HPSIR_MAX_RXLEN 2047 +static struct dma_slave_config sa1100_irda_sir_tx = { + .direction = DMA_TO_DEVICE, + .dst_addr = __PREG(Ser2UTDR), + .dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE, + .dst_maxburst = 4, +}; + +static struct dma_slave_config sa1100_irda_fir_rx = { + .direction = DMA_FROM_DEVICE, + .src_addr = __PREG(Ser2HSDR), + .src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE, + .src_maxburst = 8, +}; + +static struct dma_slave_config sa1100_irda_fir_tx = { + .direction = DMA_TO_DEVICE, + .dst_addr = __PREG(Ser2HSDR), + .dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE, + .dst_maxburst = 8, +}; + +static unsigned sa1100_irda_dma_xferred(struct sa1100_buf *buf) +{ + struct dma_chan *chan = buf->chan; + struct dma_tx_state state; + enum dma_status status; + + status = chan->device->device_tx_status(chan, buf->cookie, &state); + if (status != DMA_PAUSED) + return 0; + + return sg_dma_len(&buf->sg) - state.residue; +} + +static int sa1100_irda_dma_request(struct device *dev, struct sa1100_buf *buf, + const char *name, struct dma_slave_config *cfg) +{ + dma_cap_mask_t m; + int ret; + + dma_cap_zero(m); + dma_cap_set(DMA_SLAVE, m); + + buf->chan = dma_request_channel(m, sa11x0_dma_filter_fn, (void *)name); + if (!buf->chan) { + dev_err(dev, "unable to request DMA channel for %s\n", + name); + return -ENOENT; + } + + ret = dmaengine_slave_config(buf->chan, cfg); + if (ret) + dev_warn(dev, "DMA slave_config for %s returned %d\n", + name, ret); + + buf->dev = buf->chan->device->dev; + + return 0; +} + +static void sa1100_irda_dma_start(struct sa1100_buf *buf, + enum dma_transfer_direction dir, dma_async_tx_callback cb, void *cb_p) +{ + struct dma_async_tx_descriptor *desc; + struct dma_chan *chan = buf->chan; + + desc = chan->device->device_prep_slave_sg(chan, &buf->sg, 1, dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (desc) { + desc->callback = cb; + desc->callback_param = cb_p; + buf->cookie = dmaengine_submit(desc); + dma_async_issue_pending(chan); + } +} + /* * Allocate and map the receive buffer, unless it is already allocated. */ static int sa1100_irda_rx_alloc(struct sa1100_irda *si) { - if (si->rxskb) + if (si->dma_rx.skb) return 0; - si->rxskb = alloc_skb(HPSIR_MAX_RXLEN + 1, GFP_ATOMIC); - - if (!si->rxskb) { + si->dma_rx.skb = alloc_skb(HPSIR_MAX_RXLEN + 1, GFP_ATOMIC); + if (!si->dma_rx.skb) { printk(KERN_ERR "sa1100_ir: out of memory for RX SKB\n"); return -ENOMEM; } @@ -92,11 +175,14 @@ static int sa1100_irda_rx_alloc(struct sa1100_irda *si) * Align any IP headers that may be contained * within the frame. */ - skb_reserve(si->rxskb, 1); + skb_reserve(si->dma_rx.skb, 1); + + sg_set_buf(&si->dma_rx.sg, si->dma_rx.skb->data, HPSIR_MAX_RXLEN); + if (dma_map_sg(si->dma_rx.dev, &si->dma_rx.sg, 1, DMA_FROM_DEVICE) == 0) { + dev_kfree_skb_any(si->dma_rx.skb); + return -ENOMEM; + } - si->rxbuf_dma = dma_map_single(si->dev, si->rxskb->data, - HPSIR_MAX_RXLEN, - DMA_FROM_DEVICE); return 0; } @@ -106,7 +192,7 @@ static int sa1100_irda_rx_alloc(struct sa1100_irda *si) */ static void sa1100_irda_rx_dma_start(struct sa1100_irda *si) { - if (!si->rxskb) { + if (!si->dma_rx.skb) { printk(KERN_ERR "sa1100_ir: rx buffer went missing\n"); return; } @@ -114,254 +200,87 @@ static void sa1100_irda_rx_dma_start(struct sa1100_irda *si) /* * First empty receive FIFO */ - Ser2HSCR0 = si->hscr0 | HSCR0_HSSP; + Ser2HSCR0 = HSCR0_HSSP; /* * Enable the DMA, receiver and receive interrupt. */ - sa1100_clear_dma(si->rxdma); - sa1100_start_dma(si->rxdma, si->rxbuf_dma, HPSIR_MAX_RXLEN); - Ser2HSCR0 = si->hscr0 | HSCR0_HSSP | HSCR0_RXE; + dmaengine_terminate_all(si->dma_rx.chan); + sa1100_irda_dma_start(&si->dma_rx, DMA_DEV_TO_MEM, NULL, NULL); + + Ser2HSCR0 = HSCR0_HSSP | HSCR0_RXE; } -/* - * Set the IrDA communications speed. - */ -static int sa1100_irda_set_speed(struct sa1100_irda *si, int speed) +static void sa1100_irda_check_speed(struct sa1100_irda *si) { - unsigned long flags; - int brd, ret = -EINVAL; - - switch (speed) { - case 9600: case 19200: case 38400: - case 57600: case 115200: - brd = 3686400 / (16 * speed) - 1; - - /* - * Stop the receive DMA. - */ - if (IS_FIR(si)) - sa1100_stop_dma(si->rxdma); - - local_irq_save(flags); - - Ser2UTCR3 = 0; - Ser2HSCR0 = HSCR0_UART; - - Ser2UTCR1 = brd >> 8; - Ser2UTCR2 = brd; - - /* - * Clear status register - */ - Ser2UTSR0 = UTSR0_REB | UTSR0_RBB | UTSR0_RID; - Ser2UTCR3 = UTCR3_RIE | UTCR3_RXE | UTCR3_TXE; - - if (si->pdata->set_speed) - si->pdata->set_speed(si->dev, speed); - - si->speed = speed; - - local_irq_restore(flags); - ret = 0; - break; - - case 4000000: - local_irq_save(flags); - - si->hscr0 = 0; - - Ser2HSSR0 = 0xff; - Ser2HSCR0 = si->hscr0 | HSCR0_HSSP; - Ser2UTCR3 = 0; - - si->speed = speed; - - if (si->pdata->set_speed) - si->pdata->set_speed(si->dev, speed); - - sa1100_irda_rx_alloc(si); - sa1100_irda_rx_dma_start(si); - - local_irq_restore(flags); - - break; - - default: - break; + if (si->newspeed) { + sa1100_irda_set_speed(si, si->newspeed); + si->newspeed = 0; } - - return ret; } /* - * Control the power state of the IrDA transmitter. - * State: - * 0 - off - * 1 - short range, lowest power - * 2 - medium range, medium power - * 3 - maximum range, high power - * - * Currently, only assabet is known to support this. + * HP-SIR format support. */ -static int -__sa1100_irda_set_power(struct sa1100_irda *si, unsigned int state) +static void sa1100_irda_sirtxdma_irq(void *id) { - int ret = 0; - if (si->pdata->set_power) - ret = si->pdata->set_power(si->dev, state); - return ret; -} - -static inline int -sa1100_set_power(struct sa1100_irda *si, unsigned int state) -{ - int ret; - - ret = __sa1100_irda_set_power(si, state); - if (ret == 0) - si->power = state; + struct net_device *dev = id; + struct sa1100_irda *si = netdev_priv(dev); - return ret; -} + dma_unmap_sg(si->dma_tx.dev, &si->dma_tx.sg, 1, DMA_TO_DEVICE); + dev_kfree_skb(si->dma_tx.skb); + si->dma_tx.skb = NULL; -static int sa1100_irda_startup(struct sa1100_irda *si) -{ - int ret; + dev->stats.tx_packets++; + dev->stats.tx_bytes += sg_dma_len(&si->dma_tx.sg); - /* - * Ensure that the ports for this device are setup correctly. - */ - if (si->pdata->startup) { - ret = si->pdata->startup(si->dev); - if (ret) - return ret; - } - - /* - * Configure PPC for IRDA - we want to drive TXD2 low. - * We also want to drive this pin low during sleep. - */ - PPSR &= ~PPC_TXD2; - PSDR &= ~PPC_TXD2; - PPDR |= PPC_TXD2; - - /* - * Enable HP-SIR modulation, and ensure that the port is disabled. - */ - Ser2UTCR3 = 0; - Ser2HSCR0 = HSCR0_UART; - Ser2UTCR4 = si->utcr4; - Ser2UTCR0 = UTCR0_8BitData; - Ser2HSCR2 = HSCR2_TrDataH | HSCR2_RcDataL; + /* We need to ensure that the transmitter has finished. */ + do + rmb(); + while (Ser2UTSR1 & UTSR1_TBY); /* - * Clear status register + * Ok, we've finished transmitting. Now enable the receiver. + * Sometimes we get a receive IRQ immediately after a transmit... */ Ser2UTSR0 = UTSR0_REB | UTSR0_RBB | UTSR0_RID; + Ser2UTCR3 = UTCR3_RIE | UTCR3_RXE | UTCR3_TXE; - ret = sa1100_irda_set_speed(si, si->speed = 9600); - if (ret) { - Ser2UTCR3 = 0; - Ser2HSCR0 = 0; - - if (si->pdata->shutdown) - si->pdata->shutdown(si->dev); - } - - return ret; -} - -static void sa1100_irda_shutdown(struct sa1100_irda *si) -{ - /* - * Stop all DMA activity. - */ - sa1100_stop_dma(si->rxdma); - sa1100_stop_dma(si->txdma); - - /* Disable the port. */ - Ser2UTCR3 = 0; - Ser2HSCR0 = 0; + sa1100_irda_check_speed(si); - if (si->pdata->shutdown) - si->pdata->shutdown(si->dev); + /* I'm hungry! */ + netif_wake_queue(dev); } -#ifdef CONFIG_PM -/* - * Suspend the IrDA interface. - */ -static int sa1100_irda_suspend(struct platform_device *pdev, pm_message_t state) +static int sa1100_irda_sir_tx_start(struct sk_buff *skb, struct net_device *dev, + struct sa1100_irda *si) { - struct net_device *dev = platform_get_drvdata(pdev); - struct sa1100_irda *si; - - if (!dev) - return 0; - - si = netdev_priv(dev); - if (si->open) { - /* - * Stop the transmit queue - */ - netif_device_detach(dev); - disable_irq(dev->irq); - sa1100_irda_shutdown(si); - __sa1100_irda_set_power(si, 0); + si->tx_buff.data = si->tx_buff.head; + si->tx_buff.len = async_wrap_skb(skb, si->tx_buff.data, + si->tx_buff.truesize); + + si->dma_tx.skb = skb; + sg_set_buf(&si->dma_tx.sg, si->tx_buff.data, si->tx_buff.len); + if (dma_map_sg(si->dma_tx.dev, &si->dma_tx.sg, 1, DMA_TO_DEVICE) == 0) { + si->dma_tx.skb = NULL; + netif_wake_queue(dev); + dev->stats.tx_dropped++; + return NETDEV_TX_OK; } - return 0; -} - -/* - * Resume the IrDA interface. - */ -static int sa1100_irda_resume(struct platform_device *pdev) -{ - struct net_device *dev = platform_get_drvdata(pdev); - struct sa1100_irda *si; - - if (!dev) - return 0; + sa1100_irda_dma_start(&si->dma_tx, DMA_MEM_TO_DEV, sa1100_irda_sirtxdma_irq, dev); - si = netdev_priv(dev); - if (si->open) { - /* - * If we missed a speed change, initialise at the new speed - * directly. It is debatable whether this is actually - * required, but in the interests of continuing from where - * we left off it is desirable. The converse argument is - * that we should re-negotiate at 9600 baud again. - */ - if (si->newspeed) { - si->speed = si->newspeed; - si->newspeed = 0; - } - - sa1100_irda_startup(si); - __sa1100_irda_set_power(si, si->power); - enable_irq(dev->irq); - - /* - * This automatically wakes up the queue - */ - netif_device_attach(dev); - } + /* + * The mean turn-around time is enforced by XBOF padding, + * so we don't have to do anything special here. + */ + Ser2UTCR3 = UTCR3_TXE; - return 0; + return NETDEV_TX_OK; } -#else -#define sa1100_irda_suspend NULL -#define sa1100_irda_resume NULL -#endif -/* - * HP-SIR format interrupt service routines. - */ -static void sa1100_irda_hpsir_irq(struct net_device *dev) +static irqreturn_t sa1100_irda_sir_irq(struct net_device *dev, struct sa1100_irda *si) { - struct sa1100_irda *si = netdev_priv(dev); int status; status = Ser2UTSR0; @@ -414,51 +333,96 @@ static void sa1100_irda_hpsir_irq(struct net_device *dev) } - if (status & UTSR0_TFS && si->tx_buff.len) { - /* - * Transmitter FIFO is not full - */ - do { - Ser2UTDR = *si->tx_buff.data++; - si->tx_buff.len -= 1; - } while (Ser2UTSR1 & UTSR1_TNF && si->tx_buff.len); + return IRQ_HANDLED; +} - if (si->tx_buff.len == 0) { - dev->stats.tx_packets++; - dev->stats.tx_bytes += si->tx_buff.data - - si->tx_buff.head; +/* + * FIR format support. + */ +static void sa1100_irda_firtxdma_irq(void *id) +{ + struct net_device *dev = id; + struct sa1100_irda *si = netdev_priv(dev); + struct sk_buff *skb; - /* - * We need to ensure that the transmitter has - * finished. - */ - do - rmb(); - while (Ser2UTSR1 & UTSR1_TBY); + /* + * Wait for the transmission to complete. Unfortunately, + * the hardware doesn't give us an interrupt to indicate + * "end of frame". + */ + do + rmb(); + while (!(Ser2HSSR0 & HSSR0_TUR) || Ser2HSSR1 & HSSR1_TBY); - /* - * Ok, we've finished transmitting. Now enable - * the receiver. Sometimes we get a receive IRQ - * immediately after a transmit... - */ - Ser2UTSR0 = UTSR0_REB | UTSR0_RBB | UTSR0_RID; - Ser2UTCR3 = UTCR3_RIE | UTCR3_RXE | UTCR3_TXE; + /* + * Clear the transmit underrun bit. + */ + Ser2HSSR0 = HSSR0_TUR; - if (si->newspeed) { - sa1100_irda_set_speed(si, si->newspeed); - si->newspeed = 0; - } + /* + * Do we need to change speed? Note that we're lazy + * here - we don't free the old dma_rx.skb. We don't need + * to allocate a buffer either. + */ + sa1100_irda_check_speed(si); - /* I'm hungry! */ - netif_wake_queue(dev); - } + /* + * Start reception. This disables the transmitter for + * us. This will be using the existing RX buffer. + */ + sa1100_irda_rx_dma_start(si); + + /* Account and free the packet. */ + skb = si->dma_tx.skb; + if (skb) { + dma_unmap_sg(si->dma_tx.dev, &si->dma_tx.sg, 1, + DMA_TO_DEVICE); + dev->stats.tx_packets ++; + dev->stats.tx_bytes += skb->len; + dev_kfree_skb_irq(skb); + si->dma_tx.skb = NULL; } + + /* + * Make sure that the TX queue is available for sending + * (for retries). TX has priority over RX at all times. + */ + netif_wake_queue(dev); +} + +static int sa1100_irda_fir_tx_start(struct sk_buff *skb, struct net_device *dev, + struct sa1100_irda *si) +{ + int mtt = irda_get_mtt(skb); + + si->dma_tx.skb = skb; + sg_set_buf(&si->dma_tx.sg, skb->data, skb->len); + if (dma_map_sg(si->dma_tx.dev, &si->dma_tx.sg, 1, DMA_TO_DEVICE) == 0) { + si->dma_tx.skb = NULL; + netif_wake_queue(dev); + dev->stats.tx_dropped++; + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + + sa1100_irda_dma_start(&si->dma_tx, DMA_MEM_TO_DEV, sa1100_irda_firtxdma_irq, dev); + + /* + * If we have a mean turn-around time, impose the specified + * specified delay. We could shorten this by timing from + * the point we received the packet. + */ + if (mtt) + udelay(mtt); + + Ser2HSCR0 = HSCR0_HSSP | HSCR0_TXE; + + return NETDEV_TX_OK; } static void sa1100_irda_fir_error(struct sa1100_irda *si, struct net_device *dev) { - struct sk_buff *skb = si->rxskb; - dma_addr_t dma_addr; + struct sk_buff *skb = si->dma_rx.skb; unsigned int len, stat, data; if (!skb) { @@ -469,11 +433,10 @@ static void sa1100_irda_fir_error(struct sa1100_irda *si, struct net_device *dev /* * Get the current data position. */ - dma_addr = sa1100_get_dma_pos(si->rxdma); - len = dma_addr - si->rxbuf_dma; + len = sa1100_irda_dma_xferred(&si->dma_rx); if (len > HPSIR_MAX_RXLEN) len = HPSIR_MAX_RXLEN; - dma_unmap_single(si->dev, si->rxbuf_dma, len, DMA_FROM_DEVICE); + dma_unmap_sg(si->dma_rx.dev, &si->dma_rx.sg, 1, DMA_FROM_DEVICE); do { /* @@ -501,7 +464,7 @@ static void sa1100_irda_fir_error(struct sa1100_irda *si, struct net_device *dev } while (Ser2HSSR0 & HSSR0_EIF); if (stat & HSSR1_EOF) { - si->rxskb = NULL; + si->dma_rx.skb = NULL; skb_put(skb, len); skb->dev = dev; @@ -518,28 +481,23 @@ static void sa1100_irda_fir_error(struct sa1100_irda *si, struct net_device *dev netif_rx(skb); } else { /* - * Remap the buffer. + * Remap the buffer - it was previously mapped, and we + * hope that this succeeds. */ - si->rxbuf_dma = dma_map_single(si->dev, si->rxskb->data, - HPSIR_MAX_RXLEN, - DMA_FROM_DEVICE); + dma_map_sg(si->dma_rx.dev, &si->dma_rx.sg, 1, DMA_FROM_DEVICE); } } /* - * FIR format interrupt service routine. We only have to - * handle RX events; transmit events go via the TX DMA handler. - * - * No matter what, we disable RX, process, and the restart RX. + * We only have to handle RX events here; transmit events go via the TX + * DMA handler. We disable RX, process, and the restart RX. */ -static void sa1100_irda_fir_irq(struct net_device *dev) +static irqreturn_t sa1100_irda_fir_irq(struct net_device *dev, struct sa1100_irda *si) { - struct sa1100_irda *si = netdev_priv(dev); - /* * Stop RX DMA */ - sa1100_stop_dma(si->rxdma); + dmaengine_pause(si->dma_rx.chan); /* * Framing error - we throw away the packet completely. @@ -555,7 +513,7 @@ static void sa1100_irda_fir_irq(struct net_device *dev) /* * Clear out the DMA... */ - Ser2HSCR0 = si->hscr0 | HSCR0_HSSP; + Ser2HSCR0 = HSCR0_HSSP; /* * Clear selected status bits now, so we @@ -577,74 +535,124 @@ static void sa1100_irda_fir_irq(struct net_device *dev) * No matter what happens, we must restart reception. */ sa1100_irda_rx_dma_start(si); -} -static irqreturn_t sa1100_irda_irq(int irq, void *dev_id) -{ - struct net_device *dev = dev_id; - if (IS_FIR(((struct sa1100_irda *)netdev_priv(dev)))) - sa1100_irda_fir_irq(dev); - else - sa1100_irda_hpsir_irq(dev); return IRQ_HANDLED; } /* - * TX DMA completion handler. + * Set the IrDA communications speed. */ -static void sa1100_irda_txdma_irq(void *id) +static int sa1100_irda_set_speed(struct sa1100_irda *si, int speed) { - struct net_device *dev = id; - struct sa1100_irda *si = netdev_priv(dev); - struct sk_buff *skb = si->txskb; + unsigned long flags; + int brd, ret = -EINVAL; - si->txskb = NULL; + switch (speed) { + case 9600: case 19200: case 38400: + case 57600: case 115200: + brd = 3686400 / (16 * speed) - 1; - /* - * Wait for the transmission to complete. Unfortunately, - * the hardware doesn't give us an interrupt to indicate - * "end of frame". - */ - do - rmb(); - while (!(Ser2HSSR0 & HSSR0_TUR) || Ser2HSSR1 & HSSR1_TBY); + /* Stop the receive DMA, and configure transmit. */ + if (IS_FIR(si)) { + dmaengine_terminate_all(si->dma_rx.chan); + dmaengine_slave_config(si->dma_tx.chan, + &sa1100_irda_sir_tx); + } - /* - * Clear the transmit underrun bit. - */ - Ser2HSSR0 = HSSR0_TUR; + local_irq_save(flags); - /* - * Do we need to change speed? Note that we're lazy - * here - we don't free the old rxskb. We don't need - * to allocate a buffer either. - */ - if (si->newspeed) { - sa1100_irda_set_speed(si, si->newspeed); - si->newspeed = 0; - } + Ser2UTCR3 = 0; + Ser2HSCR0 = HSCR0_UART; - /* - * Start reception. This disables the transmitter for - * us. This will be using the existing RX buffer. - */ - sa1100_irda_rx_dma_start(si); + Ser2UTCR1 = brd >> 8; + Ser2UTCR2 = brd; - /* - * Account and free the packet. - */ - if (skb) { - dma_unmap_single(si->dev, si->txbuf_dma, skb->len, DMA_TO_DEVICE); - dev->stats.tx_packets ++; - dev->stats.tx_bytes += skb->len; - dev_kfree_skb_irq(skb); + /* + * Clear status register + */ + Ser2UTSR0 = UTSR0_REB | UTSR0_RBB | UTSR0_RID; + Ser2UTCR3 = UTCR3_RIE | UTCR3_RXE | UTCR3_TXE; + + if (si->pdata->set_speed) + si->pdata->set_speed(si->dev, speed); + + si->speed = speed; + si->tx_start = sa1100_irda_sir_tx_start; + si->irq = sa1100_irda_sir_irq; + + local_irq_restore(flags); + ret = 0; + break; + + case 4000000: + if (!IS_FIR(si)) + dmaengine_slave_config(si->dma_tx.chan, + &sa1100_irda_fir_tx); + + local_irq_save(flags); + + Ser2HSSR0 = 0xff; + Ser2HSCR0 = HSCR0_HSSP; + Ser2UTCR3 = 0; + + si->speed = speed; + si->tx_start = sa1100_irda_fir_tx_start; + si->irq = sa1100_irda_fir_irq; + + if (si->pdata->set_speed) + si->pdata->set_speed(si->dev, speed); + + sa1100_irda_rx_alloc(si); + sa1100_irda_rx_dma_start(si); + + local_irq_restore(flags); + + break; + + default: + break; } - /* - * Make sure that the TX queue is available for sending - * (for retries). TX has priority over RX at all times. - */ - netif_wake_queue(dev); + return ret; +} + +/* + * Control the power state of the IrDA transmitter. + * State: + * 0 - off + * 1 - short range, lowest power + * 2 - medium range, medium power + * 3 - maximum range, high power + * + * Currently, only assabet is known to support this. + */ +static int +__sa1100_irda_set_power(struct sa1100_irda *si, unsigned int state) +{ + int ret = 0; + if (si->pdata->set_power) + ret = si->pdata->set_power(si->dev, state); + return ret; +} + +static inline int +sa1100_set_power(struct sa1100_irda *si, unsigned int state) +{ + int ret; + + ret = __sa1100_irda_set_power(si, state); + if (ret == 0) + si->power = state; + + return ret; +} + +static irqreturn_t sa1100_irda_irq(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct sa1100_irda *si = netdev_priv(dev); + + return si->irq(dev, si); } static int sa1100_irda_hard_xmit(struct sk_buff *skb, struct net_device *dev) @@ -660,62 +668,19 @@ static int sa1100_irda_hard_xmit(struct sk_buff *skb, struct net_device *dev) if (speed != si->speed && speed != -1) si->newspeed = speed; - /* - * If this is an empty frame, we can bypass a lot. - */ + /* If this is an empty frame, we can bypass a lot. */ if (skb->len == 0) { - if (si->newspeed) { - si->newspeed = 0; - sa1100_irda_set_speed(si, speed); - } + sa1100_irda_check_speed(si); dev_kfree_skb(skb); return NETDEV_TX_OK; } - if (!IS_FIR(si)) { - netif_stop_queue(dev); - - si->tx_buff.data = si->tx_buff.head; - si->tx_buff.len = async_wrap_skb(skb, si->tx_buff.data, - si->tx_buff.truesize); - - /* - * Set the transmit interrupt enable. This will fire - * off an interrupt immediately. Note that we disable - * the receiver so we won't get spurious characteres - * received. - */ - Ser2UTCR3 = UTCR3_TIE | UTCR3_TXE; - - dev_kfree_skb(skb); - } else { - int mtt = irda_get_mtt(skb); - - /* - * We must not be transmitting... - */ - BUG_ON(si->txskb); - - netif_stop_queue(dev); - - si->txskb = skb; - si->txbuf_dma = dma_map_single(si->dev, skb->data, - skb->len, DMA_TO_DEVICE); - - sa1100_start_dma(si->txdma, si->txbuf_dma, skb->len); - - /* - * If we have a mean turn-around time, impose the specified - * specified delay. We could shorten this by timing from - * the point we received the packet. - */ - if (mtt) - udelay(mtt); + netif_stop_queue(dev); - Ser2HSCR0 = si->hscr0 | HSCR0_HSSP | HSCR0_TXE; - } + /* We must not already have a skb to transmit... */ + BUG_ON(si->dma_tx.skb); - return NETDEV_TX_OK; + return si->tx_start(skb, dev, si); } static int @@ -762,6 +727,69 @@ sa1100_irda_ioctl(struct net_device *dev, struct ifreq *ifreq, int cmd) return ret; } +static int sa1100_irda_startup(struct sa1100_irda *si) +{ + int ret; + + /* + * Ensure that the ports for this device are setup correctly. + */ + if (si->pdata->startup) { + ret = si->pdata->startup(si->dev); + if (ret) + return ret; + } + + /* + * Configure PPC for IRDA - we want to drive TXD2 low. + * We also want to drive this pin low during sleep. + */ + PPSR &= ~PPC_TXD2; + PSDR &= ~PPC_TXD2; + PPDR |= PPC_TXD2; + + /* + * Enable HP-SIR modulation, and ensure that the port is disabled. + */ + Ser2UTCR3 = 0; + Ser2HSCR0 = HSCR0_UART; + Ser2UTCR4 = si->utcr4; + Ser2UTCR0 = UTCR0_8BitData; + Ser2HSCR2 = HSCR2_TrDataH | HSCR2_RcDataL; + + /* + * Clear status register + */ + Ser2UTSR0 = UTSR0_REB | UTSR0_RBB | UTSR0_RID; + + ret = sa1100_irda_set_speed(si, si->speed = 9600); + if (ret) { + Ser2UTCR3 = 0; + Ser2HSCR0 = 0; + + if (si->pdata->shutdown) + si->pdata->shutdown(si->dev); + } + + return ret; +} + +static void sa1100_irda_shutdown(struct sa1100_irda *si) +{ + /* + * Stop all DMA activity. + */ + dmaengine_terminate_all(si->dma_rx.chan); + dmaengine_terminate_all(si->dma_tx.chan); + + /* Disable the port. */ + Ser2UTCR3 = 0; + Ser2HSCR0 = 0; + + if (si->pdata->shutdown) + si->pdata->shutdown(si->dev); +} + static int sa1100_irda_start(struct net_device *dev) { struct sa1100_irda *si = netdev_priv(dev); @@ -769,26 +797,17 @@ static int sa1100_irda_start(struct net_device *dev) si->speed = 9600; - err = request_irq(dev->irq, sa1100_irda_irq, 0, dev->name, dev); - if (err) - goto err_irq; - - err = sa1100_request_dma(DMA_Ser2HSSPRd, "IrDA receive", - NULL, NULL, &si->rxdma); + err = sa1100_irda_dma_request(si->dev, &si->dma_rx, "Ser2ICPRc", + &sa1100_irda_fir_rx); if (err) goto err_rx_dma; - err = sa1100_request_dma(DMA_Ser2HSSPWr, "IrDA transmit", - sa1100_irda_txdma_irq, dev, &si->txdma); + err = sa1100_irda_dma_request(si->dev, &si->dma_tx, "Ser2ICPTr", + &sa1100_irda_sir_tx); if (err) goto err_tx_dma; /* - * The interrupt must remain disabled for now. - */ - disable_irq(dev->irq); - - /* * Setup the serial port for the specified speed. */ err = sa1100_irda_startup(si); @@ -803,44 +822,60 @@ static int sa1100_irda_start(struct net_device *dev) if (!si->irlap) goto err_irlap; + err = request_irq(dev->irq, sa1100_irda_irq, 0, dev->name, dev); + if (err) + goto err_irq; + /* * Now enable the interrupt and start the queue */ si->open = 1; sa1100_set_power(si, power_level); /* low power mode */ - enable_irq(dev->irq); + netif_start_queue(dev); return 0; +err_irq: + irlap_close(si->irlap); err_irlap: si->open = 0; sa1100_irda_shutdown(si); err_startup: - sa1100_free_dma(si->txdma); + dma_release_channel(si->dma_tx.chan); err_tx_dma: - sa1100_free_dma(si->rxdma); + dma_release_channel(si->dma_rx.chan); err_rx_dma: - free_irq(dev->irq, dev); -err_irq: return err; } static int sa1100_irda_stop(struct net_device *dev) { struct sa1100_irda *si = netdev_priv(dev); + struct sk_buff *skb; + + netif_stop_queue(dev); - disable_irq(dev->irq); + si->open = 0; sa1100_irda_shutdown(si); /* - * If we have been doing DMA receive, make sure we + * If we have been doing any DMA activity, make sure we * tidy that up cleanly. */ - if (si->rxskb) { - dma_unmap_single(si->dev, si->rxbuf_dma, HPSIR_MAX_RXLEN, - DMA_FROM_DEVICE); - dev_kfree_skb(si->rxskb); - si->rxskb = NULL; + skb = si->dma_rx.skb; + if (skb) { + dma_unmap_sg(si->dma_rx.dev, &si->dma_rx.sg, 1, + DMA_FROM_DEVICE); + dev_kfree_skb(skb); + si->dma_rx.skb = NULL; + } + + skb = si->dma_tx.skb; + if (skb) { + dma_unmap_sg(si->dma_tx.dev, &si->dma_tx.sg, 1, + DMA_TO_DEVICE); + dev_kfree_skb(skb); + si->dma_tx.skb = NULL; } /* Stop IrLAP */ @@ -849,14 +884,11 @@ static int sa1100_irda_stop(struct net_device *dev) si->irlap = NULL; } - netif_stop_queue(dev); - si->open = 0; - /* * Free resources */ - sa1100_free_dma(si->txdma); - sa1100_free_dma(si->rxdma); + dma_release_channel(si->dma_tx.chan); + dma_release_channel(si->dma_rx.chan); free_irq(dev->irq, dev); sa1100_set_power(si, 0); @@ -888,11 +920,15 @@ static int sa1100_irda_probe(struct platform_device *pdev) struct net_device *dev; struct sa1100_irda *si; unsigned int baudrate_mask; - int err; + int err, irq; if (!pdev->dev.platform_data) return -EINVAL; + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return irq < 0 ? irq : -ENXIO; + err = request_mem_region(__PREG(Ser2UTCR0), 0x24, "IrDA") ? 0 : -EBUSY; if (err) goto err_mem_1; @@ -907,22 +943,27 @@ static int sa1100_irda_probe(struct platform_device *pdev) if (!dev) goto err_mem_4; + SET_NETDEV_DEV(dev, &pdev->dev); + si = netdev_priv(dev); si->dev = &pdev->dev; si->pdata = pdev->dev.platform_data; + sg_init_table(&si->dma_rx.sg, 1); + sg_init_table(&si->dma_tx.sg, 1); + /* * Initialise the HP-SIR buffers */ err = sa1100_irda_init_iobuf(&si->rx_buff, 14384); if (err) goto err_mem_5; - err = sa1100_irda_init_iobuf(&si->tx_buff, 4000); + err = sa1100_irda_init_iobuf(&si->tx_buff, IRDA_SIR_MAX_FRAME); if (err) goto err_mem_5; dev->netdev_ops = &sa1100_irda_netdev_ops; - dev->irq = IRQ_Ser2ICP; + dev->irq = irq; irda_init_max_qos_capabilies(&si->qos); @@ -996,6 +1037,74 @@ static int sa1100_irda_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +/* + * Suspend the IrDA interface. + */ +static int sa1100_irda_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct sa1100_irda *si; + + if (!dev) + return 0; + + si = netdev_priv(dev); + if (si->open) { + /* + * Stop the transmit queue + */ + netif_device_detach(dev); + disable_irq(dev->irq); + sa1100_irda_shutdown(si); + __sa1100_irda_set_power(si, 0); + } + + return 0; +} + +/* + * Resume the IrDA interface. + */ +static int sa1100_irda_resume(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct sa1100_irda *si; + + if (!dev) + return 0; + + si = netdev_priv(dev); + if (si->open) { + /* + * If we missed a speed change, initialise at the new speed + * directly. It is debatable whether this is actually + * required, but in the interests of continuing from where + * we left off it is desirable. The converse argument is + * that we should re-negotiate at 9600 baud again. + */ + if (si->newspeed) { + si->speed = si->newspeed; + si->newspeed = 0; + } + + sa1100_irda_startup(si); + __sa1100_irda_set_power(si, si->power); + enable_irq(dev->irq); + + /* + * This automatically wakes up the queue + */ + netif_device_attach(dev); + } + + return 0; +} +#else +#define sa1100_irda_suspend NULL +#define sa1100_irda_resume NULL +#endif + static struct platform_driver sa1100ir_driver = { .probe = sa1100_irda_probe, .remove = sa1100_irda_remove, diff --git a/include/linux/sa11x0-dma.h b/include/linux/sa11x0-dma.h new file mode 100644 index 0000000..65839a5 --- /dev/null +++ b/include/linux/sa11x0-dma.h @@ -0,0 +1,24 @@ +/* + * SA11x0 DMA Engine support + * + * Copyright (C) 2012 Russell King + * + * 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 __LINUX_SA11X0_DMA_H +#define __LINUX_SA11X0_DMA_H + +struct dma_chan; + +#if defined(CONFIG_DMA_SA11X0) || defined(CONFIG_DMA_SA11X0_MODULE) +bool sa11x0_dma_filter_fn(struct dma_chan *, void *); +#else +static inline bool sa11x0_dma_filter_fn(struct dma_chan *c, void *d) +{ + return false; +} +#endif + +#endif |