• 输入设备驱动


    输入设备是典型的字符驱动。

    工作机理

    输入设备驱动工作机理:

    1. 底层在按键发生时,产生一个中断(或者驱动通过timer定时查询), 
    2. 然后CPU通过总线(SPI, I2C)读取键值,并将它们放入到一个缓冲区
    3. 字符设备驱动管理该缓冲区。驱动的read() API让用户可以读取键值

    只有中断,键值是具体设备相关,其他是输入设备通用。

    因此,内核设计了输入子系统


    输入子系统框架

    引用链接中架构图,点击打开链接





    按键驱动分析

    那么以GPIO按键驱动做个分析。

    GPIO driver

    drivers/input/keyboard/gpio_keys.c

    源码链接

    probe()

    对照输入子系统架构图, GPIO按键驱动调用了“Input Core"中提供的通用API, 见下面代码中的

    input = devm_input_allocate_device(dev);
    error = input_register_device(input);

     /dev/input目录下的事件都是在驱动中调用input_register_device(struct input_dev *dev)产生的。


    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);
    	struct fwnode_handle *child = NULL;
    	struct gpio_keys_drvdata *ddata;
    	struct input_dev *input;
    	size_t size;
    	int i, error;
    	int wakeup = 0;
    	if (!pdata) {
    		pdata = gpio_keys_get_devtree_pdata(dev);
    		if (IS_ERR(pdata))
    			return PTR_ERR(pdata);
    	}
    	size = sizeof(struct gpio_keys_drvdata) +
    			pdata->nbuttons * sizeof(struct gpio_button_data);
    	ddata = devm_kzalloc(dev, size, GFP_KERNEL);
    	if (!ddata) {
    		dev_err(dev, "failed to allocate state
    ");
    		return -ENOMEM;
    	}
    	ddata->keymap = devm_kcalloc(dev,
    				     pdata->nbuttons, sizeof(ddata->keymap[0]),
    				     GFP_KERNEL);
    	if (!ddata->keymap)
    		return -ENOMEM;
    	input = devm_input_allocate_device(dev);
    	if (!input) {
    		dev_err(dev, "failed to allocate input device
    ");
    		return -ENOMEM;
    	}
    	ddata->pdata = pdata;
    	ddata->input = input;
    	mutex_init(&ddata->disable_lock);
    	platform_set_drvdata(pdev, ddata);
    	input_set_drvdata(input, ddata);
    	input->name = pdata->name ? : pdev->name;
    	input->phys = "gpio-keys/input0";
    	input->dev.parent = dev;
    	input->open = gpio_keys_open;
    	input->close = gpio_keys_close;
    	input->id.bustype = BUS_HOST;
    	input->id.vendor = 0x0001;
    	input->id.product = 0x0001;
    	input->id.version = 0x0100;
    	input->keycode = ddata->keymap;
    	input->keycodesize = sizeof(ddata->keymap[0]);
    	input->keycodemax = pdata->nbuttons;
    	/* Enable auto repeat feature of Linux input subsystem */
    	if (pdata->rep)
    		__set_bit(EV_REP, input->evbit);
    	for (i = 0; i < pdata->nbuttons; i++) {
    		const struct gpio_keys_button *button = &pdata->buttons[i];
    		if (!dev_get_platdata(dev)) {
    			child = device_get_next_child_node(dev, child);
    			if (!child) {
    				dev_err(dev,
    					"missing child device node for entry %d
    ",
    					i);
    				return -EINVAL;
    			}
    		}
    		error = gpio_keys_setup_key(pdev, input, ddata,
    					    button, i, child);
    		if (error) {
    			fwnode_handle_put(child);
    			return error;
    		}
    		if (button->wakeup)
    			wakeup = 1;
    	}
    	fwnode_handle_put(child);
    	error = devm_device_add_group(dev, &gpio_keys_attr_group);
    	if (error) {
    		dev_err(dev, "Unable to export keys/switches, error: %d
    ",
    			error);
    		return error;
    	}
    	error = input_register_device(input);
    	if (error) {
    		dev_err(dev, "Unable to register input device, error: %d
    ",
    			error);
    		return error;
    	}
    	device_init_wakeup(dev, wakeup);
    	return 0;
    }

    ISR

    GPIO按键通过 input core提供的API, input_event()和input_sync(),来汇报按键事件

    static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
    {
    	struct gpio_button_data *bdata = dev_id;
    	struct input_dev *input = bdata->input;
    	unsigned long flags;
    	BUG_ON(irq != bdata->irq);
    	spin_lock_irqsave(&bdata->lock, flags);
    	if (!bdata->key_pressed) {
    		if (bdata->button->wakeup)
    			pm_wakeup_event(bdata->input->dev.parent, 0);
    		input_event(input, EV_KEY, *bdata->code, 1);
    		input_sync(input);
    		if (!bdata->release_delay) {
    			input_event(input, EV_KEY, *bdata->code, 0);
    			input_sync(input);
    			goto out;
    		}
    		bdata->key_pressed = true;
    	}
    	if (bdata->release_delay)
    		mod_timer(&bdata->release_timer,
    			jiffies + msecs_to_jiffies(bdata->release_delay));
    out:
    	spin_unlock_irqrestore(&bdata->lock, flags);
    	return IRQ_HANDLED;
    }


    Input Core

    设备注册,事件,同步等相关API, drivers/input/input.c , 点击打开链接

    linux VFS相关API (file_operations), drivers/input/evdev.c, 点击打开链接


    platform device

    一般的,在板级的初始化c文件里面。比如:archarmmach-mx6oard-mx6q-sabresd.c 

    #if defined(CONFIG_KEYBOARD_GPIO) || defined(CONFIG_KEYBOARD_GPIO_MODULE)
    #define GPIO_BUTTON(gpio_num, ev_code, act_low, descr, wake, debounce)    
    {                                
        .gpio        = gpio_num,                
        .type        = EV_KEY,                
        .code        = ev_code,                
        .active_low    = act_low,                
        .desc        = "btn " descr,                
        .wakeup        = wake,                    
        .debounce_interval = debounce,                
    }
    
    static struct gpio_keys_button imx6q_buttons[] = {
        GPIO_BUTTON(SABRESD_KEY_USER1, KEY_VOLUMEUP, 1, "user-key-1", 0, 1),
        GPIO_BUTTON(SABRESD_KEY_USER2, KEY_VOLUMEDOWN, 1, "user-key-2", 0, 1),
        GPIO_BUTTON(SABRESD_KEY_WHIBUSB, KEY_F1, 1, "whibusb", 0, 1),
        GPIO_BUTTON(SABRESD_KEY_WHIBUSL, KEY_F2, 1, "whibusl", 0, 1),
        GPIO_BUTTON(SABRESD_KEY_WHIBUSR, KEY_F3, 1, "whibusr", 0, 1),
    };
    
    static struct gpio_keys_platform_data imx6q_button_data = {
        .buttons    = imx6q_buttons,
        .nbuttons    = ARRAY_SIZE(imx6q_buttons),
    };
    
    static struct platform_device imx6q_button_device = {
        .name        = "gpio-keys",
        .id        = -1,
        .num_resources  = 0,
        .dev        = {
            .platform_data = &imx6q_button_data,
        }
    };
    
    static void __init imx6q_add_device_buttons(void)
    {
        platform_device_register(&imx6q_button_device);
    }
    #else
    static void __init imx6q_add_device_buttons(void) {}
    #endif

    上面注册了5个按键设备。然后在board_init()初始化函数里面,添加imx6q_add_device_buttons()。我们就可以通过应用层操作了。比如:按下某个按键的时候,在read()函数中获取哪个键被按下。


    利用proc文件系统。cat /proc/bus/input/devices  查看一下按键是哪个event。

    I: Bus=0011 Vendor=0002 Product=0006 Version=0000
    N: Name="ImExPS/2 Generic Explorer Mouse"
    P: Phys=isa0060/serio1/input0
    S: Sysfs=/devices/platform/i8042/serio1/input/input4
    U: Uniq=
    H: Handlers=mouse0 event3 
    B: PROP=1
    B: EV=7
    B: KEY=1f0000 0 0 0 0 0 0 0 0
    B: REL=143
    


    platform driver

    drivers/input/keyboard/gpio_keys.c

    static struct platform_driver gpio_keys_device_driver = {
    	.probe		= gpio_keys_probe,
    	.driver		= {
    		.name	= "gpio-keys",
    		.pm	= &gpio_keys_pm_ops,
    		.of_match_table = gpio_keys_of_match,
    	}
    };
    

  • 相关阅读:
    我读过的书 编程爱好者
    HarmonyOS ListContainer基础用法
    HarmonyOS ListContainer 读取网络json数组
    HarmonyOS Activity页面跳转
    HarmonyOS ListContainer 图文并排
    HarmonyOS 线性布局练习一 登录页面
    jsonserver 环境搭建及使用方法
    HarmonyOS 真机调试
    在win下设置C语言环境变量
    使用 Eclipse 调试 Java 程序的 10 个技巧
  • 原文地址:https://www.cnblogs.com/feiwatson/p/9478227.html
Copyright © 2020-2023  润新知