linux 将devices 下spi 驱动注册为设备?,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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值