#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#define MYLED_CNT 1 //设备号个数
#define MYLED_NAME "myled" //名字
//LED设备结构体
struct myled_dev{
dev_t devid; //设备号
struct cdev cdev; //cdev结构体
struct class *class; //类
struct device *device; //设备号
int led_gpio; //GPIO编号
};
static struct myled_dev myled; //LED设备
static int myled_open(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t myled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
char kern_buf[1];
ret = copy_from_user(kern_buf, buf, cnt); //得到应用层传过来的数据
if(ret > 0)
{
printk("copy form user failed\n");
return -EFAULT;
}
if(kern_buf[0] == 0)
{
gpio_set_value(myled.led_gpio, 0);
}
else if(kern_buf[0] == 1)
{
gpio_set_value(myled.led_gpio, 1);
}
return 0;
}
static int myled_init(struct device_node *nd)
{
const char *str;
int val;
int ret;
//使用 of_get_named_gpio 函数获取设备树中 led 节点指定的gpio 管脚,得到一个 GPIO 编号,
//那么这个编号是 linux 内核 GPIO 子系统对 gpio 的编号,一个编号就对应一个 gpio,
//那么我们这里得到的这个编号就对应设备树中 led-gpio 指定的那个管脚。
//参数1:设备节点 参数2:要获取的GPIO信息的属性名
//参数3:GPIO索引,因为一个属性里面可能包含多个GPIO,此参数指定要获取哪个GPIO的编号,如果只有一个GPIO信息,此参数为0
//返回值:获取到的GPIO编号,为负值时,获取失败
myled.led_gpio = of_get_named_gpio(nd, "led-gpio", 0);
//获得GPIO编号后,使用该函数判断编号是否有效
if(!gpio_is_valid(myled.led_gpio))
{
printk("of_get_named_gpio is failed\n");
return -EINVAL;
}
//申请使用GPIO,参数1:gpio编号,参数2:给gpio设置名字
//返回值:0成功,其他失败
ret = gpio_request(myled.led_gpio, "pl_led");
if(ret)
{
printk("gpio_request is failed\n");
return ret;
}
//用于读取属性中字符串的值,参数1:设备节点, 参数2:要读取的属性名字
//参数3:读取到的字符串 返回值:0成功,负值失败
ret = of_property_read_string(nd, "default-state", &str);
if(!ret)
{
if(!strcmp(str, "on"))
{
val = 1;
}
else
{
val = 0;
}
}
else
{
val = 0;
}
//将gpio设置为输出模式,并设置GPIO初始电平状态
gpio_direction_output(myled.led_gpio, val);
return 0;
}
//LED设备操作函数
static struct file_operations myled_fops = {
.owner = THIS_MODULE,
.open = myled_open,
.write = myled_write,
};
//platform驱动的probe函数,当驱动与设备匹配成功后此函数就会执行
//参数1:platform设备指针,返回值:0成功, 其他负值失败
static int myled_probe(struct platform_device *pdev)
{
int ret;
printk("driver and device has matched\n");
//led初始化
ret = myled_init(pdev->dev.of_node);
if(ret)
return ret;
//注册字符设备驱动
//动态分配设备号 参数1:保存申请到的设备号,
//参数2:次设备号起始地址,以该地址连续申请多个次设备号,一般从0开始
//参数3:要申请的设备号数量 参数4:设备名字
//返回值:成功返回0,失败返回负数
ret = alloc_chrdev_region(&myled.devid, 0, MYLED_CNT, MYLED_NAME);
if(ret)
goto out1;
//初始化cdev
myled.cdev.owner = THIS_MODULE;
cdev_init(&myled.cdev, &myled_fops);
//向linux系统添加字符设备cdev
ret = cdev_add(&myled.cdev, myled.devid, MYLED_CNT);
if(ret)
goto out2;
//自动创建设备节点的工作是在驱动程序的入口函数中完成的
//一般在cdev_add函数后面添加自动创建设备节点相关代码
//首先需要创建类class,参数1:一般为THIS_MODULE,参数2:类的名字
//返回值:指向结构体class的指针
myled.class = class_create(THIS_MODULE, MYLED_NAME);
if(IS_ERR(myled.class))
{
ret = PTR_ERR(myled.class);
goto out3;
}
//创建好类之后还不能实现自动创建设备节点,还需要在这个类下创建一个设备
//参数1:设备要创建在哪个类下
//参数2:为父设备,一般为NULL
//参数3:设备号
//参数4:为设备可能会用到的数据,一般为NULL
//参数5:设备名字,在/dev/设备名字
myled.device = device_create(myled.class, &pdev->dev, myled.devid, NULL, MYLED_NAME);
if(IS_ERR(myled.device))
{
ret = PTR_ERR(myled.device);
goto out4;
}
return 0;
//设备创建失败,需要删除掉类(上面创建了类)
out4:
class_destroy(myled.class);
//创建类class失败,需要删除相应的字符设备(上面添加了字符设备)
out3:
cdev_del(&myled.cdev);
//向linux系统添加字符设备cdev失败,释放掉设备号(上面动态分配了设备号)
out2:
unregister_chrdev_region(myled.devid, MYLED_CNT);
//动态分配设备号失败,释放gpio
out1:
//不使用某个GPIO时,调用gpio_free释放gpio
gpio_free(myled.led_gpio);
return ret;
}
//platform驱动模块卸载时,此函数会执行
static int myled_remove(struct platform_device *dev)
{
printk("led platform driver remove\n");
//删除创建的设备
device_destroy(myled.class, myled.devid);
//删除类
class_destroy(myled.class);
//删除字符设备
cdev_del(&myled.cdev);
//释放掉设备号
unregister_chrdev_region(myled.devid, MYLED_CNT);
//释放gpio
gpio_free(myled.led_gpio);
return 0;
}
//匹配列表
static const struct of_device_id led_of_match[] = {
{.compatible = "lnb, led"},
{}
};
//platform驱动结构体
static struct platform_driver myled_driver = {
.driver = {
.name = "zynq-led", //驱动名字,用于和设备匹配
.of_match_table = led_of_match, //设备树匹配表,用于和设备树中定义的设备匹配
},
.probe = myled_probe, //probe函数
.remove = myled_remove, //remove函数
};
//模块入口函数
static int __init myled_driver_init(void)
{
return platform_driver_register(&myled_driver);
}
//模块出口函数
static void __exit myled_driver_exit(void)
{
platform_driver_unregister(&myled_driver);
}
module_init(myled_driver_init);
module_exit(myled_driver_exit);
MODULE_AUTHOR("wnyears");
MODULE_DESCRIPTION("Platform Driver");
MODULE_LICENSE("GPL");
ZYNQ7020 linux下platform框架的驱动led
最新推荐文章于 2024-07-19 09:14:59 发布