Chinaunix首页 | 论坛 | 博客
  • 博客访问: 181708
  • 博文数量: 44
  • 博客积分: 627
  • 博客等级: 中士
  • 技术积分: 345
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-20 21:55
文章分类

全部博文(44)

文章存档

2012年(44)

分类: LINUX

2012-08-08 17:11:26

DMA原理  

2010-08-05 21:39:26|  分类: arm linux设备驱 |  标签: |字号 

下面是S3C2440A数据手册上的一段截图,展示了4个DMA通道和每个通道对应的DMA源:
DMA原理 - 尘 - 学习点滴
 

 对这些DMA通道和其对应DMA源的管理在文件linux/arch/arm/mach-s3c2440/dma.c中实现。

在文件dma.c中定义了一个结构体数组static struct s3c24xx_dma_map __initdata s3c2440_dma_mappings[],

这个结构体将所有DMA源和每个DMA源所能请求的DMA通道联系了起来。

这个管理结构体原型如下:

struct s3c24xx_dma_map {
 const char  *name; //DMA源的名
 struct s3c24xx_dma_addr  hw_addr; //源的物理地址。

 unsigned long   channels[S3C2410_DMA_CHANNELS]; //在S3C2440中S3C2410_DMA_CHANNELS为4,。

 unsigned long   channels_rx[S3C2410_DMA_CHANNELS];
};

 

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",                         //DMA源为SD卡  ,该源可以对四个DMA通道发出DMA请求。                 
  .channels[0] = S3C2410_DCON_CH0_SDI | DMA_CH_VALID, // DMA_CH_VALID表明将该通道初始化为可用。       
  .channels[1] = S3C2440_DCON_CH1_SDI | DMA_CH_VALID, 

//S3C2440_DCON_CH2_SDI将被写入DMA控制寄存器,用于DMA通道请求源选择。

  .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[0] = S3C2440_DCON_CH0_I2SSDO | DMA_CH_VALID,
  .channels[2] = S3C2410_DCON_CH2_I2SSDO | DMA_CH_VALID,
  .hw_addr.to = S3C2410_PA_IIS + S3C2410_IISFIFO,
 },
 [DMACH_PCM_IN] = {
  .name  = "pcm-in",
  .channels[0] = S3C2440_DCON_CH0_PCMIN | DMA_CH_VALID,
  .channels[2] = S3C2440_DCON_CH2_PCMIN | DMA_CH_VALID,
  .hw_addr.from = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA,
 },
 [DMACH_PCM_OUT] = {
  .name  = "pcm-out",
  .channels[1] = S3C2440_DCON_CH1_PCMOUT | DMA_CH_VALID,
  .channels[3] = S3C2440_DCON_CH3_PCMOUT | DMA_CH_VALID,
  .hw_addr.to = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA,
 },
 [DMACH_MIC_IN] = {
  .name  = "mic-in",
  .channels[2] = S3C2440_DCON_CH2_MICIN | DMA_CH_VALID,
  .channels[3] = S3C2440_DCON_CH3_MICIN | DMA_CH_VALID,
  .hw_addr.from = S3C2440_PA_AC97 + S3C_AC97_MIC_DATA,
 },
 [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,
 },
};

/*************************************************************************************************************************/

//当某DMA通道被使用时调用该函数,置该DMA通道被占用标识。

//获取该DMA源对应的DMA通道请求源选择,存于chan->dcon中最终将被写入DMA控制寄存器。

static void s3c2440_dma_select(struct s3c2410_dma_chan *chan,
          struct s3c24xx_dma_map *map)
{
 chan->dcon = map->channels[chan->number] & ~DMA_CH_VALID;
}

static struct s3c24xx_dma_selection __initdata s3c2440_dma_sel = {
 .select  = s3c2440_dma_select,   //通道选择函数
 .dcon_mask = 7 << 24,    //屏蔽DMA控制寄存器中用于设置DMA请求源的位。
 .map  = s3c2440_dma_mappings, //上面初始化的 源-----通道 管理结构体数组
 .map_size = ARRAY_SIZE(s3c2440_dma_mappings),  //源的数目。
};

//有一些DMA源不只对应一个DMA通道,将这些源提了出来单独用一个结构体来管理。

//DMA源DMACH_TIMER也对应多个DMA通道,但没有出现在下面结构体中,因为

//定时器本身并不进行数据传输,而其他DMA源都是在数据传输时请求一个DMA通道进行,

//源的寄存器与内存的数据传输。定时器是在DMA进行传输时让它来定时触发传输操作

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注册到内核定义了一个s3c2440_dma_driver。

static struct sysdev_driver s3c2440_dma_driver = {
 .add = s3c2440_dma_add,
};

//函数s3c2440_dma_init()即是完成了这个注册的工作。

static int __init s3c2440_dma_init(void)
{
 return sysdev_driver_register(&s3c2440_sysclass, &s3c2440_dma_driver);
}

//这个注册过程最重要的工作就是调用了函数s3c2440_dma_driver->add,即是函数s3c2440_dma_add()。

这个函数的实现仍然是在文件linux/arch/arm/mach-s3c2440/dma.c中。

static int __init s3c2440_dma_add(struct sys_device *sysdev)
{
 s3c2410_dma_init();                                                             (1)   
 s3c24xx_dma_order_set(&s3c2440_dma_order);                 (2)
 return s3c24xx_dma_init_map(&s3c2440_dma_sel);             (3)
}

/*

函数s3c2440_dma_add()的工作就是将文件linux/arch/arm/mach-s3c2440/dma.c中初始化的

一些量拷到文件linux/arch/arm/plat-s3c24xx/dma.c中,或初始化文件linux/arch/arm/plat-s3c24xx/dma.c中

的结构体。文件linux/arch/arm/plat-s3c24xx/dma.c中定义了一些通用的DMA操作函数,这些函数最终是对

一个具体平台的硬件的操作。

*/

(1)

int __init s3c2410_dma_init(void)
{//4个DMA通道,起始中断号IRQ_DMA0,每一个DMA通道的寄存器覆盖地址范围为0x40。
 return s3c24xx_dma_init(4, IRQ_DMA0, 0x40); 
}

 

int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq,
       unsigned int stride)
{
 struct s3c2410_dma_chan *cp;
 int channel;
 int ret;

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

//本函数的实现是在文件linux/arch/arm/plat-s3c24xx/dma.c中,这个文件中还实现了其他DMA操作函数。

//dma_channels是在该文件中的全局变量,在其他DMA操作函数中要用这个变量。

 dma_channels = channels;      //设置通道数目,

 dma_base = ioremap(S3C24XX_PA_DMA, stride * channels);
 if (dma_base == NULL) {
  printk(KERN_ERR "dma failed to remap register block\n");
  return -ENOMEM;
 }

 //创建一个内存池,在创建内存管理结构体s3c2410_dma_buf时便是从该池中获取这些结构体内存。

//DMA的操作是外设与内存的数据传输,这些内存就是用结构体s3c2410_dma_buf来管理的。

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

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

//该结构体数组在文件linux/arch/arm/plat-s3c24xx/dma.c中定义,对于S3C2440这个结构体又四个元素,

//每个元素代表一个DMA通道。这个循环是要初始化每一个通道的中断号,通道寄存器基地址等字段。
  cp = &s3c2410_chans[channel] ;//

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

  /* dma channel irqs are in order.. */
  cp->number = channel;    //通道号
  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;
}

(2)

//该函数是将文件linux/arch/arm/mach-s3c2440/dma.c中初始化的结构体s3c2440_dma_order。

//拷到文件linux/arch/arm/plat-s3c24xx/dma.c中来,并用指针dma_order指向

//在文件linux/arch/arm/plat-s3c24xx/dma.c中实现了一些通用的DMA操作函数,这些函数

//将会有对s3c2440_dma_order的操作。

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

(3)

//该函数是将文件linux/arch/arm/mach-s3c2440/dma.c中初始化的结构体s3c2440_dma_sel

//拷到文件linux/arch/arm/plat-s3c24xx/dma.c中来。

//在文件linux/arch/arm/plat-s3c24xx/dma.c中实现了一些通用的DMA操作函数,这些函数

//将会有对s3c2440_dma_sel的操作。

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

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