cregit-Linux how code gets into the kernel

Release 4.11 drivers/dma/ep93xx_dma.c

Directory: drivers/dma
/*
 * Driver for the Cirrus Logic EP93xx DMA Controller
 *
 * Copyright (C) 2011 Mika Westerberg
 *
 * DMA M2P implementation is based on the original
 * arch/arm/mach-ep93xx/dma-m2p.c which has following copyrights:
 *
 *   Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
 *   Copyright (C) 2006 Applied Data Systems
 *   Copyright (C) 2009 Ryan Mallon <rmallon@gmail.com>
 *
 * This driver is based on dw_dmac and amba-pl08x drivers.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#include <linux/clk.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/dmaengine.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

#include <linux/platform_data/dma-ep93xx.h>

#include "dmaengine.h"

/* M2P registers */

#define M2P_CONTROL			0x0000

#define M2P_CONTROL_STALLINT		BIT(0)

#define M2P_CONTROL_NFBINT		BIT(1)

#define M2P_CONTROL_CH_ERROR_INT	BIT(3)

#define M2P_CONTROL_ENABLE		BIT(4)

#define M2P_CONTROL_ICE			BIT(6)


#define M2P_INTERRUPT			0x0004

#define M2P_INTERRUPT_STALL		BIT(0)

#define M2P_INTERRUPT_NFB		BIT(1)

#define M2P_INTERRUPT_ERROR		BIT(3)


#define M2P_PPALLOC			0x0008

#define M2P_STATUS			0x000c


#define M2P_MAXCNT0			0x0020

#define M2P_BASE0			0x0024

#define M2P_MAXCNT1			0x0030

#define M2P_BASE1			0x0034


#define M2P_STATE_IDLE			0

#define M2P_STATE_STALL			1

#define M2P_STATE_ON			2

#define M2P_STATE_NEXT			3

/* M2M registers */

#define M2M_CONTROL			0x0000

#define M2M_CONTROL_DONEINT		BIT(2)

#define M2M_CONTROL_ENABLE		BIT(3)

#define M2M_CONTROL_START		BIT(4)

#define M2M_CONTROL_DAH			BIT(11)

#define M2M_CONTROL_SAH			BIT(12)

#define M2M_CONTROL_PW_SHIFT		9

#define M2M_CONTROL_PW_8		(0 << M2M_CONTROL_PW_SHIFT)

#define M2M_CONTROL_PW_16		(1 << M2M_CONTROL_PW_SHIFT)

#define M2M_CONTROL_PW_32		(2 << M2M_CONTROL_PW_SHIFT)

#define M2M_CONTROL_PW_MASK		(3 << M2M_CONTROL_PW_SHIFT)

#define M2M_CONTROL_TM_SHIFT		13

#define M2M_CONTROL_TM_TX		(1 << M2M_CONTROL_TM_SHIFT)

#define M2M_CONTROL_TM_RX		(2 << M2M_CONTROL_TM_SHIFT)

#define M2M_CONTROL_NFBINT		BIT(21)

#define M2M_CONTROL_RSS_SHIFT		22

#define M2M_CONTROL_RSS_SSPRX		(1 << M2M_CONTROL_RSS_SHIFT)

#define M2M_CONTROL_RSS_SSPTX		(2 << M2M_CONTROL_RSS_SHIFT)

#define M2M_CONTROL_RSS_IDE		(3 << M2M_CONTROL_RSS_SHIFT)

#define M2M_CONTROL_NO_HDSK		BIT(24)

#define M2M_CONTROL_PWSC_SHIFT		25


#define M2M_INTERRUPT			0x0004

#define M2M_INTERRUPT_MASK		6


#define M2M_STATUS			0x000c

#define M2M_STATUS_CTL_SHIFT		1

#define M2M_STATUS_CTL_IDLE		(0 << M2M_STATUS_CTL_SHIFT)

#define M2M_STATUS_CTL_STALL		(1 << M2M_STATUS_CTL_SHIFT)

#define M2M_STATUS_CTL_MEMRD		(2 << M2M_STATUS_CTL_SHIFT)

#define M2M_STATUS_CTL_MEMWR		(3 << M2M_STATUS_CTL_SHIFT)

#define M2M_STATUS_CTL_BWCWAIT		(4 << M2M_STATUS_CTL_SHIFT)

#define M2M_STATUS_CTL_MASK		(7 << M2M_STATUS_CTL_SHIFT)

#define M2M_STATUS_BUF_SHIFT		4

#define M2M_STATUS_BUF_NO		(0 << M2M_STATUS_BUF_SHIFT)

#define M2M_STATUS_BUF_ON		(1 << M2M_STATUS_BUF_SHIFT)

#define M2M_STATUS_BUF_NEXT		(2 << M2M_STATUS_BUF_SHIFT)

#define M2M_STATUS_BUF_MASK		(3 << M2M_STATUS_BUF_SHIFT)

#define M2M_STATUS_DONE			BIT(6)


#define M2M_BCR0			0x0010

#define M2M_BCR1			0x0014

#define M2M_SAR_BASE0			0x0018

#define M2M_SAR_BASE1			0x001c

#define M2M_DAR_BASE0			0x002c

#define M2M_DAR_BASE1			0x0030


#define DMA_MAX_CHAN_BYTES		0xffff

#define DMA_MAX_CHAN_DESCRIPTORS	32

struct ep93xx_dma_engine;

/**
 * struct ep93xx_dma_desc - EP93xx specific transaction descriptor
 * @src_addr: source address of the transaction
 * @dst_addr: destination address of the transaction
 * @size: size of the transaction (in bytes)
 * @complete: this descriptor is completed
 * @txd: dmaengine API descriptor
 * @tx_list: list of linked descriptors
 * @node: link used for putting this into a channel queue
 */

struct ep93xx_dma_desc {
	
u32				src_addr;
	
u32				dst_addr;
	
size_t				size;
	
bool				complete;
	
struct dma_async_tx_descriptor	txd;
	
struct list_head		tx_list;
	
struct list_head		node;
};

/**
 * struct ep93xx_dma_chan - an EP93xx DMA M2P/M2M channel
 * @chan: dmaengine API channel
 * @edma: pointer to to the engine device
 * @regs: memory mapped registers
 * @irq: interrupt number of the channel
 * @clk: clock used by this channel
 * @tasklet: channel specific tasklet used for callbacks
 * @lock: lock protecting the fields following
 * @flags: flags for the channel
 * @buffer: which buffer to use next (0/1)
 * @active: flattened chain of descriptors currently being processed
 * @queue: pending descriptors which are handled next
 * @free_list: list of free descriptors which can be used
 * @runtime_addr: physical address currently used as dest/src (M2M only). This
 *                is set via .device_config before slave operation is
 *                prepared
 * @runtime_ctrl: M2M runtime values for the control register.
 *
 * As EP93xx DMA controller doesn't support real chained DMA descriptors we
 * will have slightly different scheme here: @active points to a head of
 * flattened DMA descriptor chain.
 *
 * @queue holds pending transactions. These are linked through the first
 * descriptor in the chain. When a descriptor is moved to the @active queue,
 * the first and chained descriptors are flattened into a single list.
 *
 * @chan.private holds pointer to &struct ep93xx_dma_data which contains
 * necessary channel configuration information. For memcpy channels this must
 * be %NULL.
 */

struct ep93xx_dma_chan {
	
struct dma_chan			chan;
	
const struct ep93xx_dma_engine	*edma;
	
void __iomem			*regs;
	
int				irq;
	
struct clk			*clk;
	
struct tasklet_struct		tasklet;
	/* protects the fields following */
	
spinlock_t			lock;
	
unsigned long			flags;
/* Channel is configured for cyclic transfers */

#define EP93XX_DMA_IS_CYCLIC		0

	
int				buffer;
	
struct list_head		active;
	
struct list_head		queue;
	
struct list_head		free_list;
	
u32				runtime_addr;
	
u32				runtime_ctrl;
};

/**
 * struct ep93xx_dma_engine - the EP93xx DMA engine instance
 * @dma_dev: holds the dmaengine device
 * @m2m: is this an M2M or M2P device
 * @hw_setup: method which sets the channel up for operation
 * @hw_shutdown: shuts the channel down and flushes whatever is left
 * @hw_submit: pushes active descriptor(s) to the hardware
 * @hw_interrupt: handle the interrupt
 * @num_channels: number of channels for this instance
 * @channels: array of channels
 *
 * There is one instance of this struct for the M2P channels and one for the
 * M2M channels. hw_xxx() methods are used to perform operations which are
 * different on M2M and M2P channels. These methods are called with channel
 * lock held and interrupts disabled so they cannot sleep.
 */

struct ep93xx_dma_engine {
	
struct dma_device	dma_dev;
	
bool			m2m;
	
int			(*hw_setup)(struct ep93xx_dma_chan *);
	
void			(*hw_shutdown)(struct ep93xx_dma_chan *);
	
void			(*hw_submit)(struct ep93xx_dma_chan *);
	
int			(*hw_interrupt)(struct ep93xx_dma_chan *);

#define INTERRUPT_UNKNOWN	0

#define INTERRUPT_DONE		1

#define INTERRUPT_NEXT_BUFFER	2

	
size_t			num_channels;
	
struct ep93xx_dma_chan	channels[];
};


static inline struct device *chan2dev(struct ep93xx_dma_chan *edmac) { return &edmac->chan.dev->device; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg24100.00%1100.00%
Total24100.00%1100.00%


static struct ep93xx_dma_chan *to_ep93xx_dma_chan(struct dma_chan *chan) { return container_of(chan, struct ep93xx_dma_chan, chan); }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg24100.00%1100.00%
Total24100.00%1100.00%

/** * ep93xx_dma_set_active - set new active descriptor chain * @edmac: channel * @desc: head of the new active descriptor chain * * Sets @desc to be the head of the new active descriptor chain. This is the * chain which is processed next. The active list must be empty before calling * this function. * * Called with @edmac->lock held and interrupts disabled. */
static void ep93xx_dma_set_active(struct ep93xx_dma_chan *edmac, struct ep93xx_dma_desc *desc) { BUG_ON(!list_empty(&edmac->active)); list_add_tail(&desc->node, &edmac->active); /* Flatten the @desc->tx_list chain into @edmac->active list */ while (!list_empty(&desc->tx_list)) { struct ep93xx_dma_desc *d = list_first_entry(&desc->tx_list, struct ep93xx_dma_desc, node); /* * We copy the callback parameters from the first descriptor * to all the chained descriptors. This way we can call the * callback without having to find out the first descriptor in * the chain. Useful for cyclic transfers. */ d->txd.callback = desc->txd.callback; d->txd.callback_param = desc->txd.callback_param; list_move_tail(&d->node, &edmac->active); } }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg111100.00%1100.00%
Total111100.00%1100.00%

/* Called with @edmac->lock held and interrupts disabled */
static struct ep93xx_dma_desc * ep93xx_dma_get_active(struct ep93xx_dma_chan *edmac) { return list_first_entry_or_null(&edmac->active, struct ep93xx_dma_desc, node); }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg2696.30%266.67%
Masahiro Yamada13.70%133.33%
Total27100.00%3100.00%

/** * ep93xx_dma_advance_active - advances to the next active descriptor * @edmac: channel * * Function advances active descriptor to the next in the @edmac->active and * returns %true if we still have descriptors in the chain to process. * Otherwise returns %false. * * When the channel is in cyclic mode always returns %true. * * Called with @edmac->lock held and interrupts disabled. */
static bool ep93xx_dma_advance_active(struct ep93xx_dma_chan *edmac) { struct ep93xx_dma_desc *desc; list_rotate_left(&edmac->active); if (test_bit(EP93XX_DMA_IS_CYCLIC, &edmac->flags)) return true; desc = ep93xx_dma_get_active(edmac); if (!desc) return false; /* * If txd.cookie is set it means that we are back in the first * descriptor in the chain and hence done with it. */ return !desc->txd.cookie; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg63100.00%2100.00%
Total63100.00%2100.00%

/* * M2P DMA implementation */
static void m2p_set_control(struct ep93xx_dma_chan *edmac, u32 control) { writel(control, edmac->regs + M2P_CONTROL); /* * EP93xx User's Guide states that we must perform a dummy read after * write to the control register. */ readl(edmac->regs + M2P_CONTROL); }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg35100.00%1100.00%
Total35100.00%1100.00%


static int m2p_hw_setup(struct ep93xx_dma_chan *edmac) { struct ep93xx_dma_data *data = edmac->chan.private; u32 control; writel(data->port & 0xf, edmac->regs + M2P_PPALLOC); control = M2P_CONTROL_CH_ERROR_INT | M2P_CONTROL_ICE | M2P_CONTROL_ENABLE; m2p_set_control(edmac, control); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg58100.00%1100.00%
Total58100.00%1100.00%


static inline u32 m2p_channel_state(struct ep93xx_dma_chan *edmac) { return (readl(edmac->regs + M2P_STATUS) >> 4) & 0x3; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg28100.00%1100.00%
Total28100.00%1100.00%


static void m2p_hw_shutdown(struct ep93xx_dma_chan *edmac) { u32 control; control = readl(edmac->regs + M2P_CONTROL); control &= ~(M2P_CONTROL_STALLINT | M2P_CONTROL_NFBINT); m2p_set_control(edmac, control); while (m2p_channel_state(edmac) >= M2P_STATE_ON) cpu_relax(); m2p_set_control(edmac, 0); while (m2p_channel_state(edmac) == M2P_STATE_STALL) cpu_relax(); }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg72100.00%1100.00%
Total72100.00%1100.00%


static void m2p_fill_desc(struct ep93xx_dma_chan *edmac) { struct ep93xx_dma_desc *desc; u32 bus_addr; desc = ep93xx_dma_get_active(edmac); if (!desc) { dev_warn(chan2dev(edmac), "M2P: empty descriptor list\n"); return; } if (ep93xx_dma_chan_direction(&edmac->chan) == DMA_MEM_TO_DEV) bus_addr = desc->src_addr; else bus_addr = desc->dst_addr; if (edmac->buffer == 0) { writel(desc->size, edmac->regs + M2P_MAXCNT0); writel(bus_addr, edmac->regs + M2P_BASE0); } else { writel(desc->size, edmac->regs + M2P_MAXCNT1); writel(bus_addr, edmac->regs + M2P_BASE1); } edmac->buffer ^= 1; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg13599.26%266.67%
Vinod Koul10.74%133.33%
Total136100.00%3100.00%


static void m2p_hw_submit(struct ep93xx_dma_chan *edmac) { u32 control = readl(edmac->regs + M2P_CONTROL); m2p_fill_desc(edmac); control |= M2P_CONTROL_STALLINT; if (ep93xx_dma_advance_active(edmac)) { m2p_fill_desc(edmac); control |= M2P_CONTROL_NFBINT; } m2p_set_control(edmac, control); }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg57100.00%1100.00%
Total57100.00%1100.00%


static int m2p_hw_interrupt(struct ep93xx_dma_chan *edmac) { u32 irq_status = readl(edmac->regs + M2P_INTERRUPT); u32 control; if (irq_status & M2P_INTERRUPT_ERROR) { struct ep93xx_dma_desc *desc = ep93xx_dma_get_active(edmac); /* Clear the error interrupt */ writel(1, edmac->regs + M2P_INTERRUPT); /* * It seems that there is no easy way of reporting errors back * to client so we just report the error here and continue as * usual. * * Revisit this when there is a mechanism to report back the * errors. */ dev_err(chan2dev(edmac), "DMA transfer failed! Details:\n" "\tcookie : %d\n" "\tsrc_addr : 0x%08x\n" "\tdst_addr : 0x%08x\n" "\tsize : %zu\n", desc->txd.cookie, desc->src_addr, desc->dst_addr, desc->size); } /* * Even latest E2 silicon revision sometimes assert STALL interrupt * instead of NFB. Therefore we treat them equally, basing on the * amount of data we still have to transfer. */ if (!(irq_status & (M2P_INTERRUPT_STALL | M2P_INTERRUPT_NFB))) return INTERRUPT_UNKNOWN; if (ep93xx_dma_advance_active(edmac)) { m2p_fill_desc(edmac); return INTERRUPT_NEXT_BUFFER; } /* Disable interrupts */ control = readl(edmac->regs + M2P_CONTROL); control &= ~(M2P_CONTROL_STALLINT | M2P_CONTROL_NFBINT); m2p_set_control(edmac, control); return INTERRUPT_DONE; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg13084.42%150.00%
Alexander Sverdlin2415.58%150.00%
Total154100.00%2100.00%

/* * M2M DMA implementation */
static int m2m_hw_setup(struct ep93xx_dma_chan *edmac) { const struct ep93xx_dma_data *data = edmac->chan.private; u32 control = 0; if (!data) { /* This is memcpy channel, nothing to configure */ writel(control, edmac->regs + M2M_CONTROL); return 0; } switch (data->port) { case EP93XX_DMA_SSP: /* * This was found via experimenting - anything less than 5 * causes the channel to perform only a partial transfer which * leads to problems since we don't get DONE interrupt then. */ control = (5 << M2M_CONTROL_PWSC_SHIFT); control |= M2M_CONTROL_NO_HDSK; if (data->direction == DMA_MEM_TO_DEV) { control |= M2M_CONTROL_DAH; control |= M2M_CONTROL_TM_TX; control |= M2M_CONTROL_RSS_SSPTX; } else { control |= M2M_CONTROL_SAH; control |= M2M_CONTROL_TM_RX; control |= M2M_CONTROL_RSS_SSPRX; } break; case EP93XX_DMA_IDE: /* * This IDE part is totally untested. Values below are taken * from the EP93xx Users's Guide and might not be correct. */ if (data->direction == DMA_MEM_TO_DEV) { /* Worst case from the UG */ control = (3 << M2M_CONTROL_PWSC_SHIFT); control |= M2M_CONTROL_DAH; control |= M2M_CONTROL_TM_TX; } else { control = (2 << M2M_CONTROL_PWSC_SHIFT); control |= M2M_CONTROL_SAH; control |= M2M_CONTROL_TM_RX; } control |= M2M_CONTROL_NO_HDSK; control |= M2M_CONTROL_RSS_IDE; control |= M2M_CONTROL_PW_16; break; default: return -EINVAL; } writel(control, edmac->regs + M2M_CONTROL); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg18092.78%133.33%
Rafal Prylowski126.19%133.33%
Vinod Koul21.03%133.33%
Total194100.00%3100.00%


static void m2m_hw_shutdown(struct ep93xx_dma_chan *edmac) { /* Just disable the channel */ writel(0, edmac->regs + M2M_CONTROL); }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg23100.00%1100.00%
Total23100.00%1100.00%


static void m2m_fill_desc(struct ep93xx_dma_chan *edmac) { struct ep93xx_dma_desc *desc; desc = ep93xx_dma_get_active(edmac); if (!desc) { dev_warn(chan2dev(edmac), "M2M: empty descriptor list\n"); return; } if (edmac->buffer == 0) { writel(desc->src_addr, edmac->regs + M2M_SAR_BASE0); writel(desc->dst_addr, edmac->regs + M2M_DAR_BASE0); writel(desc->size, edmac->regs + M2M_BCR0); } else { writel(desc->src_addr, edmac->regs + M2M_SAR_BASE1); writel(desc->dst_addr, edmac->regs + M2M_DAR_BASE1); writel(desc->size, edmac->regs + M2M_BCR1); } edmac->buffer ^= 1; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg138100.00%2100.00%
Total138100.00%2100.00%


static void m2m_hw_submit(struct ep93xx_dma_chan *edmac) { struct ep93xx_dma_data *data = edmac->chan.private; u32 control = readl(edmac->regs + M2M_CONTROL); /* * Since we allow clients to configure PW (peripheral width) we always * clear PW bits here and then set them according what is given in * the runtime configuration. */ control &= ~M2M_CONTROL_PW_MASK; control |= edmac->runtime_ctrl; m2m_fill_desc(edmac); control |= M2M_CONTROL_DONEINT; if (ep93xx_dma_advance_active(edmac)) { m2m_fill_desc(edmac); control |= M2M_CONTROL_NFBINT; } /* * Now we can finally enable the channel. For M2M channel this must be * done _after_ the BCRx registers are programmed. */ control |= M2M_CONTROL_ENABLE; writel(control, edmac->regs + M2M_CONTROL); if (!data) { /* * For memcpy channels the software trigger must be asserted * in order to start the memcpy operation. */ control |= M2M_CONTROL_START; writel(control, edmac->regs + M2M_CONTROL); } }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg9483.93%150.00%
Rafal Prylowski1816.07%150.00%
Total112100.00%2100.00%

/* * According to EP93xx User's Guide, we should receive DONE interrupt when all * M2M DMA controller transactions complete normally. This is not always the * case - sometimes EP93xx M2M DMA asserts DONE interrupt when the DMA channel * is still running (channel Buffer FSM in DMA_BUF_ON state, and channel * Control FSM in DMA_MEM_RD state, observed at least in IDE-DMA operation). * In effect, disabling the channel when only DONE bit is set could stop * currently running DMA transfer. To avoid this, we use Buffer FSM and * Control FSM to check current state of DMA channel. */
static int m2m_hw_interrupt(struct ep93xx_dma_chan *edmac) { u32 status = readl(edmac->regs + M2M_STATUS); u32 ctl_fsm = status & M2M_STATUS_CTL_MASK; u32 buf_fsm = status & M2M_STATUS_BUF_MASK; bool done = status & M2M_STATUS_DONE; bool last_done; u32 control; struct ep93xx_dma_desc *desc; /* Accept only DONE and NFB interrupts */ if (!(readl(edmac->regs + M2M_INTERRUPT) & M2M_INTERRUPT_MASK)) return INTERRUPT_UNKNOWN; if (done) { /* Clear the DONE bit */ writel(0, edmac->regs + M2M_INTERRUPT); } /* * Check whether we are done with descriptors or not. This, together * with DMA channel state, determines action to take in interrupt. */ desc = ep93xx_dma_get_active(edmac); last_done = !desc || desc->txd.cookie; /* * Use M2M DMA Buffer FSM and Control FSM to check current state of * DMA channel. Using DONE and NFB bits from channel status register * or bits from channel interrupt register is not reliable. */ if (!last_done && (buf_fsm == M2M_STATUS_BUF_NO || buf_fsm == M2M_STATUS_BUF_ON)) { /* * Two buffers are ready for update when Buffer FSM is in * DMA_NO_BUF state. Only one buffer can be prepared without * disabling the channel or polling the DONE bit. * To simplify things, always prepare only one buffer. */ if (ep93xx_dma_advance_active(edmac)) { m2m_fill_desc(edmac); if (done && !edmac->chan.private) { /* Software trigger for memcpy channel */ control = readl(edmac->regs + M2M_CONTROL); control |= M2M_CONTROL_START; writel(control, edmac->regs + M2M_CONTROL); } return INTERRUPT_NEXT_BUFFER; } else { last_done = true; } } /* * Disable the channel only when Buffer FSM is in DMA_NO_BUF state * and Control FSM is in DMA_STALL state. */ if (last_done && buf_fsm == M2M_STATUS_BUF_NO && ctl_fsm == M2M_STATUS_CTL_STALL) { /* Disable interrupts and the channel */ control = readl(edmac->regs + M2M_CONTROL); control &= ~(M2M_CONTROL_DONEINT | M2M_CONTROL_NFBINT | M2M_CONTROL_ENABLE); writel(control, edmac->regs + M2M_CONTROL); return INTERRUPT_DONE; } /* * Nothing to do this time. */ return INTERRUPT_NEXT_BUFFER; }

Contributors

PersonTokensPropCommitsCommitProp
Rafal Prylowski16866.93%150.00%
Mika Westerberg8333.07%150.00%
Total251100.00%2100.00%

/* * DMA engine API implementation */
static struct ep93xx_dma_desc * ep93xx_dma_desc_get(struct ep93xx_dma_chan *edmac) { struct ep93xx_dma_desc *desc, *_desc; struct ep93xx_dma_desc *ret = NULL; unsigned long flags; spin_lock_irqsave(&edmac->lock, flags); list_for_each_entry_safe(desc, _desc, &edmac->free_list, node) { if (async_tx_test_ack(&desc->txd)) { list_del_init(&desc->node); /* Re-initialize the descriptor */ desc->src_addr = 0; desc->dst_addr = 0; desc->size = 0; desc->complete = false; desc->txd.cookie = 0; desc->txd.callback = NULL; desc->txd.callback_param = NULL; ret = desc; break; } } spin_unlock_irqrestore(&edmac->lock, flags); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg141100.00%1100.00%
Total141100.00%1100.00%


static void ep93xx_dma_desc_put(struct ep93xx_dma_chan *edmac, struct ep93xx_dma_desc *desc) { if (desc) { unsigned long flags; spin_lock_irqsave(&edmac->lock, flags); list_splice_init(&desc->tx_list, &edmac->free_list); list_add(&desc->node, &edmac->free_list); spin_unlock_irqrestore(&edmac->lock, flags); } }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg72100.00%1100.00%
Total72100.00%1100.00%

/** * ep93xx_dma_advance_work - start processing the next pending transaction * @edmac: channel * * If we have pending transactions queued and we are currently idling, this * function takes the next queued transaction from the @edmac->queue and * pushes it to the hardware for execution. */
static void ep93xx_dma_advance_work(struct ep93xx_dma_chan *edmac) { struct ep93xx_dma_desc *new; unsigned long flags; spin_lock_irqsave(&edmac->lock, flags); if (!list_empty(&edmac->active) || list_empty(&edmac->queue)) { spin_unlock_irqrestore(&edmac->lock, flags); return; } /* Take the next descriptor from the pending queue */ new = list_first_entry(&edmac->queue, struct ep93xx_dma_desc, node); list_del_init(&new->node); ep93xx_dma_set_active(edmac, new); /* Push it to the hardware */ edmac->edma->hw_submit(edmac); spin_unlock_irqrestore(&edmac->lock, flags); }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg113100.00%1100.00%
Total113100.00%1100.00%


static void ep93xx_dma_tasklet(unsigned long data) { struct ep93xx_dma_chan *edmac = (struct ep93xx_dma_chan *)data; struct ep93xx_dma_desc *desc, *d; struct dmaengine_desc_callback cb; LIST_HEAD(list); memset(&cb, 0, sizeof(cb)); spin_lock_irq(&edmac->lock); /* * If dma_terminate_all() was called before we get to run, the active * list has become empty. If that happens we aren't supposed to do * anything more than call ep93xx_dma_advance_work(). */ desc = ep93xx_dma_get_active(edmac); if (desc) { if (desc->complete) { /* mark descriptor complete for non cyclic case only */ if (!test_bit(EP93XX_DMA_IS_CYCLIC, &edmac->flags)) dma_cookie_complete(&desc->txd); list_splice_init(&edmac->active, &list); } dmaengine_desc_get_callback(&desc->txd, &cb); } spin_unlock_irq(&edmac->lock); /* Pick up the next descriptor from the queue */ ep93xx_dma_advance_work(edmac); /* Now we can release all the chained descriptors */ list_for_each_entry_safe(desc, d, &list, node) { dma_descriptor_unmap(&desc->txd); ep93xx_dma_desc_put(edmac, desc); } dmaengine_desc_callback_invoke(&cb, NULL); }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg12269.32%233.33%
Dave Jiang2815.91%116.67%
Vinod Koul147.95%116.67%
Dan J Williams84.55%116.67%
Russell King42.27%116.67%
Total176100.00%6100.00%


static irqreturn_t ep93xx_dma_interrupt(int irq, void *dev_id) { struct ep93xx_dma_chan *edmac = dev_id; struct ep93xx_dma_desc *desc; irqreturn_t ret = IRQ_HANDLED; spin_lock(&edmac->lock); desc = ep93xx_dma_get_active(edmac); if (!desc) { dev_warn(chan2dev(edmac), "got interrupt while active list is empty\n"); spin_unlock(&edmac->lock); return IRQ_NONE; } switch (edmac->edma->hw_interrupt(edmac)) { case INTERRUPT_DONE: desc->complete = true; tasklet_schedule(&edmac->tasklet); break; case INTERRUPT_NEXT_BUFFER: if (test_bit(EP93XX_DMA_IS_CYCLIC, &edmac->flags)) tasklet_schedule(&edmac->tasklet); break; default: dev_warn(chan2dev(edmac), "unknown interrupt!\n"); ret = IRQ_NONE; break; } spin_unlock(&edmac->lock); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg155100.00%2100.00%
Total155100.00%2100.00%

/** * ep93xx_dma_tx_submit - set the prepared descriptor(s) to be executed * @tx: descriptor to be executed * * Function will execute given descriptor on the hardware or if the hardware * is busy, queue the descriptor to be executed later on. Returns cookie which * can be used to poll the status of the descriptor. */
static dma_cookie_t ep93xx_dma_tx_submit(struct dma_async_tx_descriptor *tx) { struct ep93xx_dma_chan *edmac = to_ep93xx_dma_chan(tx->chan); struct ep93xx_dma_desc *desc; dma_cookie_t cookie; unsigned long flags; spin_lock_irqsave(&edmac->lock, flags); cookie = dma_cookie_assign(tx); desc = container_of(tx, struct ep93xx_dma_desc, txd); /* * If nothing is currently prosessed, we push this descriptor * directly to the hardware. Otherwise we put the descriptor * to the pending queue. */ if (list_empty(&edmac->active)) { ep93xx_dma_set_active(edmac, desc); edmac->edma->hw_submit(edmac); } else { list_add_tail(&desc->node, &edmac->queue); } spin_unlock_irqrestore(&edmac->lock, flags); return cookie; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg11896.72%150.00%
Russell King43.28%150.00%
Total122100.00%2100.00%

/** * ep93xx_dma_alloc_chan_resources - allocate resources for the channel * @chan: channel to allocate resources * * Function allocates necessary resources for the given DMA channel and * returns number of allocated descriptors for the channel. Negative errno * is returned in case of failure. */
static int ep93xx_dma_alloc_chan_resources(struct dma_chan *chan) { struct ep93xx_dma_chan *edmac = to_ep93xx_dma_chan(chan); struct ep93xx_dma_data *data = chan->private; const char *name = dma_chan_name(chan); int ret, i; /* Sanity check the channel parameters */ if (!edmac->edma->m2m) { if (!data) return -EINVAL; if (data->port < EP93XX_DMA_I2S1 || data->port > EP93XX_DMA_IRDA) return -EINVAL; if (data->direction != ep93xx_dma_chan_direction(chan)) return -EINVAL; } else { if (data) { switch (data->port) { case EP93XX_DMA_SSP: case EP93XX_DMA_IDE: if (!is_slave_direction(data->direction)) return -EINVAL; break; default: return -EINVAL; } } } if (data && data->name) name = data->name; ret = clk_enable(edmac->clk); if (ret) return ret; ret = request_irq(edmac->irq, ep93xx_dma_interrupt, 0, name, edmac); if (ret) goto fail_clk_disable; spin_lock_irq(&edmac->lock); dma_cookie_init(&edmac->chan); ret = edmac->edma->hw_setup(edmac); spin_unlock_irq(&edmac->lock); if (ret) goto fail_free_irq; for (i = 0; i < DMA_MAX_CHAN_DESCRIPTORS; i++) { struct ep93xx_dma_desc *desc; desc = kzalloc(sizeof(*desc), GFP_KERNEL); if (!desc) { dev_warn(chan2dev(edmac), "not enough descriptors\n"); break; } INIT_LIST_HEAD(&desc->tx_list); dma_async_tx_descriptor_init(&desc->txd, chan); desc->txd.flags = DMA_CTRL_ACK; desc->txd.tx_submit = ep93xx_dma_tx_submit; ep93xx_dma_desc_put(edmac, desc); } return i; fail_free_irq: free_irq(edmac->irq, edmac); fail_clk_disable: clk_disable(edmac->clk); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg34797.47%125.00%
Russell King51.40%250.00%
Andy Shevchenko41.12%125.00%
Total356100.00%4100.00%

/** * ep93xx_dma_free_chan_resources - release resources for the channel * @chan: channel * * Function releases all the resources allocated for the given channel. * The channel must be idle when this is called. */
static void ep93xx_dma_free_chan_resources(struct dma_chan *chan) { struct ep93xx_dma_chan *edmac = to_ep93xx_dma_chan(chan); struct ep93xx_dma_desc *desc, *d; unsigned long flags; LIST_HEAD(list); BUG_ON(!list_empty(&edmac->active)); BUG_ON(!list_empty(&edmac->queue)); spin_lock_irqsave(&edmac->lock, flags); edmac->edma->hw_shutdown(edmac); edmac->runtime_addr = 0; edmac->runtime_ctrl = 0; edmac->buffer = 0; list_splice_init(&edmac->free_list, &list); spin_unlock_irqrestore(&edmac->lock, flags); list_for_each_entry_safe(desc, d, &list, node) kfree(desc); clk_disable(edmac->clk); free_irq(edmac->irq, edmac); }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg151100.00%1100.00%
Total151100.00%1100.00%

/** * ep93xx_dma_prep_dma_memcpy - prepare a memcpy DMA operation * @chan: channel * @dest: destination bus address * @src: source bus address * @len: size of the transaction * @flags: flags for the descriptor * * Returns a valid DMA descriptor or %NULL in case of failure. */
static struct dma_async_tx_descriptor * ep93xx_dma_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src, size_t len, unsigned long flags) { struct ep93xx_dma_chan *edmac = to_ep93xx_dma_chan(chan); struct ep93xx_dma_desc *desc, *first; size_t bytes, offset; first = NULL; for (offset = 0; offset < len; offset += bytes) { desc = ep93xx_dma_desc_get(edmac); if (!desc) { dev_warn(chan2dev(edmac), "couln't get descriptor\n"); goto fail; } bytes = min_t(size_t, len - offset, DMA_MAX_CHAN_BYTES); desc->src_addr = src + offset; desc->dst_addr = dest + offset; desc->size = bytes; if (!first) first = desc; else list_add_tail(&desc->node, &first->tx_list); } first->txd.cookie = -EBUSY; first->txd.flags = flags; return &first->txd; fail: ep93xx_dma_desc_put(edmac, first); return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg18899.47%150.00%
H Hartley Sweeten10.53%150.00%
Total189100.00%2100.00%

/** * ep93xx_dma_prep_slave_sg - prepare a slave DMA operation * @chan: channel * @sgl: list of buffers to transfer * @sg_len: number of entries in @sgl * @dir: direction of tha DMA transfer * @flags: flags for the descriptor * @context: operation context (ignored) * * Returns a valid DMA descriptor or %NULL in case of failure. */
static struct dma_async_tx_descriptor * ep93xx_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len, enum dma_transfer_direction dir, unsigned long flags, void *context) { struct ep93xx_dma_chan *edmac = to_ep93xx_dma_chan(chan); struct ep93xx_dma_desc *desc, *first; struct scatterlist *sg; int i; if (!edmac->edma->m2m && dir != ep93xx_dma_chan_direction(chan)) { dev_warn(chan2dev(edmac), "channel was configured with different direction\n"); return NULL; } if (test_bit(EP93XX_DMA_IS_CYCLIC, &edmac->flags)) { dev_warn(chan2dev(edmac), "channel is already used for cyclic transfers\n"); return NULL; } first = NULL; for_each_sg(sgl, sg, sg_len, i) { size_t len = sg_dma_len(sg); if (len > DMA_MAX_CHAN_BYTES) { dev_warn(chan2dev(edmac), "too big transfer size %zu\n", len); goto fail; } desc = ep93xx_dma_desc_get(edmac); if (!desc) { dev_warn(chan2dev(edmac), "couln't get descriptor\n"); goto fail; } if (dir == DMA_MEM_TO_DEV) { desc->src_addr = sg_dma_address(sg); desc->dst_addr = edmac->runtime_addr; } else { desc->src_addr = edmac->runtime_addr; desc->dst_addr = sg_dma_address(sg); } desc->size = len; if (!first) first = desc; else list_add_tail(&desc->node, &first->tx_list); } first->txd.cookie = -EBUSY; first->txd.flags = flags; return &first->txd; fail: ep93xx_dma_desc_put(edmac, first); return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg29096.35%120.00%
Vinod Koul72.33%360.00%
Alexandre Bounine41.33%120.00%
Total301100.00%5100.00%

/** * ep93xx_dma_prep_dma_cyclic - prepare a cyclic DMA operation * @chan: channel * @dma_addr: DMA mapped address of the buffer * @buf_len: length of the buffer (in bytes) * @period_len: length of a single period * @dir: direction of the operation * @flags: tx descriptor status flags * * Prepares a descriptor for cyclic DMA operation. This means that once the * descriptor is submitted, we will be submitting in a @period_len sized * buffers and calling callback once the period has been elapsed. Transfer * terminates only when client calls dmaengine_terminate_all() for this * channel. * * Returns a valid DMA descriptor or %NULL in case of failure. */
static struct dma_async_tx_descriptor * ep93xx_dma_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t dma_addr, size_t buf_len, size_t period_len, enum dma_transfer_direction dir, unsigned long flags) { struct ep93xx_dma_chan *edmac = to_ep93xx_dma_chan(chan); struct ep93xx_dma_desc *desc, *first; size_t offset = 0; if (!edmac->edma->m2m && dir != ep93xx_dma_chan_direction(chan)) { dev_warn(chan2dev(edmac), "channel was configured with different direction\n"); return NULL; } if (test_and_set_bit(EP93XX_DMA_IS_CYCLIC, &edmac->flags)) { dev_warn(chan2dev(edmac), "channel is already used for cyclic transfers\n"); return NULL; } if (period_len > DMA_MAX_CHAN_BYTES) { dev_warn(chan2dev(edmac), "too big period length %zu\n", period_len); return NULL; } /* Split the buffer into period size chunks */ first = NULL; for (offset = 0; offset < buf_len; offset += period_len) { desc = ep93xx_dma_desc_get(edmac); if (!desc) { dev_warn(chan2dev(edmac), "couln't get descriptor\n"); goto fail; } if (dir == DMA_MEM_TO_DEV) { desc->src_addr = dma_addr + offset; desc->dst_addr = edmac->runtime_addr; } else { desc->src_addr = edmac->runtime_addr; desc->dst_addr = dma_addr + offset; } desc->size = period_len; if (!first) first = desc; else list_add_tail(&desc->node, &first->tx_list); } first->txd.cookie = -EBUSY; return &first->txd; fail: ep93xx_dma_desc_put(edmac, first); return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg27497.51%120.00%
Vinod Koul31.07%240.00%
Peter Ujfalusi31.07%120.00%
Alexandre Bounine10.36%120.00%
Total281100.00%5100.00%

/** * ep93xx_dma_terminate_all - terminate all transactions * @chan: channel * * Stops all DMA transactions. All descriptors are put back to the * @edmac->free_list and callbacks are _not_ called. */
static int ep93xx_dma_terminate_all(struct dma_chan *chan) { struct ep93xx_dma_chan *edmac = to_ep93xx_dma_chan(chan); struct ep93xx_dma_desc *desc, *_d; unsigned long flags; LIST_HEAD(list); spin_lock_irqsave(&edmac->lock, flags); /* First we disable and flush the DMA channel */ edmac->edma->hw_shutdown(edmac); clear_bit(EP93XX_DMA_IS_CYCLIC, &edmac->flags); list_splice_init(&edmac->active, &list); list_splice_init(&edmac->queue, &list); /* * We then re-enable the channel. This way we can continue submitting * the descriptors by just calling ->hw_submit() again. */ edmac->edma->hw_setup(edmac); spin_unlock_irqrestore(&edmac->lock, flags); list_for_each_entry_safe(desc, _d, &list, node) ep93xx_dma_desc_put(edmac, desc); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg11890.77%150.00%
Maxime Ripard129.23%150.00%
Total130100.00%2100.00%


static int ep93xx_dma_slave_config(struct dma_chan *chan, struct dma_slave_config *config) { struct ep93xx_dma_chan *edmac = to_ep93xx_dma_chan(chan); enum dma_slave_buswidth width; unsigned long flags; u32 addr, ctrl; if (!edmac->edma->m2m) return -EINVAL; switch (config->direction) { case DMA_DEV_TO_MEM: width = config->src_addr_width; addr = config->src_addr; break; case DMA_MEM_TO_DEV: width = config->dst_addr_width; addr = config->dst_addr; break; default: return -EINVAL; } switch (width) { case DMA_SLAVE_BUSWIDTH_1_BYTE: ctrl = 0; break; case DMA_SLAVE_BUSWIDTH_2_BYTES: ctrl = M2M_CONTROL_PW_16; break; case DMA_SLAVE_BUSWIDTH_4_BYTES: ctrl = M2M_CONTROL_PW_32; break; default: return -EINVAL; } spin_lock_irqsave(&edmac->lock, flags); edmac->runtime_addr = addr; edmac->runtime_ctrl = ctrl; spin_unlock_irqrestore(&edmac->lock, flags); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg15391.62%133.33%
Maxime Ripard127.19%133.33%
Vinod Koul21.20%133.33%
Total167100.00%3100.00%

/** * ep93xx_dma_tx_status - check if a transaction is completed * @chan: channel * @cookie: transaction specific cookie * @state: state of the transaction is stored here if given * * This function can be used to query state of a given transaction. */
static enum dma_status ep93xx_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie, struct dma_tx_state *state) { return dma_cookie_status(chan, cookie, state); }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg2376.67%133.33%
Russell King620.00%133.33%
Andy Shevchenko13.33%133.33%
Total30100.00%3100.00%

/** * ep93xx_dma_issue_pending - push pending transactions to the hardware * @chan: channel * * When this function is called, all pending transactions are pushed to the * hardware and executed. */
static void ep93xx_dma_issue_pending(struct dma_chan *chan) { ep93xx_dma_advance_work(to_ep93xx_dma_chan(chan)); }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg19100.00%1100.00%
Total19100.00%1100.00%


static int __init ep93xx_dma_probe(struct platform_device *pdev) { struct ep93xx_dma_platform_data *pdata = dev_get_platdata(&pdev->dev); struct ep93xx_dma_engine *edma; struct dma_device *dma_dev; size_t edma_size; int ret, i; edma_size = pdata->num_channels * sizeof(struct ep93xx_dma_chan); edma = kzalloc(sizeof(*edma) + edma_size, GFP_KERNEL); if (!edma) return -ENOMEM; dma_dev = &edma->dma_dev; edma->m2m = platform_get_device_id(pdev)->driver_data; edma->num_channels = pdata->num_channels; INIT_LIST_HEAD(&dma_dev->channels); for (i = 0; i < pdata->num_channels; i++) { const struct ep93xx_dma_chan_data *cdata = &pdata->channels[i]; struct ep93xx_dma_chan *edmac = &edma->channels[i]; edmac->chan.device = dma_dev; edmac->regs = cdata->base; edmac->irq = cdata->irq; edmac->edma = edma; edmac->clk = clk_get(NULL, cdata->name); if (IS_ERR(edmac->clk)) { dev_warn(&pdev->dev, "failed to get clock for %s\n", cdata->name); continue; } spin_lock_init(&edmac->lock); INIT_LIST_HEAD(&edmac->active); INIT_LIST_HEAD(&edmac->queue); INIT_LIST_HEAD(&edmac->free_list); tasklet_init(&edmac->tasklet, ep93xx_dma_tasklet, (unsigned long)edmac); list_add_tail(&edmac->chan.device_node, &dma_dev->channels); } dma_cap_zero(dma_dev->cap_mask); dma_cap_set(DMA_SLAVE, dma_dev->cap_mask); dma_cap_set(DMA_CYCLIC, dma_dev->cap_mask); dma_dev->dev = &pdev->dev; dma_dev->device_alloc_chan_resources = ep93xx_dma_alloc_chan_resources; dma_dev->device_free_chan_resources = ep93xx_dma_free_chan_resources; dma_dev->device_prep_slave_sg = ep93xx_dma_prep_slave_sg; dma_dev->device_prep_dma_cyclic = ep93xx_dma_prep_dma_cyclic; dma_dev->device_config = ep93xx_dma_slave_config; dma_dev->device_terminate_all = ep93xx_dma_terminate_all; dma_dev->device_issue_pending = ep93xx_dma_issue_pending; dma_dev->device_tx_status = ep93xx_dma_tx_status; dma_set_max_seg_size(dma_dev->dev, DMA_MAX_CHAN_BYTES); if (edma->m2m) { dma_cap_set(DMA_MEMCPY, dma_dev->cap_mask); dma_dev->device_prep_dma_memcpy = ep93xx_dma_prep_dma_memcpy; edma->hw_setup = m2m_hw_setup; edma->hw_shutdown = m2m_hw_shutdown; edma->hw_submit = m2m_hw_submit; edma->hw_interrupt = m2m_hw_interrupt; } else { dma_cap_set(DMA_PRIVATE, dma_dev->cap_mask); edma->hw_setup = m2p_hw_setup; edma->hw_shutdown = m2p_hw_shutdown; edma->hw_submit = m2p_hw_submit; edma->hw_interrupt = m2p_hw_interrupt; } ret = dma_async_device_register(dma_dev); if (unlikely(ret)) { for (i = 0; i < edma->num_channels; i++) { struct ep93xx_dma_chan *edmac = &edma->channels[i]; if (!IS_ERR_OR_NULL(edmac->clk)) clk_put(edmac->clk); } kfree(edma); } else { dev_info(dma_dev->dev, "EP93xx M2%s DMA ready\n", edma->m2m ? "M" : "P"); } return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg54698.56%150.00%
Maxime Ripard81.44%150.00%
Total554100.00%2100.00%

static const struct platform_device_id ep93xx_dma_driver_ids[] = { { "ep93xx-dma-m2p", 0 }, { "ep93xx-dma-m2m", 1 }, { }, }; static struct platform_driver ep93xx_dma_driver = { .driver = { .name = "ep93xx-dma", }, .id_table = ep93xx_dma_driver_ids, };
static int __init ep93xx_dma_module_init(void) { return platform_driver_probe(&ep93xx_dma_driver, ep93xx_dma_probe); }

Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg18100.00%1100.00%
Total18100.00%1100.00%

subsys_initcall(ep93xx_dma_module_init); MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>"); MODULE_DESCRIPTION("EP93xx DMA driver"); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
Mika Westerberg460591.53%310.34%
Rafal Prylowski2625.21%26.90%
Maxime Ripard330.66%13.45%
Vinod Koul300.60%517.24%
Dave Jiang280.56%13.45%
Alexander Sverdlin240.48%13.45%
Russell King220.44%620.69%
Dan J Williams80.16%13.45%
Alexandre Bounine60.12%13.45%
Andy Shevchenko50.10%26.90%
Peter Ujfalusi30.06%13.45%
Krzysztof Kozlowski10.02%13.45%
H Hartley Sweeten10.02%13.45%
Arnd Bergmann10.02%13.45%
Laurent Pinchart10.02%13.45%
Masahiro Yamada10.02%13.45%
Total5031100.00%29100.00%
Directory: drivers/dma
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.