Revision 87b87a3fc0eec58d95e4216392f889a26439ad22 authored by Daniel Drake on 09 April 2012, 23:14:20 UTC, committed by Chris Ball on 21 April 2012, 00:44:25 UTC
Commit c79396c191bc19 ("mmc: sdhci: prevent card detection activity for non-removable cards") disables card detection where the cards are marked as non-removable. This makes sense, but the implementation detail of calling mmc_card_is_removable() causes some problems, because mmc_card_is_removable() is overloaded with CONFIG_MMC_UNSAFE_RESUME semantics. In the OLPC XO case, we need CONFIG_MMC_UNSAFE_RESUME because our root filesystem is stored on SD, but we also have external SD card slots where we want automatic card detection. Refine the check to only apply to hosts marked as MMC_CAP_NONREMOVABLE, which is defined to mean that the card is *really* nonremovable. This could be revisited in future if we find a way to improve CONFIG_MMC_UNSAFE_RESUME semantics. Signed-off-by: Daniel Drake <dsd@laptop.org> Acked-by: Chuanxiao Dong <chuanxiao.dong@intel.com> [stable@: please apply to 3.3-stable] Cc: stable <stable@vger.kernel.org> Signed-off-by: Chris Ball <cjb@laptop.org>
1 parent a99aa9b
timb_dma.c
/*
* timb_dma.c timberdale FPGA DMA driver
* Copyright (c) 2010 Intel Corporation
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* Supports:
* Timberdale FPGA DMA engine
*/
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/timb_dma.h>
#include "dmaengine.h"
#define DRIVER_NAME "timb-dma"
/* Global DMA registers */
#define TIMBDMA_ACR 0x34
#define TIMBDMA_32BIT_ADDR 0x01
#define TIMBDMA_ISR 0x080000
#define TIMBDMA_IPR 0x080004
#define TIMBDMA_IER 0x080008
/* Channel specific registers */
/* RX instances base addresses are 0x00, 0x40, 0x80 ...
* TX instances base addresses are 0x18, 0x58, 0x98 ...
*/
#define TIMBDMA_INSTANCE_OFFSET 0x40
#define TIMBDMA_INSTANCE_TX_OFFSET 0x18
/* RX registers, relative the instance base */
#define TIMBDMA_OFFS_RX_DHAR 0x00
#define TIMBDMA_OFFS_RX_DLAR 0x04
#define TIMBDMA_OFFS_RX_LR 0x0C
#define TIMBDMA_OFFS_RX_BLR 0x10
#define TIMBDMA_OFFS_RX_ER 0x14
#define TIMBDMA_RX_EN 0x01
/* bytes per Row, video specific register
* which is placed after the TX registers...
*/
#define TIMBDMA_OFFS_RX_BPRR 0x30
/* TX registers, relative the instance base */
#define TIMBDMA_OFFS_TX_DHAR 0x00
#define TIMBDMA_OFFS_TX_DLAR 0x04
#define TIMBDMA_OFFS_TX_BLR 0x0C
#define TIMBDMA_OFFS_TX_LR 0x14
#define TIMB_DMA_DESC_SIZE 8
struct timb_dma_desc {
struct list_head desc_node;
struct dma_async_tx_descriptor txd;
u8 *desc_list;
unsigned int desc_list_len;
bool interrupt;
};
struct timb_dma_chan {
struct dma_chan chan;
void __iomem *membase;
spinlock_t lock; /* Used to protect data structures,
especially the lists and descriptors,
from races between the tasklet and calls
from above */
bool ongoing;
struct list_head active_list;
struct list_head queue;
struct list_head free_list;
unsigned int bytes_per_line;
enum dma_transfer_direction direction;
unsigned int descs; /* Descriptors to allocate */
unsigned int desc_elems; /* number of elems per descriptor */
};
struct timb_dma {
struct dma_device dma;
void __iomem *membase;
struct tasklet_struct tasklet;
struct timb_dma_chan channels[0];
};
static struct device *chan2dev(struct dma_chan *chan)
{
return &chan->dev->device;
}
static struct device *chan2dmadev(struct dma_chan *chan)
{
return chan2dev(chan)->parent->parent;
}
static struct timb_dma *tdchantotd(struct timb_dma_chan *td_chan)
{
int id = td_chan->chan.chan_id;
return (struct timb_dma *)((u8 *)td_chan -
id * sizeof(struct timb_dma_chan) - sizeof(struct timb_dma));
}
/* Must be called with the spinlock held */
static void __td_enable_chan_irq(struct timb_dma_chan *td_chan)
{
int id = td_chan->chan.chan_id;
struct timb_dma *td = tdchantotd(td_chan);
u32 ier;
/* enable interrupt for this channel */
ier = ioread32(td->membase + TIMBDMA_IER);
ier |= 1 << id;
dev_dbg(chan2dev(&td_chan->chan), "Enabling irq: %d, IER: 0x%x\n", id,
ier);
iowrite32(ier, td->membase + TIMBDMA_IER);
}
/* Should be called with the spinlock held */
static bool __td_dma_done_ack(struct timb_dma_chan *td_chan)
{
int id = td_chan->chan.chan_id;
struct timb_dma *td = (struct timb_dma *)((u8 *)td_chan -
id * sizeof(struct timb_dma_chan) - sizeof(struct timb_dma));
u32 isr;
bool done = false;
dev_dbg(chan2dev(&td_chan->chan), "Checking irq: %d, td: %p\n", id, td);
isr = ioread32(td->membase + TIMBDMA_ISR) & (1 << id);
if (isr) {
iowrite32(isr, td->membase + TIMBDMA_ISR);
done = true;
}
return done;
}
static void __td_unmap_desc(struct timb_dma_chan *td_chan, const u8 *dma_desc,
bool single)
{
dma_addr_t addr;
int len;
addr = (dma_desc[7] << 24) | (dma_desc[6] << 16) | (dma_desc[5] << 8) |
dma_desc[4];
len = (dma_desc[3] << 8) | dma_desc[2];
if (single)
dma_unmap_single(chan2dev(&td_chan->chan), addr, len,
DMA_TO_DEVICE);
else
dma_unmap_page(chan2dev(&td_chan->chan), addr, len,
DMA_TO_DEVICE);
}
static void __td_unmap_descs(struct timb_dma_desc *td_desc, bool single)
{
struct timb_dma_chan *td_chan = container_of(td_desc->txd.chan,
struct timb_dma_chan, chan);
u8 *descs;
for (descs = td_desc->desc_list; ; descs += TIMB_DMA_DESC_SIZE) {
__td_unmap_desc(td_chan, descs, single);
if (descs[0] & 0x02)
break;
}
}
static int td_fill_desc(struct timb_dma_chan *td_chan, u8 *dma_desc,
struct scatterlist *sg, bool last)
{
if (sg_dma_len(sg) > USHRT_MAX) {
dev_err(chan2dev(&td_chan->chan), "Too big sg element\n");
return -EINVAL;
}
/* length must be word aligned */
if (sg_dma_len(sg) % sizeof(u32)) {
dev_err(chan2dev(&td_chan->chan), "Incorrect length: %d\n",
sg_dma_len(sg));
return -EINVAL;
}
dev_dbg(chan2dev(&td_chan->chan), "desc: %p, addr: 0x%llx\n",
dma_desc, (unsigned long long)sg_dma_address(sg));
dma_desc[7] = (sg_dma_address(sg) >> 24) & 0xff;
dma_desc[6] = (sg_dma_address(sg) >> 16) & 0xff;
dma_desc[5] = (sg_dma_address(sg) >> 8) & 0xff;
dma_desc[4] = (sg_dma_address(sg) >> 0) & 0xff;
dma_desc[3] = (sg_dma_len(sg) >> 8) & 0xff;
dma_desc[2] = (sg_dma_len(sg) >> 0) & 0xff;
dma_desc[1] = 0x00;
dma_desc[0] = 0x21 | (last ? 0x02 : 0); /* tran, valid */
return 0;
}
/* Must be called with the spinlock held */
static void __td_start_dma(struct timb_dma_chan *td_chan)
{
struct timb_dma_desc *td_desc;
if (td_chan->ongoing) {
dev_err(chan2dev(&td_chan->chan),
"Transfer already ongoing\n");
return;
}
td_desc = list_entry(td_chan->active_list.next, struct timb_dma_desc,
desc_node);
dev_dbg(chan2dev(&td_chan->chan),
"td_chan: %p, chan: %d, membase: %p\n",
td_chan, td_chan->chan.chan_id, td_chan->membase);
if (td_chan->direction == DMA_DEV_TO_MEM) {
/* descriptor address */
iowrite32(0, td_chan->membase + TIMBDMA_OFFS_RX_DHAR);
iowrite32(td_desc->txd.phys, td_chan->membase +
TIMBDMA_OFFS_RX_DLAR);
/* Bytes per line */
iowrite32(td_chan->bytes_per_line, td_chan->membase +
TIMBDMA_OFFS_RX_BPRR);
/* enable RX */
iowrite32(TIMBDMA_RX_EN, td_chan->membase + TIMBDMA_OFFS_RX_ER);
} else {
/* address high */
iowrite32(0, td_chan->membase + TIMBDMA_OFFS_TX_DHAR);
iowrite32(td_desc->txd.phys, td_chan->membase +
TIMBDMA_OFFS_TX_DLAR);
}
td_chan->ongoing = true;
if (td_desc->interrupt)
__td_enable_chan_irq(td_chan);
}
static void __td_finish(struct timb_dma_chan *td_chan)
{
dma_async_tx_callback callback;
void *param;
struct dma_async_tx_descriptor *txd;
struct timb_dma_desc *td_desc;
/* can happen if the descriptor is canceled */
if (list_empty(&td_chan->active_list))
return;
td_desc = list_entry(td_chan->active_list.next, struct timb_dma_desc,
desc_node);
txd = &td_desc->txd;
dev_dbg(chan2dev(&td_chan->chan), "descriptor %u complete\n",
txd->cookie);
/* make sure to stop the transfer */
if (td_chan->direction == DMA_DEV_TO_MEM)
iowrite32(0, td_chan->membase + TIMBDMA_OFFS_RX_ER);
/* Currently no support for stopping DMA transfers
else
iowrite32(0, td_chan->membase + TIMBDMA_OFFS_TX_DLAR);
*/
dma_cookie_complete(txd);
td_chan->ongoing = false;
callback = txd->callback;
param = txd->callback_param;
list_move(&td_desc->desc_node, &td_chan->free_list);
if (!(txd->flags & DMA_COMPL_SKIP_SRC_UNMAP))
__td_unmap_descs(td_desc,
txd->flags & DMA_COMPL_SRC_UNMAP_SINGLE);
/*
* The API requires that no submissions are done from a
* callback, so we don't need to drop the lock here
*/
if (callback)
callback(param);
}
static u32 __td_ier_mask(struct timb_dma *td)
{
int i;
u32 ret = 0;
for (i = 0; i < td->dma.chancnt; i++) {
struct timb_dma_chan *td_chan = td->channels + i;
if (td_chan->ongoing) {
struct timb_dma_desc *td_desc =
list_entry(td_chan->active_list.next,
struct timb_dma_desc, desc_node);
if (td_desc->interrupt)
ret |= 1 << i;
}
}
return ret;
}
static void __td_start_next(struct timb_dma_chan *td_chan)
{
struct timb_dma_desc *td_desc;
BUG_ON(list_empty(&td_chan->queue));
BUG_ON(td_chan->ongoing);
td_desc = list_entry(td_chan->queue.next, struct timb_dma_desc,
desc_node);
dev_dbg(chan2dev(&td_chan->chan), "%s: started %u\n",
__func__, td_desc->txd.cookie);
list_move(&td_desc->desc_node, &td_chan->active_list);
__td_start_dma(td_chan);
}
static dma_cookie_t td_tx_submit(struct dma_async_tx_descriptor *txd)
{
struct timb_dma_desc *td_desc = container_of(txd, struct timb_dma_desc,
txd);
struct timb_dma_chan *td_chan = container_of(txd->chan,
struct timb_dma_chan, chan);
dma_cookie_t cookie;
spin_lock_bh(&td_chan->lock);
cookie = dma_cookie_assign(txd);
if (list_empty(&td_chan->active_list)) {
dev_dbg(chan2dev(txd->chan), "%s: started %u\n", __func__,
txd->cookie);
list_add_tail(&td_desc->desc_node, &td_chan->active_list);
__td_start_dma(td_chan);
} else {
dev_dbg(chan2dev(txd->chan), "tx_submit: queued %u\n",
txd->cookie);
list_add_tail(&td_desc->desc_node, &td_chan->queue);
}
spin_unlock_bh(&td_chan->lock);
return cookie;
}
static struct timb_dma_desc *td_alloc_init_desc(struct timb_dma_chan *td_chan)
{
struct dma_chan *chan = &td_chan->chan;
struct timb_dma_desc *td_desc;
int err;
td_desc = kzalloc(sizeof(struct timb_dma_desc), GFP_KERNEL);
if (!td_desc) {
dev_err(chan2dev(chan), "Failed to alloc descriptor\n");
goto out;
}
td_desc->desc_list_len = td_chan->desc_elems * TIMB_DMA_DESC_SIZE;
td_desc->desc_list = kzalloc(td_desc->desc_list_len, GFP_KERNEL);
if (!td_desc->desc_list) {
dev_err(chan2dev(chan), "Failed to alloc descriptor\n");
goto err;
}
dma_async_tx_descriptor_init(&td_desc->txd, chan);
td_desc->txd.tx_submit = td_tx_submit;
td_desc->txd.flags = DMA_CTRL_ACK;
td_desc->txd.phys = dma_map_single(chan2dmadev(chan),
td_desc->desc_list, td_desc->desc_list_len, DMA_TO_DEVICE);
err = dma_mapping_error(chan2dmadev(chan), td_desc->txd.phys);
if (err) {
dev_err(chan2dev(chan), "DMA mapping error: %d\n", err);
goto err;
}
return td_desc;
err:
kfree(td_desc->desc_list);
kfree(td_desc);
out:
return NULL;
}
static void td_free_desc(struct timb_dma_desc *td_desc)
{
dev_dbg(chan2dev(td_desc->txd.chan), "Freeing desc: %p\n", td_desc);
dma_unmap_single(chan2dmadev(td_desc->txd.chan), td_desc->txd.phys,
td_desc->desc_list_len, DMA_TO_DEVICE);
kfree(td_desc->desc_list);
kfree(td_desc);
}
static void td_desc_put(struct timb_dma_chan *td_chan,
struct timb_dma_desc *td_desc)
{
dev_dbg(chan2dev(&td_chan->chan), "Putting desc: %p\n", td_desc);
spin_lock_bh(&td_chan->lock);
list_add(&td_desc->desc_node, &td_chan->free_list);
spin_unlock_bh(&td_chan->lock);
}
static struct timb_dma_desc *td_desc_get(struct timb_dma_chan *td_chan)
{
struct timb_dma_desc *td_desc, *_td_desc;
struct timb_dma_desc *ret = NULL;
spin_lock_bh(&td_chan->lock);
list_for_each_entry_safe(td_desc, _td_desc, &td_chan->free_list,
desc_node) {
if (async_tx_test_ack(&td_desc->txd)) {
list_del(&td_desc->desc_node);
ret = td_desc;
break;
}
dev_dbg(chan2dev(&td_chan->chan), "desc %p not ACKed\n",
td_desc);
}
spin_unlock_bh(&td_chan->lock);
return ret;
}
static int td_alloc_chan_resources(struct dma_chan *chan)
{
struct timb_dma_chan *td_chan =
container_of(chan, struct timb_dma_chan, chan);
int i;
dev_dbg(chan2dev(chan), "%s: entry\n", __func__);
BUG_ON(!list_empty(&td_chan->free_list));
for (i = 0; i < td_chan->descs; i++) {
struct timb_dma_desc *td_desc = td_alloc_init_desc(td_chan);
if (!td_desc) {
if (i)
break;
else {
dev_err(chan2dev(chan),
"Couldnt allocate any descriptors\n");
return -ENOMEM;
}
}
td_desc_put(td_chan, td_desc);
}
spin_lock_bh(&td_chan->lock);
dma_cookie_init(chan);
spin_unlock_bh(&td_chan->lock);
return 0;
}
static void td_free_chan_resources(struct dma_chan *chan)
{
struct timb_dma_chan *td_chan =
container_of(chan, struct timb_dma_chan, chan);
struct timb_dma_desc *td_desc, *_td_desc;
LIST_HEAD(list);
dev_dbg(chan2dev(chan), "%s: Entry\n", __func__);
/* check that all descriptors are free */
BUG_ON(!list_empty(&td_chan->active_list));
BUG_ON(!list_empty(&td_chan->queue));
spin_lock_bh(&td_chan->lock);
list_splice_init(&td_chan->free_list, &list);
spin_unlock_bh(&td_chan->lock);
list_for_each_entry_safe(td_desc, _td_desc, &list, desc_node) {
dev_dbg(chan2dev(chan), "%s: Freeing desc: %p\n", __func__,
td_desc);
td_free_desc(td_desc);
}
}
static enum dma_status td_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
struct dma_tx_state *txstate)
{
enum dma_status ret;
dev_dbg(chan2dev(chan), "%s: Entry\n", __func__);
ret = dma_cookie_status(chan, cookie, txstate);
dev_dbg(chan2dev(chan), "%s: exit, ret: %d\n", __func__, ret);
return ret;
}
static void td_issue_pending(struct dma_chan *chan)
{
struct timb_dma_chan *td_chan =
container_of(chan, struct timb_dma_chan, chan);
dev_dbg(chan2dev(chan), "%s: Entry\n", __func__);
spin_lock_bh(&td_chan->lock);
if (!list_empty(&td_chan->active_list))
/* transfer ongoing */
if (__td_dma_done_ack(td_chan))
__td_finish(td_chan);
if (list_empty(&td_chan->active_list) && !list_empty(&td_chan->queue))
__td_start_next(td_chan);
spin_unlock_bh(&td_chan->lock);
}
static struct dma_async_tx_descriptor *td_prep_slave_sg(struct dma_chan *chan,
struct scatterlist *sgl, unsigned int sg_len,
enum dma_transfer_direction direction, unsigned long flags,
void *context)
{
struct timb_dma_chan *td_chan =
container_of(chan, struct timb_dma_chan, chan);
struct timb_dma_desc *td_desc;
struct scatterlist *sg;
unsigned int i;
unsigned int desc_usage = 0;
if (!sgl || !sg_len) {
dev_err(chan2dev(chan), "%s: No SG list\n", __func__);
return NULL;
}
/* even channels are for RX, odd for TX */
if (td_chan->direction != direction) {
dev_err(chan2dev(chan),
"Requesting channel in wrong direction\n");
return NULL;
}
td_desc = td_desc_get(td_chan);
if (!td_desc) {
dev_err(chan2dev(chan), "Not enough descriptors available\n");
return NULL;
}
td_desc->interrupt = (flags & DMA_PREP_INTERRUPT) != 0;
for_each_sg(sgl, sg, sg_len, i) {
int err;
if (desc_usage > td_desc->desc_list_len) {
dev_err(chan2dev(chan), "No descriptor space\n");
return NULL;
}
err = td_fill_desc(td_chan, td_desc->desc_list + desc_usage, sg,
i == (sg_len - 1));
if (err) {
dev_err(chan2dev(chan), "Failed to update desc: %d\n",
err);
td_desc_put(td_chan, td_desc);
return NULL;
}
desc_usage += TIMB_DMA_DESC_SIZE;
}
dma_sync_single_for_device(chan2dmadev(chan), td_desc->txd.phys,
td_desc->desc_list_len, DMA_MEM_TO_DEV);
return &td_desc->txd;
}
static int td_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
unsigned long arg)
{
struct timb_dma_chan *td_chan =
container_of(chan, struct timb_dma_chan, chan);
struct timb_dma_desc *td_desc, *_td_desc;
dev_dbg(chan2dev(chan), "%s: Entry\n", __func__);
if (cmd != DMA_TERMINATE_ALL)
return -ENXIO;
/* first the easy part, put the queue into the free list */
spin_lock_bh(&td_chan->lock);
list_for_each_entry_safe(td_desc, _td_desc, &td_chan->queue,
desc_node)
list_move(&td_desc->desc_node, &td_chan->free_list);
/* now tear down the running */
__td_finish(td_chan);
spin_unlock_bh(&td_chan->lock);
return 0;
}
static void td_tasklet(unsigned long data)
{
struct timb_dma *td = (struct timb_dma *)data;
u32 isr;
u32 ipr;
u32 ier;
int i;
isr = ioread32(td->membase + TIMBDMA_ISR);
ipr = isr & __td_ier_mask(td);
/* ack the interrupts */
iowrite32(ipr, td->membase + TIMBDMA_ISR);
for (i = 0; i < td->dma.chancnt; i++)
if (ipr & (1 << i)) {
struct timb_dma_chan *td_chan = td->channels + i;
spin_lock(&td_chan->lock);
__td_finish(td_chan);
if (!list_empty(&td_chan->queue))
__td_start_next(td_chan);
spin_unlock(&td_chan->lock);
}
ier = __td_ier_mask(td);
iowrite32(ier, td->membase + TIMBDMA_IER);
}
static irqreturn_t td_irq(int irq, void *devid)
{
struct timb_dma *td = devid;
u32 ipr = ioread32(td->membase + TIMBDMA_IPR);
if (ipr) {
/* disable interrupts, will be re-enabled in tasklet */
iowrite32(0, td->membase + TIMBDMA_IER);
tasklet_schedule(&td->tasklet);
return IRQ_HANDLED;
} else
return IRQ_NONE;
}
static int __devinit td_probe(struct platform_device *pdev)
{
struct timb_dma_platform_data *pdata = pdev->dev.platform_data;
struct timb_dma *td;
struct resource *iomem;
int irq;
int err;
int i;
if (!pdata) {
dev_err(&pdev->dev, "No platform data\n");
return -EINVAL;
}
iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!iomem)
return -EINVAL;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
if (!request_mem_region(iomem->start, resource_size(iomem),
DRIVER_NAME))
return -EBUSY;
td = kzalloc(sizeof(struct timb_dma) +
sizeof(struct timb_dma_chan) * pdata->nr_channels, GFP_KERNEL);
if (!td) {
err = -ENOMEM;
goto err_release_region;
}
dev_dbg(&pdev->dev, "Allocated TD: %p\n", td);
td->membase = ioremap(iomem->start, resource_size(iomem));
if (!td->membase) {
dev_err(&pdev->dev, "Failed to remap I/O memory\n");
err = -ENOMEM;
goto err_free_mem;
}
/* 32bit addressing */
iowrite32(TIMBDMA_32BIT_ADDR, td->membase + TIMBDMA_ACR);
/* disable and clear any interrupts */
iowrite32(0x0, td->membase + TIMBDMA_IER);
iowrite32(0xFFFFFFFF, td->membase + TIMBDMA_ISR);
tasklet_init(&td->tasklet, td_tasklet, (unsigned long)td);
err = request_irq(irq, td_irq, IRQF_SHARED, DRIVER_NAME, td);
if (err) {
dev_err(&pdev->dev, "Failed to request IRQ\n");
goto err_tasklet_kill;
}
td->dma.device_alloc_chan_resources = td_alloc_chan_resources;
td->dma.device_free_chan_resources = td_free_chan_resources;
td->dma.device_tx_status = td_tx_status;
td->dma.device_issue_pending = td_issue_pending;
dma_cap_set(DMA_SLAVE, td->dma.cap_mask);
dma_cap_set(DMA_PRIVATE, td->dma.cap_mask);
td->dma.device_prep_slave_sg = td_prep_slave_sg;
td->dma.device_control = td_control;
td->dma.dev = &pdev->dev;
INIT_LIST_HEAD(&td->dma.channels);
for (i = 0; i < pdata->nr_channels; i++) {
struct timb_dma_chan *td_chan = &td->channels[i];
struct timb_dma_platform_data_channel *pchan =
pdata->channels + i;
/* even channels are RX, odd are TX */
if ((i % 2) == pchan->rx) {
dev_err(&pdev->dev, "Wrong channel configuration\n");
err = -EINVAL;
goto err_free_irq;
}
td_chan->chan.device = &td->dma;
dma_cookie_init(&td_chan->chan);
spin_lock_init(&td_chan->lock);
INIT_LIST_HEAD(&td_chan->active_list);
INIT_LIST_HEAD(&td_chan->queue);
INIT_LIST_HEAD(&td_chan->free_list);
td_chan->descs = pchan->descriptors;
td_chan->desc_elems = pchan->descriptor_elements;
td_chan->bytes_per_line = pchan->bytes_per_line;
td_chan->direction = pchan->rx ? DMA_DEV_TO_MEM :
DMA_MEM_TO_DEV;
td_chan->membase = td->membase +
(i / 2) * TIMBDMA_INSTANCE_OFFSET +
(pchan->rx ? 0 : TIMBDMA_INSTANCE_TX_OFFSET);
dev_dbg(&pdev->dev, "Chan: %d, membase: %p\n",
i, td_chan->membase);
list_add_tail(&td_chan->chan.device_node, &td->dma.channels);
}
err = dma_async_device_register(&td->dma);
if (err) {
dev_err(&pdev->dev, "Failed to register async device\n");
goto err_free_irq;
}
platform_set_drvdata(pdev, td);
dev_dbg(&pdev->dev, "Probe result: %d\n", err);
return err;
err_free_irq:
free_irq(irq, td);
err_tasklet_kill:
tasklet_kill(&td->tasklet);
iounmap(td->membase);
err_free_mem:
kfree(td);
err_release_region:
release_mem_region(iomem->start, resource_size(iomem));
return err;
}
static int __devexit td_remove(struct platform_device *pdev)
{
struct timb_dma *td = platform_get_drvdata(pdev);
struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
int irq = platform_get_irq(pdev, 0);
dma_async_device_unregister(&td->dma);
free_irq(irq, td);
tasklet_kill(&td->tasklet);
iounmap(td->membase);
kfree(td);
release_mem_region(iomem->start, resource_size(iomem));
platform_set_drvdata(pdev, NULL);
dev_dbg(&pdev->dev, "Removed...\n");
return 0;
}
static struct platform_driver td_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
.probe = td_probe,
.remove = __exit_p(td_remove),
};
module_platform_driver(td_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Timberdale DMA controller driver");
MODULE_AUTHOR("Pelagicore AB <info@pelagicore.com>");
MODULE_ALIAS("platform:"DRIVER_NAME);
![swh spinner](/static/img/swh-spinner.gif)
Computing file changes ...