内核驱动 (二)Linux按键驱动分析
一、按键驱动
1、对按键驱动添加设备信息
Device Drivers ---> |
struct gpio_keys_button {/* Configurationparameters */unsigned int code; 按键对应的编码,其定义在Input.hint gpio; 设置按键对应引脚int active_low; 设置按键按下是否是低电平(低电平有效 1)const char *desc; 给按键起个名字,相当于led4 led5 led6等unsigned int type; 设备按键类型, 默认类型是EV_KEYint wakeup; 设置是否设置为唤醒源int debounce_interval; 消抖间隔时间,单位毫秒,默认为5毫秒bool can_disable; 设置是否共享中断int value; 绝对坐标值unsigned int irq; irq中断号的设定
};
结构体介绍完毕,那么就仿照Mach-Louis210.c在mach-Louis210.c定义设备信息如下:
/* gpio keys (add by Louis) */设置按键的GPIO信息
static struct gpio_keys_button buttons[] = { [0] = {.code = KEY_UP, 按键对应的键码.gpio = S5PV210_GPH0(0), 按键对应的IO口.active_low = 1, 通过查看驱动代码,可得知表示是否按键按下是低电平,如是则设1.desc = "KEY_UP", 申请io口,申请中断时使用的名字.type = EV_KEY, 输入设备的事件类型,按键用EV_KEY.debounce_interval = 50, 防抖动用,间隔多久时间},[1] = {.code = KEY_DOWN,.gpio = S5PV210_GPH0(1),.active_low = 1,.desc = "KEY_DOWN",.type = EV_KEY,.debounce_interval = 50,},[2] = {.code = KEY_LEFT,.gpio = S5PV210_GPH0(2),.active_low = 1,.desc = "KEY_LEFT",.type = EV_KEY,.debounce_interval = 50,},[3] = {.code = KEY_RIGHT,.gpio = S5PV210_GPH0(3),.active_low = 1,.desc = "KEY_RIGHT",.type = EV_KEY,.debounce_interval = 50,},[4] = {.code = KEY_ENTER,.gpio = S5PV210_GPH0(4),.active_low = 1,.desc = "KEY_ENTER",.type = EV_KEY,.debounce_interval = 50,},[5] = {.code = KEY_BACK,.gpio = S5PV210_GPH0(5),.active_low = 1,.desc = "KEY_BACK",.type = EV_KEY,.debounce_interval = 50,},[6] = {.code = KEY_MENU,.gpio = S5PV210_GPH2(6),.active_low = 1,.desc = "KEY_MENU",.type = EV_KEY,.debounce_interval = 50,},[7] = {.code = KEY_POWER,.gpio = S5PV210_GPH2(7),.active_low = 1,.desc = "KEY_POWER",.type = EV_KEY,.debounce_interval = 50,},
};设置按键的设备平台数据
static struct gpio_keys_platform_data Louis210_keys_pdata = {.buttons = buttons, 对应的按键的GPIO信息.nbuttons = ARRAY_SIZE(buttons), 获取要定义的按键个数.rep = 1, //同一次按键是否重复上报
};static struct platform_device Louis210_keys = {.name = "gpio-keys",.dev = {.platform_data = &Louis210_keys_pdata,},.id = -1,
};添加平台数据至平台设备数组
static struct platform_device *Louis210_devices[] __initdata = {&Louis210_keys,
};
2、对按键驱动的测试
在/proc/bus/input目录下执行 cat devices可以看到:
[root@Louis210: gpio-keys]# cd /proc/bus/input/
[root@Louis210: input]# cat devices
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/platform/gpio-keys/input/input2
U: Uniq=
H: Handlers=kbd event2
B: PROP=0
B: EV=100003
B: KEY=40000800 101680 0 0 10000000
struct input_id {__u16 bustype;__u16 vendor;__u16 product;__u16 version;}列出的信息
当有按键按下时,我们可以通过读取/dev/input/event2文件来查看上报事件,此时我们以16进制查看类型读取显示该文件,使用hexdump命令:
[root@Louis210: input]# hexdump /dev/input/event2
0000000 4cb8 386d c2ff 000c 0001 0069 0001 0000
0000010 4cb8 386d c2ff 000c 0000 0000 0000 0000
0000020 4cb8 386d 2078 000f 0001 0069 0000 0000
0000030 4cb8 386d 2078 000f 0000 0000 0000 00000000040 4cbc 386d 99d6 000d 0001 0067 0001 0000
0000050 4cbc 386d 99d6 000d 0000 0000 0000 0000
0000060 4cbd 386d 032e 0001 0001 0067 0000 0000
0000070 4cbd 386d 032e 0001 0000 0000 0000 00000000080 4cc0 386d b0e1 0002 0001 006c 0001 0000
0000090 4cc0 386d b0e1 0002 0000 0000 0000 0000
00000a0 4cc0 386d 5c7b 0005 0001 006c 0000 0000
00000b0 4cc0 386d 5c7b 0005 0000 0000 0000 0000
本代码设置三个按键,当我按下三个按键的时候,会上报三组数据,就以第一组数据为例,当我按下按键的时候(保持按下这个动作,不松开按键),会打印出两组数据:
[root@Louis210: input]# hexdump /dev/input/event2
0000000 4cb8 386d c2ff 000c 0001 0069 0001 0000
0000010 4cb8 386d c2ff 000c 0000 0000 0000 0000
测试按键程序:key.c
#include<stdint.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
#include<linux/input.h>int main(void)
{int fd;int flag=0;struct input_event ev_key;fd=open("/dev/input/event2", O_RDWR); 读写方式打开if(fd < 0){perror("open device buttons");close(fd);return -1;}while(1){read(fd,&ev_key,sizeof(struct input_event));if(flag!=ev_key.type) 这里是判断是否有上报按键事件的发生。printf("type:%d,code:%d,value:%d,sec:%d,nsec:%d\n",ev_key.type,ev_key.code,ev_key.value,ev_key.time.tv_sec,ev_key.time.tv_usec);flag=ev_key.type;}close(fd);return 0;
}
struct input_event {struct timeval time; 系统开机时间__u16 type;__u16 code;__s32 value;
};struct timeval {__kernel_time_t tv_sec; 秒 /* seconds */__kernel_suseconds_t tv_usec; 微妙 /*microseconds */
};可以看出来,这里是按键按下时候的系统时间(第一个参数单位是秒,第二个参数单位是微秒)
后面的参数为事件驱动类型(对应的是ev_key)
后面两个是按键的编码和按键的值(这里的按键值是松开是0或者按键按键其值为1)。
3、对按键驱动分析
分析按键驱动之前,我觉得有必要先学习一下有关输入系统的介绍:
Input子系统分为三层,从下至上分别是设备驱动层,核心层以及事件处理层。
按键的驱动定义在gpio_keys.c(drivers\input\keyboard)文件中,老规矩首先看到这个宏:
late_initcall(gpio_keys_init);
module_exit(gpio_keys_exit);
这里有个宏late_initcall,这个宏的跟之前遇见的subsys_initcall的功能是一样的,只不过其优先级不同,具体的他们全部定义在Init.h (include\linux)中:
#define early_initcall(fn) __define_initcall(fn,early)
#define pure_initcall(fn) __define_initcall(fn,0)
#define core_initcall(fn) __define_initcall(fn,1)
#define core_initcall_sync(fn) __define_initcall(fn,1s)
#define postcore_initcall(fn) __define_initcall(fn,2)
#define postcore_initcall_sync(fn) __define_initcall(fn,2s)
#define arch_initcall(fn) __define_initcall(fn,3)
#define arch_initcall_sync(fn) __define_initcall(fn,3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn,4s)
#define fs_initcall(fn) __define_initcall(fn,5)
#define fs_initcall_sync(fn) __define_initcall(fn,5s)
#define rootfs_initcall(fn) __define_initcall(fn,rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn,6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn,7s)
#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn)
通过上面的宏可以看出他们的后面的参数不同,系数越小优先级越大,可以看出:
优先级由大到小为:subsys_initcall>module_init > late_initcall
接下来就来看gpio_keys_init 和gpio_keys_exit:
static int __init gpio_keys_init(void)
{return platform_driver_register(&gpio_keys_device_driver);
}static void __exit gpio_keys_exit(void)
{platform_driver_unregister(&gpio_keys_device_driver);
}
他们分别调用了平台驱动注册和注销函数,先看平台驱动注册函数的参数结构体:
static struct platform_driver gpio_keys_device_driver = {.probe = gpio_keys_probe,.remove = gpio_keys_remove,.driver = {.name = "gpio-keys",.owner = THIS_MODULE,.pm = &gpio_keys_pm_ops,.of_match_table = of_match_ptr(gpio_keys_of_match),}
};
static SIMPLE_DEV_PM_OPS(gpio_keys_pm_ops, gpio_keys_suspend,gpio_keys_resume);
生成的,该宏定义在Pm.h (include\linux),根据该宏的定义,最终会调用gpio_keys_remove和gpio_keys_suspend函数控制电源的进入工作状态或者低功耗状态。
static int gpio_keys_probe(struct platform_device *pdev)
{struct device *dev = &pdev->dev;const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
获取平台设备信息,即得到Louis210_keys_pdata的数据信息。struct gpio_keys_drvdata *ddata; 驱动信息的数据结构体,struct input_dev *input; 定义了一个输入设备,int i, error;int wakeup = 0;if (!pdata) {pdata = gpio_keys_get_devtree_pdata(dev);if (IS_ERR(pdata))return PTR_ERR(pdata);}ddata = kzalloc(sizeof(struct gpio_keys_drvdata) + 分配且清空数据空间pdata->nbuttons * sizeof(struct gpio_button_data),GFP_KERNEL);input = input_allocate_device(); 分配一个input设备if (!ddata || !input) {dev_err(dev, "failed to allocate state\n");error = -ENOMEM;goto fail1;}ddata->pdata = pdata;ddata->input = input;mutex_init(&ddata->disable_lock); 为ddata初始化互斥锁(互斥体),mutex的使用场合跟信号量基本相同,一般用户那些进程之间竞争,且占用时间较长的场合,当占用时间较短是,一般使用互旋锁。platform_set_drvdata(pdev, ddata); 将驱动数据保存到驱动平台数据中,后期将会使用保存的函数input_set_drvdata(input, ddata); 将驱动数据保存到输入设备中中,后期将会使用保存的函数input->name = pdata->name ? : pdev->name;input->phys = "gpio-keys/input0";input->dev.parent = &pdev->dev;input->open = gpio_keys_open; 在挂起或者唤醒的时候会调用open和close函数input->close = gpio_keys_close;input->id.bustype = BUS_HOST;input->id.vendor = 0x0001;input->id.product = 0x0001;input->id.version = 0x0100;/* Enable auto repeat feature of Linux input subsystem */if (pdata->rep) 给按键设置可重复多次按下的特性__set_bit(EV_REP, input->evbit);循环获取每个按键的信息,并通过gpio_keys_setup_key函数为每个按键初始化引脚,滤波消抖,申请外部中断,申请定时器中断平且设定中断定时器服务函数for (i = 0; i < pdata->nbuttons; i++) { const struct gpio_keys_button *button = &pdata->buttons[i];struct gpio_button_data *bdata = &ddata->data[i];error = gpio_keys_setup_key(pdev, input, bdata, button);if (error)goto fail2;if (button->wakeup)wakeup = 1;}error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
在kobj目录下创建一个属性集合,并显示集合中的属性文件。如果文件已存在,会报错。以函数为例,
这里将会在gpio-keys/目录下创建一个属性文件gpio_keys_attr_group,gpio_keys_attr_group最终会调用:if (error) {dev_err(dev, "Unable to export keys/switches, error: %d\n",error);goto fail2;}error = input_register_device(input); 向内核注册一个input设备if (error) {dev_err(dev, "Unable to register input device, error: %d\n",error);goto fail3;}device_init_wakeup(&pdev->dev, wakeup); 电源管理有关return 0;fail3:sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group); 删除之前创建的属性文件fail2:while (--i >= 0)gpio_remove_key(&ddata->data[i]); 释放掉申请的引脚,取消申请的队列等等platform_set_drvdata(pdev, NULL);fail1:input_free_device(input);kfree(ddata);/* If we have no platform data, we allocated pdata dynamically. */if (!dev_get_platdata(&pdev->dev))kfree(pdata);return error;
}structgpio_keys_drvdata {const struct gpio_keys_platform_data *pdata; 定义的平台信息,包括引脚等的信息struct input_dev *input; 分配了一个输入设备struct mutex disable_lock; 分配一个互斥锁struct gpio_button_data data[0]; 分配一个按键数据结构体
};电源管理有关
staticinline int device_init_wakeup(struct device *dev, bool val)
{device_set_wakeup_capable(dev, val); 设置设备能不能被唤醒device_set_wakeup_enable(dev, val); 设置设备使不使用唤醒;return 0;}
刚才有看到在probe函数中,调用了好多自定义的一些函数,接下来逐个分析一下这些函数的实现。
先看一下设置按键的函数gpio_keys_setup_key:
static int gpio_keys_setup_key(struct platform_device *pdev,struct input_dev *input,struct gpio_button_data *bdata,const struct gpio_keys_button *button)
{const char *desc = button->desc ? button->desc : "gpio_keys";struct device *dev = &pdev->dev;irq_handler_t isr;unsigned long irqflags;int irq, error;bdata->input = input;bdata->button = button;spin_lock_init(&bdata->lock);/**************************************************************************初始化自旋锁,自旋锁适用于临界区不是很大的情况(临界区的执行时间比较短)
总结自旋锁使用流程:1、首先定义一个自旋锁:spinlock_t lock2、初始化自旋锁:spin_lock_init(lock)3、获取自旋锁(1)spin_trylock(lock)//假如获得锁返回真,否则返回假,返回假货不会原地打转的等着获得锁(2)spin_lock(lock)//假如获得锁返回真,否则返回假,返回假货将会原地打转的等着获得锁4、释放掉自旋锁spin_unlock(lock);
**************************************************************************/if (gpio_is_valid(button->gpio)) { //测试端口是否合法error = gpio_request_one(button->gpio, GPIOF_IN, desc); //申请IO口,并设置为输入模式if (error < 0) {dev_err(dev, "Failed to request GPIO %d, error %d\n",button->gpio, error);return error;}if (button->debounce_interval) {error = gpio_set_debounce(button->gpio,button->debounce_interval * 1000); 按键消抖处理/* use timer if gpiolib doesn't provide debounce */if (error < 0)bdata->timer_debounce =button->debounce_interval;}irq = gpio_to_irq(button->gpio); 申请GPIO中断,设置为外部中断,同时获取中断号if (irq < 0) {error = irq;dev_err(dev,"Unable to get irq number for GPIO %d, error %d\n",button->gpio, error);goto fail;}bdata->irq = irq; 赋值中断号INIT_WORK(&bdata->work, gpio_keys_gpio_work_func); /**************************************************************************创建工作,关联工作函数 将函数gpio_keys_gpio_work_func假如工作队列,让中断的第二阶段
去执行该函数。实际上&bdata->work是一个描述工作队列的结构体,变量定义为struct work_struct work总结工作队列过程:1、首先定义一个工作结构体:struct work_struct work2、将定义的工作加入内核工作队列并且绑定放入队列的函数:INIT_WORK(work, work_func) 或者 INIT_DELAYED_WORK(work, work_func)3、当需要执行工作函数的时:(1)对应的INIT_WORK 执行schedule_work(work)会马上调用work_func函数(2) 对应的NIT_DELAYED_WORK 执行schedule_delayed_work(time,work)会在time时间后执行work_func4、取消工作队列中的工作:(1)对应的INIT_WORK cancel_work_sync(work)(2)对应的NIT_DELAYED_WORKcancel_delayed_work_sync//取消延时工作并且等待其完成工作cancel_delayed_work(work)//取消延时工作
**************************************************************************/setup_timer(&bdata->timer,gpio_keys_gpio_timer, (unsigned long)bdata);/**************************************************************************
总结定时器的使用方法:1、定义一个定时器timer_listmytimer2、初始化定时器并赋值成员setup_timer(mytimer, timer_func, data);3、增加定时器add_timer(mytimer)4、该修改定时器的定时时间expire mod_timer(mytimer,expire)5、取消定时器,有两个可选择1. del_timer(mytimer) 直接删除定时器2. del_timer_sync(mytimer)等待本次定时器处理完毕再取消(不适用中断上下文)isr = gpio_keys_gpio_isr; //设置外部中断服务函数,并配置中断处理标志为上升沿触发和下降沿触发irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;设定中断处理的属性,也就是中断触发方式处理方式等等,假如设置IRQF_SHARED表明多个设备共享
一个中断,此时会用到dev_id;当设置IRQF_DISABLED时候表明,中断为快速中断
**************************************************************************/} else {if (!button->irq) {dev_err(dev, "No IRQ specified\n");return -EINVAL;}bdata->irq = button->irq;if (button->type && button->type != EV_KEY) {dev_err(dev, "Only EV_KEY allowed for IRQ buttons.\n");return -EINVAL;}bdata->timer_debounce = button->debounce_interval;setup_timer(&bdata->timer,gpio_keys_irq_timer, (unsigned long)bdata);isr = gpio_keys_irq_isr;irqflags = 0;}input_set_capability(input, button->type ?: EV_KEY, button->code);设定该按键具有什么能力,以本例来说,button1 button2 button3具有按键Left Right End的能力/** If platform has specified that the button can be disabled,* we don't want it to share the interrupt line.*/if (!button->can_disable)irqflags |= IRQF_SHARED;/**************************************************************************
设置是否共享中断,这里涉及到中断共享机制:
多个中断共享一个中断线必须用IRQF_SHARED做标记,以IRQF_SHARED作为标记的中断假如要向内核申请成功
一个中断需要两个条件之一:
该中断还没有被申请
该中断虽然被申请了,但是已经申请的中断也有IRQF_SHARED做标记
当发生共享中断时,所用挂载到此中断的中断服务函数都会得到响应(遍历所有该中断线上的中断),所以说,
当该中断设备为共享中断的时候,中断服务函数首先需要判断是否是自己的dev_id假如不是自己的dev_id那么
返回IRQ_NOTE(表明不是本中断),假如检测到时本中断的话就会执行中断里面的函数,最后返回IRQ_HANDLED。
**************************************************************************/error = request_any_context_irq(bdata->irq, isr, irqflags, desc, bdata);/**************************************************************************
申请一个中断线,其中bdata->irq为要申请的中断线(中断号);isr为中断服务函数;irqflags中断的触发
方式或者处理方式;desc要申请中断的描述符;bdata为dev_id,区分共享中断线线而设定的。申请外部中断的流程:1、申请某个引脚为外部中断 intirq = gpio_to_irq(button->gpio);2、设备外部中断函数irq_handler_tisr = gpio_keys_gpio_isr;3、设置中断发出类型 unsignedlong flags =?4、描述该中断的一个assic字符串的名字 constchar *name=?5、设备dev-id 是一个空函数指针 void*dev_id =?
**************************************************************************/if (error < 0) {dev_err(dev, "Unable to claim irq %d; error %d\n",bdata->irq, error);goto fail;}return 0;fail:if (gpio_is_valid(button->gpio))gpio_free(button->gpio);return error;
}
总结设置按键的函数gpio_keys_setup_key作用:
该函数主要是申请外部中断,设定中断服务函数,由于在消抖的时候会用到定时器,这个函数还初始化了定时器。并且绑定了定时器中断服务函数。
接下来看一下按键服务函数gpio_keys_gpio_isr:
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
/*************************************************************************
首先看中断服务函数的返回值:
IRQ_NONE ,还没有发生中断
IRQ_HANDLED 中断已经处理完毕
IRQ_WAKE_THREAD中断需要唤醒处理线程
**************************************************************************/struct gpio_button_data *bdata = dev_id;BUG_ON(irq != bdata->irq); 代码调试用的if (bdata->button->wakeup) 根据wakeup保存非睡眠状态pm_stay_awake(bdata->input->dev.parent);/*************************************************************************
设置定时器定时时间,定时器时间到后就会执行定时器服务函数
(在按键设置函数gpio_keys_setup_key中已经设置定时器服务函数为gpio_keys_gpio_timer)
**************************************************************************/if (bdata->timer_debounce)mod_timer(&bdata->timer, jiffies + msecs_to_jiffies(bdata->timer_debounce));elseschedule_work(&bdata->work); 执行中断底部的队列部分return IRQ_HANDLED;
}static void gpio_keys_gpio_timer(unsigned long_data)
{structgpio_button_data *bdata = (struct gpio_button_data *)_data;schedule_work(&bdata->work); 执行中断底部的队列部分;
}
static void gpio_keys_gpio_work_func(struct work_struct *work)
{struct gpio_button_data *bdata = container_of(work, struct gpio_button_data, work);gpio_keys_gpio_report_event(bdata);if (bdata->button->wakeup) //电源管理相关pm_relax(bdata->input->dev.parent);
}
可以看到队列工作就是为了调用时间上报函数gpio_keys_gpio_report_event:
static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
{const struct gpio_keys_button *button = bdata->button;struct input_dev *input = bdata->input;unsigned int type = button->type ?: EV_KEY;int state = (gpio_get_value_cansleep(button->gpio) ? 1 : 0) ^ button->active_low;/*************************************************************************
gpio_get_value_cansleep是获取按键的值,其gpio_get_value区别是,有些芯片的引脚与
cpu是依靠一些总线连接的比如iic总线,那么这些引脚就有可能产生休眠,因此要用
gpio_get_value_cansleep来获取按键的值,获取后异或button->active_low(之前设定的
是1),那么当有按键按下时,gpio_get_value_cansleep得到的是低电平也就是0,所以0^1
=1,这里也是再说名,当按键按下时,获取的state应该是1。
**************************************************************************/if (type == EV_ABS) { 这个类型判断是不是EV_ABS(绝对坐标)事件,本次是EV_KEY事件if (state)input_event(input, type, button->code, button->value);} else {input_event(input, type, button->code, !!state);}input_sync(input); 上报事件上报完成
}
到这里上报事件就完工了,回想一下,整个输入子系统的流程可以总结为两部:
1、首先定义申请注册一个输入设备。
2、申请按键为外部中断,当按键按下时,在中断服务函数中处理,并上报按键事件。
内核驱动 (二)Linux按键驱动分析相关推荐
- linux内核按键驱动,嵌入式Linux按键驱动框架
前言 本文将通过轮询.中断.poll机制.异步通知和同步互斥阻塞等方式编写按键驱动程序.本节的驱动框架是在<嵌入式Linux驱动框架的搭建>的基础上进行改进的,所以本文只讲解修改的部分. ...
- linux 按键驱动代码分析
原文地址:http://blog.csdn.NET/woshidahuaidan2011/article/details/51695147 二.按键驱动 1.对按键驱动添加设备信息 linux-3.1 ...
- 有限状态机的嵌入式Linux按键驱动设计(转载)
本文转载自边缘之火<有限状态机的嵌入式Linux按键驱动设计(转载)> 原文链接: http://www.eccn.com/design_2010052509381340.htm 秦国栋 ...
- AW9523 linux 按键驱动解析
AW9523 linux 按键驱动解析 硬件介绍 AW9523是国产芯片,中文手册也是看着方便很多,我从datasheet中摘录一些编写驱动过程中重要信息贴到下面,当然,最好还是看芯片手册,项目使用A ...
- STM32MP157驱动开发——Linux IIO驱动(下)
STM32MP157驱动开发--Linux IIO驱动(下) 0.前言 一.IIO 触发缓冲区 1.IIO 触发器 2.申请触发器 3.释放触发器 4.注册触发器 5.注销触发器 6. IIO 缓冲区 ...
- STM32MP157驱动开发——Linux IIO驱动(上)
STM32MP157驱动开发--Linux IIO驱动(上 ) 0.前言 一.IIO 子系统简介 1.iio_dev 结构体 2.iio_dev 申请与释放 3.iio_dev 注册与注销 4.iio ...
- STM32MP157驱动开发——Linux 音频驱动
STM32MP157驱动开发--Linux 音频驱动 一.简介 1.CS42L51 简介 2.I2S总线 3.STM32MP1 SAI 总线接口 二.驱动开发 1.音频驱动 1)修改设备树 i2c 接 ...
- STM32MP157驱动开发——Linux 网络设备驱动
STM32MP157驱动开发--Linux 网络设备驱动 一.简介 STM32MP1 GMAC 接口简介 YT8511C 详解 二.驱动开发 1.网络外设的设备树 2.设备驱动 三.测试 网速测试 参 ...
- RK3399外设驱动之RTC驱动(二):hym8563驱动
RK3399外设驱动之RTC驱动(二):hym8563驱动 文章目录 RK3399外设驱动之RTC驱动(二):hym8563驱动 hym8563硬件相关 注册驱动 probe函数 hym8563_in ...
- linux键盘驱动程序分析,基于Linux按键驱动分析与编程
硬件平台:Mini2440 Size of NAND:256M linux kernel:linux-2.6.32.2 一.首先编写按键驱动要用到的Mini2440的硬件是中断控制器和定时器 那么li ...
最新文章
- 事件响应的优先级、stopProgapation禁止下层组件响应
- RHEL5中配置无线
- 把接口作为函数的参数,那么任何实现了接口的类的实例都可以作为此函数的参数传递...
- 英语语法---名词详解
- VTK:可视化之Hanoi
- ASP.NET Core IHostEnvironment和IApplicationLifetime介绍
- matlab里path,Matlab设置永久path
- gpio 树莓派3a+_树莓派4正式发布:35美元起售!真香
- 苹果电脑查看python版本_Mac 如何修改系统默认 Python 版本?
- 市场营销分析--页面广告统计
- js ajax通用方法,目前5个流行的AJAX调用JavaScript库
- directx游戏开发终极指南 directx游戏编程教程
- 小程序内嵌的H5页面如何跳转到其他小程序?
- 怎么做真人qq秀_【假期怎么过】看完这8部真人秀,再去英国留学!
- RTT笔记-分析自动初始化机制
- html5下拉菜单清除样式,如何取消下拉菜单格式
- vulnhub——XXE练习
- STM32-F407入门学习专题(九) STM32外设之ADC
- guess在Java中用法_guess的用法
- JAVA——从基础学起(一)Java语言基础
热门文章
- 免费P2P穿透通信(4) RDT可靠通信模块测试使用
- 数显之家快讯:【SHIO世硕心语】2021,新的一年写给自己的5句话!
- 20万+网易音乐人图鉴: 95后超70%、女歌手突破5万
- 记录Win10因为管理员权限而出现的访问COM口被占用的问题
- 蛋白质二级结构预测工具psipred安装使用
- python进阶day13
- 【定量分析、量化金融与统计学】R语言方差分析的outliers陷阱
- 电源纹波怎么测量,纹波和噪声的区别
- Android 百度地图反向Geo “PERMISSION UNFINISHED“
- EXCEL中小数点后面的0怎么去掉