分类: LINUX
2016-12-07 19:28:17
详解ARM的AMBA设备中的DMA设备PL08X的Linux驱动-3
Version: 20100423
Author: green-waste(at)163.com
相关链接:
详解ARM的AMBA设备中的DMA设备PL08X的Linux驱动-1
http://blog.163.com/againinput4@yeah/blog/static/1227642712010323103932603/
详解ARM的AMBA设备中的DMA设备PL08X的Linux驱动-2
http://blog.163.com/againinput4@yeah/blog/static/122764271201032310434871/
详解ARM的AMBA设备中的DMA设备PL08X的Linux驱动-3
http://blog.163.com/againinput4@yeah/blog/static/1227642712010323104557214/
------------------------------------------------------------------------------------------------
/*
* Enable the DMA channel
* ASSUMES All other configuration bits have been set
* as desired before this co
*/
void pl08x_enable_dmac_chan(unsigned int cnum)
{
void __iomem *cbase = pd.base + PL08X_OS_CHAN_BASE +
(cnum * PL08X_OS_CHAN);
unsigned int r = 0;
/*
* Do not access config register until channel shows as disabled
*/
/* 等到对应channel是disable状态了,再接下来去enable它,会更安全 */
while ((readl(pd.base + PL08X_OS_ENCHNS) & (1 << cnum))
& PL08X_MASK_ENCHNS);
/*
* Do not access config register until channel shows as inactive
*/
/* 等到对应channel是inactive状态了,再接下来去enable它,会更安全 */
r = readl(cbase + PL08X_OS_CCFG);
while ((r & PL08X_MASK_ACTIVE) || (r & PL08X_MASK_CEN))
r = readl(cbase + PL08X_OS_CCFG);
writel(r | PL08X_MASK_CEN, cbase + PL08X_OS_CCFG);
mb();
}
/* 检测对应的位,得到是否是active的状态,对应着是否是busy */
static int pl08x_dma_busy(dmach_t ci)
{
unsigned int reg;
unsigned int chan_base = (unsigned int)pd.base
+ PL08X_OS_CHAN_BASE;
chan_base += ci * PL08X_OS_CHAN;
/*
* Check channel is inactive
*/
reg = readl(chan_base + PL08X_OS_CCFG);
return reg & PL08X_MASK_ACTIVE;
}
/*
* Function for the shared work queue
* - scheduled by the interrupt handler when sufficient
* list entries need releasing
*/
void pl08x_wqfunc(struct work_struct *work)
{
if (pd.pd)
pl08x_memrelease();
}
/* 根据datasheet,PL080和PL081对应的设备ID分别是0x00041080和0x00041081
详情参考:
3.4.16 Peripheral Identification Registers 0-3
3.4.17 PrimeCell Identification Registers 0-3 */
static struct amba_id pl08x_ids[] = {
/* PL080 */
{
.id = 0x00041080,
.mask = 0x000fffff,
},
/* PL081 */
{
.id = 0x00041081,
.mask = 0x000fffff,
},
{ 0, 0 },
};
#define DRIVER_NAME "pl08xdmac"
static int pl08x_probe(struct amba_device *amba_dev, void *id);
static struct amba_driver pl08x_amba_driver = {
.drv.name = "pl08xdmaapi",
.id_table = pl08x_ids,
.probe = pl08x_probe,
};
/*
* This single pool easier to manage than on
*/
int pl08x_make_LLIs(void)
{
int ret = 0;
/*
* Make a pool of LLI buffers
*/
pd.pool = dma_pool_create(pl08x_amba_driver.drv.name, &pd.dmac->dev,
PL08X_LLI_TSFR_SIZE, PL08X_ALIGN, PL08X_ALLOC);
if (!pd.pool) {
ret = -ENOMEM;
kfree(pd.chanllis);
}
pd.pool_ctr = 0;
return ret;
}
/* 需要提到一点的是,如果你的驱动在初始化的时候,设置了对应的
上图中的config寄存器中的ITC位,即终止计数中断,那么就会在每一个LLI传完之后就调用下面的这个irq中断函数,如果没有设置ITC,而只是对最有一个LLI的ITC 设置了,那么就是所有的传输完成了,才会调用下面的irq中断函数了 */
/*
* issue pending() will start the next transfer
* - it on
* CAUTION the work queue function may run during the handler
* CAUTION function callbacks may have interesting side effects
*/
static irqreturn_t pl08x_irq(int irq, void *dev)
{
u32 mask = 0;
u32 reg;
unsigned long flags;
int c;
/* 如果发现有任何错误,就退出
实际上此处很多种错误,而目前的此版本的驱动,暂时没加入更多的判断,仅仅是报错而已 */
reg = readl(pd.base + PL08X_OS_ISR_ERR);
mb();
if (reg) {
/*
* An error interrupt (on on
*/
dev_err(&pd.dmac->dev,
"%s - Error interrupt, register value 0x%08x\n",
__func__, reg);
/*
* Simply clear ALL PL08X error interrupts,
* regardless of channel and cause
*/
writel(0x000000FF, pd.base + PL08X_OS_ICLR_ERR);
}
reg = readl(pd.base + PL08X_OS_ISR);
mb();
for (c = 0; c < MAX_DMA_CHANNELS; c++) {
if ((1 << c) & reg) {
struct pl08x_txd *entry = NULL;
struct pl08x_txd *next;
spin_lock_irqsave(&pd.chanwrap[c]->lock, flags);
if (pd.chanwrap[c]->at) {
/*
* Update last completed
*/
pd.chanwrap[c]->lc =
(pd.chanwrap[c]->at->tx.cookie);
/*
* Callback peripheral driver for p/m
* to signal completion
*/
if (pd.chanwrap[c]->at->tx.callback) {
/*
* Pass channel number
*/
((struct pl08x_callback_param *)
pd.chanwrap[c]->at->tx.callback_param)->act = c;
/* 此处,真正去调用你之前挂载的callback函数,在callback函数里面,常见要做的事情就是清除你设备的DMA enable位,然后调用complete去使得completion变量完成,使得你驱动得以继续执行。 */
pd.chanwrap[c]->at->tx.callback(
pd.chanwrap[c]->at->tx.callback_param);
}
/*
* Device callbacks should NOT clear
* the current transaction on the channel
*/
if (!pd.chanwrap[c]->at)
BUG();
/*
* Free the descriptor if it's not for a device
* using a circular buffer
*/
if (!(pd.chanwrap[c]->at->pcd->circular_buffer)) {
list_add_tail(&pd.chanwrap[c]->at->txd_list,
&pd.chanwrap[c]->release_list);
pd.chanwrap[c]->at = NULL;
if (pd.pool_ctr > PL08X_MAX_ALLOCS) {
schedule_work(&pd.dwork);
}
}
/*
* else descriptor for circular
* buffers on
* client has disabled dma
*/
}
/*
* If descriptor queued, set it up
*/
if (!list_empty(&pd.chanwrap[c]->desc_list)) {
list_for_each_entry_safe(entry,
next, &pd.chanwrap[c]->desc_list, txd_list) {
list_del_init(&entry->txd_list);
pd.chanwrap[c]->at = entry;
break;
}
}
spin_unlock_irqrestore(&pd.chanwrap[c]->lock, flags);
mask |= (1 << c);
}
}
/*
* Clear on
*/
writel(mask, pd.base + PL08X_OS_ICLR_TC);
mb();
return mask ? IRQ_HANDLED : IRQ_NONE;
}
/* 全局性地Enable PL08x */
static void pl08x_ensure_on(void){
unsigned int reg;
reg = readl(pd.base + PL08X_OS_CFG);
reg &= PL08X_MASK_CFG;
reg |= PL08X_MASK_EN;
mb();
writel(reg, pd.base + PL08X_OS_CFG);
mb();
}
/* 为目前支持的多个channel进行必要的初始化,包括申请空间,设置初始值 */
/*
* Initialise the DMAC channels.
* Make a local wrapper to hold required da
*/
static int pl08x_dma_enumerate_channels(void)
{
int dma_chan;
struct pl08x_dma_chan *local_chan;
dma_cap_set(DMA_MEMCPY, dmac.cap_mask);
dma_cap_set(DMA_SLAVE, dmac.cap_mask);
if (dmac.chancnt)
dev_err(&pd.dmac->dev, "%s - chancnt already set\n", __func__);
INIT_LIST_HEAD(&dmac.channels);
for (dma_chan = 0; dma_chan < MAX_DMA_CHANNELS; dma_chan++) {
local_chan = kzalloc(sizeof(struct pl08x_dma_chan),
GFP_KERNEL);
if (!local_chan) {
dev_err(&pd.dmac->dev,
"%s - no memory for channel\n", __func__);
return dmac.chancnt;
}
/*
* Save the DMAC channel number
* to indicate which registers to access
*/
local_chan->chan_id = dma_chan;
local_chan->chan.device = &dmac;
atomic_set(&local_chan->last_issued, 0);
local_chan->lc = atomic_read(&local_chan->last_issued);
if (pd.pd->reserved_for_slave)
local_chan->slave_on
else
dev_err(&pd.dmac->dev,
"%s - can't establish channels reserved for slaves\n",
__func__);
spin_lock_init(&local_chan->lock);
INIT_LIST_HEAD(&local_chan->desc_list);
INIT_LIST_HEAD(&local_chan->release_list);
list_add_tail(&local_chan->chan.device_node, &dmac.channels);
pd.chanwrap[dmac.chancnt++] = local_chan;
}
return dmac.chancnt;
}
/* 经典的probe函数,对驱动进行必要的软件和硬件的初始化 */
static int pl08x_probe(struct amba_device *amba_dev, void *id)
{
int ret = 0;
/* 为后面要用到的LLI申请空间 */
ret = pl08x_make_LLIs();
if (ret)
return -ENOMEM;
/* 获得自己资源,其是你开始进行amba设备注册时候的,已经初始化好的对应的资源,包括基地址,中断号等 */
ret = amba_request_regions(amba_dev, NULL);
if (ret)
return ret;
pd.max_num_llis = 0;
/*
* We have our own work queue for cleaning memory
*/
INIT_WORK(&pd.dwork, pl08x_wqfunc);
pd.waitq = kmalloc(sizeof(wait_queue_head_t), GFP_KERNEL);
init_waitqueue_head(pd.waitq);
spin_lock_init(&pd.lock);
pd.base = ioremap(amba_dev->res.start, SZ_4K);
if (!pd.base) {
ret = -ENOMEM;
goto out1;
}
/*
* Attach the interrupt handler
*/
writel(0x000000FF, pd.base + PL08X_OS_ICLR_ERR);
writel(0x000000FF, pd.base + PL08X_OS_ICLR_TC);
mb();
ret = request_irq(amba_dev->irq[0], pl08x_irq, IRQF_DISABLED,
DRIVER_NAME, &amba_dev->dev);
if (ret) {
dev_err(&pd.dmac->dev, "%s - Failed to attach interrupt %d\n",
__func__, amba_dev->irq[0]);
goto out2;
}
/*
* Da
*/
pd.dmac = amba_dev;
amba_set_drvdata(amba_dev, &pd);
pd.pd = (struct pl08x_platform_da
/*
* Negotiate for channels with the platform
* and its DMA requirements
*/
dmac.dev = &amba_dev->dev;
ret = pl08x_dma_enumerate_channels();
if (!ret) {
dev_warn(&pd.dmac->dev,
"%s - failed to enumerate channels - %d\n",
__func__, ret);
goto out2;
} else {
/* 将自己这个pl08x的DMA驱动,注册到DMA Engine 框架中
别处看到某人评论说是这个DMA Engine驱动框架属于老的了,有更好的新的,不了解,有空去看看 */
ret = dma_async_device_register(&dmac);
if (ret) {
dev_warn(&pd.dmac->dev,
"%s - failed to register as an async device - %d\n",
__func__, ret);
goto out2;
}
}
/* 硬件上确保启动DMA了 */
pl08x_ensure_on();
dev_info(&pd.dmac->dev,
"%s - ARM(R) PL08X DMA driver found\n",
__func__);
goto out;
out2:
iounmap(pd.base);
out1:
amba_release_regions(amba_dev);
out:
return ret;
}
static int __init pl08x_init(void)
{
int retval;
retval = amba_driver_register(&pl08x_amba_driver);
if (retval)
printk(KERN_WARNING
"PL08X::pl08x_init() - failed to register as an amba device - %d\n",
retval);
return retval;
}
device_initcall(pl08x_init);
/* 下面这些函数,都是挂靠在DMA Engine架构下面的函数,此 pl08x的DMA驱动也要是实现对应的函数,以便其他程序调用这些接口,使用pl08x。
下面对通用的调用此pl08x 的DMA驱动的大概流程:
(1) 得到当前可以用于DMA传输的数据buffer
a:如果已经有了普的数据buffer,那么一般用dma_map_single去将普通的CPU访问的buffer映射成DMA可以访问的buffer。
b:如果没有现存buffer,那么一般用dma_alloc_writecombine,自己申请一个,申请的此buffer,是combine,绑定一起的,即不论是DMA还是CPU,都可以访问的。
(2) 根据自己的需求,设置好传输的client的一堆参数后,然后调用
dma_async_client_register(txclient);
dma_async_client_chan_request(txclient);
去注册申请对应的client。
(3) 设置好scatter/gather list的信息后,调用
device_prep_slave_sg去寻找并获得和自己匹配的那个描述符desc。
(4) 然后设置对应的callback函数和对应参数
desc->callback = as353x_nand_dma_complete_callback;
desc->callback_param = &info->callback_param;
(5) 都准备好了后,再调用 desc->tx_submit(desc);去提交你的DMA请求。
(6) 一切完毕后,调用
info->txchan->device->device_issue_pending(info->txchan);
真正的开始DMA的数据传输。
(7) 之后,你就可以调用
wait_for_completion_timeout
去等待你的传输完毕了。
其中,你的前面注册的callback函数里面,会调用complete(&info->done);,去完成对应的变量,而你的callback函数,会在DMA完全传输完毕后,DMA驱动中的irq中会被调用。
/* ===============================================================
* The DMA ENGINE API
* ===============================================================
*/
static int pl08x_alloc_chan_resources(struct dma_chan *chan,
struct dma_client *client)
{
struct pl08x_dma_chan *local_chan = container_of
(chan, struct pl08x_dma_chan, chan);
int assigned = 0;
/*
* Channels may be reserved for slaves
* Channels doing slave DMA can on
*/
if (local_chan->slave_on
if (!client->slave)
return -EBUSY;
} else if (client->slave)
return -EBUSY;
/* Slave DMA clients can on
if ((client->slave) && (chan->client_count))
return -EBUSY;
else {
/* 这部分没完全看懂,直译是用Slave DMA的channel,只能有一个client,但是具体为什么,不懂。。。*/
/* Channels doing slave DMA can on
if ((local_chan->slave) && (chan->client_count)) {
dev_err(&pd.dmac->dev,
"%s - channel %d already has a slave - local_chan->slave %p\n",
__func__,
local_chan->chan_id,
local_chan->slave);
return -EBUSY;
}
/* Check channel is idle */
if (pl08x_dma_busy(local_chan->chan_id)) {
dev_err(&pd.dmac->dev,
"%s - channel %d not idle\n",
__func__, local_chan->chan_id);
return -EIO;
}
local_chan->slave = client->slave;
}
return assigned;
}
static void pl08x_free_chan_resources(struct dma_chan *chan)
{
dev_warn(&pd.dmac->dev, "%s - UNIMPLEMENTED\n", __func__);
}
/* 此submit函数是你之前准备好了一切后,最后调用此函数提交你的请求,但是实际并没开始真正的DMA传输,要等到最后的issue pending才真正开始 */
/*
* First make the LLIs (could/should we do this earlier??)
* slave (m/p) - no queued transactions allowed at present
* TODO allow queued transactions for non circular buffers
* Set up the channel active txd as inactive
* m2m - transactions may be queued
* If no active txd on channel
* set it up as inactive
* - issue_pending() will set active & start
* else
* queue it
* Lock channel since there may be (at least for m2m) multiple calls
*
* Return < 0 for error
*/
static dma_cookie_t pl08x_tx_submit(struct dma_async_tx_descriptor *tx)
{
int num_llis;
unsigned long flags;
struct pl08x_txd *local_txd = container_of(tx, struct pl08x_txd, tx);
struct pl08x_dma_chan *local_chan =
container_of(tx->chan, struct pl08x_dma_chan, chan);
int pl08x_chan_num = local_chan->chan_id;
/* 为当前的desc描述符,填充好对应的LLI */
num_llis = fill_LLIS_for_desc(local_txd, pl08x_chan_num);
if (num_llis) {
spin_lock_irqsave(&local_chan->lock, flags);
atomic_inc(&local_chan->last_issued);
tx->cookie = atomic_read(&local_chan->last_issued);
if (local_chan->at) {
/*
* If this device not using a circular buffer then
* queue this new descriptor.
* The descriptor for a circular buffer continues
* to be used until the channel is freed
*/
if (local_txd->pcd->circular_buffer)
dev_err(&pd.dmac->dev,
"%s - attempting to queue a circular buffer\n",
__func__);
else
list_add_tail(&local_txd->txd_list,
&local_chan->desc_list);
} else {
local_txd->slave = local_chan->slave;
local_chan->at = local_txd;
local_txd->active = 0;
}
spin_unlock_irqrestore(&local_chan->lock, flags);
return tx->cookie;
} else
return -EINVAL;
}
/* 为DMA的memcpy做一些准备工作,主要就是初始化一些参数,尤其是传输方向 */
/*
* Initialize a descriptor to be used by submit
*/
static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy(
struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
size_t len, unsigned long flags)
{
struct pl08x_txd *local_txd;
local_txd = kzalloc(sizeof(struct pl08x_txd), GFP_KERNEL);
if (!local_txd) {
dev_err(&pd.dmac->dev,
"%s - no memory for descriptor\n", __func__);
return NULL;
} else {
dma_async_tx_descriptor_init(&local_txd->tx, chan);
local_txd->srcbus.addr = src;
local_txd->dstbus.addr = dest;
local_txd->tx.tx_submit = pl08x_tx_submit;
local_txd->len = len;
/*
* dmaengine.c has these directions hard coded,
* but not acessible
*/
local_txd->dstdir = DMA_FROM_DEVICE;
local_txd->srcdir = DMA_TO_DEVICE;
INIT_LIST_HEAD(&local_txd->txd_list);
/*
* Ensure the platform da
*/
local_txd->pcd = &pd.pd->sd[PL08X_DMA_SIGNALS];
}
return &local_txd->tx;
}
static struct dma_async_tx_descriptor *pl08x_prep_dma_interrupt(
struct dma_chan *chan, unsigned long flags)
{
struct dma_async_tx_descriptor *retval = NULL;
return retval;
}
/* 提供了一个函数,用于检测当前DMA是否已经完成了,实际好像很少用到,多数是用complete机制 */
/*
* Co
* may give problems - could schedule where indicated.
* If slaves are relying on interrupts to signal completion this
* function must not be called with interrupts disabled
*/
static enum dma_status pl08x_dma_is_complete(struct dma_chan *chan,
dma_cookie_t cookie,
dma_cookie_t *done,
dma_cookie_t *used)
{
struct pl08x_dma_chan *local_chan = container_of(chan,
struct pl08x_dma_chan, chan);
dma_cookie_t last_used;
dma_cookie_t last_complete;
enum dma_status ret;
last_used = atomic_read(&local_chan->last_issued);
last_complete = local_chan->lc;
if (done)
*done = last_complete;
if (used)
*used = last_used;
ret = dma_async_is_complete(cookie, last_complete, last_used);
if (ret == DMA_SUCCESS)
return ret;
/*
* schedule(); could be inserted here
*/
/*
* This cookie not complete yet
*/
last_used = atomic_read(&local_chan->last_issued);
last_complete = local_chan->lc;
if (done)
*done = last_complete;
if (used)
*used = last_used;
return dma_async_is_complete(cookie, last_complete, last_used);
}
/* 真正的提交DMA请求,进行DMA传输的函数 */
/*
* Slave transactions callback to the slave device to allow
* synchronization of slave DMA signals with the DMAC enable
*/
static void pl08x_issue_pending(struct dma_chan *chan)
{
struct pl08x_dma_chan *local_chan
= container_of(chan, struct pl08x_dma_chan, chan);
int pl08x_chan_num = local_chan->chan_id;
if (local_chan->at) {
/* 如果当前没人在用此channel,那么就可以真正去使用了,先去设置寄存器的值,然后通知client DMA开始了,最后真正的设置物理上的寄存器,开始DMA传输。 */
if (!local_chan->at->active) {
pl08x_set_cregs(local_chan->at, local_chan->chan_id);
if (local_chan->slave) {
/*
* Allow slaves to activate signals
* concurrent to the DMAC enable
*/
if (local_chan->at->tx.callback) {
((struct pl08x_callback_param *)
local_chan->at->tx.callback_param)->act =
PL08X_SIGNAL_START;
local_chan->at->tx.callback(
local_chan->at->tx.callback_param);
}
}
pl08x_enable_dmac_chan(local_chan->chan_id);
local_chan->at->active = 1;
}
/*
* else skip active transfer
* Calls with active txd occur for NET_DMA
* - there can be queued descriptors
*/
}
/*
* else - calls with no active descriptor occur for NET_DMA
*/
}
/* 上层程序调用此函数,对提出的DMA请求和scatter/gather list 信息进行预处理,其实主要就是根据你当前的一些参数,包括设备的DMA的地址进行匹配,找到合适的配置参数,用于以后的DMA各个参数的设置 */
struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_da
unsigned long flags)
{
struct pl08x_txd *local_txd;
unsigned int reg = 0;
int i;
/*
* Current implementation ASSUMES on
*/
if (sg_len != 1)
BUG();
local_txd = kmalloc(sizeof(struct pl08x_txd), GFP_KERNEL);
if (!local_txd) {
dev_err(&pd.dmac->dev, "%s - no local_txd\n", __func__);
return NULL;
} else {
struct pl08x_dma_chan *local_chan =
container_of(chan, struct pl08x_dma_chan, chan);
dma_async_tx_descriptor_init(&local_txd->tx, chan);
if (direction == DMA_TO_DEVICE) {
local_txd->srcbus.addr = sgl->dma_address;
local_txd->dstbus.addr =
reg = local_chan->slave->tx_reg;
} else if (direction == DMA_FROM_DEVICE) {
local_txd->srcbus.addr =
reg = local_chan->slave->rx_reg;
local_txd->dstbus.addr = sgl->dma_address;
} else {
dev_err(&pd.dmac->dev,
"%s - direction unsupported\n", __func__);
return NULL;
}
/*
* Find the device array entry for this txd
* so that the txd has access to the peripheral da
*/
for (i = 0; i < PL08X_DMA_SIGNALS; i++) {
/* 找到和自己的匹配的那个参数,这些参数,是之前在amba设备注册时候,已经设置和初始化好的一堆设置参数。 */
if (reg == (((unsigned int)(pd.pd->sd[i].io_addr))))
break;
}
local_txd->pcd = &pd.pd->sd[i];
local_txd->tx.tx_submit = pl08x_tx_submit;
local_txd->len = sgl->length;
INIT_LIST_HEAD(&local_txd->txd_list);
}
return &local_txd->tx;
}
/* 结束所有的通道 */
/*
* CAUTION: Called by ALSA interrupt handler
*/
void pl08x_terminate_all(struct dma_chan *chan)
{
struct pl08x_dma_chan *local_chan =
container_of(chan, struct pl08x_dma_chan, chan);
int pl08x_chan_num = local_chan->chan_id;
if (local_chan->slave->dev) {
pl08x_disable_dmac_chan(pl08x_chan_num);
/*
* Allow slaves to activate signals
* concurrent to the DMAC enable
*/
if (local_chan->at) {
if (local_chan->at->tx.callback) {
((struct pl08x_callback_param *)
local_chan->at->tx.callback_param)
->act = PL08X_SIGNAL_STOP;
local_chan->at->tx.callback(
local_chan->at->tx.callback_param);
}
}
}
}