内核版本:linux2.6.32.2
硬件资源:s3c2440
参考: 韦东山SPI视频教程
内容概括:
1、I2C 驱动框架回顾
2、SPI 框架简单介绍
3、master 驱动框架
3.1 驱动侧
3.2 设备侧
4、SPI 设备驱动框架
4.1 设备册
4.2 驱动侧
5、设备驱动程序实例
1、I2C 驱动框架回顾
在前面学习 I2C 驱动程序的时候我们知道,I2C 驱动框架分为两层,一层是控制器驱动程序 i2c_adapter,它一般是由芯片厂商写好的,主要提供一个 algorithm 底层的 i2c 协议的收发函数。i2c_adapter 驱动是基于 platform 模型,在driver侧的probe函数里,取出资源信息进行设置,最后将adapter注册到i2c_bus_type,注册时会调用 i2c_scan_static_board_info,扫描并使用 i2c_new_device 创建设备(设备层的设备)。我们还提到了4种创建device的方式。
另一层是设备驱动层,基于 i2c_bus_type ,这个就很简单了,在设备驱动层 device 只需要提供一个从设备地址和名字,在 driver 里使用 i2c_smbus_read_byte_data 等类似的函数进行收发即可了,i2c_smbus_read_byte_data 等函数最终就会调用到 我们的 i2c_adapter->algorithm 里的收发函数进行收发。
2、SPI 框架简单介绍
对于SPI的大框架,与I2C是完全一致的,也分为两层,控制器驱动程序层叫 spi_master ,主要提供transfer函数,进行spi协议的收发。spi_master 也是基于 Platform 模型的,注册 spi_master 时也会扫描一个链表进行注册设备,简直太相似了。
另一层是设备驱动层,基于 spi_bus_type,相比 i2c 在device中需要提供的信息多一些,需要有名字、片选、最大速率、模式、中断号等等,在driver里则使用spi_read、spi_writer 等函数,最终也会调用到 master->transfer 函数进行发送接收。
相比 I2C ,SPI驱动的框架是要简单的,因为它少了两种注册device的方式,另外它不需要像I2C一样去发送Start信号和设备地址去探测设备,SPI只需要片选选中就行了。但是它的底层收发的控制,相对I2C要复杂一点,毕竟4根线。
3、master 驱动框架
之前,分析的驱动程序都是 S3C2410S3C2440 平台的,由于我的开发板 Mini2440 没有SPI设备,因此厂商带的内核里关于 SPI 驱动部分不完整,而且在s3c23xx_spi_probe函数里注册master的时候写的十分复杂,干扰信息太多,不适合分析学习,因此,我搜索了一下其他平台的代码,发现 atmel_spi.c (driversspi),里 atmel 实现的底层控制器驱动简单清晰多了,因此就拿它开刀,分析Master驱动框架。
3.1 驱动侧
前面简介里,我提到 master 驱动框架是基于 platform 平台的(我分析的这俩都是,其它的不清楚),那么肯定就要注册platform_driver了,下面我们就开看看。
分配一个platfrom_driver结构
static struct platform_driver atmel_spi_driver = {
.driver = {
.name = "atmel_spi",
.owner = THIS_MODULE,
},
.suspend = atmel_spi_suspend,
.resume = atmel_spi_resume,
.remove = __exit_p(atmel_spi_remove),
};
将 atmel_spi_driver 注册到 platform_bus_type ,匹配设备 probe
static int __init atmel_spi_init(void)
{
return platform_driver_probe(&atmel_spi_driver, atmel_spi_probe);
}
我们之前都是将 probe 函数,直接放在driver结构体里,这里不是,而是调用了 platform_driver_probe ,就不贴代码了,还看段函数介绍,大致了解下什么意思。
/**
* platform_driver_probe - register driver for non-hotpluggable device
* @drv: platform driver structure
* @probe: the driver probe routine, probably from an __init section
*
* Use this instead of platform_driver_register() when you know the device
* is not hotpluggable and has already been registered, and you want to
* remove its run-once probe() infrastructure from memory after the driver
* has bound to the device.
*
* One typical use for this would be with drivers for controllers integrated
* into system-on-chip processors, where the controller devices have been
* configured as part of board setup.
*
* Returns zero if the driver registered and bound to a device, else returns
* a negative error code and with the driver not registered.
*/
1、适用于非热插拔设备
2、通常Probe位于__init段3、当你知道device是非热拔插的,而且设备已经被注册了,而且你想在probe函数调用一次之后就销毁它节省空间,使用 platform_driver_probe 而非 platform_driver_register。
4、一个典型的应用是,用在完整的控制器驱动,控制器设备被当作 board setup 的一部分(在板子初始化的时候,设备就已经被注册了,放在board_info里)
5、返回0 ,如果driver注册成功且匹配到一个device ,以后再也无法被别的device probe了。
6、否则,返回一个错误,且driver未注册。
显然,我们写的正式一个控制器驱动程序,设备侧确实是早已注册(后边会讲)。
疑问:有人说使用 platform_driver_probe 时 driver 只能被一个 device 匹配绑定,之后再也无法被别的device probe,难道说,我有俩spi控制器还需要写两个控制器驱动程序么?我认为这种说法是不对的,我猜大概是driver注册时,会匹配一遍device链表,把能支持的device都probe,之后再有deivce注册进来就不行了。这个有待验证。
i2c驱动框架里,是在driver->probe 分配设置注册adapter,想必spi也是在driver->probe里分配设置注册master。
static int __init atmel_spi_probe(struct platform_device *pdev)
{
struct resource *regs;
int irq;
struct clk *clk;
int ret;
struct spi_master *master;
struct atmel_spi *as;
/* 获取 device 侧提供的Io内存以及中断 */
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq = platform_get_irq(pdev, 0);
/* 获取 spi 时钟,一会好使能它 */
clk = clk_get(&pdev->dev, "spi_clk");
/* 分配一个spi_master结构 额外加上一个 atmel_spi 用来存放其它信息 */
master = spi_alloc_master(&pdev->dev, sizeof *as);
/* 设置 master */
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; // 所支持的模式
master->bus_num = pdev->id; // 控制器编号,用于分辨外围spi设备是连接在哪一个控制器上
master->num_chipselect = 4; // 片选最大值+1,spi设备的片选值要小于它
master->setup = atmel_spi_setup; // 一个控制器上可能接有多个spi设备,它们的频率和模式是不一样的,用于设备之间切换时设置这些信息。
master->transfer = atmel_spi_transfer; // 最重要的发送函数
master->cleanup = atmel_spi_cleanup;
/* 将 Master 放入 pdev->dev->p->driver_data 里*/
platform_set_drvdata(pdev, master);
/* as 指向 master->dev->p->driver_data ,填充多出来那个 atmel_spi 结构 */
as = spi_master_get_devdata(master);
as->buffer = dma_alloc_coherent(&pdev->dev, BUFFER_SIZE,
&as->buffer_dma, GFP_KERNEL);
spin_lock_init(&as->lock);
INIT_LIST_HEAD(&as->queue);
as->pdev = pdev;
as->regs = ioremap(regs->start, (regs->end - regs->start) + 1);
as->irq = irq;
as->clk = clk;
/* 注册中断 使能时钟 */
ret = request_irq(irq, atmel_spi_interrupt, 0,
dev_name(&pdev->dev), master);
clk_enable(clk);
/* 设置硬件寄存器 */
spi_writel(as, CR, SPI_BIT(SWRST));
spi_writel(as, CR, SPI_BIT(SWRST)); /* AT91SAM9263 Rev B workaround */
spi_writel(as, MR, SPI_BIT(MSTR) | SPI_BIT(MODFDIS));
spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
spi_writel(as, CR, SPI_BIT(SPIEN));
/* 注册master */
ret = spi_register_master(master);
return 0;
}
对于master的设置过程注释已经说的很明白了,我们还得看看分配和注册过程。
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
{
struct spi_master *master;
master = kzalloc(size + sizeof *master, GFP_KERNEL);
device_initialize(&master->dev); // 初始化设备
master->dev.class = &spi_master_class;
master->dev.parent = get_device(dev); // 在 sysfs 平台设备xxx目录下创建目录
spi_master_set_devdata(master, &master[1]);
return master;
}
1、spi_alloc_master 实际申请的内存大小为一个struct master + struct atmel_spi,并用master->dev->p->driver_data 指向这个多出来的 struct atmel_spi 空间,用来存放 master 的中断 、寄存器等东西。
2、初始化 master->dev ,设置它的父设备等。
int spi_register_master(struct spi_master *master)
{
/* 将master注册到内核中去 */
dev_set_name(&master->dev, "spi%u", master->bus_num);
status = device_add(&master->dev);
/* 扫描spi设备信息,创建设备 */
scan_boardinfo(master);
}
1、设置 master->dev 的名字,例如 spi0、spi1 ...
2、device_add 注册设备
3、扫描spi设备信息:scan_boardinfo(master)
static void scan_boardinfo(struct spi_master *master)
{
struct boardinfo *bi;
mutex_lock(&board_lock);
list_for_each_entry(bi, &board_list, list) {
struct spi_board_info *chip = bi->board_info;
unsigned n;
/* 如果说 board_info 提供的bus_num 和 master—>bus_num 一致,则调用 spi_new_device */
for (n = bi->n_board_info; n > 0; n--, chip++) {
if (chip->bus_num != master->bus_num)
continue;
(void) spi_new_device(master, chip); // 我们放到设备驱动层,在分析它