linux spi驱动分析(基于STM32)

linux kernel 版本2.6.30, spi驱动基于platform_device, platform_driver驱动模型来编写.

SPI分为主从设备,一个主设备下可心挂接多个从设备,linux驱动中使用struct spi_master结构来表示一个主设备,
使用struct spi_device表示一个从设备.从设备通过spi_device->master指针来表示挂接到哪个主设备下.

一个CPU中一般都会有多个SPI主设备,SPI不同的主设备通过spi_master->bus_num来区分.
一般在定义platform_device的时候,通过platform_device->id来指定SPI主设备的bus_num号(在probe函数中,
spi_master->bus_num = platform_device->id).

总的来说,linux下的spi驱动可以分为两部分,
 一个是SPI主控器的驱动(主设备),这部分的驱动主要负责将数据一个字节一个字节的从SPI总线上传出去,或从SPI总线上
一个字节一个字节的读回来,它不用去理解几个字节组合起来是什么意思.
 另一部分驱动是从设备的驱动,因为从设备不同,驱动也不一样,主设备的驱动可以只有一个,但是如果从设备有好几个,那么
从设备的驱动也可能有好几个.如一个SPI主设备下挂有一个1M的SPI Flash,一个SPI的时钟芯片.对SPI Flash来说,往0xFFFFF地址
写数据,协议可能是这样:
字节0 字节1 字节2 字节3 字节4
地址0 地址1 地址2 地址3 数据
而往SPI时钟芯片写数据,协议可能是这样:
字节0        字节1
寄存器地址   寄存器值
所以从设备驱动就必须理解几个字节组合起来时,表示什么意思.如拿上面的SPI Flash来举例,从设备写5个数据,那前4个字节表示
SPI Flash的地址,最后一个字节表示往这个地址写的数据.从设备驱动写这5个字节时,最后会通过主设备驱动写到SPI总线上的(主
从驱动通过struct spi_message传输数据),SPI主设备驱动并不知道5个字节是什么意思,它只需要将从设备驱动传给它的这5个字
节写到SPI总线上就可以了.

platform_device驱动:
static struct platform_device spi_stm32_dev1 = {
 .name           = "spi_stm32",
 .id             = 0,
 .num_resources  = ARRAY_SIZE(spi_stm32_dev1_resources),
 .resource       = spi_stm32_dev1_resources,
};

static struct platform_device spi_stm32_dev2 = {
 .name           = "spi_stm32",
 .id             = 1,
 .num_resources  = ARRAY_SIZE(spi_stm32_dev2_resources),
 .resource       = spi_stm32_dev2_resources,
};

platform_device->id区别不同的spi主设备.
  
  platform_device_register(&spi_stm32_dev1); // 注册platform_device
  
  // 以下的信息是从设备驱动的信息
  // spi flash分区信息
  static struct mtd_partition
 |---------> spi_stm32_flash_partitions__stm32f4_som[] = {
 |  {
 |   .name = "spi_flash_part0",
 |   .size = FLASH_JFFS2_OFFSET__STM32F4_SOM,
 |   .offset = 0,
 |  },
 |  {
 |   .name = "spi_flash_part1",
 |   .size = FLASH_SIZE__STM32F4_SOM -
 |    FLASH_JFFS2_OFFSET__STM32F4_SOM,
 |   .offset = FLASH_JFFS2_OFFSET__STM32F4_SOM,
 |  },
 | };
 | // 分区信息和从设备驱动名称, 会有一个对应的从设备驱动(.name也="m25p32")
 | static struct flash_platform_data
 |  spi_stm32_flash_data__stm32f4_som = {      <---------|
 |  .name = "m25p32",       |
 -------------.parts =  spi_stm32_flash_partitions__stm32f4_som,  |
   .nr_parts =         |
   ARRAY_SIZE(spi_stm32_flash_partitions__stm32f4_som),    |
   .type = "m25p32",       |
  };          |
            |
  static struct spi_stm32_slv       |
 |-------> spi_stm32_flash_slv__stm32f4_som  = {    |
 |  .cs_gpio = SPI_FLASH_CS_GPIO__STM32F4_SOM,   |
 |  .timeout = 3,        |
 | };          |
 | static struct spi_board_info       |
 |  spi_stm32_flash_info__stm32f4_som = {    |
 |           |
 |  .modalias = "m25p32",      |
 |  .platform_data = &spi_stm32_flash_data__stm32f4_som, ----
 |  .max_speed_hz = 25000000,
 |  .bus_num = 4,
 |  .chip_select = 0,
 --------------.controller_data = &spi_stm32_flash_slv__stm32f4_som,
   .mode = SPI_MODE_3,
  };
  
  //注册从设备信息, 只将spi_board_info挂到全局链表board_list
  //struct spi_board_info 结构包含了从设备的所有信息
  spi_register_board_info(&spi_stm32_flash_info__stm32f4_som,
   sizeof(spi_stm32_flash_info__stm32f4_som) /
   sizeof(struct spi_board_info));


platform_driver驱动:

static int __devinit spi_stm32_probe(struct platform_device *dev)
{
 struct spi_master *m = NULL;
 struct spi_stm32 *c = NULL;
 int bus

 ......

 bus = dev->id;
 // 分配spi_master,sizeof *c为私有数据的大小, 通过spi_master_get_devdata()获取该私有数据
 m = spi_alloc_master(&dev->dev, sizeof *c);

 c = spi_master_get_devdata(m);
 m->bus_num = bus; // spi主设备id
 
 // 通过workqueue处理实际的数据传输
 c->workqueue = create_singlethread_workqueue(dev_name(&dev->dev));
 INIT_WORK(&c->work, spi_stm32_handle); // spi_stm32_handle将数据通过spi总线上传送 
 
 m->setup = spi_stm32_setup;
 m->cleanup = spi_stm32_cleanup;
 m->transfer = spi_stm32_transfer; // 当有数据传输时,spi设备驱动会调用此函数,该函数会调用queue_work,触发spi_stm32_handle函数进行数据传输
 c->slave = NULL; 
 ret = spi_register_master(m);// 注册spi_master 
}

spi_register_master调用scan_boardinfo函数,将spi设备与spi_master关联

void scan_boardinfo(struct spi_master *master)
{
 list_for_each_entry(bi, &board_list, list) {
  struct spi_board_info *chip = bi->board_info;
  unsigned  n;

  for (n = bi->n_board_info; n > 0; n--, chip++) {
   if (chip->bus_num != master->bus_num) // 设备的bus_num与spi_master的bus_num是否一样
    continue;
   (void) spi_new_device(master, chip);
  }
 }
}

spi_new_device调用spi_alloc_device分配一个struct spi_device, 将spi_device->master指向scan_boardinfo参数master,
这样spi_device与spi_master就关联起来了,spi_alloc_device还将spi_board_info信息copy过来.

下面来看一下从设备驱动
从设备驱动在/driver/mtd/devices/m25p80.c中

static struct spi_driver m25p80_driver = {
 .id_table = m25p_ids,
 .probe = m25p_probe,
 .remove = __devexit_p(m25p_remove),
}

m25p_ids中有"m25p32",当内核初始化时检查到与spi_board_info->modalias相等时,调用m25p_probe函数,
该函数中主要工作:

1.  注册spi_device的操作函数
 flash->mtd.erase = m25p80_erase;
 flash->mtd.read = m25p80_read;
 flash->mtd.write = m25p80_write;
2.     调用add_mtd_partitions注册分区信息

该函数中有一个结构struct m25p, 该结构中有:
 struct spi_device *spi;
 struct mtd_info mtd;
 这样spi_device和mtd设备就可以关联起来了.
 
拿m25p80_write来举例:
......
 struct m25p *flash = mtd_to_m25p(mtd);
 struct spi_transfer t[2];
 struct spi_message m; // 从设备驱动与主设备驱动以struct spi_message结构来交互数据

 // t[0]传输的是write命令和地址
 t[0].tx_buf = flash->command;
 t[0].len = m25p_cmdsz(flash);
 spi_message_add_tail(&t[0], &m);
 // t[1]传输的是要写进去的数据
 t[1].tx_buf = buf; // 传进来的数据缓冲区和长度
 t[1].len    = len;
 spi_message_add_tail(&t[1], &m);
 
 flash->command[0] = OPCODE_PP; // program命令
 m25p_addr2cmd(flash, to, flash->command);  //地址存到flash->command[1], flash->command[2], flash->command[3], to为要写到spi flash中的地址
 
 //设置好命令和数据之后,调用spi_sync传输数据
 spi_sync(flash->spi, &m);

 spi_sync调用spi_async, spi_async调用spi_master->transfer来将数据传给主设备驱动.

 spi_master->transfer=spi_stm32_transfer:
 //该函数只是简单的将struct spi_message结挂到c->queue队列上,然后调用queue_work函数
 list_add_tail(&msg->queue, &c->queue);
 queue_work(c->workqueue, &c->work);

 调用queue_work之后,内核会在某一时候调用spi_stm32_handle,这部分属于内核工作队列的内容,可以找相关资料看一下.
 spi_stm32_handle最终会调用spi_stm32_hw_txfifo_put函数,将数据写到spi总线上.
 

 
 
 
 
 


 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值