實際項目開發中,如果需要使用多個按鍵,很多時候都是一個gpio控制一個按鍵,但是有時候當gpio資源不夠的時候,會使用adc的方式,通過按下不同的按鍵分壓不同用來差別按鍵。
下面就以這種adc的方式去分析input子系統。
在分析之前,想想之前的方式。之前都是按鍵按下觸發中斷,然後中斷中上報鍵值或者中斷中啟動工作隊列,工作隊列中的工作進行資料上報。
那麼如果有使用adc的方式,是沒有中斷的,那麼什麼時候上報事件呢?
先看看如下的原理圖:

對應dts如下
adc-keys {
compatible = "adc-keys";
io-channels = <&saradc 0>;
io-channel-names = "buttons";
poll-interval = <100>;
keyup-threshold-microvolt = <1800000>;
esc-key {
label = "esc";
linux,code = <KEY_ESC>;
press-threshold-microvolt = <0>;
};
right-key {
label = "right";
linux,code = <KEY_RIGHT>;
press-threshold-microvolt = <400781>;
};
left-key {
label = "left";
linux,code = <KEY_LEFT>;
press-threshold-microvolt = <801562>;
};
menu-key {
label = "menu";
linux,code = <KEY_MENU>;
press-threshold-microvolt = <1198828>;
};
};
對應的驅動代碼為 drivers/input/keyboard/adc-keys.c
adc_keys_probe()
static int adc_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct input_polled_dev *poll_dev;
struct input_dev *input;
...
poll_dev = devm_input_allocate_polled_device(dev);
if (!poll_dev) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}
if (!device_property_read_u32(dev, "poll-interval", &value))
poll_dev->poll_interval = value;
poll_dev->poll = adc_keys_poll;
input = poll_dev->input;
input->name = pdev->name;
input->phys = "adc-keys/input0";
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
__set_bit(EV_KEY, input->evbit);
for (i = 0; i < st->num_keys; i++)
__set_bit(st->map[i].keycode, input->keybit);
if (device_property_read_bool(dev, "autorepeat"))
__set_bit(EV_REP, input->evbit);
error = input_register_polled_device(poll_dev);
if (error) {
dev_err(dev, "Unable to register input device: %d\n", error);
return error;
}
return 0;
}
上面的代碼忽略了IIO相關的東西,另外忽略了dts的解析
這裡的input裝置是 input_polled_dev,input_polled_dev中包含了 input_dev
其實在這裡就能看出來,adc方式的input事件上報用的是輪詢的方式,poll函數是 adc_key_poll
且輪詢間隔的時間 dts中通過poll-interval指定,之後的操作input和正常的input裝置沒有什麼差別
如果dts沒有制定poll-interval,那麼input_register_polled_device中會預設設定為500
int input_register_polled_device(struct input_polled_dev *dev)
{
struct input_polled_devres *devres = NULL;
struct input_dev *input = dev->input;
int error;
if (dev->devres_managed) {
devres = devres_alloc(devm_input_polldev_unregister,
sizeof(*devres), GFP_KERNEL);
if (!devres)
return -ENOMEM;
devres->polldev = dev;
}
input_set_drvdata(input, dev);
INIT_DELAYED_WORK(&dev->work, input_polled_device_work);
if (!dev->poll_interval)
dev->poll_interval = 500;
if (!dev->poll_interval_max)
dev->poll_interval_max = dev->poll_interval;
input->open = input_open_polled_device;
input->close = input_close_polled_device;
input->dev.groups = input_polldev_attribute_groups;
error = input_register_device(input);
if (error) {
devres_free(devres);
return error;
}
/*
* Take extra reference to the underlying input device so
* that it survives call to input_unregister_polled_device()
* and is deleted only after input_free_polled_device()
* has been invoked. This is needed to ease task of freeing
* sparse keymaps.
*/
input_get_device(input);
if (dev->devres_managed) {
dev_dbg(input->dev.parent, "%s: registering %s with devres.\n",
__func__, dev_name(&input->dev));
devres_add(input->dev.parent, devres);
}
return 0;
}
那麼poll函數什麼時候被調用?
注意看上面函數中的 input的open和close
static int input_open_polled_device(struct input_dev *input)
{
struct input_polled_dev *dev = input_get_drvdata(input);
if (dev->open)
dev->open(dev);
/* Only start polling if polling is enabled */
if (dev->poll_interval > 0) {
dev->poll(dev);
input_polldev_queue_work(dev);
}
return 0;
}
可以看到在open(/dev/input/eventX)的時候,就會啟動工作隊列
static void input_polldev_queue_work(struct input_polled_dev *dev)
{
unsigned long delay;
delay = msecs_to_jiffies(dev->poll_interval);
if (delay >= HZ)
delay = round_jiffies_relative(delay);
queue_delayed_work(system_freezable_wq, &dev->work, delay);
}
work函數為input_polled_device_work
static void input_polled_device_work(struct work_struct *work)
{
struct input_polled_dev *dev =
container_of(work, struct input_polled_dev, work.work);
dev->poll(dev);
input_polldev_queue_work(dev);
}
可以看到poll函數被調用。
綜上看來,poll方式的input子系統事件上報很容易了解,下一篇文章,我們使用這種方式自己去寫一個poll方式的驅動代碼。