一、kpd_pdrv_probe函数的分析:

  /*1. 输入设备实例  kpd_input_dev */
全局变量:static struct input_dev *kpd_input_dev;  static int kpd_pdrv_probe(struct platform_device *pdev)
{int i, r;u16 new_state[KPD_NUM_MEMS];/* initialize and register input device (/dev/input/eventX) *//*2. 初始化输入设备并分配内存空间*/kpd_input_dev = input_allocate_device();              if (!kpd_input_dev)return -ENOMEM;/*下面开始填充kpd_input_dev  设备驱动结构体*/kpd_input_dev->name = KPD_NAME;kpd_input_dev->id.bustype = BUS_HOST;kpd_input_dev->id.vendor = 0x2454;kpd_input_dev->id.product = 0x6575;kpd_input_dev->id.version = 0x0010;kpd_input_dev->open = kpd_open;/*3. 设置某位为1,以第二个参数为起始地址,EV_KEY表示要设置的位作用:告诉input子系统支持那些事件, EV_KEY 这里表示告诉input子系统支持按键事件*/__set_bit(EV_KEY, kpd_input_dev->evbit);             #if (KPD_PWRKEY_USE_EINT||KPD_PWRKEY_USE_PMIC)/*4. 设置某位为1,以第二个参数为起始地址,EV_KEY表示要设置的位作用:告诉input子系统支持那些按键, KPD_PWRKEY_MAP 这里表示告诉input子系统支持电源按键*/__set_bit(KPD_PWRKEY_MAP, kpd_input_dev->keybit);kpd_keymap[8] = 0;
#endiffor (i = 17; i < KPD_NUM_KEYS; i += 9)  /* only [8] works for Power key */kpd_keymap[i] = 0;for (i = 0; i < KPD_NUM_KEYS; i++) {if (kpd_keymap[i] != 0)__set_bit(kpd_keymap[i], kpd_input_dev->keybit);}/*5. 上述几行代码表示设置电源按键 kpd_keymap 为0,其它按键 kpd_keymap 为1*/__set_bit(250, kpd_input_dev->keybit);__set_bit(251, kpd_input_dev->keybit);#if KPD_AUTOTESTfor (i = 0; i < ARRAY_SIZE(kpd_auto_keymap); i++)__set_bit(kpd_auto_keymap[i], kpd_input_dev->keybit);
#endif#if KPD_HAS_SLIDE_QWERTY__set_bit(EV_SW, kpd_input_dev->evbit);__set_bit(SW_LID, kpd_input_dev->swbit);__set_bit(SW_LID, kpd_input_dev->sw); /* 1: lid shut => closed */
#endif#ifdef KPD_PMIC_RSTKEY_MAP__set_bit(KPD_PMIC_RSTKEY_MAP, kpd_input_dev->keybit);
#endif/*6. 指定kpd_input_dev这个平台设备sysfs中的父设备节点*/kpd_input_dev->dev.parent = &pdev->dev;        /*7. 注册input输入子系统*/r = input_register_device(kpd_input_dev); if (r) {printk(KPD_SAY "register input device failed (%d)\n", r);input_free_device(kpd_input_dev);return r;}/* register device (/dev/mt6575-kpd) *//*7. 指定kpd_dev这个平台设备sysfs中的父设备节点*/kpd_dev.parent = &pdev->dev;                   /*8. 注册混杂设备*/r = misc_register(&kpd_dev);                      if (r) {printk(KPD_SAY "register device failed (%d)\n", r);input_unregister_device(kpd_input_dev);return r;}/*8. 注册按键中断*//* register IRQ and EINT *//*9. 设置消抖时间*/kpd_set_debounce(KPD_KEY_DEBOUNCE);          /*10. 设置中断触发方式*/mt65xx_irq_set_sens(MT6575_KP_IRQ_ID, MT65xx_EDGE_SENSITIVE);       /*11 . 设置中断优先级*/mt65xx_irq_set_polarity(MT6575_KP_IRQ_ID, MT65xx_POLARITY_LOW); /*12. 注册中断处理函数*/r = request_irq(MT6575_KP_IRQ_ID, kpd_irq_handler, 0, KPD_NAME, NULL); if (r) {printk(KPD_SAY "register IRQ failed (%d)\n", r);misc_deregister(&kpd_dev);input_unregister_device(kpd_input_dev);return r;}/*13. 以下为电源键中断函数的注册*/
#if KPD_PWRKEY_USE_EINTmt65xx_eint_set_sens(KPD_PWRKEY_EINT, KPD_PWRKEY_SENSITIVE);mt65xx_eint_set_hw_debounce(KPD_PWRKEY_EINT, KPD_PWRKEY_DEBOUNCE);mt65xx_eint_registration(KPD_PWRKEY_EINT, true, KPD_PWRKEY_POLARITY,kpd_pwrkey_eint_handler, false);
#endifif(kpd_enable_lprst && get_boot_mode() == NORMAL_BOOT) {kpd_print("Normal Boot\n");
#ifdef KPD_PMIC_LPRST_TDkpd_print("Enable LPRST\n");/*14. 以下为设置按键唤醒的时间*/upmu_testmode_pwrkey_rst_en(0x01);upmu_testmode_homekey_rst_en(0x01);upmu_testmode_pwrkey_rst_td(KPD_PMIC_LPRST_TD);
#endif} else {kpd_print("Disable LPRST %d\n", kpd_enable_lprst);}/*15. 设置一个高精度定时器,并且定义了时间到期的回调函数 aee_timer_func*/hrtimer_init(&aee_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);aee_timer.function = aee_timer_func;/*以下为三个按键的初始化,也就是配置注意,默认值gpio输出是0*/
#if 1 // ylliu add. dct default value does not work.../* KCOL0: GPIO103: KCOL1: GPIO108, KCOL2: GPIO105, KCOL4: GPIO102 input + pull enable + pull up */mt_set_gpio_mode(GPIO_KPD_KCOL0_PIN, GPIO_KPD_KCOL0_PIN_M_KP_COL);mt_set_gpio_dir(GPIO_KPD_KCOL0_PIN, GPIO_DIR_IN);mt_set_gpio_pull_enable(GPIO_KPD_KCOL0_PIN, GPIO_PULL_ENABLE);mt_set_gpio_pull_select(GPIO_KPD_KCOL0_PIN, GPIO_PULL_UP);mt_set_gpio_mode(GPIO_KPD_KCOL1_PIN, GPIO_KPD_KCOL1_PIN_M_KP_COL);mt_set_gpio_dir(GPIO_KPD_KCOL1_PIN, GPIO_DIR_IN);mt_set_gpio_pull_enable(GPIO_KPD_KCOL1_PIN, GPIO_PULL_ENABLE);mt_set_gpio_pull_select(GPIO_KPD_KCOL1_PIN, GPIO_PULL_UP);mt_set_gpio_mode(GPIO_KPD_KCOL2_PIN, GPIO_KPD_KCOL2_PIN_M_KP_COL);mt_set_gpio_dir(GPIO_KPD_KCOL2_PIN, GPIO_DIR_IN);mt_set_gpio_pull_enable(GPIO_KPD_KCOL2_PIN, GPIO_PULL_ENABLE);mt_set_gpio_pull_select(GPIO_KPD_KCOL2_PIN, GPIO_PULL_UP);mt_set_gpio_mode(GPIO_KPD_KCOL4_PIN, GPIO_KPD_KCOL4_PIN_M_KP_COL);mt_set_gpio_dir(GPIO_KPD_KCOL4_PIN, GPIO_DIR_IN);mt_set_gpio_pull_enable(GPIO_KPD_KCOL4_PIN, GPIO_PULL_ENABLE);mt_set_gpio_pull_select(GPIO_KPD_KCOL4_PIN, GPIO_PULL_UP);/* KROW0: GPIO98, KROW1: GPIO97: KROW2: GPIO95 output + pull disable + pull down */mt_set_gpio_mode(GPIO_KPD_KROW0_PIN, GPIO_KPD_KROW0_PIN_M_KP_ROW);mt_set_gpio_dir(GPIO_KPD_KROW0_PIN, GPIO_DIR_OUT);mt_set_gpio_pull_enable(GPIO_KPD_KROW0_PIN, GPIO_PULL_DISABLE);  mt_set_gpio_pull_select(GPIO_KPD_KROW0_PIN, GPIO_PULL_DOWN);//  mt_set_gpio_mode(97, 1);
//  mt_set_gpio_dir(97, 1);
//  mt_set_gpio_pull_enable(97, 0);
//  mt_set_gpio_pull_select(97, 0);
//
//  mt_set_gpio_mode(95, 1);
//  mt_set_gpio_dir(95, 1);
//  mt_set_gpio_pull_enable(95, 0);
//  mt_set_gpio_pull_select(95, 0);
#endif// default disable backlight. reboot from recovery need this.kpd_disable_backlight();// store default state, resolve recovery bugs.kpd_get_keymap_state(new_state);memcpy(kpd_keymap_state, new_state, sizeof(new_state));return 0;
}

二、当执行完面probe函数进行相关初始化后,这时候,当我们按键按下了,就会触发中断,进入中断服务子程序

static irqreturn_t __tcmfunc kpd_irq_handler(int irq, void *dev_id)
{/* use _nosync to avoid deadlock */disable_irq_nosync(MT6575_KP_IRQ_ID);tasklet_schedule(&kpd_keymap_tasklet);return IRQ_HANDLED;
}

可以看到,中断服务程序里面执行了 tasklet_schedule(&kpd_keymap_tasklet);
跟踪代码可以发现,实际上是执行了这个函数kpd_keymap_handler,下面仔细分析
这个函数,详细注释如下:

static void kpd_keymap_handler(unsigned long data)
{int i, j;bool pressed;u16 new_state[KPD_NUM_MEMS], change, mask;u16 hw_keycode, linux_keycode;kpd_get_keymap_state(new_state);                                                                 //首先读取键值,并且存放于new_state中if (pmic_get_acc_state() == 1) {for (i = 0; i < KPD_NUM_MEMS; i++) {change = new_state[i] ^ kpd_keymap_state[i];                                        //进行异或操作,就是为了取出两者不同的值if (!change)continue;for (j = 0; j < 16; j++) {mask = 1U << j;if (!(change & mask))continue;hw_keycode = (i << 4) + j;     //i = 0, j = 1;                               //这里是得到hw_keycode的值printk("hw_keycode = %d ,i = %d, j = %d \n",hw_keycode,i,j);/* bit is 1: not pressed, 0: pressed */pressed = !(new_state[i] & mask);   //(new_state[i] & mask) = 0if (kpd_show_hw_keycode) {printk(KPD_SAY "(%s) HW keycode = %u\n",pressed ? "pressed" : "released",hw_keycode);}BUG_ON(hw_keycode >= KPD_NUM_KEYS);linux_keycode = kpd_keymap[hw_keycode];                                              //这里的linux_keycode恒为零。printk("linux_keycode = %d  \n",linux_keycode);if(unlikely(linux_keycode == 0)) {if (hw_keycode == 1 && pressed) { // special key, SOS.struct device *dev = &(kpd_input_dev->dev);char *envp[] = { "SOS_pressed", NULL };kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);              //建立设备文件?printk(KPD_SAY "SOS_pressed\n");// used by recovery./*这个接口会向INPUT子系统上报按键(该按键被按下)*/input_report_key(kpd_input_dev, 251, pressed);                        //如果上层检测到SOS_pressed就会做相应处理。        } else if (hw_keycode == 2 && pressed) { // special key, background.struct device *dev = &(kpd_input_dev->dev);char *envp[] = { "background_pressed", NULL };kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);printk(KPD_SAY "background_pressed\n");// used by recovery.input_report_key(kpd_input_dev, 8, pressed);} else if (hw_keycode == 4 && pressed) { // special key, mode.struct device *dev = &(kpd_input_dev->dev);char *envp[] = { "mode_pressed", NULL };kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);printk(KPD_SAY "mode_pressed\n");} else if (hw_keycode == 1 || hw_keycode == 2 || hw_keycode == 4) { // add this to turn off backlight.printk(KPD_SAY "background or SOS or mode release!\n");// used by recovery.if (hw_keycode == 1)input_report_key(kpd_input_dev, 251, pressed);else if (hw_keycode == 2)input_report_key(kpd_input_dev, 8, pressed);} else {kpd_print("Linux keycode = 0\n");continue;}}kpd_aee_handler(linux_keycode, pressed);   kpd_backlight_handler(pressed, linux_keycode);input_report_key(kpd_input_dev, linux_keycode, pressed);}}} else {printk(KPD_SAY "acc off, ignore and key...\n");}memcpy(kpd_keymap_state, new_state, sizeof(new_state));kpd_print("save new keymap state\n");enable_irq(MT6575_KP_IRQ_ID);
}

三、kpd_aee_handler函数分析

static void kpd_aee_handler(u32 keycode, u16 pressed) {if(pressed) {if(keycode == KEY_VOLUMEUP) {__set_bit(0, &aee_pressed_keys);} else if(keycode == KEY_VOLUMEDOWN) {__set_bit(1, &aee_pressed_keys);} else {return;}kpd_update_aee_state();} else {if(keycode == KEY_VOLUMEUP) {__clear_bit(0, &aee_pressed_keys);} else if(keycode == KEY_VOLUMEDOWN) {__clear_bit(1, &aee_pressed_keys);} else {return;}kpd_update_aee_state();}
}

详细分析:
1.__set_bit(0, &aee_pressed_keys),定义了一个:static u16 aee_pressed_keys;
所以__set_bit的意思是将aee_pressed_keys的bit0设置为1
2.相应的__clear_bit(0, &aee_pressed_keys);就是把aee_pressed_keys的bit0清零,
3.还有在内核的non-atomic.h文件中还有一些其它的位操作,记住__set_bit和set_bit的区别就
是前者是非原子操作,而后者是原子操作,所谓原子操作,意思是最小的执行单位,再其执行过
程中是不会被其他任务打断的。

四、背光处理函数

void kpd_backlight_handler(bool pressed, u16 linux_keycode)
{   if (kpd_suspend && !test_bit(linux_keycode, kpd_wake_keybit)) {kpd_print("Linux keycode %u is not WAKE key\n", linux_keycode);return;}/* not in suspend or the key pressed is WAKE key */if (pressed) {atomic_inc(&kpd_key_pressed);kpd_backlight_on = !!atomic_read(&kpd_key_pressed);schedule_work(&kpd_backlight_work);       //点亮背光灯kpd_print("switch backlight on\n");} else {atomic_dec(&kpd_key_pressed);mod_timer(&kpd_backlight_timer,                //KPD_BACKLIGHT_TIME控制背光时间,单位为sec,如果注释掉这句,背光将不灭jiffies + KPD_BACKLIGHT_TIME * HZ);kpd_print("activate backlight timer\n");}
}

详细分析
1.首先用到了一个位操作函数,注意这个函数是原子操作test_bit
2.全局变量static atomic_t kpd_key_pressed = ATOMIC_INIT(0);这是原子操作的初始化,kpd_key_pressed初始化为0
3.上述函数涉及到一些原子操作函数,解释如下:
atomic_inc(&kpd_key_pressed); 是对变量进行加1操作
atomic_dec(&kpd_key_pressed); 是对变量进行减1操作
!!atomic_read(&kpd_key_pressed);是读取变量的值,前面两个 !!强调该返回值不是1就是0:bool类型
4.mod_timer:该函数的作用是修改一个已经调度的定时器结构的到期时间。

五、背光控制函数

调度的是这个函数

static void kpd_switch_backlight(struct work_struct *work)
{if (kpd_backlight_on) {kpd_enable_backlight();kpd_print("backlight is on\n");} else {kpd_disable_backlight();kpd_print("backlight is off\n");}
}

这里就能够看到使能和失能背光的函数,继续跟踪:

void kpd_enable_backlight(void)
{/*mt6326_kpled_dim_duty_Full();mt6326_kpled_Enable();*/upmu_kpled_dim_duty(31);    upmu_kpled_en(1);
}
upmu_kpled_dim_duty这是控制背光电流大小从而可以控制亮度
upmu_kpled_en这是控制开关。

mod_timer函数的补充

http://www.360doc.com/content/12/0510/11/6973384_210041084.shtml

kpd驱动初步分析完毕。

linux驱动学习2(kpd驱动初步分析)相关推荐

  1. 驱动学习----字符设备驱动框架

    字符设备驱动框架 1.字符设备驱动简介 2.file_operations 3.驱动模块的加载和卸载 4.字符设备的注册与注销 5.实现设备具体操作函数 6.添加LICENSE和作者信息 7.linu ...

  2. 驱动学习1--USB设备驱动

    一:了解 1:Universal Serial Bus 通用串行总线) 内核: 2:主从结构总线.拓扑结构(金字塔形状,星形连接).最上层主机,下面各层为连接到主机的设备. 3:USB主机.Host: ...

  3. Linux 网卡驱动学习(一)(分析一个虚拟硬件的网络驱动样例)

    在Linux,网络分为两个层,各自是网络堆栈协议支持层,以及接收和发送网络协议的设备驱动程序层. 网络堆栈是硬件中独立出来的部分.主要用来支持TCP/IP等多种协议,网络设备驱动层是连接网络堆栈协议层 ...

  4. Linux驱动学习-平台设备驱动probe函数-20220410

    1.最简单的杂项设备 /* * @Descripttion: 最简单的杂项设备驱动 * @version: * @Author: topeet */ #include <linux/init.h ...

  5. 驱动学习之LED驱动框架

    一:什么是驱动框架   (1)内核中驱动部分维护者针对每个种类的驱动设计一套成熟的.标准的.典型的驱动实现,然后把不同厂家的同类硬件驱动中相同的部分抽出来自己实现好,再把不同部分留出接口给具体的驱动开 ...

  6. linux 桥接stp原理,Linux操作系统网桥源码框架初步分析

    今天处理网桥的STP的问题遇到了麻烦,对这个东东理论的倒是看了不少,没有真真学习到它的源理,来看Linux的实现,手头没有资料,看了两个钟头,只把网桥的框架结构看完,所以想先贴出来,希望有研究这块的大 ...

  7. linux v4l2系统详解,Linux摄像头驱动学习之:(一)V4L2_框架分析

    这段时间开始搞安卓camera底层驱动了,把以前的的Linux视频驱动回顾一下,本篇主要概述一下vfl2(video for linux 2). 一. V4L2框架: video for linux ...

  8. 【linux驱动之字符设备驱动基础】

    linux驱动之字符设备驱动基础 文章目录 linux驱动之字符设备驱动基础 前言 一.开启驱动学习之路 二.驱动预备知识 三.什么是驱动? 3.1 驱动概念 3.2 linux 体系架构 3.3 模 ...

  9. 【嵌入式Linux】学习路径

    太久没有做过相关的开发和学习了,技术这东西,一段时间不用就容易忘记,再加上本来就不熟悉,忘得更快.大学时候在tb买了某原子的一块开发板,就开始的时候学习了一段时间,后面因为毕业工作等原因,就放那吃灰了 ...

最新文章

  1. 工业物联网LCD数码屏的驱动原理及低功耗设计(华大半导体HC32L136)
  2. WEB高性能开发(10) - 疯狂的HTML压缩
  3. 同期及上期数据对比处理示例.sql
  4. 连遭主流社交应用抛弃,是时候宣判黑莓系统死刑了
  5. Linux下访问window挂载的磁盘
  6. 普通大学毕业后干啥_一名女孩从大学毕业后在机械行业干了十年,背后经历让人辛酸……...
  7. addroutes没添加进去_CAD中如何绘制函数曲线及添加数学公式
  8. Patchwork Quilting(拼布和绗缝)
  9. 电子海图数据购买、安装、更新及使用注意事项
  10. 环境规制指数,两种测算方法,整理好的面板数据
  11. 计算机基础知识点总结
  12. 照片放大模糊怎么变清晰?
  13. ups计算软件_一篇文章读懂UPS
  14. 005-2-Python文件操作
  15. Eclipse 开发Hadoop2.7.1可能会遇到的问题
  16. C语言正弦和余弦的值
  17. ios服务器管理系统,ios app云服务器
  18. 在线学习python
  19. 2022杭电多校8 Stormwind
  20. 128Echarts - 关系图(NPM Dependencies)

热门文章

  1. Linux Polkit本地权限提升漏洞(CVE-2021-4034)
  2. 杭电2317题 Nasty Hacks
  3. android MVP——mvp架构的应用和优化
  4. 基于html2canvas实现网页保存为图片及图片清晰度优化
  5. 多邻国(Duolingo)内推,来了!
  6. 论文解读:Semantic Neural Machine Translation using AMR
  7. Viper的使用方法
  8. Go 每日一库之 viper
  9. 浅谈“戒色”与“慎独”
  10. atof跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时('\0')才结束转换