Linux下SPI驱动的分析

1、platform_device platform_driver之间的关系:

platform_device 是板卡信息,可能有的板卡有SPI控制器,但没有使用SPI总线的时候,就会存在platform_device中没有SPI相关的板卡信息。这样系统就不会加载SPI驱动到系统中。板卡相关的信息一般在目录arch/arm/(S3C6410为例,arch\arm\plat-s5p\dev_spi.c)

板卡的主要作用是将板卡的相关信息传递给与体系结构相关的控制器驱动,让其进行配置或其他的处理(例如SPI的寄存器信息,中断号信息、管脚信息)。一句话,platform_device是信息的提供者,控制器的驱动则是信息的使用者。下面我们看看platform_device的结构体的定义:


struct platform_device {
	const char		* name;
	int				id;
	struct device		dev;
	u32				num_resources;
	struct resource	* resource;

	const struct platform_device_id	*id_entry;

	/* MFD cell pointer */
	struct mfd_cell 	*mfd_cell;
	/* arch specific additions */
	struct pdev_archdata	archdata;
};

struct platform_device s3c64xx_device_spi0 = {
	.name		  = "s3c64xx-spi",
	.id		  = 0,
	.num_resources	  = ARRAY_SIZE(s3c64xx_spi0_resource),
	.resource	  = s3c64xx_spi0_resource,
	.dev = {
		.dma_mask		= &spi_dmamask,
		.coherent_dma_mask	= DMA_BIT_MASK(32),
		.platform_data = &s3c64xx_spi0_pdata,
	},
};

从上面的结构体中可以看出,里面有个device的结构体,但是具体有上面用了?现在我也还不知道。是不是与系统中的设备有关系呢?因为板卡注册一个platform的设备,在系统中,也会唯一的出现一个设备。由此推断,该device可能在板卡注册时就已经形成了一个设备链。是不是这样呢,还得下回分解。

 

下面我们开始看看platform_driver。该功能的源码一般在driver目录下,SPI的驱动在/driver/spi/目录下的spi_s3c64xx.c文件中。首先我们先看看platform_driver的结构体是什么样的呢?

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
};


可以看到该结果提供了以下功能。

先看看spi_s3c64xx.c 中定义的 platform_driver结构体:

static struct platform_driver s3c64xx_spi_driver = {
	.driver = {
		.name	= "s3c64xx-spi",
		.owner = THIS_MODULE,
	},
	.remove = s3c64xx_spi_remove,
	.suspend = s3c64xx_spi_suspend,
	.resume = s3c64xx_spi_resume,
};
MODULE_ALIAS("platform:s3c64xx-spi");

static int __init s3c64xx_spi_init(void)
{
	return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);
}
subsys_initcall(s3c64xx_spi_init);

static void __exit s3c64xx_spi_exit(void)
{
	platform_driver_unregister(&s3c64xx_spi_driver);
}
module_exit(s3c64xx_spi_exit);

很重要的一点是paltform_driver中的name字段必须和platform_device中的注册的SPI设备中name字段必须一致。否则,系统虚拟的paltform_bus无法匹配设备和驱动,就会造成设备不能被识别。下面我们具体的看看这个文件到底实现了什么功能。它到底又为外部提供了什么样的功能呢?

从上面的源码中可以看到这样的语句subsys_initcall(s3c64xx_spi_init); 并且有__init标识,这就意味这在系统启动起来后,系统在初始化话子系统subsys的时候会调用s3c64xx_spi_init函数。现在我们看看在这个函数中调用的platform_driver_probe()函数到底是什么。可以看到函数将s3c64xx_spi_driverprobe指针指向了后面的s3c64xx_spi_probe函数。然后函数继续调用platform_driver_register()进行平台驱动的注册。可以看到这里开始注册平台驱动了哦。据说在平台驱动注册的时候,会遍历device_list列表(这里的确是这样的driver_register  -> bus_add_driver->driver_attach->bus_for_each_dev 这里就会遍历device list)查找是否存在相应的平台设备(这个是在bsp中添加的,内核中使用了name字段进行匹配的)。如果存在这个平台设备,这会调用平台驱动(struct device_driver 的元素)probe函数进行探测(相当于探测这个控制器是否存在,工作是否正常,这个是我这样的猜测的)。在platform_driver_register中可以看到struct device_driver probe函数指定为了platform_drv_probe,这就是内核会调用的函数。我再进到platform_drv_probe函数中可以看到此函数间接调用了s3c64xx_spi_driverprobes3c64xx_spi_probe

下面接着看看s3c64xx_spi_probe这里到底有做了些什么了?首先从platform_device中获取到设备的资源。然后申请一个spi_master的资源(这个结构体很特别,一个控制器对应一个spi_master的结构体,这个结构体封装了该控制器的操作),然后将SPI总线的建立函数setup()以及传输函数transfer()设置到master中。然后建立工作队列线程(create_singlethread_workqueue),并初始化SPI(s3c64xx_spi_hwinit),然后注册spi master控制器(spi_register_master函数是通用驱动其他的及linux内核提供的方法)。到这里驱动的加载流程基本已经完成。

接着我们分析分析,一个具体的协议设备又是怎么通过控制器来完成读写等操作的呢?这里需要提到spi的通用驱动。及linux为了将控制器与具体要控制的设备分开。这样做的好处就是具体的外部设备不依耐与某个具体的控制器。所以linux提供了一个通用的驱动将两则分离开,并为两则提供了接口。驱动开发者只需要实现相应的接口,就可以了。这里有必要理出通用驱动到底提供了那些接口,这些接口函数都定义在linux/driver/spi/spi.h中,以下将对该接口进行归纳:

 

//以下接口是为控制器驱动提供的,也就是说控制器驱动完成spi master结构的配置后,需要将自己注册进去。
extern struct spi_master  *spi_alloc_master(struct device *host, unsigned size);
extern int spi_register_master(struct spi_master *master);
extern void spi_unregister_master(struct spi_master *master);
extern struct spi_master *spi_busnum_to_master(u16 busnum);

//以下的接口是为具体的设备驱动提供的,具体的设备驱动可以初始化SPI控制器。并且通过spi_syn()和spi_asyn()进行数据的发送和接收。
extern int spi_setup(struct spi_device *spi);
extern int spi_async(struct spi_device *spi, struct spi_message *message);
extern int spi_async_locked(struct spi_device *spi,struct spi_message *message);

extern int spi_sync(struct spi_device *spi, struct spi_message *message);
extern int spi_sync_locked(struct spi_device *spi, struct spi_message *message);
extern int spi_bus_lock(struct spi_master *master);
extern int spi_bus_unlock(struct spi_master *master);

struct spi_transfer st[2]; 
struct spi_message  msg;  
write_enable( flash );  //写使能  
spi_message_init( &msg );  
memset( st, 0, sizeof(st) );  

flash->cmd[0] = CMD_PAGE_PROGRAM;  
flash->cmd[1] = to >> 16;  
flash->cmd[2] = to >> 8;  
flash->cmd[3] = to;  

st[ 0 ].tx_buf = flash->cmd;  
st[ 0 ].len = CMD_SZ;  
//填充spi_transfer,将transfer放在队列后面
spi_message_add_tail( &st[0], &msg );  

st[ 1 ].tx_buf = buf;  
st[ 1 ].len = len;  
spi_message_add_tail( &st[1], &msg );  
spi_sync( flash->spi, &msg );   
调用spi_master发送spi_message

要完成和SPI设备的数据传输工作,我们还需要另外两个数据结构:spi_messagespi_transferspi_message包含了一个的spi_transfer结构序列,一旦控制器接收了一个spi_message,其中的spi_transfer应该按顺序被发送,并且不能被其它spi_message打断,所以我们认为spi_message就是一次SPI数据交换的原子操作。控制器上可以同时被加入多个spi_message进行排队。另一个链表字段transfers则用于链接挂在本message下的spi_tranfer结构。complete回调函数则会在该message下的所有spi_transfer都被传输完成时被调用,以便通知协议驱动处理接收到的数据以及准备下一批需要发送的数据。到此SPI驱动已经介绍完成了。



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值