打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
ZedBoard Linux开发
     -> Device Drivers                                                                    
          -> Pmod Support (PMODS [=y])
(注意make ARCH=arm menuconfig的时候务必要把ARCH加上)并且默认是编译进内核的,而在Digilent OOB Design中是以模块驱动的形式放在ramdisk文件系统中的,因此如果重新编译内核的话,原先的load_oled,unload_oled都是无法使用的,原理很简单,上面两个命令都是脚本,并且是单纯的insmod以及rmmod驱动。下面就开始看分析pmodoled-gpio.c文件:
        首先跳到文件最后几行,找到函数 module_platform_driver(gpio_pmodoled_driver); 该函数定义在include/linux/platform_device.h文件中:
 #define module_platform_driver(__platform_driver) \
         module_driver(__platform_driver, platform_driver_register, \
                         platform_driver_unregister)
而module_driver()函数则定义在include/linux/device.h文件中:
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
        return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
        __unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
通俗一点来理解的话, module_platform_driver(gpio_pmodoled_driver);最终展开后就是如下形式:
static int __init gpio_pmodoled_driver_init(void)
{
        return platform_driver_register(&gpio_pmodoled_driver);
}
module_init(gpio_pmodoled_driver_init);
static void __exit gpio_pmodoled_driver_init(void)
{
        return platform_driver_unregister(&gpio_pmodoled_driver);
}
module_exit(gpio_pmodoled_driver_exit);
看到module_init()和module_exit()估计就熟悉很多了,其实一开始我也不习惯这些宏,不过后来习惯了以后发现还是有不少好处的,所以我们编写驱动的时候可以参考内核中的代码风格和习惯,非常具有学习意义。
        使用platform_driver_register之后就成功地注册了驱动,驱动程序对应于gpio_pmodoled_driver结构体,而且这个结构体定义就在上面几行:
static struct platform_driver gpio_pmodoled_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = gpio_pmodoled_of_match,
},
.probe = gpio_pmodoled_of_probe,
.remove = __devexit_p(gpio_pmodoled_of_remove),
};
这里重点分析用于驱动程序匹配的of_match_table,定义如下:
static const struct of_device_id gpio_pmodoled_of_match[] __devinitconst = {
{ .compatible = "dglnt,pmodoled-gpio", },
{},
};
MODULE_DEVICE_TABLE(of, gpio_pmodoled_of_match);
这里MODULE_DEVICE_TABLE来告知用户空间, 模块支持那些设备,并且数组最后需要预留一个空白成员。由于ZedBoard的Linux内核采用了设备树来传递板级的信息,因此这里的驱动匹配采用了of_device_id结构的数组,这里重点是检查.compatible字段是否匹配,如果设备树中也存在.compatible字段相同的节点,那么就加载驱动,现在来看一下设备树的源文件,源文件可以在Diglent官网上下到,也可以在内核中找到,位置如下:linux-digilent/arch/arm/boot/dts/digilent-zed.dts,可以在里面找到如下一段文字:
zed_oled {
compatible = "dglnt,pmodoled-gpio";
/* GPIO Pins */
vbat-gpio = <&gpiops 55 0>;
vdd-gpio = <&gpiops 56 0>;
res-gpio = <&gpiops 57 0>;
dc-gpio = <&gpiops 58 0>;
/* SPI-GPIOs */
spi-bus-num = <2>;
spi-speed-hz = <4000000>;
spi-sclk-gpio = <&gpiops 59 0>;
spi-sdin-gpio = <&gpiops 60 0>;
};
这里就是驱动与设备匹配的关键程序,我本人翻译了设备树的官方文档,相关链接如下:
另外如果深入到platform bus的匹配函数可以在drivers/base/platform.c
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);

/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;

/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;

/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
可以看到首先是进行设备树匹配(这里OF 是指OpenFirmware,也就是设备树使用的机制),然后再匹配设备驱动的id,最后再匹配设备和驱动的名字。
        到这里设备与驱动的匹配部分就分析完了,如果设备与驱动匹配成功,那么内核就会执行.probe字段的函数,即gpio_pmodoled_of_probe,声明如下:
static int __devinit gpio_pmodoled_of_probe(struct platform_device *pdev)
这里__devinit宏的作用标记设备初始化使用的代码,定义在include/linux/init.h中。初始化代码的特点是:在系统启动运行,且一旦运行后马上退出内存,不再占用内存,个人理解应该是到内核启动以后常常会free 掉几百k的内存,可能就包括这一部分。由于probe函数太长,这里挑重点部分介绍:
        
/* Alloc Space for platform device structure */
gpio_pmodoled_dev = (struct gpio_pmodoled_device *) kzalloc(sizeof(*gpio_pmodoled_dev), GFP_KERNEL);
/* Alloc Graphic Buffer for device */
gpio_pmodoled_dev->disp_buf = (uint8_t *) kmalloc(DISPLAY_BUF_SZ, GFP_KERNEL);
/* Get the GPIO Pins */
gpio_pmodoled_dev->iVBAT = of_get_named_gpio(np, "vbat-gpio", 0);
gpio_pmodoled_dev->iVDD = of_get_named_gpio(np, "vdd-gpio", 0);
gpio_pmodoled_dev->iRES = of_get_named_gpio(np, "res-gpio", 0);
gpio_pmodoled_dev->iDC = of_get_named_gpio(np, "dc-gpio", 0);
gpio_pmodoled_dev->iSCLK = of_get_named_gpio(np, "spi-sclk-gpio", 0);
gpio_pmodoled_dev->iSDIN = of_get_named_gpio(np, "spi-sdin-gpio", 0);
status = of_get_named_gpio(np, "spi-cs-gpio", 0);
gpio_pmodoled_dev->iCS = (status < 0) ? SPI_GPIO_NO_CHIPSELECT : status;
这里使用OpenFirmware提供的api,相关的文件在include/linux/of_gpio.h中,再回到上面给出的设备树文件中,大家是不是找到相似的字段了,更详细的内容请自己Google。

/* Get SPI Related Params */
tree_info = of_get_property(np, "spi-bus-num", NULL);
if (tree_info) {
gpio_pmodoled_dev->spi_id = be32_to_cpup((tree_info));
}
这里使用OpenFirmware api获取spi总线号用来进行后面的spi master创建
/* Alloc Space for platform data structure */
gpio_pmodoled_pdata = (struct spi_gpio_platform_data *) kzalloc(sizeof(*gpio_pmodoled_pdata), GFP_KERNEL);
/* Fill up Platform Data Structure */
gpio_pmodoled_pdata->sck = gpio_pmodoled_dev->iSCLK;
gpio_pmodoled_pdata->miso = SPI_GPIO_NO_MISO;
gpio_pmodoled_pdata->mosi = gpio_pmodoled_dev->iSDIN;
gpio_pmodoled_pdata->num_chipselect = 1;
这是定义在linux/spi/spi_gpio.h中的数据结构用来传递给后面的platform设备。
/* Alloc Space for platform data structure */
gpio_pmodoled_pdev = (struct platform_device *) kzalloc(sizeof(*gpio_pmodoled_pdev), GFP_KERNEL);
/* Fill up Platform Device Structure */
gpio_pmodoled_pdev->name = "spi_gpio";
gpio_pmodoled_pdev->id = gpio_pmodoled_dev->spi_id;
gpio_pmodoled_pdev->dev.platform_data = gpio_pmodoled_pdata;
gpio_pmodoled_dev->pdev = gpio_pmodoled_pdev;
这里的代码比较关键,一开始也比较难以理解,这里新申请了一个platform设备并且在后面进行注册,这个设备会和/drivers/spi/spi-gpio.c中的platform驱动相匹配,在这个源文件中可以看到与pmodoled-gpio.c文件中相似的代码:
static struct platform_driver spi_gpio_driver = {
.driver.name = DRIVER_NAME,
.driver.owner = THIS_MODULE,
.probe = spi_gpio_probe,
.remove = __devexit_p(spi_gpio_remove),
};
module_platform_driver(spi_gpio_driver);
这里使用了platform_driver的name字段进行驱动匹配,而DRIVER_NAME可以在文件开头找到它的定义:
#define DRIVER_NAME "spi_gpio"
这正好与之前的spi_gpio字符串相同,从而出发了驱动与设备的匹配,匹配完成之后spi_gpio平台驱动创建了一个虚拟的spi_master,并通过gpio_pmodoled_pdata结构体传递的配置信息配置了spi_master,并且这里使用了软件IO模拟SPI时序,而真正的硬件SPI驱动程序并没有被匹配,相关的文件位于drivers/spi/spi-xilinx-ps.c文件。下面继续回到pmodoled-gpio.c:
/* Register spi_gpio master */
status = platform_device_register(gpio_pmodoled_dev->pdev);
        gpio_pmodoled_dev->name = np->name;
/* Fill up Board Info for SPI device */
status = add_gpio_pmodoled_device_to_bus(gpio_pmodoled_dev);
这个函数也很关键,Step Into吧!

spi_master = spi_busnum_to_master(dev->spi_id);

这里根据busnum寻找可用的spi_master,由于之前已经成功地利用spi_gpio驱动创建了spi_master,所以这里能够成功地返回非空指针。

        spi_device = spi_alloc_device(spi_master);

这里再根据spi_master来申请spi_device,而这里的spi_device就是spi的从机设备配置。
spi_device->chip_select = 0;
spi_device->max_speed_hz = 4000000;
spi_device->mode = SPI_MODE_0;
spi_device->bits_per_word = 8;
spi_device->controller_data = (void *) dev->iCS;
spi_device->dev.platform_data = dev;
strlcpy(spi_device->modalias, SPI_DRIVER_NAME, sizeof(SPI_DRIVER_NAME));
这里将相关配置信息写入spi_device,并且最后将spi_device->modalias别名变量赋值SPI_DRIVER_NAME,而这个变量决定了后面进行的另一次spi设备与spi驱动的匹配。

status = spi_add_device(spi_device);

将设备注册内核。
dev->spi = spi_device;

put_device(&spi_master->dev);
spi_master用完了以后即使释放掉。
到这里函数运行完毕,返回gpio_pmodoled_of_probe。

if (gpio_pmodoled_dev_id == 0) {
/* Alloc Major & Minor number for char device */
status = alloc_chrdev_region(&gpio_pmodoled_dev_id, 0, MAX_PMODOLED_GPIO_DEV_NUM, DRIVER_NAME);
}
申请字符设备号
if (gpio_pmodoled_class == NULL) {
/* Create Pmodoled-gpio Device Class */
gpio_pmodoled_class = class_create(THIS_MODULE, DRIVER_NAME);
}
在sysfs中创建class入口

if (spi_drv_registered == 0) {
/* Register SPI Driver for Pmodoled Device */
status = spi_register_driver(&gpio_pmodoled_spi_driver);
spi_drv_registered = 1;
}

这里红色加粗字体是关键,之前讲到我们新注册了一个spi_device设备,与这个设备匹配的驱动正是这里注册的gpio_pmodoled_spi_driver,定义如下:

static struct spi_driver gpio_pmodoled_spi_driver = {
.driver = {
.name = SPI_DRIVER_NAME,
.bus = &spi_bus_type,
.owner = THIS_MODULE,
},
.probe = gpio_pmodoled_spi_probe,
.remove = __devexit_p(gpio_pmodoled_spi_remove),
};
这里我们又再次看到了.name = SPI_DRIVER_NAME,正是通过这个name字段进行驱动匹配的。现在我们再来看一下spi总线的匹配函数,位于drivers/spi/spi.c:
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv);

/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;

if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);

return strcmp(spi->modalias, drv->name) == 0;
}
这里的匹配过程与platform几乎相同,也是先匹配设备树中的设备,再匹配设备的id,最后匹配别名(这里与platform不同),而pmodoled驱动正是采用了别名匹配方法,可以在上面找到对别名赋值的语句:

strlcpy(spi_device->modalias, SPI_DRIVER_NAME, sizeof(SPI_DRIVER_NAME));

设备和驱动匹配成功后内核会执行probe函数,也就是gpio_pmodoled_spi_probe,到这里才是真正的大家常见的设备驱动程序入口,当我真正理解到这里的时候已经是内牛满面了……
继续分析该函数:
/* We must use SPI_MODE_0 */
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;

status = spi_setup(spi);
设置spi的工作模式。
/* Get gpio_pmodoled_device structure */
gpio_pmodoled_dev = (struct gpio_pmodoled_device *) spi->dev.platform_data;
/* Setup char driver */
status = gpio_pmodoled_setup_cdev(gpio_pmodoled_dev, &(gpio_pmodoled_dev->dev_id), spi);
在这个函数里就是进行字符驱动的设置,找了好久才找到啊,真心不容易啊,代码如下,这里就不介绍了:
cdev_init(&dev->cdev, &gpio_pmodoled_cdev_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &gpio_pmodoled_cdev_fops;
dev->spi = spi;

*dev_id = MKDEV(MAJOR(gpio_pmodoled_dev_id), cur_minor++);
status = cdev_add(&dev->cdev, *dev_id, 1);

/* Add Device node in system */
device = device_create(gpio_pmodoled_class, NULL,
*dev_id, NULL,
"%s", dev->name);


/* Initialize Mutex */
mutex_init(&gpio_pmodoled_dev->mutex);

status = gpio_pmodoled_init_gpio(gpio_pmodoled_dev);
这里是初始化gpio的地方,也重点介绍一下:
在Linux中使用了gpiolib函数库进行gpio的管理,大体的流程就是在系统启动的时候注册好所有可用的gpio设备交给gpiolib管理,这个过程在drivers/gpio/gpio-xilinxps.c中完成,然后每当需要使用gpio的时候就使用库函数gpio_request,申请成功后就可以进行各种配置,最后使用完毕后就使用库函数gpio_free。如果需要更深入的理解的话可以查看内核中的gpio文档,位于Documentation/gpio.txt中,不过要完全理解就只能看代码了,不过后来发现内核中的代码不外乎几类,看多了就习惯了。
gpio_pmodoled_disp_init(gpio_pmodoled_dev);
这里面也用到了很多gpiolib中的函数以及spi的内核函数,涉及的内容比较多,大家有兴趣的话自己google
memset(gpio_pmodoled_dev->disp_buf, 0x00, DISPLAY_BUF_SZ);

status = screen_buf_to_display(gpio_pmodoled_dev->disp_buf, gpio_pmodoled_dev);
这里面也用到了很多gpiolib中的函数以及spi的内核函数,涉及的内容比较多,大家有兴趣的话自己google
probe函数到此为止,接下来就是大家非常熟悉的结构体了!
struct file_operations gpio_pmodoled_cdev_fops = {
.owner = THIS_MODULE,
.write = gpio_pmodoled_write,
.read = gpio_pmodoled_read,
.open = gpio_pmodoled_open,
.release = gpio_pmodoled_close,
};
到此pmodoled的驱动也分析的差不多了,如果大家能够理解内核在各个阶段的行为,那么就能很容易地理解这些代码。


参考链接:
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
linux中probe函数传递参数的寻找(下)
Linux下spi驱动开发(1)
Linux关于总线、设备、驱动的注册顺序
Linux设备模型(6)_Bus
Linux驱动程序开发 - 设备驱动模型初探
miscdevice、platform_device、platform_driver的区别??大侠指教 驱动程序开发网技术社区
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服