字符(char)设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少要实现open,close,read和write系统调用。大多数字符设备是一个个只能顺序访问的数据通道。
1.管理设备号
dev_t dev = MKDEV(主设备号,次设备号) -->组合设备号
主设备号 = MAJOR(dev_t dev) -->提取主设备号
次设备号=MINOR(dev_t dev) -->提取次设备号
2.分配设备号
#include <linux/fs.h>
int register_chrdev_region(dev_t from, unsigned count, const char *name) -->静态申请
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name); -->动态分配
返回值:成功,0;失败,负数
参数说明:
count: 该类设备的设备个数
name: 设备名
baseminer:次设备号
3.注销设备号
#include <linux/fs.h>
void unregister_chrdev_region(dev_t from, unsigned count);
4.分配cdev
#include <linux/cdev.h>
方式一:静态分配
struct cdev mdev;
方式二:动态分配
struct cdev *pdev = cdev_alloc();
参数说明:
struct cdev {
struct kobject kobj; /* 内嵌的 kobject 对象 */
struct module *owner; /所属模块/
struct file_operations *ops; /文件操作结构体/
struct list_head list;
dev_t dev; /设备号/
unsigned int count;
};
如:
#include <linux/fs.h>
struct file_operations dev_fops = { //这里只是简单的举例
.llseek = NULL,
.read = dev_read,
.write = dev_write,
.ioctl = dev_ioctl,
.open = dev_open,
.release = dev_release,
};
5.初始化cdev
#include <linux/cdev.h>
void cdev_init(struct cdev *, struct file_operations *);
参数说明:
cdev: 待初始化的cdev结构
fops: 设备对应的操作函数集
6.向内核注册字符设备
#include <linux/cdev.h>
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数说明:
p: 待添加到内核的字符设备结构
dev: 设备号
count: 该类设备的设备个数
7.设备操作原型
int (*open) (struct inode *inode, struct file *file)
打开设备,响应open系统
int (*release) (struct inode *inode, struct file *file);
关闭设备,响应close系统调用
loff_t (*llseek) (struct file *file, loff_t offset, int whence)
重定位读写指针,响应lseek系统调用
ssize_t (*read) (struct file *file, char __user *buf, size_t count, loff_t *ppos)
从设备读取数据,响应read系统调用
ssize_t (*write)(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
向设备写入数据,响应write系统调用
8.创建设备节点
//创建设备节点
cls = class_create(THIS_MODULE,"irq_driver");
class_dev = device_create(cls, NULL ,MKDEV(major,0),NULL,"buttons");
//buttons为/dev目录下的设备文件名
9.内核空间和用户空间的内存拷贝
#include <linux/uaccess.h>
int copy_from_user(void *to, const void __user *from, int n)
int copy_to_user(void __user *to, const void *from, int n)
10.注销驱动
void cdev_del(struct cdev *)
11.设备控制
–>应用函数
int ioctl(int fd,unsigned long cmd,...)
参数说明
fd | 要控制的设备文件描述符 |
---|---|
cmd | 发送给设备的控制命令 |
Size | 参数长度 |
… | 第3个参数是可选的参数,存在与否是依赖于控 制命令(第 2 个参数 ) |
cmd组成
Type(类型/幻数): 表明这是属于哪个设备的命令。
Number(序号),用来区分同一设备的不同命令
Direction:参数传送的方向,可能的值是 _IOC_NONE(没有
数据传输), _IOC_READ, _IOC_WRITE(向设备写入参数)
–>驱动函数
2.6.36 之前的内核
long (*ioctl) (struct inode* node ,struct file* filp, unsigned int cmd,unsigned long arg)
2.6.36之后的内核
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg)
12.定义命令
#include <asm-generic/ioctl.h>
_IO(type,nr):不带参数的命令
_IOR(type,nr,datatype):从设备中读参数的命令
_IOW(type,nr,datatype):向设备写入参数的命令
参数说明:
type(类型/幻数): 表明这是属于哪个设备的命令。
nr序号 ,用来区分同一设备的不同命令
datatype:数据类型
如:
#define MEM_MAGIC ‘m’ //定义幻数
#define MEM_SET _IOW(MEM_MAGIC, 0, int)
13.一个简单的耳灯控制例程
–>dts配置
&soc {
earlight {
compatible = "allwinner,earlight";
gpio_en = <&r_pio PL 11 1 0 1 0>;
status = "okay";
};
};
–>驱动代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/pm_runtime.h>
//#define PER (HZ/1000)
#define LED_DEVICE_NAME "earlight"
static int led_gpio = -1;
static dev_t gpio_dev_t;
static struct cdev *char_dev;
static struct class *cls;
static struct device *dev;
static int light = 0;
static const struct of_device_id earlight_of_match[] = {
{.compatible = "allwinner,earlight",},
{ /* sentinel */ }
};
//static int earlight_control(u8 onoff) {
// if(led_gpio) {
// if(onoff) {
// return gpio_set_value(led_gpio,1);
// } else {
// return gpio_set_value(led_gpio,0);
// }
// }
//
// return -1;
//}
static int led_open (struct inode *inode, struct file *filp){
return 0;
}
ssize_t led_read (struct file *file, char __user *buf,size_t count, loff_t *ppos) {
int ret = -1;
ret = copy_to_user(buf, &light, sizeof(light));
if(ret < 0) printk(KERN_ERR"%s:error read !!!\n",__func__);
return sizeof(light);
}
ssize_t led_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
char vbuff[5] = {'\0','\0','\0','\0','\0'};
int value = 0;
int err = -1;
if(count > 4) {
printk(KERN_ERR"%s:too large to write !!!\n",__func__);
return count;
}
err = copy_from_user(vbuff, buf, count);
if(err < 0){
printk(KERN_ERR"%s:error write !!!\n",__func__);
return count;
}
if(count == 1){
value = vbuff[0];
printk(KERN_ERR"%s:value= %d\n",__func__,value);
if(value>=0 && value<=10){
light = (uint8_t)value;
} else if(value>=0x30 && value<=(10+0x30)){
light = (uint8_t)value - 0x30;
}
gpio_set_value(led_gpio,!!light);
return count;
}
//printk(KERN_ERR"%s: vbuff = %s\n",__func__,vbuff);
err = sscanf(vbuff,"%d",&value);
if(err < 0) {
printk(KERN_ERR"%s: value is invalid!!!\n",__func__);
return count;
}
printk(KERN_ERR"%s:value= %d\n",__func__,value);
if(value>=0 && value<=10){
light = (uint8_t)value;
}
gpio_set_value(led_gpio,!!light);
return count;
}
static int led_close (struct inode *inode, struct file *filp){
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_close,
};
static int earlight_probe(struct platform_device *pdev) {
struct device_node *np = pdev->dev.of_node;
int ret = -1;
printk(KERN_ERR"------%s------\n",__func__);
if (!np) {
dev_err(&pdev->dev, "laser: Device Tree np missing\n");
return -EINVAL;
}
led_gpio = of_get_named_gpio_flags(np, "gpio_en", 0, NULL);
if (!gpio_is_valid(led_gpio)) {
pr_err("%s: led_gpio is invalid. \n", __func__);
return -1;
}
ret = gpio_request(led_gpio, "ear-light");
if(ret < 0){
pr_err("%s: can't request led_gpio. \n", __func__);
return -1;
}
gpio_direction_output(led_gpio,1);
gpio_set_value(led_gpio,1);
light = 1;
/* 申请设备号 */
ret = alloc_chrdev_region(&gpio_dev_t,0,1,LED_DEVICE_NAME);
if(ret < 0){
printk(KERN_ERR"dev_t request error!\n");
goto chrdev_alloc_error;
}
/* 分配cdev */
char_dev = cdev_alloc();
if (NULL == char_dev || IS_ERR(char_dev)) {
printk(KERN_ERR"char_dev fail!\n");
goto cdev_alloc_error;
}
/* 初始化cdev */
cdev_init(char_dev, &fops);
/* 注册cdev */
ret = cdev_add(char_dev, gpio_dev_t, 1);
if(ret < 0){
printk(KERN_ERR"cdev add error!\n");
goto cdev_add_error;
}
/* 创建设备节点 */
cls = class_create(THIS_MODULE, LED_DEVICE_NAME);
dev = device_create(cls, NULL, gpio_dev_t, NULL, LED_DEVICE_NAME);
if (NULL == dev || IS_ERR(dev)) {
printk(KERN_ERR"device_create fail!\n");
goto device_creat_error;
}
return 0;
/* 异常处理 */
device_creat_error:
class_destroy(cls);
cdev_del(char_dev);
cdev_add_error:
// kfree(char_dev);
cdev_alloc_error:
unregister_chrdev_region(gpio_dev_t,1);
chrdev_alloc_error:
gpio_set_value(led_gpio, 0);
gpio_free(led_gpio);
return -1;
}
static int earlight_remove(struct platform_device *pdev) {
device_del(dev);
class_destroy(cls);
cdev_del(char_dev);
// kfree(char_dev);
unregister_chrdev_region(gpio_dev_t,1);
gpio_set_value(led_gpio, 0);
gpio_free(led_gpio);
return 0;
}
#ifdef CONFIG_PM
static int earlight_suspend(struct device *dev) {
if(led_gpio) gpio_set_value(led_gpio,0);
return 0;
}
static int earlight_resume(struct device *dev) {
if(led_gpio) gpio_set_value(led_gpio,!!light);
return 0;
}
static const struct dev_pm_ops earlight_pm = {
.suspend = earlight_suspend,
.resume = earlight_resume,
};
#define earlight_pm_ops (&earlight_pm)
#else /* CONFIG_PM */
#define earlight_pm_ops NULL
#endif /* CONFIG_PM */
static struct platform_driver earlight_driver = {
.driver = {
.name = "earlight",
.of_match_table = of_match_ptr(earlight_of_match),
.pm = earlight_pm_ops,
},
.probe = earlight_probe,
.remove = earlight_remove,
};
module_platform_driver(earlight_driver);
MODULE_LICENSE("GPL v2");
–>测试方法
开灯:echo “1” > /dev/earlight
关灯:echo “0” > /dev/earlight
–>拓展功能
android从按电源键到进入系统播放开机音乐需要好多秒的时间,带屏还好,屏亮了就知道系统起来了。如果不带屏的呢,这种产品也有许多,如机顶盒。甲方就会反馈,我怎么按半天机器才起来。其实这时候早就起来了,只是进入系统花了点时间。这时候就要改uboot的代码。个人思路,开机两秒之内屏就会亮,可以在这部分代码加上亮灯操作。以全志代码修改为例:
--- longan/brandy/brandy-2.0/u-boot-2018/drivers/video/sunxi/disp2/disp/dev_disp.c (revision 5310)
+++ longan/brandy/brandy-2.0/u-boot-2018/drivers/video/sunxi/disp2/disp/dev_disp.c (working copy)
@@ -630,6 +630,19 @@
g_disp_drv.inited = true;
tick_printf("%s finish\n", __func__);
+ user_gpio_set_t ear_gpio;
+ u32 gpio0_handle = 0;
+ if(fdt_get_one_gpio("/soc/earlight", "gpio_en", &ear_gpio) == 0) {
+ gpio0_handle = sunxi_gpio_request(&ear_gpio, 1);
+ gpio_write_one_pin_value(gpio0_handle ,1, "gpio_en");
+
+ } else {
+ tick_printf("get /soc/earlight/gpio_en error!!!\n");
+ }
+
exit:
return ret;
拓展
以上示例程序代码只是控制一个灯,如果是几个灯呢,如果是做呼吸灯,是不是要控制呼吸长短等参数,这是就需要传好多个参数,怎么办。一种办法是实现unlocked_ioctl函数,通过ioctl的方式,但是这种方式就要自己写C语言来测试,app控制还要实现JNI,有点麻烦。为了不加班,还是在read/write函数实现控制,以下示例:
ssize_t aw9106b_read (struct file *file, char __user *buf,size_t count, loff_t *ppos) {
int p;
char *str = "Parameter 1:led1,1:on,0:off\n"
"Parameter 2:led2,1:on,0:off\n"
"Parameter 3:led3,1:on,0:off\n"
"Parameter 4:led4,1:on,0:off\n"
"Parameter 5:led5,1:on,0:off\n"
"Parameter 6:led6,1:on,0:off\n"
"Parameter 7:fade in and fade out time setting level 0-5\n"
"Parameter 8:full light and full dark time setting level 0-7\n"
"ps:1,1,1,1,1,1,3,2\n";
//printk("(strlen(str)=%d, count=%ld, *ppos=%d\n",strlen(str),count,*ppos);
if(*ppos >= strlen(str)) return 0;
p = count + *ppos;
if((p - strlen(str)) >= 0){
copy_to_user(buf, str + (*ppos), strlen(str));
*ppos += strlen(str);
return strlen(str);
} else {
copy_to_user(buf, str + (*ppos), count);
*ppos += count;
return count;
}
}
ssize_t aw9106b_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
char vbuff[30];
u8 i,ret;
/* init vbuf */
for(i=0;i<sizeof(vbuff);i++) vbuff[i] = '\0';
ret = copy_from_user(vbuff, buf, count);
if(ret < 0) {
printk(KERN_ERR"%s: copy_from_user error!!!\n",__func__);
return count;
}
printk(KERN_ERR"%s: vbuff = %s\n",__func__,vbuff);
ret = sscanf(vbuff,"%d,%d,%d,%d,%d,%d,%d,%d",&on_led0_r,&on_led0_g,&on_led0_b,
&on_led1_r,&on_led1_g,&on_led1_b,
&rise_time,&hold_time);
if(ret < 0) {
printk(KERN_ERR"%s: Parameter is invalid!!!\n",__func__);
return count;
}
on_led0_r = !!on_led0_r;
on_led0_g = !!on_led0_g;
on_led0_b = !!on_led0_b;
on_led1_r = !!on_led1_r;
on_led1_g = !!on_led1_g;
on_led1_b = !!on_led1_b;
printk("on_led0_r=%d,on_led0_g=%d,on_led0_b=%d,on_led1_r=%d,on_led1_g=%d,on_led1_b=%d,rise_time=%d,hold_time=%d\n",
on_led0_r,on_led0_g,on_led0_b,
on_led1_r,on_led1_g,on_led1_b,
rise_time,hold_time);
if(on_led0_r==on_led0_g==on_led0_b==on_led1_r==on_led1_g==on_led1_b==0){
printk(KERN_ERR"%s:off all led!!!\n",__func__);
AW9106_OnOff(0);
return count;
}
if(rise_time < 0 || rise_time > 5) {
printk(KERN_ERR"%s:rise_time set error!!!\n",__func__);
return count;
}
if(hold_time < 0 || hold_time > 7) {
printk(KERN_ERR"%s:hold_time set error!!!\n",__func__);
return count;
}
AW9106_breath_ctrl();
return count;
}
echo “1,1,1,1,1,1,3,2” > /dev/led_xxx实现控制,参数有点复杂,时间久了,忘记了参数1,2,3,…是什么了,去服务器找代码看就太费事了,cat /dev/led_xxx看一下参数说明。
14.PWM控制灯光亮度例程
–>dts配置
&soc{
led_pwm:led_pwm{
compatible = "allwinner,led_pwm";
pwm_num = <1>;
status = "okay";
};
pwm1: pwm1@0300a000 {
pinctrl-names = "active", "sleep";
pinctrl-0 = <&pwm1_pin_a>;
pinctrl-1 = <&pwm1_pin_b>;
};
};
&pio{
pwm1_pin_a: pwm1@0 {
allwinner,pins = "PB10";
allwinner,function = "pwm1";
allwinner,muxsel = <0x04>;
allwinner,drive = <0x2>;
allwinner,pull = <0>;
allwinner,data = <0xffffffff>;
};
pwm1_pin_b: pwm1@1 {
allwinner,pins = "PB10";
allwinner,function = "io_disabled";
allwinner,muxsel = <0x07>;
allwinner,drive = <0x2>;
allwinner,pull = <0>;
allwinner,data = <0xffffffff>;
};
};
–>驱动代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/pwm.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define LED_PWM_DEVICE_NAME "led_pwm"
#define LEVEL (10)
static int pwm_id = -1;
static const int period = 20*1000*1000;
static uint8_t dr = LEVEL/2;
static struct pwm_device *pwm;
static int8_t is_run = 0;
static dev_t gpio_dev_t;
static struct cdev *char_dev;
static struct class *cls;
static struct device *dev;
static const struct of_device_id led_pwm_of_match[] = {
{.compatible = "allwinner,led_pwm",},
{ /* sentinel */ }
};
static void ctrl_pwm(int mdr) {
if(mdr>=0 && is_run){
pwm_config(pwm, period * mdr/LEVEL, period);
} if(mdr > 0 && !is_run){
pwm_config(pwm, period * mdr/LEVEL, period);
pwm_enable(pwm);
is_run = 1;
} /*else if(mdr == 0 && is_run) {
pwm_disable(pwm);
is_run = 0;
}*/
}
static int led_pwm_open (struct inode *inode, struct file *filp){
return 0;
}
ssize_t led_pwm_read (struct file *file, char __user *buf,size_t count, loff_t *ppos) {
copy_to_user(buf, &dr, sizeof(dr));
return sizeof(dr);
}
ssize_t led_pwm_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
char vbuff[5] = {'\0','\0','\0','\0','\0'};
int value = 0;
int err = -1;
if(count > 4) {
printk(KERN_ERR"%s:too large to write !!!\n",__func__);
return count;
}
copy_from_user(vbuff, buf, count);
if(count == 1){
value = vbuff[0];
printk(KERN_ERR"%s:value= %d\n",__func__,value);
if(value>=0 && value<=10){
dr = (uint8_t)value;
} else if(value>=0x30 && value<=(10+0x30)){
dr = (uint8_t)value - 0x30;
}
ctrl_pwm(dr);
return count;
}
//printk(KERN_ERR"%s: vbuff = %s\n",__func__,vbuff);
err = sscanf(vbuff,"%d",&value);
if(err < 0) {
printk(KERN_ERR"%s: value is invalid!!!\n",__func__);
return count;
}
printk(KERN_ERR"%s:value= %d\n",__func__,value);
if(value>=0 && value<=10){
dr = (uint8_t)value;
}
ctrl_pwm(dr);
return count;
}
static int led_pwm_close (struct inode *inode, struct file *filp){
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_pwm_open,
.read = led_pwm_read,
.write = led_pwm_write,
.release = led_pwm_close,
};
static int led_pwm_probe(struct platform_device *pdev) {
struct device_node *np = pdev->dev.of_node;
int ret = -1;
if (!np) {
dev_err(&pdev->dev, "laser: Device Tree np missing\n");
return -EINVAL;
}
ret = of_property_read_u32(np, "pwm_num", &pwm_id);
if (ret < 0) {
dev_err(&pdev->dev, "laser: pwm-id missing\n");
return ret;
}
printk("led_pwm: use pwm id is %d\n",pwm_id);
pwm = pwm_request(pwm_id, "led-pwm");
if (IS_ERR(pwm)) {
dev_err(&pdev->dev, "led_pwm: unable to request legacy PWM %d\n",pwm_id);
ret = PTR_ERR(pwm);
return ret;
}
pwm_config(pwm, period * dr/LEVEL, period);
ret = pwm_set_polarity(pwm, PWM_POLARITY_INVERSED);
//ret = pwm_set_polarity(pwm, PWM_POLARITY_NORMAL);
if (ret < 0)
printk("pwm set polarity fail, ret = %d\n", ret);
//pwm_enable(pwm);
is_run = 0;
/* 申请设备号 */
ret = alloc_chrdev_region(&gpio_dev_t,0,1,LED_PWM_DEVICE_NAME);
if(ret < 0){
printk(KERN_ERR"dev_t request error!\n");
goto chrdev_alloc_error;
}
/* 分配cdev */
char_dev = cdev_alloc();
if (NULL == char_dev || IS_ERR(char_dev)) {
printk(KERN_ERR"char_dev fail!\n");
goto cdev_alloc_error;
}
/* 初始化cdev */
cdev_init(char_dev, &fops);
/* 注册cdev */
ret = cdev_add(char_dev, gpio_dev_t, 1);
if(ret < 0){
printk(KERN_ERR"cdev add error!\n");
goto cdev_add_error;
}
/* 创建设备节点 */
cls = class_create(THIS_MODULE, LED_PWM_DEVICE_NAME);
dev = device_create(cls, NULL, gpio_dev_t, NULL, LED_PWM_DEVICE_NAME);
if (NULL == dev || IS_ERR(dev)) {
printk(KERN_ERR"device_create fail!\n");
goto device_creat_error;
}
return 0;
/* 异常处理 */
device_creat_error:
class_destroy(cls);
cdev_del(char_dev);
cdev_add_error:
kfree(char_dev);
cdev_alloc_error:
unregister_chrdev_region(gpio_dev_t,1);
chrdev_alloc_error:
pwm_disable(pwm);
pwm_free(pwm);
return -1;
}
static int led_pwm_remove(struct platform_device *pdev) {
device_del(dev);
class_destroy(cls);
cdev_del(char_dev);
kfree(char_dev);
unregister_chrdev_region(gpio_dev_t,1);
pwm_disable(pwm);
pwm_free(pwm);
return 0;
}
#ifdef CONFIG_PM
static int led_pwm_suspend(struct device *dev) {
return 0;
}
static int led_pwm_resume(struct device *dev) {
return 0;
}
static const struct dev_pm_ops led_pwm_pm = {
.suspend = led_pwm_suspend,
.resume = led_pwm_resume,
};
#define led_pwm_pm_ops (&led_pwm_pm)
#else /* CONFIG_PM */
#define led_pwm_pm_ops NULL
#endif /* CONFIG_PM */
static struct platform_driver led_pwm_driver = {
.driver = {
.name = "led_pwm",
.of_match_table = of_match_ptr(led_pwm_of_match),
.pm = led_pwm_pm_ops,
},
.probe = led_pwm_probe,
.remove = led_pwm_remove,
};
module_platform_driver(led_pwm_driver);
MODULE_LICENSE("GPL v2");