• 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

  • 相关阅读:
    JSP具体条款——response对象
    智课雅思词汇---三、aud和auto和bene是什么意思
    如何实现无刷新图片上传
    智课雅思词汇---二、词根acu和acr
    FormData是什么
    ajax如何上传文件(整理)
    js插件---评分插件Rating如何使用
    js插件---Amaze UI dialog如何使用
    js插件---layer.js使用体验是怎样
    算法答疑---06:月度开销
  • 原文地址:https://www.cnblogs.com/hoys/p/1998143.html
Copyright © 2020-2023  润新知