刘术河 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