概要
本文主要介绍基于设备树的platform设备驱动编写流程,以一个简单的led设备为案例展开。其中涉及pinctrl和gpio子系统,以及字符设备驱动,platform设备驱动。本文的内容是基于nxp的imx6ull开发板的资源。
设备资源编辑流程
编辑设备树
本节内容使用了pinctrl和gpio子系统,在设备树中描述所需资源的信息,通过pinctrl的方式表达。在设备树的iomuxc下添加pinctrl_led:gpioled节点
pinctrl_led: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
};
然后在设备树的根目录下添加myled设备
myled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "myled";
status = "okay";
pinctrl_name = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
};
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 定义在imx6ul-pinfunc.h文件中,我们可以在该文件中看到其被定义为5个数值
#define MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x0068 0x02F4 0x0000 0x5 0x0
这5个数值的含义如下:
<mux_reg conf_reg input_reg mux_mode input_val> 前三个为寄存器偏移地址,后两个为寄存器配置数据。
而在MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 后的0x10B0表示引脚的电气特性。
编辑platform设备驱动需要的资源
static const struct of_device_id led_idtable[] = {
{.compatible = "myled"},
{}
};
int myled_probe(struct platform_device *pdev);
int myled_remove(struct platform_device *pdev);
static struct platform_driver ledplatform_driver = {
.probe = myled_probe,
.remove = myled_remove,
.driver = {
.name = "Braju's Led",
.of_match_table = of_match_ptr(led_idtable),
},
};
在写代码前先明确自己需要哪些资源,在编写platform设备驱动的过程中,我们首先需要一个platform driver来表述我们的驱动信息。以下是其原型
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
其中probe是当匹配到设备时触发的回调函数,主要用于设备信息的初始化。
remove函数是当驱动被卸载的时候触发,用于释放驱动创建占用的资源
shutdown函数在系统关机时被调用
suspend在系统休眠时调用
resume在系统唤醒时调用
driver,该结构体用来描述驱动可匹配的device以及驱动名称。驱动是依托于设备存在的,所以我们需要在driver中实现一个devicetable,表述形式为struct of_device_id *of_match_table下面是该结构体的原型
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
在这个结构体中通常使用的属性为data和compatible,data可以作数据将设备信息传递给驱动,可以用来进行热插拔的应用,用以进行设备的识别。而compatible作为与设备树中的device进行匹配的信息。
以上就是我们的platform所需要的资源了。接下来就是介绍实现过程
设备驱动实现流程
明确当前设备类型
在实现一样东西前,需要先确定好目标。这里我的目标是写一个led字符设备驱动,可以通过文件操作控制led的亮灭,所以我们定义这样一个led对象以及文件操作对象。
struct leddev_dev{
dev_t devid;
struct cdev mcdev;
struct class *mclass;
struct device *mdevice;
int marjor;
int led0;
}myleddev;
int myled_open(struct inode *pnode, struct file *pfile);
int myled_release (struct inode *pnode, struct file *pfile);
ssize_t myled_read (struct file *pfile, char __user *pdate, size_t size, loff_t * poffset);
ssize_t myled_write (struct file *pfile, const char __user *pdate, size_t size, loff_t *poffset);
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = myled_open,
.release = myled_release,
.write = myled_write,
.read = myled_read,
};
以上就是我们所需要控制的设备。
实现platform设备驱动匹配以及初始化
当所有目标都明确后我们需要实现probe,实现当匹配到设备后自动进行的初始化流程
首先是字符设备驱动的通用5个步骤。
1.申请设备id
/*1.设置设备号*/
if(my_leddev.marjor){
my_leddev.devid = MKDEV(my_leddev.marjor, 0);
register_chrdev_region(my_leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
}
else{
alloc_chrdev_region(&my_leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
my_leddev.marjor = MAJOR(my_leddev.devid);
}
2.注册cdev
/*2.注册设备*/
cdev_init(&my_leddev.mcdev, &led_fops);
cdev_add(&my_leddev.mcdev, my_leddev.devid, LEDDEV_CNT);
3.创建class
/*3.创建类*/
my_leddev.mclass = class_create(THIS_MODULE, LEDDEV_NAME);
if(IS_ERR(my_leddev.mclass)){
return PTR_ERR(my_leddev.mclass);
}
4.创建device
/*4.创建设备*/
my_leddev.mdevice = device_create(my_leddev.mclass, NULL, my_leddev.devid, NULL, LEDDEV_NAME);
if(IS_ERR(my_leddev.mdevice)){
return PTR_ERR(my_leddev.mdevice);
}
5.初始化硬件资源
通过of匹配的方式来从设备中拉取到pinctrl的信息,然后通过gpio家族函数申请并初始化硬件设备。
int led_init(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *match;
int ret = 0;
match = of_match_device(led_idtable, &pdev->dev);
if(match){
my_leddev.led0 = of_get_named_gpio(pdev->dev.of_node, "led-gpio", 0);
if(my_leddev.led0 < 0){
pr_err("can't get led gpio!\r\n");
return -EINVAL;
}
}
else{
pr_err("can't match device!\r\n");
return -ENOENT;
}
gpio_request(my_leddev.led0, "led0");
ret = gpio_direction_output(my_leddev.led0, 1);
if(ret < 0){
pr_err("can't set gpio!\r\n");
return -EINVAL;
}
return 0;
}
通过以上的5个步骤我们就完成了所有资源的初始化。
LED驱动外部接口实现
然后就是对外部应用的接口实现了,我们的目标是可以通过read读取led的状态,以及通过write来设置led的状态。
int myled_open(struct inode *pnode, struct file *pfile)
{
pfile->private_data = &my_leddev.led0;
return 0;
}
int myled_release (struct inode *pnode, struct file *pfile)
{
pfile->private_data = NULL;
return 0;
}
ssize_t myled_read (struct file *pfile, char __user *pdate, size_t size, loff_t * poffset)
{
int retv;
int led = *((int *)pfile->private_data);
retv = gpio_get_value(led);
if(retv == 0){
u8 status = 1;
retv = copy_to_user(pdate, &status, sizeof(status));
if(retv < 0){
pr_err("copy date to userspace failed!");
return EFAULT;
}
}
else if(retv == 1){
u8 status = 0;
retv = copy_to_user(pdate, &status, sizeof(status));
if(retv < 0){
pr_err("copy date to userspace failed!");
return -EFAULT;
}
}
else {
return -ENAVAIL;
}
return 1;
}
ssize_t myled_write (struct file *pfile, const char __user *pdate, size_t size, loff_t *poffset)
{
int ret;
u8 ledstatus;
int led = *((int *)pfile->private_data);
ret = copy_from_user(&ledstatus, pdate, size);
if(ret < 0){
return -EFAULT;
}
if(ledstatus == LED_ON){
gpio_set_value(led, 0);
}
else if(ledstatus == LED_OFF){
gpio_set_value(led,1);
}
return 0;
}
以上就是一个字符设备驱动实现的流程了,只能说easy。
完整代码
所有的一切都从点亮一个灯开始。只要熟练掌握了点灯,其他的都是考验内功(算法)深度。
下面给出完整代码,可以作为其他字符设备platform驱动的一个demo。
#include "linux/init.h"
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/of.h"
#include "linux/of_device.h"
#include "linux/of_gpio.h"
#include "linux/device.h"
#include "linux/cdev.h"
#include "linux/errno.h"
#include "linux/fs.h"
#include "linux/platform_device.h"
#include "linux/types.h"
#include "asm/mach/map.h"
#include "asm/uaccess.h"
#include "asm/io.h"
#define LEDDEV_CNT 1///<设备个数
#define LEDDEV_NAME "myled"
#define LED_ON 1
#define LED_OFF 0
struct leddev_dev{
dev_t devid;
struct cdev mcdev;
struct class *mclass;
struct device *mdevice;
int marjor;
int led0;
}my_leddev;
int myled_probe(struct platform_device *pdev);
int myled_remove(struct platform_device *pdev);
static const struct of_device_id led_idtable[] = {
{.compatible = "myled"},
{}
};
int myled_open(struct inode *pnode, struct file *pfile);
int myled_release (struct inode *pnode, struct file *pfile);
ssize_t myled_read (struct file *pfile, char __user *pdate, size_t size, loff_t * poffset);
ssize_t myled_write (struct file *pfile, const char __user *pdate, size_t size, loff_t *poffset);
static struct platform_driver ledplatform_driver = {
.probe = myled_probe,
.remove = myled_remove,
.driver = {
.name = "Braju's Led",
.of_match_table = of_match_ptr(led_idtable),
},
};
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = myled_open,
.release = myled_release,
.write = myled_write,
.read = myled_read,
};
int myled_open(struct inode *pnode, struct file *pfile)
{
pfile->private_data = &my_leddev.led0;
return 0;
}
int myled_release (struct inode *pnode, struct file *pfile)
{
pfile->private_data = NULL;
return 0;
}
ssize_t myled_read (struct file *pfile, char __user *pdate, size_t size, loff_t * poffset)
{
int retv;
int led = *((int *)pfile->private_data);
retv = gpio_get_value(led);
if(retv == 0){
u8 status = 1;
retv = copy_to_user(pdate, &status, sizeof(status));
if(retv < 0){
pr_err("copy date to userspace failed!");
return EFAULT;
}
}
else if(retv == 1){
u8 status = 0;
retv = copy_to_user(pdate, &status, sizeof(status));
if(retv < 0){
pr_err("copy date to userspace failed!");
return -EFAULT;
}
}
else {
return -ENAVAIL;
}
return 1;
}
ssize_t myled_write (struct file *pfile, const char __user *pdate, size_t size, loff_t *poffset)
{
int ret;
u8 ledstatus;
int led = *((int *)pfile->private_data);
ret = copy_from_user(&ledstatus, pdate, size);
if(ret < 0){
return -EFAULT;
}
if(ledstatus == LED_ON){
gpio_set_value(led, 0);
}
else if(ledstatus == LED_OFF){
gpio_set_value(led,1);
}
return 0;
}
int led_init(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *match;
int ret = 0;
match = of_match_device(led_idtable, &pdev->dev);
if(match){
my_leddev.led0 = of_get_named_gpio(pdev->dev.of_node, "led-gpio", 0);
if(my_leddev.led0 < 0){
pr_err("can't get led gpio!\r\n");
return -EINVAL;
}
}
else{
pr_err("can't match device!\r\n");
return -ENOENT;
}
gpio_request(my_leddev.led0, "led0");
ret = gpio_direction_output(my_leddev.led0, 1);
if(ret < 0){
pr_err("can't set gpio!\r\n");
return -EINVAL;
}
return 0;
}
int myled_probe(struct platform_device *pdev)
{
pr_info("led platform device was matched! \r\n");
/*1.设置设备号*/
if(my_leddev.marjor){
my_leddev.devid = MKDEV(my_leddev.marjor, 0);
register_chrdev_region(my_leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
}
else{
alloc_chrdev_region(&my_leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
my_leddev.marjor = MAJOR(my_leddev.devid);
}
/*2.注册设备*/
cdev_init(&my_leddev.mcdev, &led_fops);
cdev_add(&my_leddev.mcdev, my_leddev.devid, LEDDEV_CNT);
/*3.创建类*/
my_leddev.mclass = class_create(THIS_MODULE, LEDDEV_NAME);
if(IS_ERR(my_leddev.mclass)){
return PTR_ERR(my_leddev.mclass);
}
/*4.创建设备*/
my_leddev.mdevice = device_create(my_leddev.mclass, NULL, my_leddev.devid, NULL, LEDDEV_NAME);
if(IS_ERR(my_leddev.mdevice)){
return PTR_ERR(my_leddev.mdevice);
}
/*5.设备初始化*/
return led_init(pdev);
}
int myled_remove(struct platform_device *pdev)
{
cdev_del(&my_leddev.mcdev);
unregister_chrdev_region(my_leddev.devid, LEDDEV_CNT);
device_destroy(my_leddev.mclass, my_leddev.devid);
class_destroy(my_leddev.mclass);
return 0;
}
module_platform_driver(ledplatform_driver);
MODULE_LICENSE("GPL");