zoukankan      html  css  js  c++  java
  • 2410下DMA驱动源码分析

    首先我们由 kconfig 和 makefile 来获取 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 还有 suspend 和 resume 的操作 , 这些都是电源管理方面的东西 , 我们这里就不分析了 .

    接着看在 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_sysclass 的 dma 驱动 , 因此这里的 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 下的 4 个 channel 的源跟目的并不是所有的外设都可以使用的 .

    struct s3c24xx_dma_order {

    structs3c24xx_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; // 指定 class 为 dma_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 的函数吧 .  

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

    /* s3c2410_dma_getposition

    *

    * returns the current transfer points for the dma source and destination

    */

    int s3c2410_dma_getposition(dmach_t channel, dma_addr_t *src, dma_addr_t *dst)

    {

    // 获取保存该 channel 信息的对象 , 初始化的时候讲过
    struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);



    if (chan == NULL)

    return -EINVAL;



    if (src != NULL) // 获取源地址

    *src = dma_rdreg(chan, S3C2410_DMA_DCSRC);



    if (dst != NULL) // 获取目的地址

    *dst = dma_rdreg(chan, S3C2410_DMA_DCDST);



    return 0;

    }



    EXPORT_SYMBOL(s3c2410_dma_getposition);

    这个函数获取某个 channel 当前正在传输的源地址和目的地址 . 主要就是通过读该 channel 的源和目的寄存器获得的 . S3C2410_DMA_DCSRC, S3C2410_DMA_DCDST 就是源和目的的偏移地址 . 可参考 2410 的 datasheet.  dma_rdreg 就是读寄存器 .

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

    #define dma_rdreg(chan, reg) readl((chan)->regs + (reg))

    接着看下一个 export 的函数

    /* s3c2410_dma_devconfig

    *

    * configure the dma source/destination hardware type and address

    *

    * source: S3C2410_DMASRC_HW: source is hardware

    * S3C2410_DMASRC_MEM: source is memory

    *

    * hwcfg: the value for xxxSTCn register,

    * bit 0: 0=increment pointer, 1=leave pointer

    * bit 1: 0=source is AHB, 1=source is APB

    *

    * devaddr: physical address of the source

    */

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

    int s3c2410_dma_devconfig(int channel,

    enum s3c2410_dmasrc source,

    int hwcfg,

    unsigned
    long devaddr)

    {

    // 获取保存该 channel 信息的对象 , 初始化的时候讲过

    struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);



    if (chan == NULL)

    return -EINVAL;



    pr_debug(
    "%s: source=%d, hwcfg=%08x, devaddr=%08lx\n",

    __FUNCTION__, (
    int)source, hwcfg, devaddr);



    chan
    ->source = source; // 保存 DMA 源

    chan
    ->dev_addr = devaddr; // 保存源地址



    // 根据不同的 DMA 源来初始化 DMA channel

    switch (source) {

    case S3C2410_DMASRC_HW:

    /* source is hardware */

    pr_debug(
    "%s: hw source, devaddr=%08lx, hwcfg=%d\n",

    __FUNCTION__, devaddr, hwcfg);

    dma_wrreg(chan, S3C2410_DMA_DISRCC, hwcfg
    & 3);

    dma_wrreg(chan, S3C2410_DMA_DISRC, devaddr);
    // 源地址

    dma_wrreg(chan, S3C2410_DMA_DIDSTC, (
    0<<1) | (0<<0));



    chan
    ->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST);

    return 0;



    case S3C2410_DMASRC_MEM:

    /* source is memory */

    pr_debug(
    "%s: mem source, devaddr=%08lx, hwcfg=%d\n",

    __FUNCTION__, devaddr, hwcfg);

    dma_wrreg(chan, S3C2410_DMA_DISRCC, (
    0<<1) | (0<<0));

    dma_wrreg(chan, S3C2410_DMA_DIDST, devaddr);

    dma_wrreg(chan, S3C2410_DMA_DIDSTC, hwcfg
    & 3);



    chan
    ->addr_reg = dma_regaddr(chan, S3C2410_DMA_DISRC);

    return 0;

    }



    printk(KERN_ERR
    "dma%d: invalid source type (%d)\n", channel, source);

    return -EINVAL;

    }



    EXPORT_SYMBOL(s3c2410_dma_devconfig);

    这个函数用来配置某个 channel 的源的类型及源地址 , 然后为某种源设置好地址增长方式 , 具体寄存器含义参考 2410 datasheet, 2410 下 DMA 的各种操作模式可参考我的另一篇文章 .

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

    int s3c2410_dma_set_buffdone_fn(dmach_t channel, s3c2410_dma_cbfn_t rtn)

    {

    struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);



    if (chan == NULL)

    return -EINVAL;



    pr_debug(
    "%s: chan=%p, callback rtn=%p\n", __FUNCTION__, chan, rtn);



    chan
    ->callback_fn = rtn; // 设置回调函数



    return 0;

    }



    EXPORT_SYMBOL(s3c2410_dma_set_buffdone_fn);

    该函数主要为某个 channel 设置一个 done 的回调函数 . 该回调函数会在传输完成后被调用 .

     

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

    /* do we need to protect the settings of the fields from

    * irq?

    */

    int s3c2410_dma_set_opfn(dmach_t channel, s3c2410_dma_opfn_t rtn)

    {

    struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);



    if (chan == NULL)

    return -EINVAL;



    pr_debug(
    "%s: chan=%p, op rtn=%p\n", __FUNCTION__, chan, rtn);



    chan
    ->op_fn = rtn;



    return 0;

    }



    EXPORT_SYMBOL(s3c2410_dma_set_opfn);

    该函数主要为某个 channel 设置一个操作的回调函数 . 该回调函数会在操作该 channel 时被调用 ( 有哪些操作会在 s3c2410_dma_ctrl 里看到 )

     

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

    int s3c2410_dma_setflags(dmach_t channel, unsigned int flags)

    {

    struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);



    if (chan == NULL)

    return -EINVAL;



    pr_debug(
    "%s: chan=%p, flags=%08x\n", __FUNCTION__, chan, flags);



    chan
    ->flags = flags; // 设置标记



    return 0;

    }



    EXPORT_SYMBOL(s3c2410_dma_setflags);

    该函数主要为某个 channel 设置一个标记 , 标记有 :

    Include\asm-arm\arch-s3c2410\dma.h:

    /* flags */



    #define S3C2410_DMAF_SLOW (1<<0) /* slow, so don't worry about

    * waiting for reloads */

    #define S3C2410_DMAF_AUTOSTART (1<<1) /* auto-start if buffer queued */

    我们会在后面看到 flag 的使用

     

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

    /* DMA configuration for each channel

    *

    * DISRCC -> source of the DMA (AHB,APB)

    * DISRC -> source address of the DMA

    * DIDSTC -> destination of the DMA (AHB,APD)

    * DIDST -> destination address of the DMA

    */



    /* s3c2410_dma_config

    *

    * xfersize: size of unit in bytes (1,2,4)

    * dcon: base value of the DCONx register

    */



    int s3c2410_dma_config(dmach_t channel,

    int xferunit,

    int dcon)

    {

    struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);



    pr_debug(
    "%s: chan=%d, xfer_unit=%d, dcon=%08x\n",

    __FUNCTION__, channel, xferunit, dcon);



    if (chan == NULL)

    return -EINVAL;



    pr_debug(
    "%s: Initial dcon is %08x\n", __FUNCTION__, dcon);



    dcon
    |= chan->dcon & dma_sel.dcon_mask;



    pr_debug(
    "%s: New dcon is %08x\n", __FUNCTION__, dcon);



    // 设置每个传输单元的大小

    switch (xferunit) {

    case 1:

    dcon
    |= S3C2410_DCON_BYTE;

    break;



    case 2:

    dcon
    |= S3C2410_DCON_HALFWORD;

    break;



    case 4:

    dcon
    |= S3C2410_DCON_WORD;

    break;



    default:

    pr_debug(
    "%s: bad transfer size %d\n", __FUNCTION__, xferunit);

    return -EINVAL;

    }



    dcon
    |= S3C2410_DCON_HWTRIG; // 硬件请求模式

    dcon
    |= S3C2410_DCON_INTREQ; // 打开中断



    pr_debug(
    "%s: dcon now %08x\n", __FUNCTION__, dcon);



    // 保存配置到全局变量中

    chan
    ->dcon = dcon;

    chan
    ->xfer_unit = xferunit;



    return 0;

    }



    EXPORT_SYMBOL(s3c2410_dma_config);

    该函数主要用来配置某个 channel 的请求模式 , 传输单元大小等 . 从中可以看出目前只支持硬件请求模式

     

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

    int

    s3c2410_dma_ctrl(dmach_t channel,
    enum s3c2410_chan_op op)

    {

    struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);



    if (chan == NULL)

    return -EINVAL;



    switch (op) {

    case S3C2410_DMAOP_START:

    return s3c2410_dma_start(chan); // 开始一个 DMA 传输



    case S3C2410_DMAOP_STOP:

    return s3c2410_dma_dostop(chan); // 停止一个 DMA 传输



    case S3C2410_DMAOP_PAUSE:

    case S3C2410_DMAOP_RESUME:

    return -ENOENT;



    case S3C2410_DMAOP_FLUSH:

    return s3c2410_dma_flush(chan); //



    case S3C2410_DMAOP_STARTED: // 指示传输开始

    return s3c2410_dma_started(chan);



    case S3C2410_DMAOP_TIMEOUT:

    return 0;



    }



    return -ENOENT; /* unknown, don't bother */

    }



    EXPORT_SYMBOL(s3c2410_dma_ctrl);

    OK, 这个函数主要就是用来启用 , 停止 DMA 操作了 ,  比较重要的一个函数 . 等分析完了 export 的接口后 , 我们在来逐个分析每个 DMA 操作 .   

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

    /* s3c2410_dma_free

    *

    * release the given channel back to the system, will stop and flush

    * any outstanding transfers, and ensure the channel is ready for the

    * next claimant.

    *

    * Note, although a warning is currently printed if the freeing client

    * info is not the same as the registrant's client info, the free is still

    * allowed to go through.

    */



    int s3c2410_dma_free(dmach_t channel, struct s3c2410_dma_client *client)

    {

    struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);

    unsigned
    long flags;



    if (chan == NULL)

    return -EINVAL;



    local_irq_save(flags);



    if (chan->client != client) {

    printk(KERN_WARNING
    "dma%d: possible free from different client (channel %p, passed %p)\n",

    channel, chan
    ->client, client);

    }



    /* sort out stopping and freeing the channel */



    if (chan->state != S3C2410_DMA_IDLE) { // 该 channel 正在使用中

    pr_debug(
    "%s: need to stop dma channel %p\n",

    __FUNCTION__, chan);



    /* possibly flush the channel */

    s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STOP);
    // 停止该 channel

    }



    //reset 该 channel 的相关信息

    chan
    ->client = NULL;

    chan
    ->in_use = 0;



    if (chan->irq_claimed)

    free_irq(chan
    ->irq, (void *)chan); // 释放该中断



    chan
    ->irq_claimed = 0;



    if (!(channel & DMACH_LOW_LEVEL))

    dma_chan_map[channel]
    = NULL;



    local_irq_restore(flags);



    return 0;

    }



    EXPORT_SYMBOL(s3c2410_dma_free);

    根据注释我们很清楚了 ,  该函数主要就是释放一个 channel, 使其处于 ready 状态 ,

     

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

    /* s3c2410_request_dma

    *

    * get control of an dma channel

    */



    int s3c2410_dma_request(unsigned int channel,

    struct s3c2410_dma_client *client,

    void *dev)

    {

    struct s3c2410_dma_chan *chan;

    unsigned
    long flags;

    int err;



    pr_debug(
    "dma%d: s3c2410_request_dma: client=%s, dev=%p\n",

    channel, client
    ->name, dev);



    local_irq_save(flags);



    // 获取空闲的 channel

    chan
    = s3c2410_dma_map_channel(channel);

    if (chan == NULL) { // 无空闲 channel 则返回失败

    local_irq_restore(flags);

    return -EBUSY;

    }



    dbg_showchan(chan);



    // 保存使用该 channel 的用户等信息

    chan
    ->client = client;

    chan
    ->in_use = 1;



    if (!chan->irq_claimed) { // 该中断没注册

    pr_debug(
    "dma%d: %s : requesting irq %d\n",

    channel, __FUNCTION__, chan
    ->irq);



    chan
    ->irq_claimed = 1; // 标记注册

    local_irq_restore(flags);



    err
    = request_irq(chan->irq, s3c2410_dma_irq, IRQF_DISABLED,

    client
    ->name, (void *)chan); // 注册该中断



    local_irq_save(flags);



    if (err) { // 失败则 reset 该 channel

    chan
    ->in_use = 0;

    chan
    ->irq_claimed = 0;

    local_irq_restore(flags);



    printk(KERN_ERR
    "%s: cannot get IRQ %d for DMA %d\n",

    client
    ->name, chan->irq, chan->number);

    return err;

    }



    chan
    ->irq_enabled = 1;

    }



    local_irq_restore(flags);



    /* need to setup */



    pr_debug(
    "%s: channel initialised, %p\n", __FUNCTION__, chan);



    return 0;

    }



    EXPORT_SYMBOL(s3c2410_dma_request);

      该函数主要就是为请求的用户找到一个空闲的 channel, 并把它分配给该用户 , 同时打开中断 , 保存相关信息 .

     

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

    /* s3c2410_dma_enqueue

    *

    * queue an given buffer for dma transfer.

    *

    * id the device driver's id information for this buffer

    * data the physical address of the buffer data

    * size the size of the buffer in bytes

    *

    * If the channel is not running, then the flag S3C2410_DMAF_AUTOSTART

    * is checked, and if set, the channel is started. If this flag isn't set,

    * then an error will be returned.

    *

    * It is possible to queue more than one DMA buffer onto a channel at

    * once, and the code will deal with the re-loading of the next buffer

    * when necessary.

    */



    int s3c2410_dma_enqueue(unsigned int channel, void *id,

    dma_addr_t data,
    int size)

    {

    struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);

    struct s3c2410_dma_buf *buf;

    unsigned
    long flags;



    if (chan == NULL)

    return -EINVAL;



    pr_debug(
    "%s: id=%p, data=%08x, size=%d\n",

    __FUNCTION__, id, (unsigned
    int)data, size);



    // 从高速缓冲中分配一块 buffer 用于 DMA 传输 , dma_kmem 是我们在初始化的时候就创建好的

    buf
    = kmem_cache_alloc(dma_kmem, GFP_ATOMIC);

    if (buf == NULL) {

    pr_debug(
    "%s: out of memory (%ld alloc)\n",

    __FUNCTION__, (
    long)sizeof(*buf));

    return -ENOMEM;

    }



    //pr_debug("%s: new buffer %p\n", __FUNCTION__, buf);

    //dbg_showchan(chan);



    // 初始化这块要被传输的 buf

    buf
    ->next = NULL;

    buf
    ->data = buf->ptr = data; // 指向要传输的 data

    buf
    ->size = size; // 传输大小

    buf
    ->id = id;

    buf
    ->magic = BUF_MAGIC;



    local_irq_save(flags);



    if (chan->curr == NULL) { // 当前 channel 没有在传输

    /* we've got nothing loaded... */

    pr_debug(
    "%s: buffer %p queued onto empty channel\n",

    __FUNCTION__, buf);



    chan
    ->curr = buf; // 直接挂在 curr 上

    chan
    ->end = buf;

    chan
    ->next = NULL;

    }
    else { // 当前 channel 正在传输

    pr_debug(
    "dma%d: %s: buffer %p queued onto non-empty channel\n",

    chan
    ->number, __FUNCTION__, buf);



    if (chan->end == NULL)

    pr_debug(
    "dma%d: %s: %p not empty, and chan->end==NULL?\n",

    chan
    ->number, __FUNCTION__, chan);



    // 把 buffer 挂到队列的最后面 , 并重设 end

    chan
    ->end->next = buf;

    chan
    ->end = buf;

    }



    /* if necessary, update the next buffer field */

    if (chan->next == NULL)

    chan
    ->next = buf;



    /* check to see if we can load a buffer */

    if (chan->state == S3C2410_DMA_RUNNING) { // 该 channel 正在运行

    if (chan->load_state == S3C2410_DMALOAD_1LOADED && 1) { // 已有 buf load 了

    if (s3c2410_dma_waitforload(chan, __LINE__) == 0) { // 等待 load

    printk(KERN_ERR
    "dma%d: loadbuffer:"

    "timeout loading buffer\n",

    chan
    ->number);

    dbg_showchan(chan);

    local_irq_restore(flags);

    return -EINVAL;

    }

    }



    while (s3c2410_dma_canload(chan) && chan->next != NULL) { // 检查能否 load

    s3c2410_dma_loadbuffer(chan, chan
    ->next); //load buffer

    }

    }
    else if (chan->state == S3C2410_DMA_IDLE) { // 该 channel 空闲着

    if (chan->flags & S3C2410_DMAF_AUTOSTART) { // 如果设了自动启动标记 , 则直接启动该次传输

    s3c2410_dma_ctrl(chan
    ->number, S3C2410_DMAOP_START); // 启动传输

    }

    }



    local_irq_restore(flags);

    return 0;

    }



    EXPORT_SYMBOL(s3c2410_dma_enqueue);

     

       该函数首先从先前创建的高速缓冲池中获取一个 buf, 并把要传输的 data 保存在该 buf 中 , 然后根据当前 channel 的运行状态来选择是 load 该 buf, 还是直接传输该 buf.

       Channel 在运行过程中会有很多的状态 , 所有状态如下 :

    Include\asm-arm\arch-s3c2410\dma.h:

    /* enum s3c2410_dma_loadst

    *

    * This represents the state of the DMA engine, wrt to the loaded / running

    * transfers. Since we don't have any way of knowing exactly the state of

    * the DMA transfers, we need to know the state to make decisions on wether

    * we can

    *

    * S3C2410_DMA_NONE

    *

    * There are no buffers loaded (the channel should be inactive)

    *

    * S3C2410_DMA_1LOADED

    *

    * There is one buffer loaded, however it has not been confirmed to be

    * loaded by the DMA engine. This may be because the channel is not

    * yet running, or the DMA driver decided that it was too costly to

    * sit and wait for it to happen.

    *

    * S3C2410_DMA_1RUNNING

    *

    * The buffer has been confirmed running, and not finisged

    *

    * S3C2410_DMA_1LOADED_1RUNNING

    *

    * There is a buffer waiting to be loaded by the DMA engine, and one

    * currently running.

    */



    enum s3c2410_dma_loadst {

    S3C2410_DMALOAD_NONE,

    S3C2410_DMALOAD_1LOADED,

    S3C2410_DMALOAD_1RUNNING,

    S3C2410_DMALOAD_1LOADED_1RUNNING,

    };

    各种装态注释的很明显了 , 我就不再罗索了 .

    Channel 运行时会有一个正在传输的 buf, 一个已经加载的 buf, 还有很多等待加载的 buf.

    我们来把这个函数中调用的函数也逐个分析下 :

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

    /* s3c2410_dma_waitforload

    *

    * wait for the DMA engine to load a buffer, and update the state accordingly

    */



    static int

    s3c2410_dma_waitforload(
    struct s3c2410_dma_chan *chan, int line)

    {

    int timeout = chan->load_timeout; // 初始化时 load_timeout 被设成了 1 << 18

    int took;



    // 该函数只在 S3C2410_DMALOAD_1LOADED 状态下被调用

    if (chan->load_state != S3C2410_DMALOAD_1LOADED) {

    printk(KERN_ERR
    "dma%d: s3c2410_dma_waitforload() called in loadstate %d from line %d\n", chan->number, chan->load_state, line);

    return 0;

    }



    if (chan->stats != NULL)

    chan
    ->stats->loads++; // 更新统计信息



    while (--timeout > 0) {

    // 获取还剩的传输量 , 左移 (32-20) 只是把 [21:20] 位移调 , 因为它仅和 0 比较 , 所以无需确切的数据

    if ((dma_rdreg(chan, S3C2410_DMA_DSTAT) << (32-20)) != 0) {

    took
    = chan->load_timeout - timeout; // 等待了这么长时间



    // 保存统计信息 , 该函数更新最长 , 最短超时时间 , 并更新总超时时间

    s3c2410_dma_stats_timeout(chan
    ->stats, took);



    switch (chan->load_state) {

    case S3C2410_DMALOAD_1LOADED:

    // 因为有数据在传输了 , 因此更新 channel 的状态 , 从这我们也能看到 , 一次只能有一个

    //buf 被 load

    chan
    ->load_state = S3C2410_DMALOAD_1RUNNING;

    break;



    default:

    printk(KERN_ERR
    "dma%d: unknown load_state in s3c2410_dma_waitforload() %d\n", chan->number, chan->load_state);

    }



    return 1;

    }

    }



    if (chan->stats != NULL) {

    chan
    ->stats->timeout_failed++;

    }



    return 0;

    }

    该函数很简单 , 它等待已经 load 的 buf 被 start 传输 , 然后更新相关统计信息 , 也正因为 load 的 buf 被开始传输了 , 因此该函数完后 , 应该会有一个新的 buf 被 load. 至于原先 load 的 buf 是如何被 start 的 , 我们以后在看 .

    接下来我们看 s3c2410_dma_canload 函数 :

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

    /* s3c2410_dma_canload

    *

    * work out if we can queue another buffer into the DMA engine

    */

    static int

    s3c2410_dma_canload(
    struct s3c2410_dma_chan *chan)

    {

    // 在这 2 个状态下是可以 load 的

    if (chan->load_state == S3C2410_DMALOAD_NONE ||

    chan
    ->load_state == S3C2410_DMALOAD_1RUNNING)

    return 1;



    return 0;

    }

    跑完 s3c2410_dma_waitforload 后如果正确 , 则状态应该是 S3C2410_DMALOAD_1RUNNING, 所以这里就是可以加载了 , 那当然要看加载函数了

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

    /* s3c2410_dma_loadbuffer

    *

    * load a buffer, and update the channel state

    */



    static inline int

    s3c2410_dma_loadbuffer(
    struct s3c2410_dma_chan *chan,

    struct s3c2410_dma_buf *buf)

    {

    unsigned
    long reload;



    pr_debug(
    "s3c2410_chan_loadbuffer: loading buff %p (0x%08lx,0x%06x)\n",

    buf, (unsigned
    long)buf->data, buf->size);



    if (buf == NULL) {

    dmawarn(
    "buffer is NULL\n");

    return -EINVAL;

    }



    /* check the state of the channel before we do anything */

    // 状态错误 , 只能有 1 个 loaded 的 buf

    if (chan->load_state == S3C2410_DMALOAD_1LOADED) {

    dmawarn(
    "load_state is S3C2410_DMALOAD_1LOADED\n");

    }



    // 状态错误 , 只能有 1 个 loaded 的 buf

    if (chan->load_state == S3C2410_DMALOAD_1LOADED_1RUNNING) {

    dmawarn(
    "state is S3C2410_DMALOAD_1LOADED_1RUNNING\n");

    }



    /* it would seem sensible if we are the last buffer to not bother

    * with the auto-reload bit, so that the DMA engine will not try

    * and load another transfer after this one has finished...

    */

    // 判断是否要自动加载后续的 buf, 如果有后续的 buf 则自动加载

    if (chan->load_state == S3C2410_DMALOAD_NONE) {

    pr_debug(
    "load_state is none, checking for noreload (next=%p)\n",

    buf
    ->next);

    reload
    = (buf->next == NULL) ? S3C2410_DCON_NORELOAD : 0;

    }
    else {

    //pr_debug("load_state is %d => autoreload\n", chan->load_state);

    reload
    = S3C2410_DCON_AUTORELOAD;

    }



    if ((buf->data & 0xf0000000) != 0x30000000) {

    dmawarn(
    "dmaload: buffer is %p\n", (void *)buf->data);

    }



    writel(buf
    ->data, chan->addr_reg); // 写地址寄存器



    // 不解释了 , 看寄存器说明吧

    dma_wrreg(chan, S3C2410_DMA_DCON,

    chan
    ->dcon | reload | (buf->size/chan->xfer_unit));



    chan
    ->next = buf->next; // 更新链表



    /* update the state of the channel */



    // 更新状态

    switch (chan->load_state) {

    case S3C2410_DMALOAD_NONE:

    chan
    ->load_state = S3C2410_DMALOAD_1LOADED;

    break;



    case S3C2410_DMALOAD_1RUNNING:

    chan
    ->load_state = S3C2410_DMALOAD_1LOADED_1RUNNING;

    break;



    default:

    dmawarn(
    "dmaload: unknown state %d in loadbuffer\n",

    chan
    ->load_state);

    break;

    }



    return 0;

    }

    该函数主要是把要传输的数据的地址先存入寄存器中 , 等当前的传输完成后会根据时候 auto reload 的情况来确定是否开始这次的传输 .

    OK, 到目前为止讲完了所有的 export 的函数 , 现在还剩下 dma 的操作函数和中断函数没讲了 , let’s go!

    我们先看中断函数 , 该函数在一次传输完成后被调用

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

    static irqreturn_t

    s3c2410_dma_irq(
    int irq, void *devpw)

    {

    struct s3c2410_dma_chan *chan = (struct s3c2410_dma_chan *)devpw;

    struct s3c2410_dma_buf *buf;



    buf
    = chan->curr; // 当前传输完毕的 buf



    dbg_showchan(chan);



    /* modify the channel state */

    // 修改当前状态

    switch (chan->load_state) {

    case S3C2410_DMALOAD_1RUNNING:

    /* TODO - if we are running only one buffer, we probably

    * want to reload here, and then worry about the buffer

    * callback
    */



    chan
    ->load_state = S3C2410_DMALOAD_NONE;

    break;



    case S3C2410_DMALOAD_1LOADED:

    /* iirc, we should go back to NONE loaded here, we

    * had a buffer, and it was never verified as being

    * loaded.

    */



    chan
    ->load_state = S3C2410_DMALOAD_NONE;

    break;



    case S3C2410_DMALOAD_1LOADED_1RUNNING:

    /* we'll worry about checking to see if another buffer is

    * ready after we've called back the owner. This should

    * ensure we do not wait around too long for the DMA

    * engine to start the next transfer

    */



    chan
    ->load_state = S3C2410_DMALOAD_1LOADED;

    break;



    case S3C2410_DMALOAD_NONE:

    printk(KERN_ERR
    "dma%d: IRQ with no loaded buffer?\n",

    chan
    ->number);

    break;



    default:

    printk(KERN_ERR
    "dma%d: IRQ in invalid load_state %d\n",

    chan
    ->number, chan->load_state);

    break;

    }



    if (buf != NULL) {

    /* update the chain to make sure that if we load any more

    * buffers when we call the callback function, things should

    * work properly
    */



    chan
    ->curr = buf->next; // 更新传输的 buf

    buf
    ->next = NULL;



    if (buf->magic != BUF_MAGIC) {

    printk(KERN_ERR
    "dma%d: %s: buf %p incorrect magic\n",

    chan
    ->number, __FUNCTION__, buf);

    return IRQ_HANDLED;

    }



    s3c2410_dma_buffdone(chan, buf, S3C2410_RES_OK);
    //buf 传输完成后的操作



    /* free resouces */

    s3c2410_dma_freebuf(buf);
    // 释放 buf, 我们看到传输前有申请 buf

    }
    else {

    }



    /* only reload if the channel is still running... our buffer done

    * routine may have altered the state by requesting the dma channel

    * to stop or shutdown...
    */



    /* todo: check that when the channel is shut-down from inside this

    * function, we cope with unsetting reload, etc
    */

    // 还有要传输的 buf, 则继续传输

    if (chan->next != NULL && chan->state != S3C2410_DMA_IDLE) {

    unsigned
    long flags;



    switch (chan->load_state) {

    case S3C2410_DMALOAD_1RUNNING:

    /* don't need to do anything for this state */

    break;



    case S3C2410_DMALOAD_NONE:

    /* can load buffer immediately */

    break;



    case S3C2410_DMALOAD_1LOADED:

    if (s3c2410_dma_waitforload(chan, __LINE__) == 0) { // 等待被传输

    /* flag error? */

    printk(KERN_ERR
    "dma%d: timeout waiting for load (%s)\n",

    chan
    ->number, __FUNCTION__);

    return IRQ_HANDLED;

    }



    break;



    case S3C2410_DMALOAD_1LOADED_1RUNNING:

    goto no_load;



    default:

    printk(KERN_ERR
    "dma%d: unknown load_state in irq, %d\n",

    chan
    ->number, chan->load_state);

    return IRQ_HANDLED;

    }



    local_irq_save(flags);

    s3c2410_dma_loadbuffer(chan, chan
    ->next); // 加载下一个 buf

    local_irq_restore(flags);

    }
    else { // 所有的传输完成

    s3c2410_dma_lastxfer(chan);
    // 完成处理工作



    /* see if we can stop this channel.. */

    if (chan->load_state == S3C2410_DMALOAD_NONE) {

    pr_debug(
    "dma%d: end of transfer, stopping channel (%ld)\n",

    chan
    ->number, jiffies);

    s3c2410_dma_ctrl(chan
    ->number | DMACH_LOW_LEVEL,

    S3C2410_DMAOP_STOP);
    // 停止 dma 传输

    }

    }



    no_load:

    return IRQ_HANDLED;

    }

    我们看到当传输队列中还有 buf 要传输时 , 没有看到 start 的操作 , 这是为什么呢 ? 因为在 load 的时候我们分析过 , 如果后续还有 buf 要传输 , 则自动加载运行 ,  所以这里没有必要手工 start.

    s3c2410_dma_buffdone() 函数仅仅是调用前面注册的回调函数 , 这里不列出来了 .

    s3c2410_dma_freebuf() 也很简单 , 就是把 buf 归还到缓冲池去 .

    我们看下 s3c2410_dma_lastxfer

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

    /* s3c2410_dma_lastxfer

    *

    * called when the system is out of buffers, to ensure that the channel

    * is prepared for shutdown.

    */



    static inline void

    s3c2410_dma_lastxfer(
    struct s3c2410_dma_chan *chan)

    {

    #if 0

    pr_debug(
    "dma%d: s3c2410_dma_lastxfer: load_state %d\n",

    chan
    ->number, chan->load_state);

    #endif



    switch (chan->load_state) {

    case S3C2410_DMALOAD_NONE:

    break;



    case S3C2410_DMALOAD_1LOADED:

    if (s3c2410_dma_waitforload(chan, __LINE__) == 0) { // 等待加载的 buf 被执行

    /* flag error? */

    printk(KERN_ERR
    "dma%d: timeout waiting for load (%s)\n",

    chan
    ->number, __FUNCTION__);

    return;

    }

    break;



    case S3C2410_DMALOAD_1LOADED_1RUNNING:

    /* I belive in this case we do not have anything to do

    * until the next buffer comes along, and we turn off the

    * reload
    */

    return;



    default:

    pr_debug(
    "dma%d: lastxfer: unhandled load_state %d with no next\n",

    chan
    ->number, chan->load_state);

    return;



    }



    /* hopefully this'll shut the damned thing up after the transfer... */

    // 清楚自动加载标记 , 因为无后续要传输的 buf, 所以要清这个标记

    dma_wrreg(chan, S3C2410_DMA_DCON, chan
    ->dcon | S3C2410_DCON_NORELOAD);

    }

    很简单的一个函数 , 这里不多说了 .

    好了 , 到这个该着中分析操作函数了 .

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

    /* s3c2410_dma_start

    *

    * start a dma channel going

    */



    static int s3c2410_dma_start(struct s3c2410_dma_chan *chan)

    {

    unsigned
    long tmp;

    unsigned
    long flags;



    pr_debug(
    "s3c2410_start_dma: channel=%d\n", chan->number);



    local_irq_save(flags);



    if (chan->state == S3C2410_DMA_RUNNING) { // 已经有 run 的了

    pr_debug(
    "s3c2410_start_dma: already running (%d)\n", chan->state);

    local_irq_restore(flags);

    return 0;

    }



    chan
    ->state = S3C2410_DMA_RUNNING; // 更新状态



    /* check wether there is anything to load, and if not, see

    * if we can find anything to load

    */



    if (chan->load_state == S3C2410_DMALOAD_NONE) { // 没有加载过 buf

    if (chan->next == NULL) { // 没有 buf 要传送的

    printk(KERN_ERR
    "dma%d: channel has nothing loaded\n",

    chan
    ->number);

    chan
    ->state = S3C2410_DMA_IDLE;

    local_irq_restore(flags);

    return -EINVAL;

    }



    s3c2410_dma_loadbuffer(chan, chan
    ->next); // 加载 buf, 加载状态也会相应更新

    }



    dbg_showchan(chan);



    /* enable the channel */



    if (!chan->irq_enabled) {

    enable_irq(chan
    ->irq); // 使能中断

    chan
    ->irq_enabled = 1;

    }



    /* start the channel going */

    // 启动 DMA 传输 ,

    tmp
    = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);

    tmp
    &= ~S3C2410_DMASKTRIG_STOP;

    tmp
    |= S3C2410_DMASKTRIG_ON;

    dma_wrreg(chan, S3C2410_DMA_DMASKTRIG, tmp);



    pr_debug(
    "dma%d: %08lx to DMASKTRIG\n", chan->number, tmp);



    #if 0

    /* the dma buffer loads should take care of clearing the AUTO

    * reloading feature
    */

    tmp
    = dma_rdreg(chan, S3C2410_DMA_DCON);

    tmp
    &= ~S3C2410_DCON_NORELOAD;

    dma_wrreg(chan, S3C2410_DMA_DCON, tmp);

    #endif



    s3c2410_dma_call_op(chan, S3C2410_DMAOP_START);
    // 调用注册的 op 回调函数



    dbg_showchan(chan);



    /* if we've only loaded one buffer onto the channel, then chec

    * to see if we have another, and if so, try and load it so when

    * the first buffer is finished, the new one will be loaded onto

    * the channel
    */

    // 由于当前 load 的已经在运行了 , 因此如果还有要传输的 buf 则 load 进来

    if (chan->next != NULL) {

    if (chan->load_state == S3C2410_DMALOAD_1LOADED) {

    // 等待该 buf 被运行 , 别忘了我们设了自动加载运行 .

    if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {

    pr_debug(
    "%s: buff not yet loaded, no more todo\n",

    __FUNCTION__);

    }
    else {

    chan
    ->load_state = S3C2410_DMALOAD_1RUNNING;

    s3c2410_dma_loadbuffer(chan, chan
    ->next); // 加载 buf

    }



    }
    else if (chan->load_state == S3C2410_DMALOAD_1RUNNING) {

    s3c2410_dma_loadbuffer(chan, chan
    ->next); // 加载 buf

    }

    }





    local_irq_restore(flags);



    return 0;

    }

    整个启动流程就这样完了 .

     

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

    static int s3c2410_dma_dostop(struct s3c2410_dma_chan *chan)

    {

    unsigned
    long flags;

    unsigned
    long tmp;



    pr_debug(
    "%s:\n", __FUNCTION__);



    dbg_showchan(chan);



    local_irq_save(flags);



    s3c2410_dma_call_op(chan, S3C2410_DMAOP_STOP);
    // 通知用户该操作



    // 停止 DMA 传输

    tmp
    = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);

    tmp
    |= S3C2410_DMASKTRIG_STOP;

    //tmp &= ~S3C2410_DMASKTRIG_ON;

    dma_wrreg(chan, S3C2410_DMA_DMASKTRIG, tmp);



    #if 0

    /* should also clear interrupts, according to WinCE BSP */

    tmp
    = dma_rdreg(chan, S3C2410_DMA_DCON);

    tmp
    |= S3C2410_DCON_NORELOAD;

    dma_wrreg(chan, S3C2410_DMA_DCON, tmp);

    #endif



    // 更新状态

    /* should stop do this, or should we wait for flush? */

    chan
    ->state = S3C2410_DMA_IDLE;

    chan
    ->load_state = S3C2410_DMALOAD_NONE;



    local_irq_restore(flags);



    return 0;

    }

    该函数比较简单 , 接着看

     

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

    /* s3c2410_dma_flush

    *

    * stop the channel, and remove all current and pending transfers

    */



    static int s3c2410_dma_flush(struct s3c2410_dma_chan *chan)

    {

    struct s3c2410_dma_buf *buf, *next;

    unsigned
    long flags;



    pr_debug(
    "%s: chan %p (%d)\n", __FUNCTION__, chan, chan->number);



    dbg_showchan(chan);



    local_irq_save(flags);



    if (chan->state != S3C2410_DMA_IDLE) {

    pr_debug(
    "%s: stopping channel...\n", __FUNCTION__ );

    s3c2410_dma_ctrl(chan
    ->number, S3C2410_DMAOP_STOP); // 停调传输

    }



    buf
    = chan->curr;

    if (buf == NULL)

    buf
    = chan->next;



    chan
    ->curr = chan->next = chan->end = NULL;



    if (buf != NULL) {

    for ( ; buf != NULL; buf = next) {

    next
    = buf->next;



    pr_debug(
    "%s: free buffer %p, next %p\n",

    __FUNCTION__, buf, buf
    ->next);



    s3c2410_dma_buffdone(chan, buf, S3C2410_RES_ABORT);
    // 通知用户中断了传输

    s3c2410_dma_freebuf(buf);
    // 释放 buf

    }

    }



    dbg_showregs(chan);



    s3c2410_dma_waitforstop(chan);



    #if 0

    /* should also clear interrupts, according to WinCE BSP */

    {

    unsigned
    long tmp;



    tmp
    = dma_rdreg(chan, S3C2410_DMA_DCON);

    tmp
    |= S3C2410_DCON_NORELOAD;

    dma_wrreg(chan, S3C2410_DMA_DCON, tmp);

    }

    #endif



    dbg_showregs(chan);



    local_irq_restore(flags);



    return 0;

    }

      该函数的作用就是中断所有的传输 , 并把所有队列中等待传输的 buf 都清掉 .

     

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

    static int s3c2410_dma_started(struct s3c2410_dma_chan *chan)

    {

    unsigned
    long flags;



    local_irq_save(flags);



    dbg_showchan(chan);



    /* if we've only loaded one buffer onto the channel, then chec

    * to see if we have another, and if so, try and load it so when

    * the first buffer is finished, the new one will be loaded onto

    * the channel
    */

    // 看注释吧 , 不解释了

    if (chan->next != NULL) {

    if (chan->load_state == S3C2410_DMALOAD_1LOADED) {



    if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {

    pr_debug(
    "%s: buff not yet loaded, no more todo\n",

    __FUNCTION__);

    }
    else {

    chan
    ->load_state = S3C2410_DMALOAD_1RUNNING;

    s3c2410_dma_loadbuffer(chan, chan
    ->next);

    }



    }
    else if (chan->load_state == S3C2410_DMALOAD_1RUNNING) {

    s3c2410_dma_loadbuffer(chan, chan
    ->next);

    }

    }





    local_irq_restore(flags);



    return 0;



    }

    最后的这个函数就由大家自己分析吧 .

    从 s3c2410_dma_config 函数可以看出 , 该驱动只支持硬件请求模式 , 而从 s3c2410_dma_devconfig 函数可以看出 , 该驱动只支持设备和 memory 之间的 DMA 传输 .

    至于如何使用的问题 , 可以去代码里搜一下哪些地方调用了 export 出来的函数就懂了 , 2410 的板子上 PCM 会用到 DMA 传输 ,   使用流程为 :

        s3c2410_dma_request ->

    s3c2410_dma_devconfig ->

    s3c2410_dma_config ->            

    s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START);

    当然一般还会注册回调函数的 .

    到此为止整个 DMA 的操作流程都分析完了 , 希望对你有所帮助 ,


    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/leibniz_zsu/archive/2009/12/10/4979164.aspx

  • 相关阅读:
    结巴分词 0.14 版发布,Python 中文分词库
    Lazarus 1.0.2 发布,Pascal 集成开发环境
    Android全屏 去除标题栏和状态栏
    服务器日志现 Android 4.2 传将添多项新特性
    Percona XtraBackup 2.0.3 发布
    长平狐 Android 强制设置横屏或竖屏 设置全屏
    NetBeans 7.3 Beta 发布,全新的 HTML5 支持
    CppDepend现在已经支持Linux
    GromJS 1.7.18 发布,服务器端的 JavaScript
    Apache OpenWebBeans 1.1.6 发布
  • 原文地址:https://www.cnblogs.com/hoys/p/1998143.html
Copyright © 2011-2022 走看看