数据流向
用户空间打开设备。
通过 inode 里的设备号在内核中找到cdev。
cdev与一个file_operation绑定。
将该fop 返回给每个进程空间打开的file表,填充一个file 绑定操作,并返回其索引。
设备树更改
设备树主要记录开发板上的设备节点信息。这里先添加自己的节点信息,并把之前使用gpio4_io16的设备状态设为disabled。
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_leds>;
status = "default";
sysled {
lable = "sysled";
gpios = <&gpio4 16 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
default-state = "off";
};
};
my_leds {
compatible = "my-leds";
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_my_leds>;
led0{
gpios = <&gpio5 9 GPIO_ACTIVE_HIGH>;
default-state = "off";
};
led1{
gpios = <&gpio4 16 GPIO_ACTIVE_HIGH>;
default-state = "off";
};
};
之后在&iomuxc 节点下添加gpio控制的节点
pinctrl_my_leds: my-leds{
fsl,pins = <
MX6UL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x17059 /* my_led */
MX6UL_PAD_NAND_DQS__GPIO4_IO16 0x17059 /*board led*/
>
};
其中 用到了 pinctrl 和 gpio子系统
pinctrl子系统
有三个功能:
- 获取设备树中pin信息
- 根据获取到的pin信息来设置其复用功能
- 设置其电气特性操作
主要用于简化pin (pad)的电气特性设置操作,上面 0x17059就是对管脚的电气特性进行初始化,电气特性包括:上下拉,速度,驱动能力等。
MX6UL_PAD_SNVS_TAMPER9__GPIO5_IO09等定义于 imx6ull.dtsi->imx6ull-pinfunc.h->imx6ul-pinfunc.h 文件中。
gpio子系统
初始化gpio并提供相应的gpio控制API,方便开发者使用gpio。
gpios = <&gpio5 9 GPIO_ACTIVE_HIGH>;
有3个参数 第一个参数表示 该gpio 属于的chip ,第二个参数表示其属于该chip的第多少号 ,第三个表示其高电平有效。
其中gpio5节点定义于imx6ul.dtsi
驱动编写
首先定义一些变量,结构体,然后模块init函数
定义的结构体
struct gpioled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* sysled所使用的GPIO编号 */
int led1_gpio; /* 外接的led gpio编号 */
};
led_init(void)
static int __init led_init(void)
{
int ret = 0;
const char *str;
/* 设置LED所使用的GPIO */
/* 1、获取设备节点:gpioled */
gpioled.nd = of_find_node_by_path("/my_leds");
if(gpioled.nd == NULL) {
printk("gpioled node not find!\r\n");
return -EINVAL;
}
/* 2.读取status属性 */
ret = of_property_read_string(gpioled.nd, "status", &str);
if(ret < 0)
return -EINVAL;
if (strcmp(str, "okay"))
return -EINVAL;
/* 3、获取compatible属性值并进行匹配 */
ret = of_property_read_string(gpioled.nd, "compatible", &str);
if(ret < 0) {
printk("gpioled: Failed to get compatible property\n");
return -EINVAL;
}
if (strcmp(str, "my-leds")) {
printk("gpioled: Compatible match failed %s\n",str);
return -EINVAL;
}
/* 4、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
gpioled.led_gpio = of_get_named_gpio(of_find_node_by_path("/my_leds/led1"), "gpios", 0);
if(gpioled.led_gpio < 0) {
printk("can't get sysled gpios");
return -EINVAL;
}
gpioled.led1_gpio = of_get_named_gpio(of_find_node_by_path("/my_leds/led0"), "gpios", 0);
if (gpioled.led1_gpio < 0)
{
printk("can't get led1 gpios");
return -EINVAL;
}
printk("led-gpio num = %d\r\n", gpioled.led_gpio);
printk("led-gpio num = %d\r\n", gpioled.led1_gpio);
/* 5.向gpio子系统申请使用GPIO */
ret = gpio_request(gpioled.led_gpio, "LED-GPIO");
if (ret)
{
printk(KERN_ERR "gpioled: Failed to request led-gpio\n");
return ret;
}
ret = gpio_request(gpioled.led1_gpio, "LED1-GPIO");
if (ret)
{
printk(KERN_ERR "gpioled1: Failed to request led-gpio\n");
return ret;
}
/* 6、设置PI0为输出,并且输出低电平,默认关闭LED灯 */
ret = gpio_direction_output(gpioled.led_gpio, 0);
if (ret < 0)
{
printk("can't set gpio!\r\n");
}
ret = gpio_direction_output(gpioled.led1_gpio, 0);
if (ret < 0)
{
printk("can't set gpio!\r\n");
}
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (gpioled.major)
{ /* 定义了设备号 */
gpioled.devid = MKDEV(gpioled.major, 0);
ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
if (ret < 0)
{
pr_err("cannot register %s char driver [ret=%d]\n", GPIOLED_NAME, GPIOLED_CNT);
goto free_gpio;
}
}
else
{ /* 没有定义设备号 */
ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */
if (ret < 0)
{
pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", GPIOLED_NAME, ret);
goto free_gpio;
}
gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
}
printk("gpioled major=%d,minor=%d\r\n", gpioled.major, gpioled.minor);
/* 2、初始化cdev */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &gpioled_fops);
/* 3、添加一个cdev */
cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
if (ret < 0)
goto del_unregister;
/* 4、创建类 */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if (IS_ERR(gpioled.class))
{
goto del_cdev;
}
/* 5、创建设备 */
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if (IS_ERR(gpioled.device))
{
goto destroy_class;
}
return 0;
destroy_class:
class_destroy(gpioled.class);
del_cdev:
cdev_del(&gpioled.cdev);
del_unregister:
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
free_gpio:
gpio_free(gpioled.led_gpio);
gpio_free(gpioled.led1_gpio);
return -EIO;
}
获取设备树中定义的节点,
然后自行匹配一下该设备是否 处于可用状态,以及是否为正确的compatible
,然后获取树中的两个gpio子节点,
并向系统申请使用这两个管脚,
对管脚设置输出方向并设置值关闭。
之后就是注册驱动的步骤:
- 创建设备号
- 初始化cdev,设置字符设备属于该模块, 绑定fops结构体
- add cdev
- 创建类及设备,自动生成节点
led_exit(void)
注销设备号,释放其他资源(cdev,class,devid,gpio)
设置file_operations,绑定操作
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioled; /* 设置私有数据 */
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
struct gpioled_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt); /* 接收APP发送过来的数据 */
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if(ledstat == SYSLEDON) {
gpio_set_value(dev->led_gpio, 1); /* 打开sysLED灯 */
} else if(ledstat == SYSLEDOFF) {
gpio_set_value(dev->led_gpio, 0); /* 关闭sysLED灯 */
} else if (ledstat == LEDON)
{
gpio_set_value(dev->led1_gpio, 1); /* 打开LED灯 */
}else if(ledstat == LEDOFF )
{
gpio_set_value(dev->led1_gpio,0); /*关闭LED灯*/
}
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作函数 */
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};