Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3037243
  • 博文数量: 674
  • 博客积分: 17881
  • 博客等级: 上将
  • 技术积分: 4849
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-17 10:15
文章分类

全部博文(674)

文章存档

2013年(34)

2012年(146)

2011年(197)

2010年(297)

分类: LINUX

2012-05-02 17:24:13

2410DMA驱动源码分析

Author:aaron

 

    关于2410下的DMA操作模式等信息的介绍请参考我的另外一篇文章<< S3C2410:DMA介紹>>, 这里

主要以kernel2.6.22的源码来对2410DMA的驱动源码的做个分析.

首先我们由kconfigmakefile来获取DMA方面相关文件(即源码):

 Arch/arm/plat-s3c24xx/Dma.c

 Arch/arm/mach-s3c2410/Dma.c

 以上两个就是操作DMA的核心文件. 我们会逐个的来分析.

 

先看初始化函数, 哪些是初始化函数呢? 就是哪些通过module_init, core_initcall, arch_initcall等声明的函数.

首先在arch/arm/mach-s3c2410/s3c2410.c下有个初始化函数.

arch/arm/mach-s3c2410/s3c2410.c:

static int __init s3c2410_core_init(void)

{

       return sysdev_class_register(&s3c2410_sysclass);   //注册一个class

}

 

core_initcall(s3c2410_core_init);

我们以后会看到, 后面的DMA设备及DMA驱动都会注册到该类下面.

arch/arm/mach-s3c2410/s3c2410.c:

struct sysdev_class s3c2410_sysclass = {

       set_kset_name("s3c2410-core"),

};

很明显, 实际上该类并没有其他什么操作, 只是为了让DMA设备和驱动都注册到这个类下面, 以使对方可以互相找的到.

接着在arch/arm/plat-s3c24xx/Dma.c下也注册了一个类

arch/arm/plat-s3c24xx/Dma.c:

static int __init s3c24xx_dma_sysclass_init(void)

{

       int ret = sysdev_class_register(&dma_sysclass);    //注册的类

 

       if (ret != 0)

              printk(KERN_ERR "dma sysclass registration failed/n");

 

       return ret;

}

 

struct sysdev_class dma_sysclass = {

       set_kset_name("s3c24xx-dma"),

       .suspend = s3c2410_dma_suspend,   

       .resume          = s3c2410_dma_resume,

};

后面我们会看到这2个类是如何使用的. 其中的dma_sysclass还有suspendresume的操作, 这些都是电源管理方面的东西,我们这里就不分析了.

接着看在arch/arm/mach-s3c2410/Dma.c下注册了DMA的驱动程序

arch/arm/mach-s3c2410/Dma.c:

#if defined(CONFIG_CPU_S3C2410)    /*我们以2410为例*/

static struct sysdev_driver s3c2410_dma_driver = {

       .add = s3c2410_dma_add, 

};

 

static int __init s3c2410_dma_drvinit(void)

{

    //注册驱动, s3c2410_dma_driver注册到s3c2410_sysclass类下

       return sysdev_driver_register(&s3c2410_sysclass, &s3c2410_dma_driver); 

}

 

arch_initcall(s3c2410_dma_drvinit);

#endif

可以看到这个函数就是把DMA的驱动程序注册到s3c2410_sysclass的类下面, 后面我们会看到DMA设备是如何找到整个驱动并调用驱动的add函数的.

Drivers/base/sys.c:

int sysdev_driver_register(struct sysdev_class * cls,

                        struct sysdev_driver * drv)

{

       down(&sysdev_drivers_lock);

       if (cls && kset_get(&cls->kset)) {

              list_add_tail(&drv->entry, &cls->drivers);  //把驱动注册到类下面的drivers list

 

              /* If devices of this class already exist, tell the driver */

              if (drv->add) {  //如果驱动有add函数的话

                     struct sys_device *dev;

                     list_for_each_entry(dev, &cls->kset.list, kobj.entry)

                            drv->add(dev);   //为该类下的每个设备调用驱动的add函数.

              }

       } else

              list_add_tail(&drv->entry, &sysdev_drivers); //把驱动注册到类下面的drivers list

       up(&sysdev_drivers_lock);

       return 0;

}

通过上面这个函数, 我们就看到了s3c2410_dma_driver是如何注册进s3c2410_sysclass类的, 即就是把s3c2410_dma_driver挂到s3c2410_sysclass下的drivers列表下.

接着我们来看DMA设备的注册了.

Arch/arm/mach-s3c2410/s3c2410.c:

int __init s3c2410_init(void)

{

       printk("S3C2410: Initialising architecture/n");

 

       return sysdev_register(&s3c2410_sysdev);   //注册设备了

}

 

static struct sys_device s3c2410_sysdev = {

       .cls         = &s3c2410_sysclass,

};

这个函数注册了一个系统设备, 我们看到, 其实这是个虚拟设备(其实根本就不是个设备), 它仅仅是为了要触发dma驱动的那个add函数, 所有的DMA设备会在那个时候才会真正的注册. 至于这个函数是怎么调用的问题, 就由读者自己去分析吧J, 不过我记得我有文章分析过的哦.

Drivers/base/sys.c:

int sysdev_register(struct sys_device * sysdev)

{

       int error;

       struct sysdev_class * cls = sysdev->cls;

 

       if (!cls)

              return -EINVAL;

 

       /* Make sure the kset is set */

       sysdev->kobj.kset = &cls->kset;

 

       /* But make sure we point to the right type for sysfs translation */

       sysdev->kobj.ktype = &ktype_sysdev;

       error = kobject_set_name(&sysdev->kobj, "%s%d",

                      kobject_name(&cls->kset.kobj), sysdev->id);

       if (error)

              return error;

 

       pr_debug("Registering sys device '%s'/n", kobject_name(&sysdev->kobj));

 

       /* Register the object */

       error = kobject_register(&sysdev->kobj);

 

       if (!error) {

              struct sysdev_driver * drv;

 

              down(&sysdev_drivers_lock);

              /* Generic notification is implicit, because it's that

               * code that should have called us.

               */

           //对于我们分析DMA来讲,更关心的是下面这段代码

              /* Notify global drivers */

              //调用所有全局的sysdev_drivers

list_for_each_entry(drv, &sysdev_drivers, entry) {

                     if (drv->add)

                            drv->add(sysdev);

              }

 

              /* Notify class auxillary drivers */

        //接着调用具体class下面的驱动

              list_for_each_entry(drv, &cls->drivers, entry) {

                     if (drv->add)

                            drv->add(sysdev);   //驱动的add函数.

              }

              up(&sysdev_drivers_lock);

       }

       return error;

}

我们可以看到s3c2410_sysdev的类就是s3c2410_sysclass, 所以这里找到的驱动就是前面我们注册进s3c2410_sysclassdma驱动, 因此这里的add函数就是s3c2410_dma_add.

Arch/arm/mach-s3c2410/dma.c:

static int s3c2410_dma_add(struct sys_device *sysdev)

{

       s3c2410_dma_init();   //DMA初始化

       s3c24xx_dma_order_set(&s3c2410_dma_order); 

       return s3c24xx_dma_init_map(&s3c2410_dma_sel);

}

真正的DMA方面的操作就从这个函数开始了. 我们一个个函数来看.

Arch/arm/plat-s3c24xx/dma.c:

int s3c2410_dma_init(void)

{

       return s3c24xx_dma_init(4, IRQ_DMA0, 0x40);

}

我们来看下参数, 第一个参数代表dma channel(参考2410 data sheet), 第二个参数是dma的中断号, 第三个参数是每个channel对应的寄存器基地址与前一个channel的寄存器的基地址的偏移, 即如果第一个channel的第一个寄存器的地址是0x4b000000则第二个channel的第一个寄存器的地址是0x4b000040,

 接着看

Arch/arm/plat-s3c24xx/dma.c:

int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq,

                         unsigned int stride)

{

       struct s3c2410_dma_chan *cp;   //每个channel都由个s3c2410_dma_chan表示

       int channel;

       int ret;

 

       printk("S3C24XX DMA Driver, (c) 2003-2004,2006 Simtec Electronics/n");

 

       dma_channels = channels;   //保存channel的数量

 

    //把所有channel的所有寄存器地址由实地址转换成虚拟地址.

    //我们驱动中使用的都是虚拟地址.

       dma_base = ioremap(S3C24XX_PA_DMA, stride * channels); 

       if (dma_base == NULL) {

              printk(KERN_ERR "dma failed to remap register block/n");

              return -ENOMEM;

       }

    //创建一个高速缓冲对象, 具体可参考linux设备驱动程序III的第8

       dma_kmem = kmem_cache_create("dma_desc",

                                 sizeof(struct s3c2410_dma_buf), 0,

                                 SLAB_HWCACHE_ALIGN,

                                 s3c2410_dma_cache_ctor, NULL);

 

       if (dma_kmem == NULL) {

              printk(KERN_ERR "dma failed to make kmem cache/n");

              ret = -ENOMEM;

              goto err;

       }

   

    //为每个channel初始化.

       for (channel = 0; channel < channels;  channel++) {

              cp = &s3c2410_chans[channel];   //全局变量保存每个channel的信息.

 

              memset(cp, 0, sizeof(struct s3c2410_dma_chan));

 

              /* dma channel irqs are in order.. */

              cp->number = channel;   //channel

              cp->irq    = channel + irq;  //channel的中断号

              cp->regs   = dma_base + (channel * stride);   //channel的寄存器基地址

 

              /* point current stats somewhere */

              cp->stats  = &cp->stats_store;    //channel状态

              cp->stats_store.timeout_shortest = LONG_MAX;

 

              /* basic channel configuration */

 

              cp->load_timeout = 1<<18;

 

              printk("DMA channel %d at %p, irq %d/n",

                     cp->number, cp->regs, cp->irq);

       }

 

       return 0;

 

 err:

       kmem_cache_destroy(dma_kmem);

       iounmap(dma_base);

       dma_base = NULL;

       return ret;

}

这个函数就是对每个channel进行初始化, 并把每个channel的相关信息保存起来供以后的操作使用.

接着看下一个函数:

Arch/arm/plat-s3c24xx/dma.c:

int __init s3c24xx_dma_order_set(struct s3c24xx_dma_order *ord)

{

       struct s3c24xx_dma_order *nord = dma_order;   //dma_order是个全局指针

 

   //分配内存

       if (nord == NULL)

              nord = kmalloc(sizeof(struct s3c24xx_dma_order), GFP_KERNEL);

 

       if (nord == NULL) {

              printk(KERN_ERR "no memory to store dma channel order/n");

              return -ENOMEM;

       }

 

    //保存ord信息

       dma_order = nord;

       memcpy(nord, ord, sizeof(struct s3c24xx_dma_order));

       return 0;

}

这个函数主要是分配了一个内存用来保存order信息, 我们来看传进来的参数

Arch/arm/mach-s3c2410/dma.c:

static struct s3c24xx_dma_order __initdata s3c2410_dma_order = {

       .channels = {

              [DMACH_SDI]     = {

                     .list  = {

                            [0]   = 3 | DMA_CH_VALID,

                            [1]   = 2 | DMA_CH_VALID,

                            [2]   = 0 | DMA_CH_VALID,

                     },

              },

              [DMACH_I2S_IN]       = {

                     .list  = {

                            [0]   = 1 | DMA_CH_VALID,

                            [1]   = 2 | DMA_CH_VALID,

                     },

              },

       },

};

注意这个变量用__initdata定义了, 因此它只在初始化的时候存在, 所以我们有必要分配一块内存来保存它的信息. 这也是上面那个函数的作用, 那这个s3c2410_dma_order到底有什么作用呢, 我们看这个结构的解释

Include/asm-arm/plat-s3c24xx/dma.h::

/* struct s3c24xx_dma_order

 *

 * information provided by either the core or the board to give the

 * dma system a hint on how to allocate channels

*/

//注释说的很明确了吧, 就是用来指导系统如何分配dma channel,因为2410下的4channel的源跟目的并不是所有的外设都可以使用的.

struct s3c24xx_dma_order {

       struct s3c24xx_dma_order_ch      channels[DMACH_MAX];

};

看完了s3c24xx_dma_order_set, 我们接着看s3c24xx_dma_init_map

Arch/arm/plat-s3c24xx/dma.c:

int __init s3c24xx_dma_init_map(struct s3c24xx_dma_selection *sel)

{

       struct s3c24xx_dma_map *nmap;

       size_t map_sz = sizeof(*nmap) * sel->map_size;

       int ptr;

 

       nmap = kmalloc(map_sz, GFP_KERNEL);  //分配内存

       if (nmap == NULL)

              return -ENOMEM;

 

       //保存信息

       memcpy(nmap, sel->map, map_sz);

       memcpy(&dma_sel, sel, sizeof(*sel));

 

       dma_sel.map = nmap;

 

    //检查是否正确

       for (ptr = 0; ptr < sel->map_size; ptr++)

              s3c24xx_dma_check_entry(nmap+ptr, ptr);

 

       return 0;

}

这个函数和s3c24xx_dma_order_set的作用一样, 也是先分配一块内存然后在保存信息. 我们来看参数:

Arch/arm/mach-s3c2410/dma.c:

static struct s3c24xx_dma_selection __initdata s3c2410_dma_sel = {

       .select            = s3c2410_dma_select,

       .dcon_mask    = 7 << 24,

       .map              = s3c2410_dma_mappings,

       .map_size       = ARRAY_SIZE(s3c2410_dma_mappings),

};

呵呵也是用__initdata定义的, 难怪要重新分配内存并保存起来, 那这些是什么信息呢, 我们看到主要就是个map, 我们接着来看这个map中到底存了些什么东西.

Arch/arm/mach-s3c2410/dma.c:

static struct s3c24xx_dma_map __initdata s3c2410_dma_mappings[] = {

       [DMACH_XD0] = {

              .name            = "xdreq0",

              .channels[0]   = S3C2410_DCON_CH0_XDREQ0 | DMA_CH_VALID,

       },

       [DMACH_XD1] = {

              .name            = "xdreq1",

              .channels[1]   = S3C2410_DCON_CH1_XDREQ1 | DMA_CH_VALID,

       },

       [DMACH_SDI] = {

              .name            = "sdi",

              .channels[0]   = S3C2410_DCON_CH0_SDI | DMA_CH_VALID,

              .channels[2]   = S3C2410_DCON_CH2_SDI | DMA_CH_VALID,

              .channels[3]   = S3C2410_DCON_CH3_SDI | DMA_CH_VALID,

              .hw_addr.to    = S3C2410_PA_IIS + S3C2410_IISFIFO,

              .hw_addr.from       = S3C2410_PA_IIS + S3C2410_IISFIFO,

       },

       [DMACH_SPI0] = {

              .name            = "spi0",

              .channels[1]   = S3C2410_DCON_CH1_SPI | DMA_CH_VALID,

              .hw_addr.to    = S3C2410_PA_SPI + S3C2410_SPTDAT,

              .hw_addr.from       = S3C2410_PA_SPI + S3C2410_SPRDAT,

       },

       [DMACH_SPI1] = {

              .name            = "spi1",

              .channels[3]   = S3C2410_DCON_CH3_SPI | DMA_CH_VALID,

              .hw_addr.to    = S3C2410_PA_SPI + 0x20 + S3C2410_SPTDAT,

              .hw_addr.from       = S3C2410_PA_SPI + 0x20 + S3C2410_SPRDAT,

       },

       [DMACH_UART0] = {

              .name            = "uart0",

              .channels[0]   = S3C2410_DCON_CH0_UART0 | DMA_CH_VALID,

              .hw_addr.to    = S3C2410_PA_UART0 + S3C2410_UTXH,

              .hw_addr.from       = S3C2410_PA_UART0 + S3C2410_URXH,

       },

       [DMACH_UART1] = {

              .name            = "uart1",

              .channels[1]   = S3C2410_DCON_CH1_UART1 | DMA_CH_VALID,

              .hw_addr.to    = S3C2410_PA_UART1 + S3C2410_UTXH,

              .hw_addr.from       = S3C2410_PA_UART1 + S3C2410_URXH,

       },

         [DMACH_UART2] = {

              .name            = "uart2",

              .channels[3]   = S3C2410_DCON_CH3_UART2 | DMA_CH_VALID,

              .hw_addr.to    = S3C2410_PA_UART2 + S3C2410_UTXH,

              .hw_addr.from       = S3C2410_PA_UART2 + S3C2410_URXH,

       },

       [DMACH_TIMER] = {

              .name            = "timer",

              .channels[0]   = S3C2410_DCON_CH0_TIMER | DMA_CH_VALID,

              .channels[2]   = S3C2410_DCON_CH2_TIMER | DMA_CH_VALID,

              .channels[3]   = S3C2410_DCON_CH3_TIMER | DMA_CH_VALID,

       },

       [DMACH_I2S_IN] = {

              .name            = "i2s-sdi",

              .channels[1]   = S3C2410_DCON_CH1_I2SSDI | DMA_CH_VALID,

              .channels[2]   = S3C2410_DCON_CH2_I2SSDI | DMA_CH_VALID,

              .hw_addr.from       = S3C2410_PA_IIS + S3C2410_IISFIFO,

       },

       [DMACH_I2S_OUT] = {

              .name            = "i2s-sdo",

              .channels[2]   = S3C2410_DCON_CH2_I2SSDO | DMA_CH_VALID,

              .hw_addr.to    = S3C2410_PA_IIS + S3C2410_IISFIFO,

       },

       [DMACH_USB_EP1] = {

              .name            = "usb-ep1",

              .channels[0]   = S3C2410_DCON_CH0_USBEP1 | DMA_CH_VALID,

       },

       [DMACH_USB_EP2] = {

              .name            = "usb-ep2",

              .channels[1]   = S3C2410_DCON_CH1_USBEP2 | DMA_CH_VALID,

       },

       [DMACH_USB_EP3] = {

              .name            = "usb-ep3",

              .channels[2]   = S3C2410_DCON_CH2_USBEP3 | DMA_CH_VALID,

       },

       [DMACH_USB_EP4] = {

              .name            = "usb-ep4",

              .channels[3]   =S3C2410_DCON_CH3_USBEP4 | DMA_CH_VALID,

       },

};

一大堆东西, 我们还是来看这个结构的注释吧

Include/asm-arm/plat-s3c24xx/dma.h:

/* struct s3c24xx_dma_map

 *

 * this holds the mapping information for the channel selected

 * to be connected to the specified device

*/

//保存了一些被选择使用的channel和规定的设备间的一些map信息.具体到了使用的时候就会明白了

struct s3c24xx_dma_map {

       const char             *name;

       struct s3c24xx_dma_addr  hw_addr;

 

       unsigned long        channels[S3C2410_DMA_CHANNELS];

};

Ok, 这样就把s3c2410_dma_add函数分析完了, 到这里把每个channel的各种信息包括各channel的寄存器地址, 中断号, 跟设备的关系等信息都保存好了,  但是虽然每个channel都初始化好了, 但是还记得吗, 到目前为址, 我们仅仅是向系统注册了一个虚拟的设备, 真真的DMA设备还没注册进系统呢,  因此接下来就是要注册DMA设备了, 在哪呢?

Arch/arm/plat-s3c24xx/dma.c:

static int __init s3c24xx_dma_sysdev_register(void)

{

       struct s3c2410_dma_chan *cp = s3c2410_chans;  //这个全局变量里已经保存了channel信息哦

       int channel, ret;

 

    //对每个channel操作

       for (channel = 0; channel < dma_channels; cp++, channel++) {

              cp->dev.cls = &dma_sysclass;  //指定classdma_sysclass

              cp->dev.id  = channel;  //channel

              ret = sysdev_register(&cp->dev);  //注册设备

 

              if (ret) {

                     printk(KERN_ERR "error registering dev for dma %d/n",

                            channel);

                    return ret;

              }

       }

 

       return 0;

}

 

late_initcall(s3c24xx_dma_sysdev_register);  //注意这行, 它会在初始化完毕后被调用,

这个函数把所有的channel注册到dma_sysclass类下, 我们前面看到注册设备时会调用该类的add函数, 还好这里的dma_sysclass类没有add函数, 我们可以轻松下了.

Ok, 到这里DMA设备算是全部准备好了, 可以随时被请求使用了, 到这里我们总结一下:

Arch/arm/mach-s3c2410/dma.c 下的代码主要是跟具体板子相关的代码, 而真正核心的代码都在

Arch/arm/plat-s3c24xx/dma.c, 因此如果我们有块跟2410类似的板子的话, 主要实现的就是

Arch/arm/mach-s3c2410/dma.c 这个文件了,

同时我们也不难推测, 使用DMA的函数应该都在Arch/arm/plat-s3c24xx/dma.c. 没错, 说的更具体些就是这个文件下被EXPORT_SYMBOL出来的函数都是提供给外部使用的, 也就是其他部分使用DMA的接口. 知道了这些我们接着来分析这些被EXPORT_SYMBOL的函数吧.

http://blog.csdn.net/aaronychen/article/details/4056156

阅读(1350) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~