Linux SPI字符设备驱动开发(三)- 芯片中SPI主控制器驱动的注册

本文以海思hisi3518EV200 芯片提供的原生SDK:Linux3.4内核为例说明

主控制器的驱动在Linux内核源码的 “inux-3.4.y/drivers/spi/spi.c”文件中,我们通过分析这个文件来学习

一、spi_init(void)

在这里插入图片描述它是芯片SPI功能的初始化函数。其中:

 - kmalloc(SPI_BUFSIZ, GFP_KERNEL);  // 分配 SPI buf 内存
 - bus_register(&spi_bus_type); 
   //注册SPI总线。
   //只有注册了 SPI总线,将来从设备才能通过SPI总线注册驱动
   //执行结束后,会在系统/sys/bus 目录下生成 spi 子目录
 - class_register(&spi_master_class);  
   //注册spi类,
   //执行结束后会在 /sys/spi 目录下生成 spi_master
   //相当于向系统注册的芯片上的 SPI控制器
  
   
 - 参数 spi_bus_type 的定义也在本文件中,如下:
struct bus_type spi_bus_type = {
 .name  = "spi",
 .dev_attrs = spi_dev_attrs,
 .match  = spi_match_device,  //本文单独介绍
 .uevent  = spi_uevent,
 .pm  = &spi_pm,  //和电源管理相关的结构体
};
由这个结构体可知,芯片中SPI总线的名字就叫“spi

二、spi_match_device(struct device *dev, struct device_driver *drv)

该函数在spi总线上匹配设备和设备驱动,用于驱动程序检查platform_device是否在其支持列表里。参数spi_device 和spi_driver 都是在写从设备驱动时我们自己编程的

static int spi_match_device(struct device *dev, struct device_driver *drv)
{
	const struct spi_device *spi = to_spi_device(dev);
 	const struct spi_driver *sdrv = to_spi_driver(drv);

	/* Attempt an OF style match */
	/* 不匹配返回 0;匹配返回非 0,指向 struct of_device_id类型的指针
         * dev:需要查找的设备; drv:驱动程序结构体
         */
 	if (of_driver_match_device(dev, drv))
  		return 1;
	
	/*在驱动中查找设备ID,找到则返回真,否则假 */
	if (sdrv->id_table)
  		return !!spi_match_id(sdrv->id_table, spi);
	
	/* * 
	 * 比较设备别名和驱动名称,匹配则返回真 
	 * 如果匹配,驱动里的probe函数将会被调用
	 * */
	return strcmp(spi->modalias, drv->name) == 0;
}

函数调用追踪:of_driver_match_device -> of_match_device -> of_match_node (匹配设备和驱动的设备号)

  1. spi_device: 描述SPI设备信息
struct spi_device {
	struct device  dev; //嵌入到设备模型中用的
 	struct spi_master *master;//芯片上的spi主控制器,每个控制器对应一个master。
 	                          //一个设备只对应一个 master,但可以有多个从设备
 	                          
	u32   max_speed_hz; //设置最大的始终频率
	u8   chip_select;   //spi片选位,一般 SPI的 CS位为 0时,spi开始工作
 	u8   mode;          //根据时钟相位 CPHA(0或1)和时钟极性 CPOL(0或1)的不同组合,
 	                    //将spi分成四种模式
 	                    
#define SPI_CPHA 0x01   /* clock phase */
#define SPI_CPOL 0x02   /* clock polarity */
#define SPI_MODE_0 (0|0)   /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04   /* chipselect active high? */
#define SPI_LSB_FIRST 0x08   /* per-word bits-on-wire */
#define SPI_3WIRE 0x10   /* SI/SO signals shared */
#define SPI_LOOP 0x20   /* loopback mode */
#define SPI_NO_CS 0x40   /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80   /* slave pulls low to pause */
	
	u8   bits_per_word; //数据传输的单位,如果一次传输一个字节,则设为8;
			    //如果一次传输一个字节,则设为16;
 	int   irq;          //中断号。
 	void   *controller_state;//一般设置为 NULL 即可
 	void   *controller_data;
	char   modalias[SPI_NAME_SIZE];  //很重要!一般设备和驱动能否匹配就要看它,
	                   //编写从设备驱动时设备和驱动要写成相同的modalias才能匹配
 /*
 1. likely need more hooks for more protocol options affecting how
 2. the controller talks to each chip, like:
 3.  - memory packing (12 bit samples into low bits, others zeroed)
 4.  - priority
 5.  - drop chipselect after each word
 6.  - chipselect delays
 7.  - ...
  */
};

下图是spi的四种工作模式。
8. CPOL表示空闲时(没有进行数据传输)时钟信号的电平,CPOL=0表示低电平(mode 0、mode 1),数据传输CPOL=1表示高电平(mode 2 、mode 3)。
9. 每个时钟周期都有两次电平的跳变,上升沿和下降沿,CPHA就表示在每个时钟周期里是第一个边沿采样数据还是第二个边沿采样数据,具体第一个边沿或者第二个边沿是上升沿还是下降沿则由CPOL决定。
在这里插入图片描述
2. spi_driver: 描述SPI驱动信息

struct spi_driver {
 	const struct spi_device_id *id_table; //驱动中包含支持的设备 ID
 	int   (*probe)(struct spi_device *spi);
 	int   (*remove)(struct spi_device *spi);
	void   (*shutdown)(struct spi_device *spi);
	int   (*suspend)(struct spi_device *spi, pm_message_t mesg);
	int   (*resume)(struct spi_device *spi);
	struct device_driver driver;
};

总结:spi_init()主要做第三件事:
1. 初始化SPI缓存,为SPI分配内存
2. 注册SPI总线,匹配外设的设备和驱动,使得spi接口的外部驱动模块可以加载到内核
3. 注册芯片上的spi主控制器(spi_master)

这是驱动功能的整体初始化,那么,在具体实现的层面,spi_master主控制器是如何注册进系统内核,又是如何与外设驱动匹配的呢?

在前面的博文 SPI驱动框架 中已经提到,芯片上的SPI主控制器是一个平台设备,通过 Platform 总线注册进内核,注册过程中,通过platform_deviceplatform_driver 分别描述SPI主控制器的设备和驱动信息,先看下这两个结构体。

三、platform_device

在 Hi3518E_SDK_V1.0.4.0/osdrv/opensource/kernel/linux-3.4.y/drivers/spi$ 目录下的 spi-hisilicon.c 文件中:
(备注:你跟我如果用的不是一款芯片,可以试试在 arch/arm/machXXX/dev-spi.c 文件中查找这些调用,每个芯片对应的位置是不同的)

static struct platform_device *hi_spi_platform_devices[] = {
 &hi_spi0_platform_device,
 &hi_spi1_platform_device,
};

如上,hisi3518EV200 芯片提供了2个SPI接口,spi0(支持3.3/1.8V输入)和spi1(仅支持3.3V输入),所以在芯片的原生SDK中声明了2个spi接口的 platform_device 。

static struct platform_device hi_spi0_platform_device = {
 .name           = "hi-spi-master",
 .id             = 0,
 .num_resources  = ARRAY_SIZE(hi_spi0_resource),
 .resource       = hi_spi0_resource,
};

如上,是 spi1 的platform_device。

  • .name: 指定spi主控制器 platform_device 的名字,驱动platform_driver 能否与这个设备匹配,就看名字是否一致。所以platform_driver 的 name 也得是这个。
  • .id: 2个接口。spi0就写0;spi1 就写1。
  • .num_resources和 .resource: 这些是关于IO口资源、DMA资源和中断资源的,芯片已经给定,我们不需要关心。

将来调用 platform_add_devices函数, 通过 platform_device 结构体就可以将SPI0设备注册到platform总线上。

四、platform_driver

static struct platform_driver hi_spi_platform_driver = {
	.probe  = hi_spi_probe,
  	.remove  = hi_spi_remove,
 	.suspend = hi_spi_suspend,
 	.resume  = hi_spi_resume,
	.driver  = {
		.name = "hi-spi-master",
		.owner = THIS_MODULE,
 	},
};

如上,platform_driver的声明在同一文件中。spi0 和spi1 性质完全一样,所以驱动只要有一个就行,无需再分spi0 和spi1。

  • .probe: spi_master 注册的关键函数,当platform_device 和 platform_driver 匹配成功时,该函数执行,开始注册spi_master。
  • .remove: 从platform 总线上卸载SPI主控制器(spi_master )
  • .suspend:使spi_master睡眠,不工作
  • .resume:唤醒spi_master,使其开始正常工作
  • .name:必须与 platform_device 结构体中的name一一致,设备和驱动才能匹配成功
  • .owner:驱动属于哪个模块,一般都是 THIS_MODULE

五、platform初始化

内核kernel 启动初始化时,会调用 hi_spi_init() 函数,将platform_device 和 platform_driver注册到platform总线上并且根据name 进行匹配。

platform_driver_register(&hi_spi_platform_driver);
//注册platform_driver

hi_spi_set_platdata(hi_spi_pds[i], hi_spi_platform_devices[i]);
    
platform_add_devices(hi_spi_platform_devices, ARRAY_SIZE(hi_spi_platform_devices));
//注册platform_device

六、hi_spi_probe()函数

匹配成功后,开始执行hi_spi_probe()函数。它调用了以下几个关键函数:

spi_alloc_master(&pdev->dev,sizeof(struct hi_spi_driver_data));
spi_register_master(master);

1.spi_alloc_master(&pdev->dev,sizeof(struct hi_spi_driver_data));
功能:分配master结构体,程序中一个spi主控制器用一个master来描述

struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
{
	struct spi_master *master;
	if (!dev)
  		return NULL;

	/*为 master开辟内存,包括 master结构体大小和驱动信息(size)*/
	master = kzalloc(size + sizeof *master, GFP_KERNEL);
 	if (!master)
  		return NULL;
  		
	/*设备模型中的初始化设备函数,直接调用*/
	device_initialize(&master->dev);
	
	/*spi_master_class在spi子系统初始化时已经注册好了(hi_init)*/
 	master->dev.class = &spi_master_class;
 	
	/*设置当前设备的父设备,关于设备模型的*/
 	master->dev.parent = get_device(dev);
 	
 	spi_master_set_devdata(master, &master[1]);
 	return master;
}

2.spi_register_master(master);
功能:注册spi_master控制器
(这里只挑函数中重点代码进行解析)

int spi_register_master(struct spi_master *master)
{	
	(省略部分代码.../*
	 *SPI主控制器支持的片选数不能为0,否则还怎么挂接从设备啊.
	 *一个接口对应一个master,一个master对应一条SPI总线,
	 *一条总线上可能挂有多个设备,num_chipselect就表示该总线上的设备数
	 */
	if (master->num_chipselect == 0)
  		return -EINVAL;

	/*如果总线号bus_num小于 0 则动态分配一个总线号*/
	if (master->bus_num < 0) {
 	 /* FIXME switch to an IDR based scheme, something like
  	  * I2C now uses, so we can't run out of "dynamic" IDs
 	  */
	  	master->bus_num = atomic_dec_return(&dyn_bus_id);
 	 	dynamic = 1;
 	 }
	
	/*把master加入到设备模型中*/
	dev_set_name(&master->dev, "spi%u", master->bus_num);
 	status = device_add(&master->dev);
 	if (status < 0)
  	goto done;
 	dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev),
   		dynamic ? " (dynamic)" : "");

执行到这里,spi主控制器就注册完成,下一步就是注册spi外设的设备和驱动,以及主控制器和外设之间的匹配!

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值