使用设备树来编写led驱动程序

时间:2020-12-22 08:16:37

在总线设备驱动模型中,平台设备是写在c文件中。使用设备树时,平台设备事先并不存在,在dts文件中构造节点,节点里面含有资源。dts文件被编译成dtb文件,然后传递给内核。内核会解析dtb文件,得到一个个device_node,每个节点对应一个device_node结构体,每个device_node结构体变成一个platform_device结构体,该结构体中就含有资源,这些资源来源于dts文件。接下来的处理过程和总线设备驱动模型就一样了,如果设备与驱动相匹配,就调用驱动中的probe函数。可以这么认为,设备树是对总线设备驱动模型的一种改进,它仍然属于总线设备驱动模型的一种。

看一下这个设备树文件:

 // SPDX-License-Identifier: GPL-2.0
/*
* SAMSUNG SMDK2440 board device tree source
*
* Copyright (c) 2018 weidongshan@qq.com
* dtc -I dtb -O dts -o jz2440.dts jz2440.dtb
*/
//这里定义了一些宏,使用的是c语言的语法
#define S3C2410_GPA(_nr) ((0<<16) + (_nr))
#define S3C2410_GPB(_nr) ((1<<16) + (_nr))
#define S3C2410_GPC(_nr) ((2<<16) + (_nr))
#define S3C2410_GPD(_nr) ((3<<16) + (_nr))
#define S3C2410_GPE(_nr) ((4<<16) + (_nr))
#define S3C2410_GPF(_nr) ((5<<16) + (_nr))
#define S3C2410_GPG(_nr) ((6<<16) + (_nr))
#define S3C2410_GPH(_nr) ((7<<16) + (_nr))
#define S3C2410_GPJ(_nr) ((8<<16) + (_nr))
#define S3C2410_GPK(_nr) ((9<<16) + (_nr))
#define S3C2410_GPL(_nr) ((10<<16) + (_nr))
#define S3C2410_GPM(_nr) ((11<<16) + (_nr)) /dts-v1/; / {
model = "SMDK24440";
compatible = "samsung,smdk2440"; #address-cells = <>;
#size-cells = <>; memory@ {
device_type = "memory";
reg = <0x30000000 0x4000000>;
};
/*
cpus {
cpu {
compatible = "arm,arm926ej-s";
};
};
*/
chosen { //设置了内核的命令行参数
bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
}; led {
compatible = "jz2440_led"; //以后就使用compatible在内核里面找到能够支持这个节点的驱动程序,即找到能够支持这个节点的平台drv.
reg = <S3C2410_GPF() >;//reg是register的缩写。在arm系统里面,寄存器和内存是被同样对待的,因为寄存器的访问空间与内存的访问空间没什么差别。
};
};

单板启动之后,

cd /sys/devices/platform/

ls 下会看到xxxxxxx,假设是50005.led。

cd 50005.led

ls会看到这样的文件

driver_override    of_node       subsystem      modalias      power   uevent

cd of_node

ls

compatible     name      reg

cat compatible     就会显示  jz2440_led

cat  name             就会显示led

hexdump -C reg

对应着8个字节,

00 05   00 05  00  00  00  01

对应着两个数值,来源于    reg = <S3C2410_GPF(5) 1>

00 05   00 05    对应S3C2410_GPF(5)代表着寄存器的起始地址,对应着这个宏    #define S3C2410_GPF(_nr) ((5<<16) + (_nr)),表示高16位是5,低16位也是5.

00 00 00 01对应着是1,本意就是寄存器的大小。

在总线设备驱动模型中,是通过名字来进行设备与驱动的匹配。那么在利用设备树时,是利用什么进行匹配的呢。

 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))//用来判断从设备树中得到的platform_devcie与提供的platform_drv
                         //是否匹配,
return ; /* 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) == );
}
 static inline int of_driver_match_device(struct device *dev,
const struct device_driver *drv)
{
  /*从这里可以看出,平台drv中有个成员变量,of_match_table*/
return of_match_device(drv->of_match_table, dev) != NULL;
}
 struct device_driver {
const char *name;
struct bus_type *bus; struct module *owner;
const char *mod_name; /* used for built-in modules */ bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ const struct of_device_id *of_match_table; //该指针指向一项或多项of_device_id int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups; const struct dev_pm_ops *pm; struct driver_private *p;
};
 struct of_device_id
{
char name[];
char type[];
char compatible[];//从dts里得到的platform_device里有compatible属性,两者比较,如果一样的话,就是匹配。
#ifdef __KERNEL__
void *data;
#else
kernel_ulong_t data;
#endif
};
 struct platform_device {
const char * name;
int id;
struct device dev;//对于dts生成的设备platform_device,这里含有Of_node,of_node中含有属性,这含有哪些属性呢,这取决于设备树,比如:
//compatible pin等。这个compatible属性用来寻找支持它的platform_driver.这个compatible是最优先比较。
u32 num_resources;
struct resource * resource; const struct platform_device_id *id_entry; /* MFD cell pointer */
struct mfd_cell *mfd_cell; /* arch specific additions */
struct pdev_archdata archdata;
};

利用设备树编写的led驱动程序如下:

 #include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h> #define S3C2440_GPA(n) (0<<16 | n)
#define S3C2440_GPB(n) (1<<16 | n)
#define S3C2440_GPC(n) (2<<16 | n)
#define S3C2440_GPD(n) (3<<16 | n)
#define S3C2440_GPE(n) (4<<16 | n)
#define S3C2440_GPF(n) (5<<16 | n)
#define S3C2440_GPG(n) (6<<16 | n)
#define S3C2440_GPH(n) (7<<16 | n)
#define S3C2440_GPI(n) (8<<16 | n)
#define S3C2440_GPJ(n) (9<<16 | n) static int led_pin;
static volatile unsigned int *gpio_con;
static volatile unsigned int *gpio_dat; /* 123. 分配/设置/注册file_operations
* 4. 入口
* 5. 出口
*/ static int major;
static struct class *led_class; static unsigned int gpio_base[] = {
0x56000000, /* GPACON */
0x56000010, /* GPBCON */
0x56000020, /* GPCCON */
0x56000030, /* GPDCON */
0x56000040, /* GPECON */
0x56000050, /* GPFCON */
0x56000060, /* GPGCON */
0x56000070, /* GPHCON */
, /* GPICON */
0x560000D0, /* GPJCON */
}; static int led_open (struct inode *node, struct file *filp)
{
/* 把LED引脚配置为输出引脚 */
/* GPF5 - 0x56000050 */
int bank = led_pin >> ;
int base = gpio_base[bank]; int pin = led_pin & 0xffff;
gpio_con = ioremap(base, );
if (gpio_con) {
printk("ioremap(0x%x) = 0x%x\n", base, gpio_con);
}
else {
return -EINVAL;
} gpio_dat = gpio_con + ; *gpio_con &= ~(<<(pin * ));
*gpio_con |= (<<(pin * )); return ;
} static ssize_t led_write (struct file *filp, const char __user *buf, size_t size, loff_t *off)
{
/* 根据APP传入的值来设置LED引脚 */
unsigned char val;
int pin = led_pin & 0xffff; copy_from_user(&val, buf, ); if (val)
{
/* 点灯 */
*gpio_dat &= ~(<<pin);
}
else
{
/* 灭灯 */
*gpio_dat |= (<<pin);
} return ; /* 已写入1个数据 */
} static int led_release (struct inode *node, struct file *filp)
{
printk("iounmap(0x%x)\n", gpio_con);
iounmap(gpio_con);
return ;
} static struct file_operations myled_oprs = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
}; static int led_probe(struct platform_device *pdev)
{
struct resource *res; /* 根据platform_device的资源进行ioremap */
res = platform_get_resource(pdev, IORESOURCE_MEM, );
led_pin = res->start; major = register_chrdev(, "myled", &myled_oprs); led_class = class_create(THIS_MODULE, "myled");
device_create(led_class, NULL, MKDEV(major, ), NULL, "led"); /* /dev/led */ return ;
} static int led_remove(struct platform_device *pdev)
{
unregister_chrdev(major, "myled");
device_destroy(led_class, MKDEV(major, ));
class_destroy(led_class); return ;
} static const struct of_device_id of_match_leds[] = {
{ .compatible = "jz2440_led", .data = NULL },
{ /* sentinel */ }
}; struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
.of_match_table = of_match_leds, /* 能支持哪些来自于dts的platform_device */
}
}; static int myled_init(void)
{
platform_driver_register(&led_drv);
return ;
} static void myled_exit(void)
{
platform_driver_unregister(&led_drv);
} module_init(myled_init);
module_exit(myled_exit); MODULE_LICENSE("GPL");

在设备树中形如下面这样的写法太别扭了

reg = <S3C2410_GPF(5) 1>;  
S3C2410_GPF(5)这个地方本来定义了引脚,你非要把它当做register。看一下能否将register去掉,能不能使用另外一种方法指定引脚。
 // SPDX-License-Identifier: GPL-2.0
/*
* SAMSUNG SMDK2440 board device tree source
*
* Copyright (c) 2018 weidongshan@qq.com
* dtc -I dtb -O dts -o jz2440.dts jz2440.dtb
*/ #define S3C2410_GPA(_nr) ((0<<16) + (_nr))
#define S3C2410_GPB(_nr) ((1<<16) + (_nr))
#define S3C2410_GPC(_nr) ((2<<16) + (_nr))
#define S3C2410_GPD(_nr) ((3<<16) + (_nr))
#define S3C2410_GPE(_nr) ((4<<16) + (_nr))
#define S3C2410_GPF(_nr) ((5<<16) + (_nr))
#define S3C2410_GPG(_nr) ((6<<16) + (_nr))
#define S3C2410_GPH(_nr) ((7<<16) + (_nr))
#define S3C2410_GPJ(_nr) ((8<<16) + (_nr))
#define S3C2410_GPK(_nr) ((9<<16) + (_nr))
#define S3C2410_GPL(_nr) ((10<<16) + (_nr))
#define S3C2410_GPM(_nr) ((11<<16) + (_nr)) /dts-v1/; / {
model = "SMDK24440";
compatible = "samsung,smdk2440"; #address-cells = <>;
#size-cells = <>; memory@ {
device_type = "memory";
reg = <0x30000000 0x4000000>;
};
/*
cpus {
cpu {
compatible = "arm,arm926ej-s";
};
};
*/
chosen {
bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
}; led {
compatible = "jz2440_led";
pin = <S3C2410_GPF()>;
};
};
 #include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h> #define S3C2440_GPA(n) (0<<16 | n)
#define S3C2440_GPB(n) (1<<16 | n)
#define S3C2440_GPC(n) (2<<16 | n)
#define S3C2440_GPD(n) (3<<16 | n)
#define S3C2440_GPE(n) (4<<16 | n)
#define S3C2440_GPF(n) (5<<16 | n)
#define S3C2440_GPG(n) (6<<16 | n)
#define S3C2440_GPH(n) (7<<16 | n)
#define S3C2440_GPI(n) (8<<16 | n)
#define S3C2440_GPJ(n) (9<<16 | n) static int led_pin;
static volatile unsigned int *gpio_con;
static volatile unsigned int *gpio_dat; /* 123. 分配/设置/注册file_operations
* 4. 入口
* 5. 出口
*/ static int major;
static struct class *led_class; static unsigned int gpio_base[] = {
0x56000000, /* GPACON */
0x56000010, /* GPBCON */
0x56000020, /* GPCCON */
0x56000030, /* GPDCON */
0x56000040, /* GPECON */
0x56000050, /* GPFCON */
0x56000060, /* GPGCON */
0x56000070, /* GPHCON */
, /* GPICON */
0x560000D0, /* GPJCON */
}; static int led_open (struct inode *node, struct file *filp)
{
/* 把LED引脚配置为输出引脚 */
/* GPF5 - 0x56000050 */
int bank = led_pin >> ;
int base = gpio_base[bank]; int pin = led_pin & 0xffff;
gpio_con = ioremap(base, );
if (gpio_con) {
printk("ioremap(0x%x) = 0x%x\n", base, gpio_con);
}
else {
return -EINVAL;
} gpio_dat = gpio_con + ; *gpio_con &= ~(<<(pin * ));
*gpio_con |= (<<(pin * )); return ;
} static ssize_t led_write (struct file *filp, const char __user *buf, size_t size, loff_t *off)
{
/* 根据APP传入的值来设置LED引脚 */
unsigned char val;
int pin = led_pin & 0xffff; copy_from_user(&val, buf, ); if (val)
{
/* 点灯 */
*gpio_dat &= ~(<<pin);
}
else
{
/* 灭灯 */
*gpio_dat |= (<<pin);
} return ; /* 已写入1个数据 */
} static int led_release (struct inode *node, struct file *filp)
{
printk("iounmap(0x%x)\n", gpio_con);
iounmap(gpio_con);
return ;
} static struct file_operations myled_oprs = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
}; static int led_probe(struct platform_device *pdev)
{
struct resource *res; /* 根据platform_device的资源进行ioremap */
res = platform_get_resource(pdev, IORESOURCE_MEM, );
if (res) {
led_pin = res->start;
}
else {
/* 获得pin属性 */
of_property_read_s32(pdev->dev.of_node, "pin", &led_pin);
} if (!led_pin)
{
printk("can not get pin for led\n");
return -EINVAL;
} major = register_chrdev(, "myled", &myled_oprs); led_class = class_create(THIS_MODULE, "myled");
device_create(led_class, NULL, MKDEV(major, ), NULL, "led"); /* /dev/led */ return ;
} static int led_remove(struct platform_device *pdev)
{
unregister_chrdev(major, "myled");
device_destroy(led_class, MKDEV(major, ));
class_destroy(led_class); return ;
} static const struct of_device_id of_match_leds[] = {
{ .compatible = "jz2440_led", .data = NULL },
{ /* sentinel */ }
}; struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
.of_match_table = of_match_leds, /* 能支持哪些来自于dts的platform_device */
}
}; static int myled_init(void)
{
platform_driver_register(&led_drv);
return ;
} static void myled_exit(void)
{
platform_driver_unregister(&led_drv);
} module_init(myled_init);
module_exit(myled_exit); MODULE_LICENSE("GPL");