平台设备驱动
九、平台设备驱动9.1 平台设备驱动概述9.2 GPIO驱动9.2.1 简介9.2.2 硬件名称9.2.3 引脚控制器9.2.4 引脚控制子系统9.2.5 GPIO控制器驱动9.3 RGB LED平台设备模块9.3.1 简介9.3.2 设备树9.3.3 ledRGB代码分析:9.3.4 RGBled全部代码九、平台设备驱动
9.1 平台设备驱动概述
在嵌入式系统中,设备通常并不通过总线连接,将字符设备转为平台设备。在Linux系统中有一类特殊总线被构建被称为平台总线,平台设备驱动是被静态枚举的,而非动态发现。
驱动定义了platform_driver数据结构使用举例:/* Define platform driver structure */static struct platform_driver my_platform_driver = {.probe = my_probe,.remove = my_remove,.driver = {.name = "hellokeys",.of_match_table = my_of_ids,.owner = THIS_MODULE,}};...platform_driver_register(&my_platform_driver)
修改设备树,目录如下:
/arch.arn.boot/dts/
添加内容如下:
hellkeys{compatible = "arrow, hellokeys";};
hellokeys_sam.c平台设备代码:
定义了字符设备驱动、杂项字符设备驱动、平台设备驱动:
#include <linux/module.h>#include <linux/fs.h>#include <linux/platform_device.h>#include <linux/miscdevice.h>static int my_dev_open(struct inode *inode, struct file *file){pr_info("my_dev_open() is called.\n");return 0;}static int my_dev_close(struct inode *inode, struct file *file){pr_info("my_dev_close() is called.\n");return 0;}static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg){pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);return 0;}static const struct file_operations my_dev_fops = {.owner = THIS_MODULE,.open = my_dev_open,.release = my_dev_close,.unlocked_ioctl = my_dev_ioctl,};static struct miscdevice helloworld_miscdevice = {.minor = MISC_DYNAMIC_MINOR,.name = "mydev",.fops = &my_dev_fops,};/* Add probe() function */static int __init my_probe(struct platform_device *pdev){int ret_val;pr_info("my_probe() function is called.\n");ret_val = misc_register(&helloworld_miscdevice);if (ret_val != 0) {pr_err("could not register the misc device mydev");return ret_val;}pr_info("mydev: got minor %i\n",helloworld_miscdevice.minor);return 0;}/* Add remove() function */static int __exit my_remove(struct platform_device *pdev){pr_info("my_remove() function is called.\n");misc_deregister(&helloworld_miscdevice);return 0;}/* Declare a list of devices supported by the driver */static const struct of_device_id my_of_ids[] = {{.compatible = "arrow,hellokeys"},{},};MODULE_DEVICE_TABLE(of, my_of_ids);/* Define platform driver structure */static struct platform_driver my_platform_driver = {.probe = my_probe,.remove = my_remove,.driver = {.name = "hellokeys",.of_match_table = my_of_ids,.owner = THIS_MODULE,}};/* Register our platform driver */module_platform_driver(my_platform_driver);MODULE_LICENSE("GPL");MODULE_AUTHOR(" ");MODULE_DESCRIPTION("This is the simplest platform driver");
测试调试:
insmod hellkey_imx.kofind /sys -name "hellokeys"ls -l /sys/bus/platform/drivers/hellokeysls -l /sys/module/hellykeys_imx/driversls -l /sys/class/miscls -l /devrmmod hellkey_imx.ko
9.2 GPIO驱动
9.2.1 简介
在开发驱动时候,需要操作不同硬件,需要使用处理器外设寄存器,参考的文档有:
1、Applications Processor Reference Manial2、SAMA5D2 Series Datasheet3、Xplained Ultra User Guide4、硬件原理图
9.2.2 硬件名称
焊点:印在电路板上的裸片的特定表面,如D12焊点;引脚复用:单个引脚在内部进行复杂配置实现不同的功能;逻辑/规范名称:通常对应于焊点的首要功能;网络标号:描述功能线的实际用途;
9.2.3 引脚控制器
参阅datasheet.
9.2.4 引脚控制子系统
实现在driver/pinctl中,如pinctl-imx7d.c
设备树提供引脚、节点配置,pinctl驱动(/drivers/pinctl-imx.c)列出引脚和引脚组、配置引脚,gpio驱动实现gpiochip(driver/gpio)和irq_gpio(kernel/irq),pinctl子系统核心(请求引脚服用设备驱动)。
解析设备树的函数:
imx_pinctl_probe_dt()
引脚复用文件:
drivers/pinctl/pinmux.c
9.2.5 GPIO控制器驱动
GPIO控制器最主要数据结构是gpiochip,定义在:
include/linux/gpio/driver.h
GPIO控制器还提供了中断,头文件如下:
#include <linux/irq.h>
GPIO中断芯片通常分类:
1、链式GPIO中断芯片
chained_irq_enter()generic_handle_irq()chained_irq_exit()
2、通用链式GPIO中断芯片
generic_handle_irq()
3、嵌套的线程化GPIO中断芯片
片外GPIO扩展,
handle_nested_irq()
使用GPIO:
gpio_direction_input()gpio_direction_output()gpio_get_value()gpio_set_value()gpio_to_irq() //获取给定的GPIO对应的Linux IO号。
内核态与用户态交换数据:
当进程执行系统调用时,内核将在调用者的进程上下文中执行;
当内核响应中断时,内核中断处理程序将异步运行在中断上下文。
1、单变量访问
get_user() //kernel -> appput_user() //app -> kernel
2、数组访问
copy_to_user() //kernel -> appcopy_from_user() //app -> kernel
内存IO映射
外围设备的控制是通过操作其寄存器实现,设备具有多个寄存器,通过内存地址空间(MMIO)或者I/O地址空间(PIO)的连续地址来访问这些设备寄存器。
Linux驱动程序无法直接访问物理I/O地址,而是需要MMU映射。
将I/O内存映射到虚拟内存方法:
ioremap()iounmap()
或
#include <linux/io.h>devm_ioremap()devm_ioremap()
物理地址即外设寄存器地址,要映射到虚拟地址,SAMA5D2地址映射,如下图:
9.3 RGB LED平台设备模块
9.3.1 简介
通过控制几个LED灯,将多个Soc外设寄存器地址从物理地址映射到虚拟机地址,并且使用杂项框架为每个LED创建字符设备(misc),通过write和read来调用控制LED在内核态和用户态之间的数据交换。
SAMA5D2处理器有四组I/O,分别是PA\PB\PC\PD,PIO每条I/O线均具有常见的GPIO操作功能,如配置IO输入输出,配置上下拉,输入模式的触发模式,
9.3.2 设备树
/** at91-sama5d2_xplained_common.dtsi - Device Tree file for SAMA5D2 Xplained board** Copyright (C) Atmel,* Nicolas Ferre <nicolas.ferre@>* Ludovic.Desroches <ludovic.desroches@>** This file is dual-licensed: you can use it either under the terms* of the GPL or the X11 license, at your option. Note that this dual* licensing only applies to this file, and not this project as a* whole.*#include "sama5d2.dtsi"//#include "at91-sama5d2_xplained_ov7670.dtsi"#include "sama5d2-pinfunc.h"#include <dt-bindings/mfd/atmel-flexcom.h>#include <dt-bindings/gpio/gpio.h>#include <dt-bindings/pinctrl/at91.h>pinctrl_led_gpio_default: led_gpio_default {pinmux = <PIN_PB0__GPIO>,<PIN_PB5__GPIO>,<PIN_PB6__GPIO>;bias-pull-up;};/ {model = "Atmel SAMA5D2 Xplained";compatible = "atmel,sama5d2-xplained", "atmel,sama5d2", "atmel,sama5";chosen {stdout-path = "serial0:115200n8";};ledred {compatible = "arrow,RGBleds";label = "ledred";pinctrl-0 = <&pinctrl_led_gpio_default>;};ledgreen {compatible = "arrow,RGBleds";label = "ledgreen";};ledblue {compatible = "arrow,RGBleds";label = "ledblue";};ledclassRGB {compatible = "arrow,RGBclassleds";reg = <0xFC038000 0x4000>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_led_gpio_default>;status = "okay";red {label = "red";};green {label = "green";};blue {label = "blue";linux,default-trigger = "heartbeat";};};
9.3.3 ledRGB代码分析:
驱动包含的头文件:
#include <linux/module.h>#include <linux/fs.h> /* struct file_operations *//* platform_driver_register(), platform_set_drvdata() */#include <linux/platform_device.h>#include <linux/io.h> /* devm_ioremap(), iowrite32() */#include <linux/of.h> /* of_property_read_string() */#include <linux/uaccess.h> /* copy_from_user(), copy_to_user() */#include <linux/miscdevice.h> /* misc_register() */
定义寄存器掩码:
/* Declare masks to configure the different registers */#define PIO_PB0_MASK (1 << 0) /* blue */#define PIO_PB5_MASK (1 << 5) /* green */#define PIO_PB6_MASK (1 << 6) /* red */#define PIO_CFGR1_MASK (1 << 8) /* masked bits direction (output), no PUEN, no PDEN */#define PIO_MASK_ALL_LEDS (PIO_PB0_MASK | PIO_PB5_MASK | PIO_PB6_MASK)
定义物理寄存器地址:
/* Declare physical addresses */static int PIO_SODR1 = 0xFC038050;static int PIO_CODR1 = 0xFC038054;static int PIO_MSKR1 = 0xFC038040;static int PIO_CFGR1 = 0xFC038044;
申明devm_iomem指针,用于存放devm_ioremap()返回的虚拟地址。
定义私有数据结构:
保存每个设备特定信息
/* declare a private structure */struct led_dev{struct miscdevice led_misc_device; /* assign device for each led */u32 led_mask; /* different mask if led is R,G or B */const char *led_name; /* assigned value cannot be modified */char led_value[8];};
file_operation:
static const struct file_operations led_fops = {.owner = THIS_MODULE,.read = led_read,.write = led_write,};
misc杂项定义:
led_device->led_misc_device.fops = &led_fops;
led_write:
iowrite32()写寄存器。
static ssize_t led_write(struct file *file, const char __user *buff,size_t count, loff_t *ppos){const char *led_on = "on";const char *led_off = "off";struct led_dev *led_device;pr_info("led_write() is called.\n");led_device = container_of(file->private_data,struct led_dev, led_misc_device);/** terminal echo add \n character.* led_device->led_value = "on\n" or "off\n after copy_from_user"* count = 3 for "on\n" and 4 for "off\n"*/if(copy_from_user(led_device->led_value, buff, count)) {pr_info("Bad copied value\n");return -EFAULT;}/** Replace \n for \0 in led_device->led_value* char array to create a char string*/led_device->led_value[count-1] = '\0';pr_info("This message is received from User Space: %s\n",led_device->led_value);/* compare strings to switch on/off the LED */if(!strcmp(led_device->led_value, led_on)) {iowrite32(led_device->led_mask, PIO_CODR1_W);}else if (!strcmp(led_device->led_value, led_off)) {iowrite32(led_device->led_mask, PIO_SODR1_W);}else {pr_info("Bad value\n");return -EINVAL;}pr_info("led_write() is exit.\n");return count;}
申明与设备树匹配的compatible:
static const struct of_device_id my_of_ids[] = {{.compatible = "arrow,RGBleds"},{},};MODULE_DEVICE_TABLE(of, my_of_ids);static struct platform_driver led_platform_driver = {.probe = led_probe,.remove = led_remove,.driver = {.name = "RGBleds",.of_match_table = my_of_ids,.owner = THIS_MODULE,}};
9.3.4 RGBled全部代码
#include <linux/module.h>#include <linux/fs.h>#include <linux/platform_device.h>#include <linux/io.h>#include <linux/of.h>#include <linux/leds.h>#define PIO_SODR1_offset 0x50#define PIO_CODR1_offset 0x54#define PIO_CFGR1_offset 0x44#define PIO_MSKR1_offset 0x40#define PIO_PB0_MASK (1 << 0)#define PIO_PB5_MASK (1 << 5)#define PIO_PB6_MASK (1 << 6)#define PIO_CFGR1_MASK (1 << 8)#define PIO_MASK_ALL_LEDS (PIO_PB0_MASK | PIO_PB5_MASK | PIO_PB6_MASK)struct led_dev{u32 led_mask; /* different mask if led is R,G or B */void __iomem *base;struct led_classdev cdev;};static void led_control(struct led_classdev *led_cdev, enum led_brightness b){struct led_dev *led = container_of(led_cdev, struct led_dev, cdev);iowrite32(PIO_MASK_ALL_LEDS, led->base + PIO_SODR1_offset);if (b != LED_OFF)/* LED ON */iowrite32(led->led_mask, led->base + PIO_CODR1_offset);elseiowrite32(led->led_mask, led->base + PIO_SODR1_offset); /* LED OFF */}static int __init ledclass_probe(struct platform_device *pdev){void __iomem *g_ioremap_addr;struct device_node *child;struct resource *r;struct device *dev = &pdev->dev;int count, ret;dev_info(dev, "platform_probe enter\n");/* get our first memory resource from device tree */r = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!r) {dev_err(dev, "IORESOURCE_MEM, 0 does not exist\n");return -EINVAL;}dev_info(dev, "r->start = 0x%08lx\n", (long unsigned int)r->start);dev_info(dev, "r->end = 0x%08lx\n", (long unsigned int)r->end);/* ioremap our memory region */g_ioremap_addr = devm_ioremap(dev, r->start, resource_size(r));if (!g_ioremap_addr) {dev_err(dev, "ioremap failed \n");return -ENOMEM;}count = of_get_child_count(dev->of_node);if (!count)return -EINVAL;dev_info(dev, "there are %d nodes\n", count);/* Enable all leds and set dir to output */iowrite32(PIO_MASK_ALL_LEDS, g_ioremap_addr + PIO_MSKR1_offset);iowrite32(PIO_CFGR1_MASK, g_ioremap_addr + PIO_CFGR1_offset);/* Switch off all the leds */iowrite32(PIO_MASK_ALL_LEDS, g_ioremap_addr + PIO_SODR1_offset);for_each_child_of_node(dev->of_node, child){struct led_dev *led_device;struct led_classdev *cdev;led_device = devm_kzalloc(dev, sizeof(*led_device), GFP_KERNEL);if (!led_device)return -ENOMEM;cdev = &led_device->cdev;led_device->base = g_ioremap_addr;of_property_read_string(child, "label", &cdev->name);if (strcmp(cdev->name,"red") == 0) {led_device->led_mask = PIO_PB6_MASK;led_device->cdev.default_trigger = "heartbeat";}else if (strcmp(cdev->name,"green") == 0) {led_device->led_mask = PIO_PB5_MASK;}else if (strcmp(cdev->name,"blue") == 0) {led_device->led_mask = PIO_PB0_MASK;}else {dev_info(dev, "Bad device tree value\n");return -EINVAL;}/* Disable timer trigger until led is on */led_device->cdev.brightness = LED_OFF;led_device->cdev.brightness_set = led_control;ret = devm_led_classdev_register(dev, &led_device->cdev);if (ret) {dev_err(dev, "failed to register the led %s\n", cdev->name);of_node_put(child);return ret;}}dev_info(dev, "leds_probe exit\n");return 0;}static int __exit ledclass_remove(struct platform_device *pdev){dev_info(&pdev->dev, "leds_remove enter\n");dev_info(&pdev->dev, "leds_remove exit\n");return 0;}static const struct of_device_id my_of_ids[] = {{.compatible = "arrow,RGBclassleds"},{},};MODULE_DEVICE_TABLE(of, my_of_ids);static struct platform_driver led_platform_driver = {.probe = ledclass_probe,.remove = ledclass_remove,.driver = {.name = "RGBclassleds",.of_match_table = my_of_ids,.owner = THIS_MODULE,}};static int ledRGBclass_init(void){int ret_val;pr_info("demo_init enter\n");ret_val = platform_driver_register(&led_platform_driver);if (ret_val !=0){pr_err("platform value returned %d\n", ret_val);return ret_val;}pr_info("demo_init exit\n");return 0;}static void ledRGBclass_exit(void){pr_info("led driver enter\n");platform_driver_unregister(&led_platform_driver);pr_info("led driver exit\n");}module_init(ledRGBclass_init);module_exit(ledRGBclass_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR(" ");MODULE_DESCRIPTION("This is a driver that turns on/off RGB leds \using the LED subsystem");
测试调试:
insmod ledRGB_sam_platform.kols /dev/led*echo on > /dev/ledblueecho on > /dev/ledredecho on > /dev/ledgreenecho off > /dev/ledgreencat /dev/ledredrmmod ledRGB_sam_platform.ko
感谢阅读,祝君成功!
-by aiziyou