02.platform设备驱动实验
说明¶
总结该方法编程有两种方式,第一种是设备树还没有的时候使用的是建立device文件和driver文件,device文件中写资源定义,driver文件中写字符设备的相关驱动。第二种就是设备树有了之后,就不需要写device文件,设备树会自动生成,只需要写driver文件然后相关名字和设备树定义的名字对应。
旧方式编程¶
device文件编写¶
主要完善platform_device设备结构体,
代码编写:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/*
* 寄存器地址定义
*/
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
#define REGISTER_LENGTH 4
/* @description : 释放flatform设备模块的时候此函数会执行
* @param - dev : 要释放的设备
* @return : 无
*/
static void led_release(struct device *dev)
{
printk("led device released!\r\n");
}
/*
* 设备资源信息,也就是LED0所使用的所有寄存器
*/
static struct resource led_resources[] = {
[0] = {
.start = CCM_CCGR1_BASE,
.end = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[1] = {
.start = SW_MUX_GPIO1_IO03_BASE,
.end = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[2] = {
.start = SW_PAD_GPIO1_IO03_BASE,
.end = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[3] = {
.start = GPIO1_DR_BASE,
.end = (GPIO1_DR_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[4] = {
.start = GPIO1_GDIR_BASE,
.end = (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
};
/*
* platform设备结构体
*/
static struct platform_device led_device = {
.name = "imx6ul-led",
.id = -1,
.dev = {
.release = &led_release,
},
.num_resources = ARRAY_SIZE(led_resources),
.resource = led_resources,
};
static int __init led_device_init(void)
{
return platform_device_register(&led_device);
}
static void __exit led_device_exit(void)
{
platform_device_unregister(&led_device);
}
module_init(led_device_init);
module_exit(led_device_exit);
MODULE_LICENSE("GPL");
driver文件编写¶
主要完善platform_device设备结构体,
还有就是获取device中的资源。
driver主要代码编写:
static int led_probe(struct platform_device *dev)
{
int i = 0;
int ressize[5];
u32 val = 0;
struct resource *ledsource[5];
printk("led driver and device has matched!\r\n");
/* 1、获取资源 */
for (i = 0; i < 5; i++) {
ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); /* 依次MEM类型资源 */
if (!ledsource[i]) {
dev_err(&dev->dev, "No MEM resource for always on\n");
return -ENXIO;
}
ressize[i] = resource_size(ledsource[i]);
}
/* 2、初始化LED */
/* 寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);
/* 初始化LED相关 */
/* 注册字符设备驱动 */
return 0;
}
static int led_remove(struct platform_device *dev)
{
/*取消映射 删除设备等*/
return 0;
}
/* platform驱动结构体 */
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */
},
.probe = led_probe,
.remove = led_remove,
};
/*
* @description : 驱动模块加载函数
* @param : 无
* @return : 无
*/
static int __init leddriver_init(void)
{
return platform_driver_register(&led_driver);
}
/*
* @description : 驱动模块卸载函数
* @param : 无
* @return : 无
*/
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
设备树式编程¶
设备树式编程将device的生成工作交给了设备树,所以我们只需要修改driver相关的代码即可。
设备树文件编写¶
在设备树根下添加节点
alientekled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "alientek-led";
status = "okay";
reg = <
0X020C406C 0X04/* CCM_CCGR1_BASE */
0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
0X0209C000 0X04 /* GPIO1_DR_BASE */
0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
};
属性#address-cells 和#size-cells 都为 1,表示 reg 属性中起始地址占用一个字长(cell),地址长度也占用一个字长(cell)。 属性 compatbile 设置 alphaled 节点兼容性为“atkalpha-led”。 属性 status 设置状态为“okay”。 如“0X020C406C 0X04”表示 I.MX6ULL 的 CCM_CCGR1 寄存器,其中寄存器首地址为 0X020C406C,长度为 4 个字节。
然后编译dts。
然后将其放到tftpboot目录下,因为我启动用的tftp
可以进入到/proc/device-tree/base下查看是否有alphaled这个节点信息。
可以进入到/sys/firmware/devicetree/base/alphaled下查看是否有alphaled这个节点的相关信息,可以查看一下 compatible、status 等属性值是否和我们设置的一致。
然后再在驱动开发中的注册的时候。就可以通过of相关的函数获取设备信息。
如先定义一个设备节点
然后就可以通过节点名,获得节点信息
通过节点,就能够获取里面的各个节点信息
struct property *proper;
//获得compatible属性内容
proper = of_find_property(nd, "compatible", NULL);
int ret;
const char *str;
//获得status属性内容
ret = of_property_read_string(nd, "status", &str);
u32 regdata[14];
//获得reg属性内容
ret = of_property_read_u32_array(nd, "reg", regdata, 10);
driver文件编写¶
platform_driver结构体多加了一个匹配表信息,匹配表里面的compatible信息要和节点信息一样。
/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
{ .compatible = "alientek-led" },
{ /* Sentinel */ }
};
static struct platform_driver led_driver = {
.driver = {
.name = "alientekled",
.of_match_table = led_of_match
},
.probe = led_probe,
.remove = led_remove
};
然后就是获取数据的那一块代码变了,有两种方式。
第一种:
1、读节点信息
2、读取节点中的reg信息
3、ioremap地址映射
第二种:
1、读节点信息
2、of_iomap地址映射
driver主要不同的代码编写:
static int led_probe(struct platform_device *dev)
{
uint32_t val = 0;
uint32_t regdata[14];
int ret;
//读节点信息,有两种方式
led.nd = of_find_node_by_path("/alientekled");//正点原子
//led.nd = dev->dev.of_node; //韦东山
//第一种 读REG然后寄存器地址映射
ret = of_property_read_u32_array(led.nd, "reg", regdata, 10);
if(ret < 0) {
printk("reg property read failed!\r\n");
}
IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
GPIO1_DR = ioremap(regdata[6], regdata[7]);
GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
// 第二种 使用节点信息,寄存器地址映射
// IMX6U_CCM_CCGR1 = of_iomap(led.nd, 0);
// SW_MUX_GPIO1_IO03 = of_iomap(led.nd, 1);
// SW_PAD_GPIO1_IO03 = of_iomap(led.nd, 2);
// GPIO1_DR = of_iomap(led.nd, 3);
// GPIO1_GDIR = of_iomap(led.nd, 4);
/* 初始化LED相关 */
/* 注册字符设备驱动 */
return 0;
}