前言
Linux驱动中设备基本都是挂在总线上,但是有一些我们不知道类型的字符设备我们可以挂在misc下,linux还有专门为了输入设备提供了一个input子系统。这些输入设备通常都是通过按压触摸等某种方式产生一个中断,然后cpu通过某种协议去读键值坐标等放到一个缓冲区域并上报给用户。
misc设备
所有的misc设备主设备号都是10,从设备号不同。misc会自动创建cdev 不需要我们手动创建
struct miscdevice {
int minor; /* 子设备号 */
const char *name; /* 设备名字 */
const struct file_operations *fops; /* 设备操作集 */
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};
int misc_register(struct miscdevice * misc);//注册misc设备
int misc_deregister(struct miscdevice *misc);//注销misc设备
demo
misc_led{
#address-cells = <1>;
#size-cells = <1>;
compatible = "misc-demo";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#define led_NAME "misc_led" /* 名字 */
#define led_MINOR 222 /* 子设备号 */
/* led设备结构体 */
struct led_dev{
struct class *class; /* 类 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* beep所使用的GPIO编号 */
int led_flag;
};
struct led_dev led; /* beep设备 */
/* 设备操作函数 */
static struct file_operations led_fops = {
.owner = THIS_MODULE,
};
static ssize_t std_show(struct device *dev, struct device_attribute *attr, char *buf){
return sprintf(buf, "%d\n",led.led_flag);
}
static ssize_t std_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len){
sscanf(buf, "%d",&led.led_flag);
printk("led.led_flag[%d]\n",led.led_flag);
if(led.led_flag == 0)
{
gpio_set_value(led.led_gpio, 0);
}else{
gpio_set_value(led.led_gpio, 1);
}
return len;
}
static DEVICE_ATTR_RW(std);
static struct attribute *demo_class_attrs[] = {
&dev_attr_std.attr,
NULL,
};
static const struct attribute_group demo_group = {
.attrs = demo_class_attrs,
};
static const struct attribute_group *demo_groups[] = {
&demo_group,
NULL,
};
/* MISC设备结构体 */
static struct miscdevice led_miscdev = {
.minor = led_MINOR,
.name = led_NAME,
.fops = &led_fops,
.groups = demo_groups,
};
static int led_probe(struct platform_device *dev){
int ret = 0;
printk("match device-tree success!\n");
led.nd = of_find_node_by_path("/misc_led");
if(led.nd == NULL) {
printk("misc_led node not find!\r\n");
return -EINVAL;
}
led.led_gpio = of_get_named_gpio(led.nd, "led-gpio", 0);
if(led.led_gpio < 0) {
printk("can't get led-gpio");
return -EINVAL;
}
ret = gpio_direction_output(led.led_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
ret = misc_register(&led_miscdev);
if(ret < 0){
printk("misc device register failed!\r\n");
return -EFAULT;
}
return 0;
}
static int led_remove(struct platform_device *dev){
/* 注销设备的时候关闭LED灯 */
gpio_set_value(led.led_gpio, 1);
/* 注销misc设备 */
misc_deregister(&led_miscdev);
return 0;
}
/* 匹配列表 */
static const struct of_device_id misc_of_match[] = {
{ .compatible = "misc-demo" },
{ /* Sentinel */ }
};
/* platform驱动结构体 */
static struct platform_driver led_driver = {
.driver = {
.name = "misc-leds", /* 驱动名字,用于和设备匹配 */
.of_match_table = misc_of_match, /* 设备树匹配表 */
},
.probe = led_probe,
.remove = led_remove,
};
static int __init led_init(void){
return platform_driver_register(&led_driver);
}
static void __exit led_exit(void){
platform_driver_unregister(&led_driver);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bin");
INPUT子系统
input 作为linux内核为管理输入设备所提供的一种手段,如按键,触摸屏,某些传感器等,按键和键盘代表按键信息,触摸屏代表坐标信息,因此用户处理就不同,驱动只需要上报事件,键值坐标等。
input子系统作为linux一个大类,所以所有的input设备主设备号都为13
input子系统分为三层关系
- 驱动层,设备的驱动程序,用来向核心层上报输入信息。
- 核心层,为驱动层提供接口,告诉事件层输入信息正在处理。
- 事件层,主要用来与用户进行交互。
struct input_dev {
const char *name;//设备名
const char *phys;//设备在系统中的路径
const char *uniq;//设备id
struct input_id id;//input设备id
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图 */
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图 */
unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标的位图 */
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 绝对坐标的位图 */
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 杂项事件的位图 */
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /*LED 相关的位图 */
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];/* sound 有关的位图 */
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 压力反馈的位图 */
unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /*开关状态的位图 */
unsigned int hint_events_per_packet;
unsigned int keycodemax;//支持最大按键数
unsigned int keycodesize;//键值字节数
void *keycode;//存储键值的数组的首地址
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);
struct ff_device *ff;
/*
连击事件
*/
unsigned int repeat_key;
struct timer_list timer;
int rep[REP_CNT];
struct input_mt *mt;//多点触控区域
struct input_absinfo *absinfo;
unsigned long key[BITS_TO_LONGS(KEY_CNT)];//设备当前按键状态
unsigned long led[BITS_TO_LONGS(LED_CNT)];//设备当前灯状态
unsigned long snd[BITS_TO_LONGS(SND_CNT)];//设备当前声音状态
unsigned long sw[BITS_TO_LONGS(SW_CNT)];//设备当前开关状态
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);//用于处理传递设备的事件
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);//事件处理函数,主要是接收用户下发的命令
struct input_handle __rcu *grab;
spinlock_t event_lock;
struct mutex mutex;
unsigned int users;//打开该设备的用户数量
bool going_away;
struct device dev;
/*
*/
struct list_head h_list;
struct list_head node;
unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;
bool devres_managed;
};
struct input_event {
struct timeval time; /* 事件发生的时间 */
__u16 type; /* 事件类型 */
__u16 code; /* 事件码 */
__s32 value; /* 事件值 */
};
evbit 可选事件类型
#define EV_SYN 0x00 /* 同步事件 */
#define EV_KEY 0x01 /* 按键事件 */
#define EV_REL 0x02 /* 相对坐标事件 */
#define EV_ABS 0x03 /* 绝对坐标事件 */
#define EV_MSC 0x04 /* 杂项(其他)事件 */
#define EV_SW 0x05 /* 开关事件 */
#define EV_LED 0x11 /* LED */
#define EV_SND 0x12 /* 声音 */
#define EV_REP 0x14 /* 重复事件 */
#define EV_FF 0x15 /* 压力事件 */
#define EV_PWR 0x16 /* 电源事件 */
#define EV_FF_STATUS 0x17 /* 压力状态事件 */
操作函数
/*
申请input_dev
*/
struct input_dev *input_allocate_device(void)
struct input_dev *devm_input_allocate_device(struct device *dev)
void input_free_device(struct input_dev *dev)//释放input_dev
/*
注册与销毁
*/
int input_register_device(struct input_dev *dev)
void input_unregister_device(struct input_dev *dev)
/*
* @description : 上报事件
* @param - dev : input设备结构
* @param - type : 向中断处理函数传参
* @param - code : 事件码,也就是我们注册的按键值
* @param - value : 事件值
*/
void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value)
void input_report_key(struct input_dev *dev,unsigned int code, int value)//上报按键类型
void input_report_rel(struct input_dev *dev, unsigned int code, int value)//上报相对坐标
void input_report_abs(struct input_dev *dev, unsigned int code, int value)//上报绝对坐标
void input_report_switch(struct input_dev *dev, unsigned int code, int value)//上报开关状态
void input_mt_sync(struct input_dev *dev)//上报同步事件 ,在上报完事件后一定要同步。
模板
struct input_dev *input; /* input 结构体变量 */
/* 驱动入口函数 */
static int __init xxx_init(void)
{
input= input_allocate_device(); /* 申请 input_dev */
input->name = "input-key"; /* 设置 input_dev 名字 */
//第一种方法
__set_bit(EV_KEY, input->evbit); /* 设置产生按键事件 */
__set_bit(EV_REP, input->evbit); /* 重复事件 */
__set_bit(KEY_0, input->keybit); /*设置产生哪些按键值 */
//第二种方法
input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
input->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
//第三种方法
input->evbit[0] = BIT_MASK(EV_KEY) |BIT_MASK(EV_REP);
input_set_capability(input, EV_KEY, KEY_0);
/* 注册 input_dev */
input_register_device(input);
return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
input_unregister_device(input); /* 注销 input_dev */
input_free_device(input); /* 删除 input_dev */
}
demo
dts
input_key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "input-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
status = "okay";
};
#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/timer.h>
#include <linux/input.h>
#define DEMO_NAME "key_input" /* 名字 */
struct key_dev{
int gpio;
unsigned char key_value;
int irqnumber;
char irq_name[20];
irqreturn_t (*handler)(int, void *);
};
struct demo_dev{
struct device_node *node; /* 设备节点 */
struct timer_list timer;/* 定义一个定时器*/
struct key_dev keys;
struct input_dev *input; /* input结构体 */
};
struct demo_dev demo; /* demo设备 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct demo_dev *dev = (struct demo_dev *)dev_id;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定时 */
return IRQ_RETVAL(IRQ_HANDLED);
}
void timer_function(unsigned long arg)
{
unsigned char value;
struct demo_dev *dev = (struct demo_dev *)arg;
value = gpio_get_value(dev->keys.gpio); /* 读取IO值 */
if(value == 0){
/*按下按键*/
printk(" the key down ! [%d]\n",dev->keys.key_value);
input_report_key(dev->input, dev->keys.key_value, 1);/* 最后一个参数表示按下还是松开,1为按下,0为松开 */
}else{
/*按键释放*/
printk(" the key up ! [%d]\n",dev->keys.key_value);
input_report_key(dev->input, dev->keys.key_value, 0);
}
input_sync(dev->input);
}
static int demo_probe(struct platform_device *dev)
{
int ret = 0;
int input_init_func_flag = 0;
/*
io初始化部分
*/
demo.node = of_find_node_by_path("/key");
if (demo.node== NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
demo.keys.gpio = of_get_named_gpio(demo.node ,"key-gpio",0);
if (demo.keys.gpio< 0) {
printk("can't get key-gpio\n");
return -EINVAL;
}
memset(demo.keys.irq_name, 0, sizeof(demo.keys.irq_name)); /* 缓冲区清零 */
sprintf(demo.keys.irq_name, "KEY%d", 0); /* 组合名字 */
gpio_request(demo.keys.gpio, demo.keys.irq_name);
gpio_direction_input(demo.keys.gpio);
demo.keys.irqnumber = irq_of_parse_and_map(demo.node, 0);
demo.keys.handler = key0_handler;
demo.keys.key_value = KEY_0;
ret = request_irq( demo.keys.irqnumber,
demo.keys.handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
demo.keys.irq_name,
&demo);
if(ret < 0){
printk("irq %d request failed!\r\n", demo.keys.irqnumber);
return -EFAULT;
}
init_timer(&demo.timer);
demo.timer.function = timer_function;
/*
input 初始化部分
*/
demo.input = input_allocate_device();
demo.input->name = DEMO_NAME;
if(input_init_func_flag == 0)
{
__set_bit(EV_KEY, demo.input->evbit); /* 设置产生按键事件 */
__set_bit(EV_REP, demo.input->evbit); /* 重复事件 */
__set_bit(KEY_0 , demo.input->keybit); /*设置产生哪些按键值 */
}else if(input_init_func_flag == 1)
{
demo.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);//设置按键事件和重复事件
input_set_capability(demo.input, EV_KEY, KEY_0);
}
/* 注册输入设备 */
ret = input_register_device(demo.input);
if (ret) {
printk("register input device failed!\r\n");
return ret;
}
return 0;
}
static int demo_remove(struct platform_device *dev)
{
del_timer_sync(&demo.timer);
free_irq(demo.keys.irqnumber, &demo);
/*释放input_dev*/
input_unregister_device(demo.input);
input_free_device(demo.input);
return 0;
}
/* 设备树匹配列表 */
static const struct of_device_id demo_of_match[] = {
{ .compatible = "input-key" },
{ /* Sentinel */ }
};
/* platform驱动结构体 */
static struct platform_driver demo_driver= {
.driver = {
.name = "input-keys", /* 驱动名字,用于和设备匹配 */
.of_match_table = demo_of_match, /* 设备树匹配表,用于和设备树匹配 */
},
.probe = demo_probe,
.remove = demo_remove,
};
static int __init demo_init(void)
{
return platform_driver_register(&demo_driver);
}
static void __exit demo_exit(void)
{
platform_driver_unregister(&demo_driver);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bin");