輸入設備(如按鍵,鍵盤,觸摸屏等)是典型的字符設備,其一般工作原理是底層在按鍵或觸摸等動作發生時產生一箇中斷,然後CPU通過SPI,I2C總線讀取鍵值。
在這些工作中之後中斷和讀鍵值是與設備相關的,而輸入事件的緩衝區管理,字符設備驅動的file_operations接口則對輸入設備是通用的。因此內核設計了輸入子系統,由核心層處理公共的工作。
先看gpio-keys platform_driver驅動代碼, 源碼
drivers/input/keyboard/gpio_keys.c
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\n");
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\n");
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\n",
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\n",
error);
return error;
}
//註冊輸入設備
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n",
error);
return error;
}
device_init_wakeup(dev, wakeup);
return 0;
}
key的硬件配置信息由設備樹傳入。
我們再看看設備樹input的描述
Documentation\devicetree\bindings\input\gpio-keys.txt中給出的例子
Subnode properties:
- gpios: OF device-tree gpio specification.
- interrupts: the interrupt line for that input.
- label: Descriptive name of the key.
- linux,code: Keycode to emit.
Example nodes:
gpio-keys {
compatible = "gpio-keys";
autorepeat;
up {
label = "GPIO Key UP";
linux,code = <103>;
gpios = <&gpio1 0 1>;
};
down {
label = "GPIO Key DOWN";
linux,code = <108>;
interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;
};
...
打算使用Interrupts的方式。
首先看一下RaspberryPi 3-b的中斷在設備樹中的描述
arch\arm\boot\dts\bcm283x.dtsi
interrupt-parent = <&intc>;
soc {
compatible = "simple-bus";
//...
intc: interrupt-controller@7e00b200 {
compatible = "brcm,bcm2835-armctrl-ic";
reg = <0x7e00b200 0x200>;
interrupt-controller;
#interrupt-cells = <2>;
};
//...
gpio: gpio@7e200000 {
compatible = "brcm,bcm2835-gpio";
reg = <0x7e200000 0xb4>;
interrupts = <2 17>, <2 18>, <2 19>, <2 20>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
//...
arch\arm\boot\dts\bcm2837.dtsi
soc {
//...
local_intc: local_intc@40000000 {
compatible = "brcm,bcm2836-l1-intc";
reg = <0x40000000 0x100>;
interrupt-controller;
#interrupt-cells = <1>;
interrupt-parent = <&local_intc>;
};
};
/* Make the BCM2835-style global interrupt controller be a child of the
* CPU-local interrupt controller.
*/
&intc {
compatible = "brcm,bcm2836-armctrl-ic";
reg = <0x7e00b200 0x200>;
interrupt-parent = <&local_intc>;
interrupts = <8>;
};
arch\arm\boot\dts\bcm270x.dtsi
soc: soc {
//...
gpio@7e200000 { /* gpio */
interrupts = <2 17>, <2 18>;
};
在arch\arm\boot\dts\bcm2708-rpi.dtsi中添加gpio_keys nodes.
gpio_keys: gpio_keys {
compatible = "gpio-keys";
};
在arch\arm\boot\dts\bcm2710-rpi-3-b.dts中添加gpio_keys nodes.
&gpio_keys {
pinctrl-names = "default";
key1_key: key1 {
interrupt-parent = <&gpio>;
interrupts = <2 20>;
gpios = <&gpio 20 GPIO_ACTIVE_LOW>;
linux,code = <100>;
label = "key1";
debounce-interval = <10>;
wakeup-source;
};
};
重新編譯設備樹
$ make bcm2709_defconfig
$ make dtbs
並將新的設備樹copy到SD卡boot下, 啓動raspberryPi
查看platform devices
pi@raspberrypi:/sys/devices/platform $ cd gpio_keys/
pi@raspberrypi:/sys/devices/platform/gpio_keys $ ls
disabled_keys driver input modalias power switches
disabled_switches driver_override keys of_node subsystem uevent
pi@raspberrypi:/sys/devices/platform/gpio_keys $ cd driver
pi@raspberrypi:/sys/devices/platform/gpio_keys/driver $ ls
bind gpio_keys module uevent unbind
pi@raspberrypi:/sys/devices/platform/gpio_keys/driver $