linux设备树-pin控制器驱动
Posted 大奥特曼打小怪兽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux设备树-pin控制器驱动相关的知识,希望对你有一定的参考价值。
----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------
上一节我们已经分析了pinctrl subsystem,这一节将会介绍pin controller driver的编写。其主要包含两个步骤:
- 为SoC pin controller分配一个pinctrl_desc,并进行初始化;
- 调用pinctrl_register将pinctrl_desc注册pinctrl subsystem;
一、pin controller设备注册
1.1 注册
我们定位到drivers/pinctrl/samsung/pinctrl-samsung.c文件:
二、注册pinctrl_desc
参考文章
NanoPi NEO Air使用十:自己编写驱动来控制LED
修改设备树
打开/home/ql/linux/H3/linux/arch/arm/boot/dts/sunxi-h3-h5.dtsi文件,发现H3的pin功能都是由pinctrl子系统控制的:
pio: pinctrl@01c20800 {
/* compatible is in per SoC .dtsi file */
reg = <0x01c20800 0x400>;
interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_PIO>, <&osc24M>, <&osc32k>;
clock-names = "apb", "hosc", "losc";
gpio-controller;
#gpio-cells = <3>;
interrupt-controller;
#interrupt-cells = <3>;
csi_pins: csi {
pins = "PE0", "PE1", "PE2", "PE3", "PE4",
"PE5", "PE6", "PE7", "PE8", "PE9",
"PE10", "PE11";
function = "csi";
};
emac_rgmii_pins: emac0 {
pins = "PD0", "PD1", "PD2", "PD3", "PD4",
"PD5", "PD7", "PD8", "PD9", "PD10",
"PD12", "PD13", "PD15", "PD16", "PD17";
function = "emac";
drive-strength = <40>;
};
i2s0_pins: i2s0 {
pins = "PA18", "PA19", "PA20", "PA21";
function = "i2s0";
};
i2c0_pins: i2c0 {
pins = "PA11", "PA12";
function = "i2c0";
};
i2c1_pins: i2c1 {
pins = "PA18", "PA19";
function = "i2c1";
};
i2c2_pins: i2c2 {
pins = "PE12", "PE13";
function = "i2c2";
};
mmc0_pins_a: mmc0@0 {
pins = "PF0", "PF1", "PF2", "PF3",
"PF4", "PF5";
function = "mmc0";
drive-strength = <30>;
bias-pull-up;
};
mmc0_cd_pin: mmc0_cd_pin@0 {
pins = "PF6";
function = "gpio_in";
bias-pull-up;
};
mmc1_pins_a: mmc1@0 {
pins = "PG0", "PG1", "PG2", "PG3",
"PG4", "PG5";
function = "mmc1";
drive-strength = <30>;
bias-pull-up;
};
mmc2_8bit_pins: mmc2_8bit {
pins = "PC5", "PC6", "PC8",
"PC9", "PC10", "PC11",
"PC12", "PC13", "PC14",
"PC15", "PC16";
function = "mmc2";
drive-strength = <30>;
bias-pull-up;
};
spdif_tx_pins_a: spdif@0 {
pins = "PA17";
function = "spdif";
};
spi0_pins: spi0 {
pins = "PC0", "PC1", "PC2", "PC3";
function = "spi0";
};
spi1_pins: spi1 {
pins = "PA15", "PA16", "PA14", "PA13";
function = "spi1";
};
uart0_pins_a: uart0@0 {
pins = "PA4", "PA5";
function = "uart0";
};
uart1_pins: uart1 {
pins = "PG6", "PG7";
function = "uart1";
};
uart1_rts_cts_pins: uart1_rts_cts {
pins = "PG8", "PG9";
function = "uart1";
};
uart2_pins: uart2 {
pins = "PA0", "PA1";
function = "uart2";
};
uart2_rts_cts_pins: uart2_rts_cts {
pins = "PA2", "PA3";
function = "uart2";
};
uart3_pins: uart3 {
pins = "PA13", "PA14";
function = "uart3";
};
uart3_rts_cts_pins: uart3_rts_cts {
pins = "PA15", "PA16";
function = "uart3";
};
pwm0_pins: pwm0 {
pins = "PA5";
function = "pwm0";
};
};
我们要把开发板上的状态灯作为普通输出IO来使用,该pin的功能也应该在这里定义。打开开发板原理图,发现状态灯的IO为PA10。
因此回到/home/ql/linux/H3/linux/arch/arm/boot/dts/sun8i-h3-nanopi.dtsi,添加如下内容:
&pio {
leds_npi: led_pins {
pins = "PA10";
function = "gpio_out";
};
};
但是我们发现该文件下,开发板自己已经有了:
&pio {
leds_npi: led_pins {
pins = "PA10";
function = "gpio_out";
};
spi0_cs_pins: spi0_cs_pins {
pins = "PC3", "PA6";
function = "gpio_out";
};
};
于是我们就不用再添加了。
继续打开该文件,在根节点下面把原来的leds 节点注释掉,新增testleds节点,修改后如下:
/*leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&leds_npi>, <&leds_r_npi>;
status {
label = "status_led";
gpios = <&pio 0 10 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
pwr {
label = "LED2";
gpios = <&r_pio 0 10 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
};*/
testleds {
compatible = "test-gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&leds_npi>;
gpios = <&pio 0 10 GPIO_ACTIVE_HIGH>;
status = "okay";
};
testleds节点的compatible 属性用来匹配驱动。pinctrl-0属性来指示使用的引脚和功能。
编写platform 驱动
新建02_led文件夹,在该文件夹下添加leddriver.c文件,内容为:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LEDDEV_CNT 1 /* 设备号长度 */
#define LEDDEV_NAME "testled" /* 设备名字 */
#define LEDOFF 0
#define LEDON 1
/* leddev设备结构体 */
struct leddev_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
struct device_node *node; /* LED设备节点 */
int led0; /* LED灯GPIO标号 */
};
struct leddev_dev leddev; /* led设备 */
/*
* @description : LED打开/关闭
* @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED
* @return : 无
*/
void led0_switch(u8 sta)
{
if (sta == LEDON )
gpio_set_value(leddev.led0, 0);
else if (sta == LEDOFF)
gpio_set_value(leddev.led0, 1);
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &leddev; /* 设置私有数据 */
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[2];
unsigned char ledstat;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\\r\\n");
return -EFAULT;
}
ledstat = databuf[0];
if (ledstat == LEDON) {
led0_switch(LEDON);
} else if (ledstat == LEDOFF) {
led0_switch(LEDOFF);
}
return 0;
}
/* 设备操作函数 */
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
/*
* @description : flatform驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - dev : platform设备
* @return : 0,成功;其他负值,失败
*/
static int led_probe(struct platform_device *dev)
{
printk("led driver and device was matched!\\r\\n");
/* 1、设置设备号 */
if (leddev.major) {
leddev.devid = MKDEV(leddev.major, 0);
register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
} else {
alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
leddev.major = MAJOR(leddev.devid);
}
/* 2、注册设备 */
cdev_init(&leddev.cdev, &led_fops);
cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
/* 3、创建类 */
leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
if (IS_ERR(leddev.class)) {
return PTR_ERR(leddev.class);
}
/* 4、创建设备 */
leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
if (IS_ERR(leddev.device)) {
return PTR_ERR(leddev.device);
}
/* 5、获取设备节点:/testleds */
leddev.node = of_find_node_by_path("/testleds");
if (leddev.node == NULL){
printk("testleds node nost find!\\r\\n");
return -EINVAL;
}
/* 6、获取设备树中testleds节点的 gpios 属性,得到 LED 所使用的 GPIO 编号 */
leddev.led0 = of_get_named_gpio(leddev.node, "gpios", 0);
if (leddev.led0 < 0) {
printk("can't get gpios\\r\\n");
return -EINVAL;
}
gpio_request(leddev.led0, "led0"); /* 申请GPIO,在使用一个 GPIO 之前一定要使用 gpio_request进行申请 */
gpio_direction_output(leddev.led0, 1); /* led0 IO设置为输出,默认高电平 */
return 0;
}
/*
* @description : platform驱动的remove函数,移除platform驱动的时候此函数会执行
* @param - dev : platform设备
* @return : 0,成功;其他负值,失败
*/
static int led_remove(struct platform_device *dev)
{
gpio_set_value(leddev.led0, 1); /* 卸载驱动的时候关闭LED */
cdev_del(&leddev.cdev); /* 删除cdev */
unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */
device_destroy(leddev.class, leddev.devid);
class_destroy(leddev.class);
return 0;
}
/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
{ .compatible = "test-gpio-leds" },
{ /* Sentinel */ }
};
/* platform驱动结构体 */
static struct platform_driver led_driver = {
.driver = {
.name = "test-led", /* 驱动名字,用于和设备匹配 */
.of_match_table = led_of_match, /* 设备树匹配表 */
},
.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");
MODULE_AUTHOR("qlexcel");
添加应用程序
在该文件夹下添加ledApp.c文件,内容为:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LEDOFF 0
#define LEDON 1
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[2];
if(argc != 3){
printf("Error Usage!\\r\\n");
return -1;
}
filename = argv[1];
/* 打开led驱动 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\\r\\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0){
printf("LED Control Failed!\\r\\n");
close(fd);
return -1;
}
retvalue = close(fd); /* 关闭文件 */
if(retvalue < 0){
printf("file %s close failed!\\r\\n", argv[1]);
return -1;
}
return 0;
}
添加Makefile
在该文件夹下添加Makefile文件,内容为:
KERNELDIR := /home/qlLinux驱动开发pinctrl子系统和gpio子系统