linux3.2 spi框架分析

刘术河 2017.04.12

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

g:\嵌入式\linux-3.2.0\arch\arm\mach-omap2\Board-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,          //用335x的spi的哪个控制器

.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

1、g:\嵌入式\linux-3.2.0\drivers\spi\Spi.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 = 1、2

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 = 0,num_chipselect=?暂时还不知道在哪设置的

搜索num_chipselect,发现一条结果

Omap_hwmod_33xx_data.c (arch\arm\mach-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 (drivers\spi): master->setup = omap2_mcspi_setup;

g:\嵌入式\linux-3.2.0\drivers\spi\Spi-omap2-mcspi.c

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

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就是我的oled的probe函数

if (ret)

goto probe_failed;

}

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

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

g:\嵌入式\linux-3.2.0\drivers\spi\Spi_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.0\drivers\spi\Spi.c

就是最核心的文件

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

1、spi_register_board_info

2、spi_register_driver

3、spi_register_master

我已经分析了 1和2,就剩分析spi_register_master

分析spi_register_master

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

找到一条信息

Spi-omap2-mcspi.c (drivers\spi): status = spi_register_master(master);

g:\嵌入式\linux-3.2.0\drivers\spi\Spi-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发送函数,oled的spi_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 (arch\arm\mach-omap2): char *name = "omap2_mcspi";

g:\嵌入式\linux-3.2.0\arch\arm\mach-omap2\Devices.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屏幕

原文地址:https://www.cnblogs.com/liushuhe1990/p/9609003.html

时间: 2024-10-10 22:54:20

linux3.2 spi框架分析的相关文章

tty初探—uart驱动框架分析

本文参考了大量牛人的博客,对大神的分享表示由衷的感谢. 主要参考: tty驱动分析 :http://www.wowotech.net/linux_kenrel/183.html Linux TTY驱动--Uart_driver底层:http://blog.csdn.net/sharecode/article/details/9196591 Linux TTY驱动--Serial Core层  :http://blog.csdn.net/sharecode/article/details/9197

二十四、V4L2框架分析和虚拟摄像头驱动编写

一.V4L2框架分析 V4L2(video for linux version 2),是内核中视频设备的驱动框架,为上层访问视频设备提供统一接口. V4L2整体框架如下图: 图中主要包括四个部分: 1. 字符设备驱动程序核心:V4L2本身就是一个字符设备,上层连接用户空间 2. V4L2驱动核心:构造通用的视频设备驱动框架,为上层操作提供统一接口 3. 平台V4L2驱动:在V4L2框架下,根据平台自身特性实现与平台相关的V4L2驱动部分,包括注册video_device和v4l2_dev 4.

Google Test测试框架分析

Google Test测试框架分析 一.简介 Google Test是由Google主导的一个开源的C++自动化测试框架,简称GTest.GTest基于xUnit单元测试体系,和CppUint类似,可以看作是JUnit.PyUnit等对C++的移植. 下图是GTest测试框架的测试过程,表示的是GTest的两种测试方式. 下面将使用一个极其简单的例子表示xUnit测试的主要过程.如对Hummer的CTXString类的成员方法GetLength进行测试.详见下面GTest代码和注释说明. //

Android Bitmap 开源图片框架分析(精华三)

主要介绍这三个框架,都挺有名的,其他的框架估计也差不多了 Android-Universal-Image-Loaderhttps://github.com/nostra13/Android-Universal-Image-Loader ImageLoaderhttps://github.com/novoda/ImageLoader Volley(综合框架,包含图片部分)https://github.com/mcxiaoke/android-volley 扯淡时间,可以跳过这段这些开源框架的源码还

Android Bitmap 开源图片框架分析(精华四)

disk缓存主要难点在于内存缓存,disk缓存其实比较简单,就是图片加载完成后把图片文件存到本地方便下次使用 同样,先贴一下官方主页的介绍(主页地址见文章最开始处)和内存缓存差不多,根据算法不同提供了几种类别,可以自行通过ImageLoaderConfiguration.discCache(..)设置<ignore_js_op> 硬盘缓存,保存是以文件的形式框架提供了4种类型,具体算法规则不同,看名字我们大概也能知道对应意思 UnlimitedDiscCache                

Linux输入子系统框架分析(1)

在Linux下的输入设备键盘.触摸屏.鼠标等都可以用输入子系统来实现驱动.输入子系统分为三层,核心层和设备驱动层,事件层.核心层和事件层由Linux输入子系统本身实现,设备驱动层由我们实现.我们在设备驱动层将输入事件上报给核心层input.c,核心层找到匹配的事件层,将事件交给事件层处理,事件层处理完后传递到用户空间. 我们最终要搞清楚的是在用户空间调用open和read最终在内核中是怎样处理的,向内核上报的事件又是谁处理的,处理完后是怎样传递到用户空间的? 上面两个图是输入子系统的框架. 下面

TI BLE协议栈软件框架分析

看源代码的时候,一般都是从整个代码的入口处开始,TI  BLE 协议栈源码也不例外.它的入口main()函数就是整个程序的入口,由系统上电时自动调用. 它主要做了以下几件事情: (一)底层硬件初始化配置 (二)创建任务并初始化任务配置 (三)检测并执行有效的任务事件 Main() 函数源码如下: 一:底层硬件初始化设置 75行,设置系统时钟,使能内存缓冲功能. 78行,关中断,刚启动时,系统运行不稳定,一般会首先关中断. 81行,硬件相关的I/O 口配置. 84行,初始化mcu 内部的flash

开源学习--SlideExpandableListView中的列表项动画实现框架分析

前面的话 开源项目Android-SlideExpandableListView是一个简单的介绍列表项动画展示的小型项目,分析这个项目可以对自定义框架及列表类动画实现有个比较清晰的认识,工作中中时常根据需求扩展定义自己的适配器,虽然具体需求不同,但架构类似,本文把最近关于该开源项目的研究心得整理分享,共同学习~ 项目简介 github地址https://github.com/tjerkw/Android-SlideExpandableListView 这是个入门级的列表项动画展示框架,实现效果如

YII框架分析笔记2:组件和事件行为管理

Yii是一个基于组件.用于开发大型 Web 应用的高性能 PHP 框架.CComponent几乎是所有类的基类,它控制着组件与事件的管理,其方法与属性如下,私有变量$_e数据存放事件(evnet,有些地方叫 hook),$_m数组存放行为(behavior). 组件管理 YII是一个纯oop框架,很多类中的成员变量的受保护或者私有的,CComponent中利用php中的魔术方法__get(),__set()来访问和设置属性,但这些方法的作用远不指这些.下面用__get()来说明 [php] vi