目录
[一 、Platform总线结合dts](#一 、Platform总线结合dts)
1.相当于把driver.c和plat.c结合起来,通过of_device_id函数和paltform来进行匹配
2.在初始化中注册palt_driver,在probe函数中(即匹配成功后)写此设备号注册以及查找设备节点(of_find_node_by_path函数)
[(3)为什么按键不用of_get_named_gpio显式获取 GPIO](#(3)为什么按键不用of_get_named_gpio显式获取 GPIO)
一 、Platform总线结合dts
在上一篇文章中,我们使用的是仅 Platform 总线和仅dts的方法。
**现在我们使用如今最新的方法:**Platform总线结合dts
-
传统方式(仅 Platform ) :每支持一块新板子,就要在
arch/arm/mach-xxx/下新增一个 C 文件(或少则几百行,多则上千行),里面充满platform_device_register、resource定义、gpio数组等。这些代码大多重复且难以阅读。 -
DTS 方式(Platform总线结合dts ) :板级描述变成几百行结构化的文本,不再需要在内核源码里为每块板子增加 C 文件。主流架构的
mach-xxx目录如今只剩下少量核心代码,大量板级文件已被删除。
结果:内核更干净,维护者更容易审查硬件描述。
设备树这种方式添加到总线的好处在于,它会匹配上了再寻找设备节点,少做很多无用功
匹配的是compatible变量,跟名字pt_led和name1没关系。

代码为:
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/of.h>
#define DEV_NAME "led"
static volatile unsigned int * sw_mux;
static volatile unsigned int * sw_pad;
static volatile unsigned int * gpio1_dr;
static volatile unsigned int * gpio1_gdir;
static void led_init(void)
{
*sw_mux = 0x05;
*sw_pad = 0x10b0;
*gpio1_gdir |= (1 << 3);
*gpio1_dr |= (1 << 3);
}
static void led_on(void)
{
*gpio1_dr &= ~(1 << 3);
}
static void led_off(void)
{
*gpio1_dr |= (1 << 3);
}
static int open(struct inode * node, struct file * file)
{
led_init();
printk("led open...\n");
return 0;
}
static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
//copy_to_user();
printk("led read...\n");
return 0;
}
static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
// "ledon" on "ledoff" off
unsigned char data[10] = {0};
size_t len_cp = len < sizeof(data) ? len : sizeof data;
int size_cp = copy_from_user(data, buf, len_cp);
if(size_cp < 0)
return size_cp;
if(!strcmp(buf, "ledon"))
led_on();
else if(!(strcmp(buf, "ledoff")))
led_off();
else
return -EINVAL;
printk("led write...\n");
return size_cp;
}
static int close(struct inode * node, struct file * file)
{
led_off();
printk("led close...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static const struct of_device_id match_table[] =
{
[0] = {.compatible = "pt-led"}
};
static struct platform_driver drv =
{
.probe = probe,
.remove = remove,
.driver =
{
.name = DEV_NAME
.of_match_table = match_table
}
};
static int __init led1_init(void)
{
int ret = platform_driver_register(&drv);
if(ret < 0)
goto err_reg;
printk("platform_driver_register ...\n");
return 0;
err_reg:
platform_driver_unregister(&drv);
printk("platform_driver_register failed\n");
return ret;
}
static int probe(struct platform_device * pdev)
{
struct device_node * pnode;
const char * pcom;
const char * pname1;
u32 led_array[8] = {0};
int ret = misc_register(&misc);
if(ret < 0)
goto err_misc;
pnode = of_find_node_by_path("/pt_led");
if(pnode == NULL)
{
printk("of_find_node_by_path err\n");
return -1;
}
of_property_read_string(pnode, "compatible", &pcom);
of_property_read_string(pnode, "name1", &pname1);
printk("led compatible = %s name1 = %s\n", pcom, pname1);
of_property_read_u32_array(pnode, "reg", led_array, sizeof(led_array) / sizeof(led_array[0]));
sw_mux = ioremap(led_array[0], led_array[1]);
sw_pad = ioremap(led_array[2], led_array[3]);
gpio1_gdir = ioremap(led_array[4], led_array[5]);
gpio1_dr = ioremap(led_array[6], led_array[7]);
printk("led_init ##############\n");
return 0;
err_misc:
misc_deregister(&misc);
printk("led_init failed ret = %d\n", ret);
return ret;
}
static void __exit led1_exit(void)
{
iounmap(gpio1_gdir);
iounmap(gpio1_dr);
iounmap(sw_pad);
iounmap(sw_mux);
misc_deregister(&misc);
printk("led_exit ##############\n");
}
module_init(led1_init);
module_exit(led1_exit);
MODULE_LICENSE("GPL");
与之前的区别:
1.相当于把driver.c和plat.c结合起来,通过of_device_id函数和paltform来进行匹配

2.在初始化中注册palt_driver,在probe函数中(即匹配成功后)写此设备号注册以及查找设备节点(of_find_node_by_path函数)
二、gpio子系统
Linux 内核提供了 GPIO 子系统,用于统一管理各个 SoC 的 GPIO 控制器,并为驱动开发者提供标准 API。
这样我就不需要使用 ioremap函数转换地址了,只需要调用 GPIO 子系统提供的标准 API 即可。GPIO 控制器驱动内部已经做了 ioremap。
需要用到这两个头文件
#include <linux/of_gpio.h>
#include <linux/gpio.h>
将上面的代码改写为:
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#define DEV_NAME "led"
static int led_gpio;
#define LED_ON 0
#define LED_OFF 1
static void led_init(void)
{
gpio_direction_output(led_gpio, LED_OFF);
}
static void led_on(void)
{
gpio_set_value(led_gpio, LED_ON);
}
static void led_off(void)
{
gpio_set_value(led_gpio, LED_OFF);
}
static int open(struct inode * node, struct file * file)
{
led_init();
printk("led open...\n");
return 0;
}
static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
//copy_to_user();
printk("led read...\n");
return 0;
}
static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
// "ledon" on "ledoff" off
unsigned char data[10] = {0};
size_t len_cp = len < sizeof(data) ? len : sizeof data;
int size_cp = copy_from_user(data, buf, len_cp);
if(size_cp < 0)
return size_cp;
if(!strcmp(buf, "ledon"))
led_on();
else if(!(strcmp(buf, "ledoff")))
led_off();
else
return -EINVAL;
printk("led write...\n");
return size_cp;
}
static int close(struct inode * node, struct file * file)
{
led_off();
printk("led close...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int probe(struct platform_device * pdev)
{
struct device_node * pnode;
int ret = misc_register(&misc);
if(IS_ERR_VALUE(ret))
goto err_misc;
pnode = of_find_node_by_path("/pt_gpioled");
if(IS_ERR(pnode))
{
ret = PTR_ERR(pnode);
goto err_find_node;
}
led_gpio = of_get_named_gpio(pnode, "led-gpio", 0);
gpio_request(led_gpio, "red_led");
gpio_direction_output(led_gpio, LED_OFF);
printk("probe led misc_register ##############\n");
return 0;
err_find_node:
printk("of_find_node_by_path err\n");
err_misc:
printk("led probe failed ret = %d\n", ret);
misc_deregister(&misc);
return ret;
}
static int remove(struct platform_device * pdev)
{
gpio_free(led_gpio);
misc_deregister(&misc);
printk("remove led misc_deregister ##############\n");
return 0;
}
static const struct of_device_id match_table[] =
{
[0] = {.compatible = "pt-gpioled"}
};
static struct platform_driver drv =
{
.probe = probe,
.remove = remove,
.driver =
{
.name = DEV_NAME,
.of_match_table = match_table
}
};
static int __init led1_init(void)
{
int ret = platform_driver_register(&drv);
if(ret < 0)
goto err_reg;
printk("platform_driver_register ...\n");
return 0;
err_reg:
printk("platform_driver_register failed ret = %d\n", ret);
platform_driver_unregister(&drv);
return ret;
}
static void __exit led1_exit(void)
{
platform_driver_unregister(&drv);
printk("platform_driver_unregister ...\n");
}
module_init(led1_init);
module_exit(led1_exit);
MODULE_LICENSE("GPL");
函数解释:
(1)led_gpio = of_get_named_gpio(pnode, "led-gpio", 0);//查找设备树的led信息获取gpio编号:
np:设备树节点指针
propname:属性名,如"led-gpio"这个必须和设备树的变量名一致
index(可以理解为数组下标):索引(因为属性可能包含多个 GPIO,如gpios = <&gpio1 3 0>, <&gpio1 4 0>;)返回值:GPIO 编号(整型),负数表示错误。
(2)gpio_request(led_gpio, "red_led"); //获取gpio,后面的是标签随意写
(3)gpio_direction_output(led_gpio, LED_OFF);//设置为输出模式,后面是输出1(宏)
(4)gpio_direction_output(led_gpio)//设置为输入模式
(5)gpio_free(led_gpio);//释放gpio
设备树:
1.这里面是引脚配置和电气属性
2.表示上电后的默认电平
三、中断
通过led的学习我们同样可以写一个按键的驱动代码:
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#define DEV_NAME "key"
static int key_gpio;
static unsigned int irq_key_num;
#define key_ON 0
#define key_OFF 1
static int arg = 100;
static irqreturn_t key_irq_handler(int dev_num, void * dev)
{
printk("dev_num = %d dev = %d\n", dev_num, *(int *)dev);
return IRQ_HANDLED;
}
static int open(struct inode * node, struct file * file)
{
printk("key open...\n");
return 0;
}
static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
//copy_to_user();
printk("key read...\n");
return 0;
}
static int close(struct inode * node, struct file * file)
{
printk("key close...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int probe(struct platform_device * pdev)
{
struct device_node * pnode;
int ret = misc_register(&misc);
if(IS_ERR_VALUE(ret))
goto err_misc;
pnode = of_find_node_by_path("/pt_key");
if(IS_ERR(pnode))
{
ret = PTR_ERR(pnode);
goto err_find_node;
}
key_gpio = of_get_named_gpio(pnode, "key-gpio", 0);
if(key_gpio < 0)
{
ret = key_gpio;
goto err_get_gpio;
}
ret = gpio_request(key_gpio, "pt_key");
if(ret < 0)
goto err_gpio_request;
irq_key_num = gpio_to_irq(key_gpio);
if(irq_key_num < 0)
{
ret = irq_key_num;
goto err_gpio_to_irq;
}
ret = request_irq(irq_key_num, key_irq_handler, IRQF_TRIGGER_FALLING, "key_irq", &arg);
if(ret < 0)
goto err_request_irq;
printk("probe key misc_register ##############\n");
return 0;
err_request_irq:
disable_irq(irq_key_num);
free_irq(irq_key_num, &arg);
printk("key err_request_irq\n");
err_gpio_to_irq:
printk("key err_gpio_to_irq\n");
err_gpio_request:
printk("key err_gpio_request\n");
err_get_gpio:
printk("key err_get_gpio\n");
err_find_node:
printk("of_find_node_by_path err\n");
err_misc:
printk("key probe faikey ret = %d\n", ret);
misc_deregister(&misc);
return ret;
}
static int remove(struct platform_device * pdev)
{
disable_irq(irq_key_num);
free_irq(irq_key_num, &arg);
gpio_free(key_gpio);
misc_deregister(&misc);
printk("remove key misc_deregister ##############\n");
return 0;
}
static const struct of_device_id match_table[] =
{
[0] = {.compatible = "pt-key"}
};
static struct platform_driver drv =
{
.probe = probe,
.remove = remove,
.driver =
{
.name = DEV_NAME,
.of_match_table = match_table
}
};
static int __init key1_init(void)
{
int ret = platform_driver_register(&drv);
if(ret < 0)
goto err_reg;
printk("platform_driver_register ...\n");
return 0;
err_reg:
printk("platform_driver_register faikey ret = %d\n", ret);
platform_driver_unregister(&drv);
return ret;
}
static void __exit key1_exit(void)
{
platform_driver_unregister(&drv);
printk("platform_driver_unregister ...\n");
}
module_init(key1_init);
module_exit(key1_exit);
MODULE_LICENSE("GPL");
函数解释:
(1)static irqreturn_t key_irq_handler(int dev_num, void * dev)中断触发后的句柄,后面的void*dev是中断请求传的参数
(2)irq_key_num = gpio_to_irq(key_gpio);//获取中断号
(3)ret = request_irq(irq_key_num, key_irq_handler, IRQF_TRIGGER_FALLING, "key_irq", &arg);
参数1:中断号
参数2:中断句柄
参数3:触发方式
参数4:中断名
参数5:传入的参数,代码这里写的全局变量static int arg = 100;
(4)结束中断
disable_irq(irq_key_num); //关闭中断
free_irq(irq_key_num, &arg);//释放中断
设备树:
在我们的dts中添加这两个信息

睡眠与唤醒机制:
我们可以在原来的代码里面使用标志位然后用while来循环判断标志位来实现各种功能,但这种方法非常浪费资源,CPU一直空转,这里我们加入一个wait_queue_head_t,它 是 Linux 内核中 等待队列头 的类型,定义在 <linux/wait.h> 中。
相关函数:
init_waitqueue_head(&wq); //初始化等待队列
wait_event_interruptible(wq, condition); //睡眠,condition为我们的标志位,为0则一直休眠,为1则唤醒
wake_up_interruptible(&wq); //唤醒,判断标志位是否为1,不为1继续休眠
加入机制后的中断代码为:
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/of_irq.h>
#include <linux/wait.h>
#include <linux/sched.h>
#define DEV_NAME "key"
static unsigned int irq_key_num;
static int arg = 100;
static wait_queue_head_t wq;
static int condition;
static irqreturn_t key_irq_handler(int dev_num, void * dev)
{
condition = 1;
wake_up_interruptible(&wq);
printk("dev_num = %d dev = %d\n", dev_num, *(int *)dev);
return IRQ_HANDLED;
}
static int open(struct inode * node, struct file * file)
{
printk("key open...\n");
return 0;
}
static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
condition = 0;
wait_event_interruptible(wq, condition);
//copy_to_user();
printk("key read...\n");
return 0;
}
static int close(struct inode * node, struct file * file)
{
printk("key close...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int probe(struct platform_device * pdev)
{
struct device_node * pnode;
int ret = misc_register(&misc);
if(IS_ERR_VALUE(ret))
goto err_misc;
pnode = of_find_node_by_path("/pt_key");
if(IS_ERR(pnode))
{
ret = PTR_ERR(pnode);
goto err_find_node;
}
irq_key_num = irq_of_parse_and_map(pnode, 0);
if(irq_key_num < 0)
{
ret = irq_key_num;
goto err_of_parse;
}
ret = request_irq(irq_key_num, key_irq_handler, IRQF_TRIGGER_FALLING, "key_irq", &arg);
if(ret < 0)
goto err_request_irq;
init_waitqueue_head(&wq);
printk("probe key misc_register ##############\n");
return 0;
err_request_irq:
disable_irq(irq_key_num);
free_irq(irq_key_num, &arg);
printk("key err_request_irq\n");
err_of_parse:
printk("key err_of_parse\n");
err_find_node:
printk("of_find_node_by_path err\n");
err_misc:
printk("key probe faikey ret = %d\n", ret);
misc_deregister(&misc);
return ret;
}
static int remove(struct platform_device * pdev)
{
disable_irq(irq_key_num);
free_irq(irq_key_num, &arg);
misc_deregister(&misc);
printk("remove key misc_deregister ##############\n");
return 0;
}
static const struct of_device_id match_table[] =
{
[0] = {.compatible = "pt-key"}
};
static struct platform_driver drv =
{
.probe = probe,
.remove = remove,
.driver =
{
.name = DEV_NAME,
.of_match_table = match_table
}
};
static int __init key1_init(void)
{
int ret = platform_driver_register(&drv);
if(ret < 0)
goto err_reg;
printk("platform_driver_register ...\n");
return 0;
err_reg:
printk("platform_driver_register faikey ret = %d\n", ret);
platform_driver_unregister(&drv);
return ret;
}
static void __exit key1_exit(void)
{
platform_driver_unregister(&drv);
printk("platform_driver_unregister ...\n");
}
module_init(key1_init);
module_exit(key1_exit);
MODULE_LICENSE("GPL");
这样在我们应用层read的时候函数就会阻塞(休眠),当我们中断后就会继续执行了。
四、错误处理
内核里面的出错必须处理,不处理系统会崩
某些函数的返回值是指针,这种怎么判断它的错误码?
这里需要用到我们的错误处理函数:

| 宏/函数 | 输入类型 | 输出/判断 | 适用场景 |
|---|---|---|---|
IS_ERR_VALUE(ret) |
unsigned long |
是否为错误值(来自负数错误码) | 整数返回值函数 |
IS_ERR(pnode) |
void * |
是否为错误指针 | 指针返回值函数 |
PTR_ERR(pnode) |
void * |
错误码(负数) | 从错误指针提取错误码 |
示例:

五、总结与补充
遇到的问题:
(1)模块加载不进去,返回错误码-16(busy)
解决办法:把默认设备树中的key和led相关的全部删掉,使用我们自己写的即可
(2)模块加载不进去,返回错误码-2
解决办法:检查driver是否与设备节点匹配(compatible、of_find_node_by_path和of_get_named_gpio)
(3)为什么按键不用of_get_named_gpio显式获取 GPIO
我们的按键中断代码只关心中断事件,并不需要直接读取按键的 GPIO 电平(比如做消抖、轮询)。
补充:
(1)Vim替换字符串:

(2)IRQF_ONESHOT中断宏:
数据过来的时候会触发中断,在没有把数据读空之前,再有数据来就不触发中断



