linux3.2 spi框架分析

Posted 紫枫术河

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux3.2 spi框架分析相关的知识,希望对你有一定的参考价值。

linux3.2 spi框架分析

刘术河 2017.04.12

oled的驱动时,核心板用的是am335xspi用的是ti自带的spi驱动框架,为了弄清楚spi底层工作流程,特意分析了spi驱动框架

g:嵌入式linux-3.2.0archarmmach-omap2Board-am335xevm.c

该文件是am335x的板级配置文件

1、配置spi的管脚复用功能

static struct pinmux_config spi1_pin_mux[] = {

{"mcasp0_aclkx.spi1_sclk",OMAP_MUX_MODE3|AM33XX_PULL_ENBL|AM33XX_INPUT_EN} //读取引脚接错了,接到了I2c引脚,不配置该引脚不行,上电是随机的,配置为i2c gpio,输入模式

{"spi0_cs0.gpio0_5",   OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},

{"mcasp0_axr0.spi1_d1",OMAP_MUX_MODE3|AM33XX_PULL_ENBL| AM33XX_INPUT_EN},

{"mcasp0_ahclkr.spi1_cs0", OMAP_MUX_MODE3 | AM33XX_PULL_ENBL

| AM33XX_PULL_UP | AM33XX_INPUT_EN},

//oled--dc   lsh  2017.06.15

{"spi0_d1.gpio0_4", OMAP_MUX_MODE7 | AM33XX_PIN_OUTPUT_PULLUP}

{NULL, 0},

};

这里硬件连错了一条线,走了一些弯路

2、配置spi的参数

static struct spi_board_info spi_info_oled_ssd1322[] = {

{

.modalias      = "oled_ssd1322",

.platform_data = GPIO_TO_PIN(0, 4),       /* oled_dc, spi_driver里使用 */

//.platform_data = GPIO_TO_PIN(0, 3),     /* 测试led*/

.irq           = -1,

.max_speed_hz  = 10000000,    //最大时钟4M

.bus_num       = 2,          //335xspi的哪个控制器

.chip_select   = 0,            /* oled_cs, 它的含义由 spi_maste确定 */

.mode = SPI_MODE_0 ,

},

};

这里要注意bus_num chip_select,他们的含义结构体并看不出确切的意思,必须要 分析驱动框架才能弄清楚

3、填充spi初始化函数

static void spi1_init(int evm_id, int profile)

{

setup_pin_mux(spi1_pin_mux);

spi_register_board_info(spi_info_oled_ssd1322,

ARRAY_SIZE(spi_info_oled_ssd1322));

return;

}

4、static struct evm_dev_cfg myd_am335x_dev_cfg[] = {

{spi1_init,DEV_ON_BASEBOARD, PROFILE_ALL},            

5、这个函数是随着板卡启动的时候被调用的

大概的流程

MACHINE_START(AM335XEVM, "am335xevm")

/* Maintainer: Texas Instruments */

.atag_offset = 0x100,

.map_io = am335x_evm_map_io,

.init_early = am33xx_init_early,

.init_irq = ti81xx_init_irq,

.handle_irq     = omap3_intc_handle_irq,

.timer = &omap3_am33xx_timer,

.init_machine = am335x_evm_init,

MACHINE_END

am335x_evm_init

   am335x_evm_setup();

setup_myd_am335x();

_configure_device(EVM_SK, myd_am335x_dev_cfg, PROFILE_NONE);

{spi1_init,DEV_ON_BASEBOARD, PROFILE_ALL},  

spi1_init

setup_pin_mux(spi1_pin_mux);

spi_register_board_info(spi_info_oled_ssd1322);

6、这个setup_pin_mux(spi1_pin_mux)就是配置管脚复用的

setup_pin_mux函数设置管脚复用

setup_pin_mux

omap_mux_init_signal

mux_mode = omap_mux_get_by_name(muxname, &partition, &mux);

old_mode = omap_mux_read(partition, mux->reg_offset);

omap_mux_write(partition, mux_mode, mux->reg_offset);

{

if (partition->flags & OMAP_MUX_REG_8BIT)

__raw_writeb(val, partition->base + reg);

else

__raw_writew(val, partition->base + reg);

}

7、spi_register_board_info(spi_info_oled_ssd1322);是重点分析对象,他是注册spi的板级信息

 

分析spi_register_board_info

1g:嵌入式linux-3.2.0driversspiSpi.c

   在这个文件里定义了spi_register_board_info函数,说明他是spi核心层

spi_register_board_info()

{

  struct boardinfo *bi;   定义一个spi板级信息

for (i = 0; i < n; i++, bi++, info++)  //这里要执行两次循环

struct spi_master *master; 分配一个spi主机结构体

memcpy(&bi->board_info, info, sizeof(*info));   把传进来的信息拷贝进去

list_add_tail(&bi->list, &board_list);     //将板级信息放进一个链表

list_for_each_entry(master, &spi_master_list, list)    //遍历master的链表每一项调 用下面的匹配函数

spi_match_master_to_boardinfo(master, &bi->board_info);

{

struct spi_device *dev;

if (master->bus_num != bi->bus_num)  //这里板级设置为2,这个目的是找到 spi设备是挂到哪个spi控制器上的

master->bus_num = 12

return;

dev = spi_new_device(master, bi);

}

上面的代码可以看出,是比较bus_num == bi->bus_num,如果不等直接退出, 相等就分配一个新的spi设备,

}

2、分析spi_new_device

spi_new_device

struct spi_device *proxy;

proxy = spi_alloc_device(master);

proxy->chip_select = chip->chip_select;      //这里板级设置为0

proxy->max_speed_hz = chip->max_speed_hz;

proxy->mode = chip->mode;

proxy->irq = chip->irq;

strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));

proxy->dev.platform_data = (void *) chip->platform_data;

//这里板级设置为GPIO_TO_PIN(0, 4),

proxy->controller_data = chip->controller_data;

proxy->controller_state = NULL;

 

spi_add_device(proxy);     //添加spi设备

3、分析spi_add_device

4、spi_add_device

if (spi->chip_select >= spi->master->num_chipselect) {return -EINVAL;}

//我们传入的chip_select = 0num_chipselect=暂时还不知道在哪设置的

搜索num_chipselect,发现一条结果

Omap_hwmod_33xx_data.c (archarmmach-omap2): .num_chipselect = 2,

这个文件是板级目录的公共文件,说明是ti官方定义的am335x系列该值为2

我们不满足出错条件继续下面的代码

bus_find_device_by_name(&spi_bus_type, NULL, dev_name(&spi->dev)); //主要是找设备

 spi_setup(spi);

{

bad_bits = spi->mode & ~spi->master->mode_bits;    //spi->mode=SPI_MODE_0=0

spi->master->setup(spi);  //这个setup函数在哪设置的?

device_add(&spi->dev);

搜索master->setup,找到一行

Spi-omap2-mcspi.c (driversspi): master->setup = omap2_mcspi_setup;

g:嵌入式linux-3.2.0driversspiSpi-omap2-mcspi.c

说明tispi核心层函数初始化放在这个文件,这个文件后面再分析

omap2_mcspi_probe

master->setup = omap2_mcspi_setup;

这个omap2_mcspi_setup函数就是spi->master->setup(spi);要调用的

进入omap2_mcspi_setup

omap2_mcspi_setup

主要是根据板级信息设置spi的模式

1、tandard 4-wire master mode

l |= OMAP2_MCSPI_CHCONF_DPE0;

2、/* wordlength */

3、/* set chipselect polarity; manage with FORCE */

4、/* set clock divisor */

5、/* set SPI mode 0..3 */

mcspi_write_chconf0(spi, l);

}

  

5、spi_add_device中的setup(spi)分析完成,接着分析device_add(&spi->dev);

device_add

  kobject_add(&dev->kobj, dev->kobj.parent, NULL)

device_create_file(dev, &uevent_attr);

MAJOR(dev->devt)

device_create_file(dev, &devt_attr);

device_create_sys_dev_entry(dev);

devtmpfs_create_node(dev);

device_add_class_symlinks(dev);

device_add_attrs(dev);

          //上面这些函数就是把spi设备挂在了sysfs文件系统上了

kobject_uevent(&dev->kobj, KOBJ_ADD);    //应该是通知文件系统更新目录

bus_probe_device(dev);

if (bus && bus->p->drivers_autoprobe)

//这个在总线注册时bus_register-priv->drivers_autoprobe = 1;

//这是总线探测的函数,感觉和平台设备很像

device_attach(dev);

bus_for_each_drv(dev->bus, NULL, dev, __device_attach);

会调用__device_attach

__device_attach

driver_match_device

return drv->bus->match ? drv->bus->match(dev, drv) : 1;

//这个match是在spi_alloc_device里设置的

spi_alloc_device

spi->dev.bus = &spi_bus_type;

//这个spi_bus_type就是 drv->bus

spi_bus_type

.match = spi_match_device,

//带这就找到了 drv->bus->match(dev, drv

 

driver_probe_device(drv, dev);

really_probe(dev, drv);

if (dev->bus->probe) {

ret = dev->bus->probe(dev);

if (ret)

goto probe_failed;

} else if (drv->probe) {

ret = drv->probe(dev);

//这个probe就是我的oledprobe函数

if (ret)

goto probe_failed;

}

 

 

//到这里可以明白,am335x的板级配置文件,其实是传入spiboard-info信息,然后调用根据传入的info新分配一个spi设备,并且把这个设备注册进总线,这个设备可以在文件系统的dev目录找到,并且注册这个spi设备的时候,通过spi虚拟总线找到oled drvprobe函数

 

//分析我注册的oled驱动,就是简单的调用spi提供的接口,去spi总线查找有没有同名的spi设备

g:嵌入式linux-3.2.0driversspiSpi_oled_drv.c

module_init(spi_oled_init);

spi_oled_init

//这一套spi总线和平台总线很像

spi_register_driver(&spi_oled_drv);    //这个接口是spi核心层函数提供的

sdrv->driver.bus = &spi_bus_type;

sdrv->driver.probe = spi_drv_probe;

sdrv->driver.remove = spi_drv_remove;

driver_register(&sdrv->driver);

bus_add_driver(drv)

 driver_attach(drv);

bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

__driver_attach

driver_match_device(drv, dev))

drv->bus->match ? drv->bus->match(dev, drv) : 1;

注:这个bus->match()就是spi_bus_type->spi_match_device

if (of_driver_match_device(dev, drv)) return 1;    //这个是比较设备树

if (sdrv->id_table)

return !!spi_match_id(sdrv->id_table, spi);    //这个是比较id_table

return strcmp(spi->modalias, drv->name) == 0;    //这是计较名字

 

这里是比较名字

注意看我的

板级信息 .modalias      = "oled_ssd1322", 是这个名字

驱动是   static struct spi_driver spi_oled_drv = {

.driver = {

.name = "oled_ssd1322",

.owner = THIS_MODULE,

},

.probe = spi_oled_probe,

.remove = __devexit_p(spi_oled_remove),

};

所以 if (!driver_match_device(drv, dev))  条件不会满足,接着执行以后的代码

 

driver_probe_device(drv, dev);

if (!device_is_registered(dev))  

return -ENODEV;

really_probe(dev, drv);

ret = drv->probe(dev);  会调用这个函数

这个函数就是注册spi驱动时系统分配的默认函数

sdrv->driver.probe = spi_drv_probe;

spi_drv_probe

const struct spi_driver *sdrv = to_spi_driver(dev->driver);

return sdrv->probe(to_spi_device(dev));

这里spi_driver->probe();就是我的驱动函数 .probe = spi_oled_probe,

//这里就找到了我注册的oled驱动的probe函数

分析一下:spi_oled_probe

static struct spi_device *spi_oled_dev;

 spi_oled_dev = spi;

 spi_oled_dc_pin = (int)spi->dev.platform_data;

gpio_request_one(spi_oled_dc_pin,GPIOF_OUT_INIT_LOW, "oled_dc");

 ker_buf = kmalloc(8192, GFP_KERNEL);

major = register_chrdev(0, "oled_ssd1322", &oled_ops);

class = class_create(THIS_MODULE, "oled_ssd1322");

device_create(class, NULL, MKDEV(major, 0), NULL, "oled_ssd1322"); /* /dev/oled_ssd1322

 

//spi设备的ops操作函数

static struct file_operations oled_ops = {

.owner            = THIS_MODULE,

.unlocked_ioctl   = oled_ioctl,

.write            = oled_write,

};

oled_write

spi_write(spi_oled_dev, ker_buf, count);

 

 

文档写到这里,把oled的设备层和驱动层分析完成,2个工作,

1、在板级信息里设置spi的参数,并且给oled起个名字:"oled_ssd1322",然后调用spi核心层的spi_register_board_info(),该函数是设置spi参数,并且分配spi设备注册进总线和文件系统,并且会去spi总线查找有没有同名的spi驱动

2、注册一个oled液晶屏驱动,他主要调用spi核心层的spi_register_driver(&spi_oled_drv);

它的首要任务,是从spi总线,查找有没有 name="oled_ssd1322"spi设备,有的话,才能进行驱动的注册和初始化,没有退出。

如果有设备,就执行probe函数,进行对spi设备的参数设置,这个参数是指oled屏幕参数。   

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

现在该分析spi主机控制器的驱动了

有一个问题是,我从哪个方向去确定master,可以从oled-spi的发送函数spi_write

spi_write

spi_message_init(&m);

spi_message_add_tail(&t, &m);

return spi_sync(spi, &m);

__spi_sync(spi, message, 0);

spi_async_locked(spi, message);

__spi_async(spi, message);

master->transfer(spi, message);

//这里就是调用spi主机控制器的发送函数了

这个spi_write 所在的文件g:嵌入式linux-3.2.0driversspiSpi.c

就是最核心的文件

这个文件有3个和注册相关函数

1、spi_register_board_info

2、spi_register_driver

3、spi_register_master

我已经分析了 12,就剩分析spi_register_master

 

分析spi_register_master

搜索哪些驱动调用这个注册函数

找到一条信息

Spi-omap2-mcspi.c (driversspi): status = spi_register_master(master);

g:嵌入式linux-3.2.0driversspiSpi-omap2-mcspi.c

该文件就是主机控制器驱动文件

分析该驱动

static int __init omap2_mcspi_init(void)

platform_driver_probe(&omap2_mcspi_driver, omap2_mcspi_probe);

这个主机控制器调用了平台驱动的模型

static struct platform_driver omap2_mcspi_driver = {

.driver = {

.name = "omap2_mcspi",

.owner = THIS_MODULE,

.pm = &omap2_mcspi_pm_ops

},

.remove = __exit_p(omap2_mcspi_remove),

};

先分析一下平台驱动模型

平台驱动核心层提供了

platform_device_register

platform_driver_register

先看platform_driver_register

struct bus_type platform_bus_type = {

.name = "platform",

.dev_attrs = platform_dev_attrs,

.match = platform_match,

.uevent = platform_uevent,

.pm = &platform_dev_pm_ops,

};

platform_driver_register

drv->driver.bus = &platform_bus_type;

drv->driver.probe = platform_drv_probe;

driver_register(&drv->driver);

bus_add_driver(drv);

 driver_attach(drv);

bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

driver_match_device(drv, dev)

drv->bus->match(dev, drv)相当于platform_match,

看看platform_match,

if (of_driver_match_device(dev, drv))

platform_match_id(pdrv->id_table, pdev)

(strcmp(pdev->name, drv->name) == 0)

driver_probe_device(drv, dev);

if (!device_is_registered(dev))

really_probe(dev, drv);

drv->probe(dev)

这里调用drv->driver.probe = platform_drv_probe;

platform_drv_probe;

struct platform_device *dev = to_platform_device(_dev);

 drv->probe(dev);  这个probe就相当于是 omap2_mcspi_probe

到这里发现平台设备和spi设备基本都差不多,内核可能就是想把spi设备单独做一条总线好区分

platform_device_register的分析类似不分析了,主要是spi控制器的地址信息

////////////////////////////////////////////////////////////////////////////////////////////////////

接着进行

platform_driver_probe(&omap2_mcspi_driver, omap2_mcspi_probe);

该探测函数,最终会调用omap2_mcspi_probe

分析omap2_mcspi_probe

omap2_mcspi_probe

struct spi_master *master;

master = spi_alloc_master(&pdev->dev, sizeof *mcspi);

master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;    //设置spi模式

master->setup = omap2_mcspi_setup;  //板级注册会调用这个函数

master->transfer = omap2_mcspi_transfer //spi发送函数,oledspi_write,最终调用它

master->num_chipselect = pdata->num_cs  ,这个值是在主机控制器设备注册时赋值

omap2_mcspi_master_setup(mcspi)

spi_register_master(master);           //注册主机控制器驱动

 

分析spi_register_master(master)

dev_set_name(&master->dev, "spi%u", master->bus_num);

  device_add(&master->dev);

    struct device *parent

  bus_add_device(dev);

bus_probe_device(dev);

            spi_match_master_to_boardinfo(master, &bi->board_info);   

//注意这里是master去匹配板级信息注册的spi设备

分析完成退回到platform_driver_probe(&omap2_mcspi_driver, omap2_mcspi_probe);

这里omap2_mcspi_driver .name ="omap2_mcspi",

肯定还有个同名的设备,它给平台驱动提供资源

搜索omap2_mcspi,发现一条信息

Devices.c (archarmmach-omap2): char *name = "omap2_mcspi";

g:嵌入式linux-3.2.0archarmmach-omap2Devices.c

这是am335x设备初始化集中注册的文件

omap_mcspi_init

char *name = "omap2_mcspi";

omap_device_build

omap_device_build_ss

 omap_device_register(pdev);

platform_device_add

device_add(&pdev->dev);

分析完成

总结:spi框架分为了2个总线结构

第一个层是主机控制器驱动和主机控制器设备,他们属于平台设备

主机控制器提供了spi的传输函数omap2_mcspi_transfer,和设置函数omap2_mcspi_setup

主机控制器设备,提供了spi控制器的资源和寄存器偏移地址

第二个层是spi设备驱动和spi设备,这个设备其实就是oled,注册spi设备有两个地方可能会调用

1、板级信息配置时候,可能会注册spi设备

2、注册spi主机控制器的时候,也会去调用,这个就看谁先执行了

板级信息的目的是告诉内核我的设备接在哪个控制器上面,和我这个设备的性能参数

真正的spi设备是我的oled屏幕,但是CPU只能操作控制器,我发送数据是要先发给控制器驱动,控制器驱动通过dma把,信号给oled屏幕

 

以上是关于linux3.2 spi框架分析的主要内容,如果未能解决你的问题,请参考以下文章

Linux驱动修炼之道-SPI驱动框架源码分析(上)转

Java SPI机制实战详解及源码分析

源码分析---SOFARPC可扩展的机制SPI

Dubbo SPI源码分析

Dubbo 源码分析 - 自适应拓展原理

Dubbo的SPI机制与JDK机制的不同及原理分析