ZedBoard Linux开发 ---- OLED驱动详解.doc_第1页
ZedBoard Linux开发 ---- OLED驱动详解.doc_第2页
ZedBoard Linux开发 ---- OLED驱动详解.doc_第3页
ZedBoard Linux开发 ---- OLED驱动详解.doc_第4页
ZedBoard Linux开发 ---- OLED驱动详解.doc_第5页
已阅读5页,还剩3页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

ZedBoard Linux开发 - OLED驱动详解 但凡单片机的初学者基本都有这样一个试验,也就是点亮第一个LED,类似于程序员的“helloworld”,我当年也是如此。本来我也是希望从LED开始学习,不过对于ZedBoard来说,可能还找不到现成的LED驱动程序可以学习(事后发现在Digilent Linux内核中,点亮一个LED所要理解的机制其实更加复杂),反而倒是提供了OLED的内核驱动,因此我们就从点亮OLED开始吧。对于OLED在用户空间的操作,之前介绍了两种方式,即shell以及编写C应用程序,博客链接如下:/thinki_cao/blog/static/8394487520143193932495/OLED的驱动在Linux内核源码中的位置是linux-digilent/drivers/pmods/pmodoled-gpio.c,而在内核配置中的位置是: - 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 = ;vdd-gpio = ;res-gpio = ;dc-gpio = ;/* SPI-GPIOs */spi-bus-num = ;spi-speed-hz = ;spi-sclk-gpio = ;spi-sdin-gpio = ;这里就是驱动与设备匹配的关键程序,我本人翻译了设备树的官方文档,相关链接如下:/thinki_cao/blog/static/83944875201411975617301另外如果深入到platform bus的匹配函数可以在drivers/base/platform.cstatic 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 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.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_NAMEspi_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_c

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论