S3C24XX DMA框架源码分析

基于S3C2440 的DMA 框架源码分析

##一、硬件初窥 S3C2440支持4通道DMA。每个通道都可以在系统总线外设总线上进行数据传送。下图是请求源为硬件模式时的每个通道的请求源: ![DMA硬件请求源](https://img-blog.csdn.net/20151209231004585)

**图1 S3C2440 DMA硬件请求源**

下面来看相关寄存器:

 DISRCn  bit  Description  Initial State
 S_ADDR  [30:0]  源起始地址  0x00000000
 DISRCCn  bit  Description Initial State
 LOC  [1]  用于选择源的位置
0:源在系统总线上
1:源在外设总线上
 0
 INC  [0]  用于选择地址是否自动增加
0:地址自动增加
1:地址固定不变(此时即便是burst 模式下,传输过程中地址自动增加,  但是一旦传输完这一次数据,地址又变为初值)
 0
 DIDSTn  bit  Description  Initial State
 D_ADDR  [30:0]  目的起始地址  0x00000000
 DIDSTCn  Bit  Description  Initial State
 CHK_INT  [2]  当设置为自动加载时,用来选择中断发生的时间
0:TC为0是产生中断
1:自动加载完成的时候产生中断
 0
 LOC  [1]  用于选择目的设备的位置
0:目的设备在系统总线上
1:目的设备在外设总线上
 0
 INC  [0]  用于选择地址是否自动增加
0:地址自动增加
1:地址固定不变(此时即便是burst模式下,传输过程中地址自动增加,但是一旦传输完这一次数据,地址又重新变为初值)
 0
 DCONn  Bit  Description  Initial State
 DMD_HS  [31]  选择为Demand模式或者是握手模式
0:选择为Demand模式
1:选择为握手模式
这 两种模式下都是当发生请求时,DMA控制器开始传输数据并且发出  应  答信号,不同点是握手模式下,当DMA控制器收到请求撤销信号,并且自  身发出应答撤销信号之后才能接收下一次请求。而在Demand模式下,并  不需要等待请求撤销信号,他只需要撤销自身的应答信号,然后等待下一  次的请求。
 0
 SYNC  [30]  选择DREQ/DACK的同步
0:DREQ and DACK 与PCLK同步
1:DREQ and DACK 与HCLK同步
因此当设备在AHB系统总线时,这一位必须为1,而当设备在APB系统  时,它应该被设为0。当设备位于外部系统时,应根据具体情况而定。
 0
 INT  [29]  是否使能中断
0:禁止中断,用户需要查看状态寄存器来判断传输是否完成
1:使能中断,所有的传输完成之后产生中断信号
 0
 TSZ  [28]  选择一个原子传输的大小
0:单元传输(一次传输一个单元)
1:突发传输(一次传输四个单元)
 0
 SERVMODE  [27]  选择是单服务模式还是整体服务模式
0:单服务模式,当一次原子传输完成后需要等待下一个DMA请求
1:整体服务模式,进行多次原子传输,知道传输计数值到达0
 0
 HWSRCSEL  [26:24]  为每一个DMA选择DMA请求源
具体参见芯片手册
 000
 SWHW_SEL  [23]  选择DMA源为软件请求模式还是硬件请求模式
0:软件请求模式,需要将寄存器DMASKTRIG的SW_TRIG置位
1:硬件请求模式
 0
 RELOAD  [22]  是否自动重新装载
0:自动重装,当目前的传输计数值变为0时,自动重装
1:不自动重装
RELOAD[1]被设置为0以防无意识地进行新的DMA传输
 0
 DSZ  [21:20]  要被传输的数据的大小
00 = Byte    01 = Half word
10 = Word   11 = reserved
 00
 TC  [19:0]  初始化传输计数 0000

上述寄存器共有 DISRCn、DISRCCn、DIDSTn、DIDSTCn、DCONn一共五个。其中n为通道号,比如DMA通道1为DISRC1,DISRCC1,DCON1,以此类推。上述寄存器中DISRCn用于配置源起始地址,DIDSTn用于配置目的起始地址。DISRCCn和DIDSTCn、DCONn用于对DMA参数和行为进行配置。其中值得注意的是

  • DSZ :代表数据的大小
  • TSZ :一次传输多少个数据
  • TC :一共传输多少次所以实际传输的数据的大小为:DSZ * TSZ * TC

我们本程序里面由于设置为数据大小为1个字节,一次传输1个数据,所以传输次数直接就是实际数据的大小了。

 DSTATn  Bit  Description  Initial State
 STAT  [21:20]  DMA控制器的状态
00:DMA控制器已经准备好接收下一个DMA请求
01:DMA控制器正在处理DMA请求
 00
 CURR_TC  [19:0]  传输计数的当前值
每个原子传输减1
 

 DCSRCn  Bit  Description  Initial State
 CURR_SRC  [30:0]  当前的源地址  0x00000000
 DCDSTn  Bit  Description  Initial State
 CURR_DST  [30:0]  当前的目的地址  0x00000000
 DMASKTRIGn  Bit  Description  Initial State
 STOP  [2]  停止DMA操作
1:当前的原子传输完成之后,就停止DMA操作。如果当前没有原子  传输正在进行,就立即结束。
 
 ON_OFF  [1]  DMA通道的开/闭
0:DMA通道关闭
1:DMA通道打开,并且处理DMA请求
 
 SW_TRIG  [0]  1:在软件请求模式时触发DMA通道  

上述寄存器主要供程序读取DMA当前参数的。其中DSTATn为当前DMA状态。CURR_TC为当前传输计数器的值。DCSRCn为当前源地址。DCDSTn为当前目的地址。而DMASKTRIGn就是DMA通道开关,用于触发或者停止DMA传送。

二、寻根溯源

2.1 设备类的注册

在 arch/arm/mach-s3c24xx.c中一个名为“s3c2440-core”的设备类被注册。

struct bus_type s3c2442_subsys = {
    .name       = "s3c2442-core",
    .dev_name   = "s3c2442-core",
};

static int __init s3c2440_core_init(void)
{
    return subsys_system_register(&s3c2440_subsys, NULL);
}

最终引起一个名为s3c2410_dma_add的函数调用。该函数在dma-32c2440.c中如下:

static int __init s3c2410_dma_add(struct device *dev,
                  struct subsys_interface *sif)
{
    s3c2410_dma_init();
    s3c24xx_dma_order_set(&s3c2410_dma_order);
    return s3c24xx_dma_init_map(&s3c2410_dma_sel);
}

该函数主要是调用了
+ s3c2410_dma_init
+ s3c24xx_dma_order_set
+ s3c24xx_dma_init_map

三个个函数,下面来逐一分析。

2.2 s3c2410_dma_init

该函数在arch/arm/plat-s3c24xx.c中实现为:

int __init s3c2410_dma_init(void)
{
    return s3c24xx_dma_init(4, IRQ_DMA0, 0x40);
}

/*初始化DMA通道。channels 为DMA通道号,对于s3c2440来说
    具有0~3共四个DMA通道。
    irq 为该通道的DMA中断号
    stride 为DMA通道的寄存器覆盖地址
 */
int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq,
                unsigned int stride)
{
    /*DMA通道描述结构体*/
    struct s3c2410_dma_chan *cp;
    int channel;
    int ret;

    printk("S3C24XX DMA Driver, Copyright 2003-2006 Simtec Electronics\n");

    /*总的DMA通道数*/
    dma_channels = channels;
    /*获取DMA寄存器基地址,映射之*/
    dma_base = ioremap(S3C24XX_PA_DMA, stride * channels);
    if (dma_base == NULL) {
        printk(KERN_ERR "dma failed to remap register block\n");
        return -ENOMEM;
    }

    /*建立DMA缓冲池*/
    dma_kmem = kmem_cache_create("dma_desc",
                     sizeof(struct s3c2410_dma_buf), 0,
                     SLAB_HWCACHE_ALIGN,
                     s3c2410_dma_cache_ctor);

    if (dma_kmem == NULL) {
        printk(KERN_ERR "dma failed to make kmem cache\n");
        ret = -ENOMEM;
        goto err;
    }
    /*初始化每个DMA通道*/
    for (channel = 0; channel < channels;  channel++) {
        cp = &s3c2410_chans[channel];
        /*清空DMA通道描述符*/
        memset(cp, 0, sizeof(struct s3c2410_dma_chan));

        /* dma channel irqs are in order.. */
        /*设置DMA通道描述符*/
        cp->number = channel; //通道号,对于S3C2440来说是0~3
        cp->irq    = channel + irq;//通道的中断号
        cp->regs   = dma_base + (channel * stride);//设置通道寄存器起始基地址

        /* point current stats somewhere */
        cp->stats  = &cp->stats_store;
        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;
}

s3c2440的DMA通道参数由s3c2410_dma_chan描述,该结构体定义如下:

struct s3c2410_dma_chan {
    /* channel state flags and information */
    /*dma通道号*/
    unsigned char        number;      /* number of this dma channel */
    /*通道是否在使用中*/
    unsigned char        in_use;      /* channel allocated */
    /*当前有没有DMA中断请求*/
    unsigned char        irq_claimed; /* irq claimed for channel */
    /*中断使能标志*/
    unsigned char        irq_enabled; /* irq enabled for channel */
    /*传输单元大小*/
    unsigned char        xfer_unit;   /* size of an transfer */

    /* channel state */

    enum s3c2410_dma_state   state;
    enum s3c2410_dma_loadst  load_state;
    struct s3c2410_dma_client *client;

    /* channel configuration */
    enum dma_data_direction  source;
    enum dma_ch      req_ch;
    unsigned long        dev_addr;
    unsigned long        load_timeout;
    unsigned int         flags;     /* channel flags */

    struct s3c24xx_dma_map  *map;       /* channel hw maps */

    /* channel's hardware position and configuration */
    void __iomem        *regs;      /* channels registers */
    void __iomem        *addr_reg;  /* data address register */
    unsigned int         irq;       /* channel irq */
    unsigned long        dcon;      /* default value of DCON */

    /* driver handles */
    s3c2410_dma_cbfn_t   callback_fn;   /* buffer done callback */
    s3c2410_dma_opfn_t   op_fn;     /* channel op callback */

    /* stats gathering */
    struct s3c2410_dma_stats *stats;
    struct s3c2410_dma_stats  stats_store;

    /* buffer list and information */
    struct s3c2410_dma_buf  *curr;      /* current dma buffer */
    struct s3c2410_dma_buf  *next;      /* next buffer to load */
    struct s3c2410_dma_buf  *end;       /* end of queue */

    /* system device */
    struct device   dev;
};

可以看到,s3c2410_dma_chan结构与前文所述的DMA寄存器大都是对应的,该结构体实际上保存了对应DMA通道的设置参数。在该函数中,主要的工作就是对一个名为s3c2410_chans的s3c2410_dma_chan结构体数组进行初始化,设置一些默认参数。该函数做了以下工作:

  • 首先映射DMA寄存器的地址,得到内核虚拟地址用于读写配置寄存器。
  • 其次建立一个DMA缓冲池,主要是针对DMA一致性做了一些工作,包括使CACHE失能等等
  • 对s3c2410_chan数组中的每一个元素逐一进行初始化。初始化包括
    • 设置DMA通道号
    • 设置DMA通道的中断号
    • 设置DMA对应的寄存器地址
    • 设置装载超时时间
    • 设置DMA状态

可以说,一个s3c2410_dma_chan结构,就是一个DMA通道对象的实例。所以设置该结构体,实际上是为DMA各个通道配置了默认参数,以方便开发人员使用。

2.3 s3c24xx_dma_order_set

int __init s3c24xx_dma_order_set(struct s3c24xx_dma_order *ord)
{
    struct s3c24xx_dma_order *nord = 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;
    }

    dma_order = nord;
    memcpy(nord, ord, sizeof(struct s3c24xx_dma_order));
    return 0;
}

在这个函数当中主要做的工作只有两件
+ 分配一个s3c24xx_dma_order结构nord
+ 从ord中复制内容到新分配的s3c24xx_dma_order结构nord中
在函数调用时ord指向了s3c2410_dma_order对象,该对象定义如下:

/* 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
*/

struct s3c24xx_dma_order {
    struct s3c24xx_dma_order_ch channels[DMACH_MAX];
};
/* struct s3c24xx_dma_order_ch
 *
 * channel map for one of the `enum dma_ch` dma channels. the list
 * entry contains a set of low-level channel numbers, orred with
 * DMA_CH_VALID, which are checked in the order in the array.
*/

struct s3c24xx_dma_order_ch {
    unsigned int    list[S3C_DMA_CHANNELS]; /* list of channels */
    unsigned int    flags;              /* flags */
};

static struct s3c24xx_dma_order __initdata s3c2440_dma_order = {
    .channels   = {
        [DMACH_SDI] = {
            .list   = {
                [0] = 3 | DMA_CH_VALID,
                [1] = 2 | DMA_CH_VALID,
                [2] = 1 | DMA_CH_VALID,
                [3] = 0 | DMA_CH_VALID,
            },
        },
        [DMACH_I2S_IN]  = {
            .list   = {
                [0] = 1 | DMA_CH_VALID,
                [1] = 2 | DMA_CH_VALID,
            },
        },
        [DMACH_I2S_OUT] = {
            .list   = {
                [0] = 2 | DMA_CH_VALID,
                [1] = 1 | DMA_CH_VALID,
            },
        },
        [DMACH_PCM_IN] = {
            .list   = {
                [0] = 2 | DMA_CH_VALID,
                [1] = 1 | DMA_CH_VALID,
            },
        },
        [DMACH_PCM_OUT] = {
            .list   = {
                [0] = 1 | DMA_CH_VALID,
                [1] = 3 | DMA_CH_VALID,
            },
        },
        [DMACH_MIC_IN] = {
            .list   = {
                [0] = 3 | DMA_CH_VALID,
                [1] = 2 | DMA_CH_VALID,
            },
        },
    },
};

该对象主要是用于保存DMA通道的可用关系的。参照图1可以知道SDI可用通道0 1 2 3四个通道,所以在这里定义也是与图1一致的。IISDI可用通道为1通道和2通道,这里定义也是与图1一致的。

        [DMACH_SDI] = {
            .list   = {
                [0] = 3 | DMA_CH_VALID,
                [1] = 2 | DMA_CH_VALID,
                [2] = 1 | DMA_CH_VALID,
                [3] = 0 | DMA_CH_VALID,
            },
        },
        [DMACH_I2S_IN]  = {
            .list   = {
                [0] = 1 | DMA_CH_VALID,
                [1] = 2 | DMA_CH_VALID,
            },
        },

s3c24xx_dma_order数组的存在,是为了反映各个通道的可用情况的。在内核中并没有使用物理DMA通道号来标记各个DMA通道,而是通过虚拟DMA通道来进行标记,虚拟DMA通道则通过映射(下面分析),和s3c24xx_dma_order的信息从而得到物理DMA通道号,最终通过物理DMA通道号查找得到物理通道的配置参数,也就是s3c2410_dma_cha结构。例如,当开发人员想要发起一次IIS_IN的DMA时,它给出一个虚拟通道号DMACH_I2S_IN,而后内核再根据s3c2440_dma_order中的信息去选择物理通道1或者2。这样开发人员就无需查找S3C24XX的用户手册才能给出一个物理通道号了。

2.4 s3c24xx_dma_init_map

前文说过,为了统一管理,内核中没有使用直接的DMA通道号,也就是说内核中,使用通道0,未必指的是S3C2440手册中给出的DMA-CH0,内核中的通道号使用的是虚拟通道号,是由实际(物理)通道号映射得来的,这个映射关系就保存在struct s3c24xx_dma_maps3c24xx_dma_order结构中。两者都可以通过虚拟通道号映射得到物理通道号,但优先使用s3c24xx_dma_order的映射。因为s3c24xx_dma_order是用于特定板子的映射。而struct s3c24xx_dma_map用于芯片的映射。

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 = kmemdup(sel->map, map_sz, GFP_KERNEL);
    if (nmap == NULL)
        return -ENOMEM;

    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;
}

由前文可以知道该函数传入的参数为s3c2440_dma_sel,该函数只是将s3c2440_dma_sel拷贝到dma_sel对象中而已。来看s3c24xx_dma_selection的定义如下:

struct s3c24xx_dma_map {
    const char      *name;

    unsigned long        channels[S3C_DMA_CHANNELS];
    unsigned long        channels_rx[S3C_DMA_CHANNELS];
};

struct s3c24xx_dma_selection {
    struct s3c24xx_dma_map  *map; /*反映虚拟通道号和物理通道号映射关系的数组*/
    unsigned long        map_size;/*map的元素个数,也就是虚拟通道号的个数*/
    unsigned long        dcon_mask;/*dcon寄存器的默认值*/

    /*选择回调函数,在选择了一个通道之后该回调被调用*/
    void    (*select)(struct s3c2410_dma_chan *chan,
              struct s3c24xx_dma_map *map);

    void    (*direction)(struct s3c2410_dma_chan *chan,
                 struct s3c24xx_dma_map *map,
                 enum dma_data_direction dir);
};

s3c24xx_dma_selection结构中保存了相关的映射信息,其中最重要的map成员保存了虚拟中通道号和物理通道号的映射情况。该成员指向一个数组,数组中每一个元素反映了一个虚拟通道号的映射关系。在s3c24xx_dma_map中成员chennels中索引为x的元素如果DMA_CH_VALID置位表示该物理通道x与该虚拟通道存在映射关系。
对于S3C2440的来说,实例如下:

static struct s3c24xx_dma_map __initdata s3c2440_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[1]    = S3C2440_DCON_CH1_SDI | DMA_CH_VALID,
        .channels[2]    = S3C2410_DCON_CH2_SDI | DMA_CH_VALID,
        .channels[3]    = S3C2410_DCON_CH3_SDI | DMA_CH_VALID,
    },
    [DMACH_SPI0] = {
        .name       = "spi0",
        .channels[1]    = S3C2410_DCON_CH1_SPI | DMA_CH_VALID,
    },
    [DMACH_SPI1] = {
        .name       = "spi1",
        .channels[3]    = S3C2410_DCON_CH3_SPI | DMA_CH_VALID,
    },
    [DMACH_UART0] = {
        .name       = "uart0",
        .channels[0]    = S3C2410_DCON_CH0_UART0 | DMA_CH_VALID,
    },
    [DMACH_UART1] = {
        .name       = "uart1",
        .channels[1]    = S3C2410_DCON_CH1_UART1 | DMA_CH_VALID,
    },
        [DMACH_UART2] = {
        .name       = "uart2",
        .channels[3]    = S3C2410_DCON_CH3_UART2 | DMA_CH_VALID,
    },
    [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,
    },
    [DMACH_I2S_OUT] = {
        .name       = "i2s-sdo",
        .channels[0]    = S3C2440_DCON_CH0_I2SSDO | DMA_CH_VALID,
        .channels[2]    = S3C2410_DCON_CH2_I2SSDO | DMA_CH_VALID,
    },
    [DMACH_PCM_IN] = {
        .name       = "pcm-in",
        .channels[0]    = S3C2440_DCON_CH0_PCMIN | DMA_CH_VALID,
        .channels[2]    = S3C2440_DCON_CH2_PCMIN | DMA_CH_VALID,
    },
    [DMACH_PCM_OUT] = {
        .name       = "pcm-out",
        .channels[1]    = S3C2440_DCON_CH1_PCMOUT | DMA_CH_VALID,
        .channels[3]    = S3C2440_DCON_CH3_PCMOUT | DMA_CH_VALID,
    },
    [DMACH_MIC_IN] = {
        .name       = "mic-in",
        .channels[2]    = S3C2440_DCON_CH2_MICIN | DMA_CH_VALID,
        .channels[3]    = S3C2440_DCON_CH3_MICIN | DMA_CH_VALID,
    },
    [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,
    },
};

这里假设给出虚拟通道号DMACH_SPI0,那么可以从s3c2440_dma_mappings[]数组中索引得到

    [DMACH_SPI0] = {
        .name       = "spi0",
        .channels[1]    = S3C2410_DCON_CH1_SPI | DMA_CH_VALID,
    },

从而可以知道DMACH_SPI0被映射到了物理通道1上。这与图1 的描述是一致的。

三、面向驱动开发工程师的API

前文所述过程中,S3C24xx的DMA基本架构已经建立起来的。至此,驱动开发的程序员就可以使用S3C24XX DMA子系统所提供的API进行开发的,下面从源码上对这些API逐一进行分析。

3.1 申请通道 s3c2410_dma_request

如果驱动开发人员想要开始一个DMA传输,那么他必须调用s3c2410_dma_request进行通道申请。下面来看源代码:

int s3c2410_dma_request(enum dma_ch 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);
    /*通过虚拟DMA通道映射得到实际通道描述信息*/
    chan = s3c2410_dma_map_channel(channel);
    if (chan == NULL) {
        local_irq_restore(flags);
        return -EBUSY;
    }

    dbg_showchan(chan);
    /*标记正在使用*/
    chan->client = client;
    chan->in_use = 1;
    /*如果中断尚未注册就注册中断*/
    if (!chan->irq_claimed) {
        pr_debug("dma%d: %s : requesting irq %d\n",
             channel, __func__, 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) {
            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", __func__, chan);

    return chan->number | DMACH_LOW_LEVEL;
}

该函数所做的工作有
+ 通过虚拟通道号映射得到物理通道号,并且通过物理通道号索引得到通道描述结构struct s3c2410_dma_chan *chan
+ 根据struct s3c2410_dma_chan *chan的描述,进行中断注册,并且标记为通道正在使用状态。

通过虚拟通道号映射得到物理通道号的工作在s3c2410_dma_map_channel中完成,该函数只是遍历了s3c2410_dma_orders3c2440_dma_mappings找到映射的物理通道号,而后再通过物理通道号在s3c2410_chans中索引得到通道描述结构。并且调用了(dma_sel.select)(dmach, ch_map)回调函数。

3.2 设置寄存器 s3c2410_dma_devconfig和s3c2410_dma_config

设置寄存器的工作由s3c2410_dma_devconfigs3c2410_dma_config完成:


/* s3c2410_dma_devconfig
 *
 * configure the dma source/destination hardware type and address
 *
 * source:    DMA_FROM_DEVICE: source is hardware
 *            DMA_TO_DEVICE: source is memory
 *
 * devaddr:   physical address of the source
*/

int s3c2410_dma_devconfig(enum dma_ch channel,
              enum dma_data_direction source,
              unsigned long devaddr)
{
    struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
    unsigned int hwcfg;

    if (chan == NULL)
        return -EINVAL;

    pr_debug("%s: source=%d, devaddr=%08lx\n",
         __func__, (int)source, devaddr);

    chan->source = source;
    chan->dev_addr = devaddr;

    switch (chan->req_ch) {
    case DMACH_XD0:
    case DMACH_XD1:
        hwcfg = 0; /* AHB */
        break;

    default:
        hwcfg = S3C2410_DISRCC_APB;
    }

    /* always assume our peripheral desintation is a fixed
     * address in memory. */
     hwcfg |= S3C2410_DISRCC_INC;

    switch (source) {
    case DMA_FROM_DEVICE:
        /* source is hardware */
        pr_debug("%s: hw source, devaddr=%08lx, hwcfg=%d\n",
             __func__, 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);
        break;

    case DMA_TO_DEVICE:
        /* source is memory */
        pr_debug("%s: mem source, devaddr=%08lx, hwcfg=%d\n",
             __func__, 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);
        break;

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

        return -EINVAL;
    }

    if (dma_sel.direction != NULL)
        (dma_sel.direction)(chan, chan->map, source);

    return 0;
}

函数s3c2410_dma_devconfig所做的工作是配置DMA传输方向,和DMA设备地址。而源数据和源地址在发起DMA请求的时候才会给出。在该函数中,首先将配置的信息保存到对应的s3c2410_dma_chan结构中,而后将配置写入寄存器中以配置DMA。

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

int s3c2410_dma_config(enum dma_ch channel,
               int xferunit)
{
    struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
    unsigned int dcon;

    pr_debug("%s: chan=%d, xfer_unit=%d\n", __func__, channel, xferunit);

    if (chan == NULL)
        return -EINVAL;
    /*通道会设置一些默认掩码,进在此进行与运算即可得到DCON寄存器的配置值*/
    dcon = chan->dcon & dma_sel.dcon_mask;
    pr_debug("%s: dcon is %08x\n", __func__, dcon);

    /*这里进行DCON的具体配置,详细参照器件手册*/
    switch (chan->req_ch) {
    case DMACH_I2S_IN:
    case DMACH_I2S_OUT:
    case DMACH_PCM_IN:
    case DMACH_PCM_OUT:
    case DMACH_MIC_IN:
    default:
        dcon |= S3C2410_DCON_HANDSHAKE;
        dcon |= S3C2410_DCON_SYNC_PCLK;
        break;

    case DMACH_SDI:
        /* note, ensure if need HANDSHAKE or not */
        dcon |= S3C2410_DCON_SYNC_PCLK;
        break;

    case DMACH_XD0:
    case DMACH_XD1:
        dcon |= S3C2410_DCON_HANDSHAKE;
        dcon |= S3C2410_DCON_SYNC_HCLK;
        break;
    }

    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", __func__, xferunit);
        return -EINVAL;
    }

    dcon |= S3C2410_DCON_HWTRIG;
    dcon |= S3C2410_DCON_INTREQ;

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

    chan->dcon = dcon;
    chan->xfer_unit = xferunit;

    return 0;
}

函数s3c2410_dma_config用于配置传输单元大小,实际上是根据用户提供的参数在配置DCON寄存器,但这里注意的是,在这里只是将配置信息写入对应的s3c2410_dma_chan结构,而没有真正写入硬件寄存器当中。dcon配置寄存器用于配置DMA具体行为,是一个十分重要的配置环节。dcon寄存器将在数据被装载到DMA通道时写入硬件寄存器。

3.3 回调函数设置 s3c2410_dma_set_opfn 、 s3c2410_dma_set_buffdone_fn

s3c2410_dma_chan中有两个成员

    s3c2410_dma_cbfn_t   callback_fn;   /* buffer done callback */
    s3c2410_dma_opfn_t   op_fn;     /* channel op callback */

其中成员callback_fn在装载数据是被调用。而op_fn在DMA操作完成时被调用。其调用过程在后续分析中会讲到。源码比较简单,下面列出源码诸位自己看。

int s3c2410_dma_set_opfn(enum dma_ch channel, s3c2410_dma_opfn_t rtn)
{
    struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);

    if (chan == NULL)
        return -EINVAL;

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

    chan->op_fn = rtn;

    return 0;
}
EXPORT_SYMBOL(s3c2410_dma_set_opfn);

int s3c2410_dma_set_buffdone_fn(enum dma_ch channel, s3c2410_dma_cbfn_t rtn)
{
    struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);

    if (chan == NULL)
        return -EINVAL;

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

    chan->callback_fn = rtn;

    return 0;
}

3.4 数据装入传送队列 s3c2410_dma_enqueue

在前文中的用户API都被调用并且设置正确之后,DMA就可以传送数据了。在详细分析s3c2410_dma_enqueue函数之前有必要分析一下DMA的传送队列及DMA状态。

3.4.1 发送队列

s3c2410_dma_chan结构中有三个成员用于维护该通道的发送队列:

  • struct s3c2410_dma_buf *curr
  • struct s3c2410_dma_buf *next
  • struct s3c2410_dma_buf *end

三个成员都指向了struct s3c2410_dma_buf结构,该结构记录了需要传送的数据起始地址,以及长度信息。其中curr成员指向的是当前需要发送缓冲,next则指向下一个发送的缓冲,而end指向最后一个发送缓冲。当每一个缓冲区数据被发送完成之后就会触发DMA中断,在中断中更新curr和next,取得下一个发送的缓冲。如此反复知道整个队列中的缓冲都被发送完毕。

3.4.2 DMA状态和装载状态

s3c2410_dma_chan结构中有两个成员:  
+ enum s3c2410_dma_loadst load_state
+ enum s3c2410_dma_state state
两个成员都为枚举变量,如下:

enum s3c2410_dma_state {
    S3C2410_DMA_IDLE,
    S3C2410_DMA_RUNNING,
    S3C2410_DMA_PAUSED
};
/* 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,
};

其中s3c2410_dma_state枚举用来表示DMA控制器的状态,与硬件上的DMA控制器状态相对应,分别有运行,空闲,暂停三种状态。而s3c2410_dma_loadst用于表示DMA缓冲装载状态。前文说过,要使用DMA传输数据,就要先将欲传输的DMA数据缓冲挂入DMA通道的缓冲队列中,DMA控制器在中断中依次取出加载到硬件然后进行发送。
+ S3C2410_DMA_NONE表示当前通道没有任何需要发送的缓冲。
+ S3C2410_DMA_1LOADED表示当前已经有一个缓冲挂入该通道,但硬件还没开始发送缓冲。
+ S3C2410_DMA_1RUNNING表示当前有缓冲正在被硬件传输,传输正在进行时。
+ S3C2410_DMA_1LOADED_1RUNNING则表示当前有一个缓冲正在传输,并且还有一个缓冲正在等待被传输。

这几种状态之间的转化关系我们可以在s3c2410_dma_irqs3c2410_dma_loadbuffer函数中直观地看到。

static inline int
s3c2410_dma_loadbuffer(struct s3c2410_dma_chan *chan,
               struct s3c2410_dma_buf *buf)
{
     /*......省略无关代码若干*/
    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 */
    /*更新DMA通道的装载状态*/
    switch (chan->load_state) {
    case S3C2410_DMALOAD_NONE://当DMA原先处于S3C2410_DMALOAD_NONE状态时,现在往硬件装载完一个传输缓冲之后,应当处于S3C2410_DMALOAD_1LOADED
        chan->load_state = S3C2410_DMALOAD_1LOADED;
        break;

    case S3C2410_DMALOAD_1RUNNING://如果原先DMA处于S3C2410_DMALOAD_1RUNNING状态,装载完一个缓冲之后应当处于S3C2410_DMALOAD_1LOADED_1RUNNING状态。
        chan->load_state = S3C2410_DMALOAD_1LOADED_1RUNNING;
        break;

    default:
        dmawarn("dmaload: unknown state %d in loadbuffer\n",
            chan->load_state);
        break;
    }

    return 0;
}

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;

    dbg_showchan(chan);

    /* modify the channel state */
    /*至此DMA一个缓冲已经发送完毕*/
    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 */
        /*如果原先DMA的状态是S3C2410_DMALOAD_1RUNNING则传输结束之后
          进入S3C2410_DMALOAD_NONE状态。
        */
        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.
         */
        /*如果原先DMA状态处于S3C2410_DMALOAD_1LOADED,传输
          结束之后,状态变为S3C2410_DMALOAD_NONE
        */
        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
         */
        /*如果原先DMA状态为S3C2410_DMALOAD_1LOADED_1RUNNING则现在变为
          S3C2410_DMALOAD_1LOADED
        */
        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;
    }

    /*......省略部分代码*/
}

从上面的代码中我们可以看出。开始时DMA处于S3C2410_DMALOAD_NONE状态。如果一个发送缓冲被写入寄存器的时候,DMA进入S3C2410_DMALOAD_1LOADED状态,此时虽然发送缓冲被写入寄存器,但实际上硬件还没开始传输。当我们读取S3C2410_DMA_DSTAT寄存器确定DMA硬件已经开始传输,则进入S3C2410_DMALOAD_1RUNNING状态。此时如果再有一个发送缓冲再写入寄存器,则DMA进入S3C2410_DMALOAD_1LOADED_1RUNNING状态。

3.4.3 s3c2410_dma_enqueue

有了上面的铺垫,我们开始分析函数s3c2410_dma_enqueue:


/* 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(enum dma_ch channel, void *id,
            dma_addr_t data, int size)
{
    /*获取通道*/
    struct s3c2410_dma_chan *chan = s3c_dma_lookup_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",
         __func__, id, (unsigned int)data, size);
    /*从DMA缓冲中分配一个DMA发送缓冲结构*/
    buf = kmem_cache_alloc(dma_kmem, GFP_ATOMIC);
    if (buf == NULL) {
        pr_debug("%s: out of memory (%ld alloc)\n",
             __func__, (long)sizeof(*buf));
        return -ENOMEM;
    }

    //pr_debug("%s: new buffer %p\n", __func__, buf);
    //dbg_showchan(chan);
    /*设置DMA发送缓冲结构,填充数据起始地址*/
    buf->next  = NULL;
    buf->data  = buf->ptr = data;
    buf->size  = size;
    buf->id    = id;
    buf->magic = BUF_MAGIC;

    local_irq_save(flags);
    /*DMA发送缓冲挂入通道中*/
    if (chan->curr == NULL) {
        /* we've got nothing loaded... */
        pr_debug("%s: buffer %p queued onto empty channel\n",
             __func__, buf);

        chan->curr = buf;
        chan->end  = buf;
        chan->next = NULL;
    } else {
        pr_debug("dma%d: %s: buffer %p queued onto non-empty channel\n",
             chan->number, __func__, buf);

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

        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) {//只有在DMA处于运行状态时才能装载数据
        if (chan->load_state == S3C2410_DMALOAD_1LOADED && 1) {
            /*
              如果有DMA有传输缓冲被装载,但任然未开始,则不能再装载
              要等DMA开始传输之后,才能再继续装载。
            */
            if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {
                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) {
            s3c2410_dma_loadbuffer(chan, chan->next);
        }
    } else if (chan->state == S3C2410_DMA_IDLE) {
        if (chan->flags & S3C2410_DMAF_AUTOSTART) {
            /*启动传输*/
            s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL,
                     S3C2410_DMAOP_START);
        }
    }

    local_irq_restore(flags);
    return 0;
}

该函数所做的工作有:
+ 分配一个传输缓冲结构并且根据用户传入得参数对该结构进行初始化
+ 将传输缓冲挂入DMA通道
+ 判断DMA状态并且装载缓冲
+ 启动传输

三、 总结

  • S3C24XX的DMA框架当中,使用struct s3c2410_dma_chan来描述一个通道,具体地来说每个这个结构记录了每个通道的详细配置参数。
  • S3C24XX的DMA框架使用虚拟通道号从而屏蔽底层与硬件紧密相关的DMA物理通道号,而且方便统一管理和调度DMA通道,框架可以自动选择合适的通道供开发人员使用,十分方便。虚拟DMA通道号和物理通道号的映射关系由s3c24xx_dma_maps3c24xx_dma_order记录。
  • S3C24XX DMA框架将框架用户需要传输的DMA数据通过链表的形式组织起来,由各个通道的struct s3c2410_dma_cha实例进行维护。启动传输之后,在每次中断中取出链表头指向的缓冲继续发送,链表头也由此往下滑动,如此反复直到所有缓冲都被发送完毕。

猜你喜欢

转载自blog.csdn.net/u013298300/article/details/50243521
今日推荐