平台设备驱动

  • 九、平台设备驱动
    • 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.ko
find /sys -name "hellokeys"
ls -l /sys/bus/platform/drivers/hellokeys
ls -l /sys/module/hellykeys_imx/drivers
ls -l /sys/class/misc
ls -l /dev
rmmod hellkey_imx.ko

9.2 GPIO驱动

9.2.1 简介

在开发驱动时候,需要操作不同硬件,需要使用处理器外设寄存器,参考的文档有:

1、Applications Processor Reference Manial
2、SAMA5D2 Series Datasheet
3、Xplained Ultra User Guide
4、硬件原理图

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 -> app
put_user()    //app    -> kernel

2、数组访问

copy_to_user()    //kernel -> app
copy_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) 2016 Atmel,*                2016 Nicolas Ferre <nicolas.ferre@atmel.com>*          2016 Ludovic.Desroches <ludovic.desroches@atmel.com>** 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.ko
ls /dev/led*
echo on > /dev/ledblue
echo on > /dev/ledred
echo on > /dev/ledgreen
echo off > /dev/ledgreen
cat /dev/ledred
rmmod ledRGB_sam_platform.ko

感谢阅读,祝君成功!
-by aiziyou

嵌入式Linux设备驱动程序开发指南9(平台设备驱动)——读书笔记相关推荐

  1. VxWorks设备驱动程序开发指南---驱动程序的分类

    8D Spaces Reliability & Stability & Efficiency 目录视图 摘要视图 订阅 VxWorks设备驱动程序开发指南(三)---驱动程序的分类 2 ...

  2. 嵌入式Linux设备驱动程序开发指南20(Linux USB设备驱动)——读书笔记

    Linux USB设备驱动 二十.Linux USB设备驱动 20.1 USB简介 20.1.1 USB2.0总线拓扑 20.1.2 USB总线枚举和设备布局 20.1.3 USB数据传输 20.1. ...

  3. 嵌入式Linux设备驱动程序开发指南14(Linux设备驱动使用DMA)——读书笔记

    Linux设备驱动使用DMA 十四.Linux设备驱动使用DMA 14.1 简介 14.2 缓存一致性 14.3 DMA控制器接口 14.4 流式DMA模块 14.4.1 sdma_sam_m2m.c ...

  4. 嵌入式Linux设备驱动程序开发指南3(构建Microchip SAMA5D2嵌入式 Linux系统)——读书笔记

    构建Microchip SAMA5D2嵌入式 Linux系统 三.构建Microchip SAMA5D2嵌入式 Linux系统 3.1 获取驱动代码 3.2 配置编译 3.2.1 bootstrap编 ...

  5. Linux字符设备驱动程序开发(1)-使用字符设备驱动

    1.编译/安装驱动 在Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码.因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块.把下面的范例代码拷贝到Linux系统中: memd ...

  6. 嵌入式Linux设备驱动程序开发指南17(IIO子系统一)——读书笔记

    IIO子系统一 十七.IIO子系统(一) 17.1 简介 17.2 数模转换--DAC实验 17.2.1 IIO缓冲区 17.2.2 触发器 17.2.3 工业I/O事件 17.2.4 iio工具 1 ...

  7. 嵌入式Linux设备驱动程序开发指南18(IIO子系统(二)具有硬件触发功能的IIO子系统ADC模块)——读书笔记

    IIO子系统二 具有硬件触发功能的IIO子系统ADC模块 十八.IIO子系统(二) 具有硬件触发功能的IIO子系统ADC模块 18.1 简介 18.2 设备树 18.3 硬件触发驱动功能分析 18.3 ...

  8. VxWorks驱动程序开发指南--驱动程序的组织结构

    8D Spaces Reliability & Stability & Efficiency 目录视图 摘要视图 订阅 VxWorks驱动程序开发指南(四)--驱动程序的组织结构 20 ...

  9. 嵌入式Linux设备驱动程序:在运行时读取驱动程序状态

    嵌入式Linux设备驱动程序:在运行时读取驱动程序状态 Embedded Linux device drivers: Reading driver state at runtime 在运行时了解驱动程 ...

最新文章

  1. Python3 列表List
  2. linux c socket 服务器 客户端 简单通讯
  3. mysql 隔离级别和锁相关
  4. 【JOURNAL】911的虚惊
  5. html中给%3cb%3e加上颜色,如何使用CSS(jQuery SVG图像替换)更改SVG图像的颜色?
  6. mysql数据库 集群_mysql数据库集群
  7. css3 中background的新增加的属性的用法(一)
  8. lintcode刷题 14. 二分查找,迭代和二分法Python实现
  9. eclipse 方法补全快捷键设置
  10. 微信开发--开发模式简单配置
  11. 前端 img 标签显示 base64 格式的 图片
  12. java里美元符_Java语言标识符中可以使用美元符
  13. arma模型预测 matlab,MATLAB中ARMA模型预测差分问题
  14. android jni c调用java,Android JNI中C调用Java方法
  15. pytorch系列(八):猫狗大战3-单机多卡无脑训练
  16. 【902】大恒相机网络配置
  17. 程序员需要熟悉的英语单词
  18. 为什么INT_MIN不是直接写成-2147483648
  19. 已声明“##”,但从未读取其值,解决办法
  20. 基于MFC的贪吃蛇游戏

热门文章

  1. 数据化运营之流量数据化运营
  2. C#开发BIMFACE系列18 服务端API之获取模型数据3:获取构件属性
  3. 为什么 头条 宇宙条_为什么美团被戏称为“开水团”,头条则被封为“宇宙条”?...
  4. Go 语言实战: 编写可维护 Go 语言代码建议
  5. 虐哭java面试官--聊一聊hashmap
  6. Scratch加减法
  7. macOS 13.0 Cocoapods安装 (you don‘t have write permissions for the /Library/Ruby/Gems/2.6.0 directory)
  8. Android源码编译make的错误处理
  9. 大数据职业规划所感所悟
  10. PyCharm使用技巧之设置背景图片