blob: 570c084d4301cc0e4321f0c6d952e4a0e39297bb [file]
/*
* drivers/amlogic/input/keyboard/gpio_keypad.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/amlogic/pm.h>
#include <linux/amlogic/scpi_protocol.h>
#undef pr_fmt
#define pr_fmt(fmt) "gpio-keypad: " fmt
#define GPIO_PWRKEY_NAME "power"
#define DEFAULT_SCAN_PERION 20
#define DEFAULT_POLL_MODE 0
#define KEY_JITTER_COUNT 1
struct pin_desc {
int current_status;
struct gpio_desc *desc;
int irq_num;
u32 code;
const char *name;
};
struct gpio_keypad {
int key_size;
int use_irq;//1:irq mode ; 0:polling mode
int scan_period;
int current_irq;
int count;
int index;
u32 pwrkey_code;
struct pin_desc *key;
struct pin_desc *current_key;
struct timer_list polling_timer;
struct input_dev *input_dev;
};
static irqreturn_t gpio_irq_handler(int irq, void *data)
{
struct gpio_keypad *keypad;
keypad = (struct gpio_keypad *)data;
keypad->current_irq = irq;
keypad->count++;
mod_timer(&(keypad->polling_timer),
jiffies+msecs_to_jiffies(20));
return IRQ_HANDLED;
}
static struct pin_desc *get_current_key(struct gpio_keypad *keypad)
{
int i;
for (i = 0; i < keypad->key_size; i++) {
if (keypad->current_irq == keypad->key[i].irq_num)
return &(keypad->key[i]);
}
return NULL;
}
static void report_key_code(struct gpio_keypad *keypad, int gpio_val)
{
struct pin_desc *key;
if (keypad->count < KEY_JITTER_COUNT)
keypad->count++;
else {
key = keypad->current_key;
key->current_status = gpio_val;
if (key->current_status) {
input_report_key(keypad->input_dev,
key->code, 0);
if (keypad->use_irq)
enable_irq(key->irq_num);
dev_info(&(keypad->input_dev->dev),
"key %d up.\n",
key->code);
} else {
input_report_key(keypad->input_dev,
key->code, 1);
if (keypad->use_irq)
disable_irq_nosync(key->irq_num);
dev_info(&(keypad->input_dev->dev),
"key %d down.\n",
key->code);
}
input_sync(keypad->input_dev);
if (key->code == keypad->pwrkey_code) {
if (key->current_status)
pm_relax(keypad->input_dev->dev.parent);
else
pm_stay_awake(keypad->input_dev->dev.parent);
}
keypad->count = 0;
}
}
static void polling_timer_handler(unsigned long data)
{
struct gpio_keypad *keypad;
struct pin_desc *key;
int i;
int gpio_val;
keypad = (struct gpio_keypad *)data;
if (keypad->use_irq) {//irq mode
keypad->current_key = get_current_key(keypad);
if (!(keypad->current_key))
return;
key = keypad->current_key;
gpio_val = gpiod_get_value(key->desc);
if (key->current_status != gpio_val)
report_key_code(keypad, gpio_val);
else
keypad->count = 0;
if (key->current_status == 0)
mod_timer(&(keypad->polling_timer),
jiffies+msecs_to_jiffies(keypad->scan_period));
} else {//polling mode
for (i = 0; i < keypad->key_size; i++) {
gpio_val = gpiod_get_value(keypad->key[i].desc);
if (keypad->key[i].current_status != gpio_val) {
keypad->index = i;
keypad->current_key = &(keypad->key[i]);
report_key_code(keypad, gpio_val);
} else if (keypad->index == i)
keypad->count = 0;
mod_timer(&(keypad->polling_timer),
jiffies+msecs_to_jiffies(keypad->scan_period));
}
}
}
static int meson_gpio_kp_probe(struct platform_device *pdev)
{
struct gpio_desc *desc;
int ret, i;
struct input_dev *input_dev;
struct gpio_keypad *keypad;
bool pwrkey_update_flg = false;
if (!(pdev->dev.of_node)) {
dev_err(&pdev->dev,
"pdev->dev.of_node == NULL!\n");
return -EINVAL;
}
keypad = devm_kzalloc(&pdev->dev,
sizeof(struct gpio_keypad), GFP_KERNEL);
if (!keypad)
return -EINVAL;
ret = of_property_read_u32(pdev->dev.of_node,
"detect_mode", &(keypad->use_irq));
if (ret)
//The default mode is polling.
keypad->use_irq = DEFAULT_POLL_MODE;
ret = of_property_read_u32(pdev->dev.of_node,
"scan_period", &(keypad->scan_period));
if (ret)
//The default scan period is 20.
keypad->scan_period = DEFAULT_SCAN_PERION;
ret = of_property_read_u32(pdev->dev.of_node,
"key_num", &(keypad->key_size));
if (ret) {
dev_err(&pdev->dev,
"failed to get key_num!\n");
return -EINVAL;
}
keypad->key = devm_kzalloc(&pdev->dev,
(keypad->key_size)*sizeof(*(keypad->key)), GFP_KERNEL);
if (!(keypad->key))
return -EINVAL;
for (i = 0; i < keypad->key_size; i++) {
//get all gpio desc.
desc = devm_gpiod_get_index(&pdev->dev, "key", i, GPIOD_IN);
if (IS_ERR_OR_NULL(desc))
return -EINVAL;
keypad->key[i].desc = desc;
//The gpio default is high level.
keypad->key[i].current_status = 1;
ret = of_property_read_u32_index(pdev->dev.of_node,
"key_code", i, &(keypad->key[i].code));
if (ret < 0) {
dev_err(&pdev->dev,
"find key_code=%d finished\n", i);
return -EINVAL;
}
ret = of_property_read_string_index(pdev->dev.of_node,
"key_name", i, &(keypad->key[i].name));
if (ret < 0) {
dev_err(&pdev->dev,
"find key_name=%d finished\n", i);
return -EINVAL;
}
if (!strcmp(keypad->key[i].name, GPIO_PWRKEY_NAME)) {
keypad->pwrkey_code = keypad->key[i].code;
pwrkey_update_flg = true;
}
gpiod_direction_input(keypad->key[i].desc);
gpiod_set_pull(keypad->key[i].desc, GPIOD_PULL_UP);
}
if (!pwrkey_update_flg)
keypad->pwrkey_code = KEY_RESERVED;
//input
input_dev = input_allocate_device();
if (!input_dev)
return -EINVAL;
set_bit(EV_KEY, input_dev->evbit);
for (i = 0; i < keypad->key_size; i++) {
set_bit(keypad->key[i].code, input_dev->keybit);
dev_info(&pdev->dev, "%s key(%d) registed.\n",
keypad->key[i].name, keypad->key[i].code);
}
input_dev->name = "gpio_keypad";
input_dev->phys = "gpio_keypad/input0";
input_dev->dev.parent = &pdev->dev;
input_dev->id.bustype = BUS_ISA;
input_dev->id.vendor = 0x0001;
input_dev->id.product = 0x0001;
input_dev->id.version = 0x0100;
input_dev->rep[REP_DELAY] = 0xffffffff;
input_dev->rep[REP_PERIOD] = 0xffffffff;
input_dev->keycodesize = sizeof(unsigned short);
input_dev->keycodemax = 0x1ff;
keypad->input_dev = input_dev;
ret = input_register_device(keypad->input_dev);
if (ret < 0) {
input_free_device(keypad->input_dev);
return -EINVAL;
}
platform_set_drvdata(pdev, keypad);
keypad->count = 0;
keypad->index = -1;
setup_timer(&(keypad->polling_timer),
polling_timer_handler, (unsigned long) keypad);
device_init_wakeup(&pdev->dev, true);
if (keypad->use_irq) {
for (i = 0; i < keypad->key_size; i++) {
keypad->key[i].irq_num =
gpiod_to_irq(keypad->key[i].desc);
ret = devm_request_irq(&pdev->dev,
keypad->key[i].irq_num,
gpio_irq_handler, IRQF_TRIGGER_FALLING,
"gpio_keypad", keypad);
if (ret) {
dev_err(&pdev->dev,
"Requesting irq failed!\n");
input_free_device(keypad->input_dev);
del_timer(&(keypad->polling_timer));
return -EINVAL;
}
}
} else {
mod_timer(&(keypad->polling_timer),
jiffies+msecs_to_jiffies(keypad->scan_period));
}
return 0;
}
static int meson_gpio_kp_remove(struct platform_device *pdev)
{
struct gpio_keypad *keypad;
keypad = platform_get_drvdata(pdev);
input_unregister_device(keypad->input_dev);
input_free_device(keypad->input_dev);
del_timer(&(keypad->polling_timer));
return 0;
}
static const struct of_device_id key_dt_match[] = {
{ .compatible = "amlogic, gpio_keypad", },
{},
};
static bool meson_gpio_pwrkey_is_valid(struct gpio_keypad *pdata)
{
return (pdata->pwrkey_code != KEY_RESERVED);
}
static int meson_gpio_kp_suspend(struct platform_device *dev,
pm_message_t state)
{
struct gpio_keypad *pdata;
pdata = (struct gpio_keypad *)platform_get_drvdata(dev);
if (meson_gpio_pwrkey_is_valid(pdata) && is_pm_freeze_mode())
return 0;
if (!pdata->use_irq)
del_timer(&(pdata->polling_timer));
return 0;
}
static int meson_gpio_kp_resume(struct platform_device *dev)
{
struct gpio_keypad *pdata;
pdata = (struct gpio_keypad *)platform_get_drvdata(dev);
if (meson_gpio_pwrkey_is_valid(pdata) && is_pm_freeze_mode())
return 0;
if (!pdata->use_irq)
mod_timer(&(pdata->polling_timer),
jiffies+msecs_to_jiffies(5));
if (get_resume_method() == POWER_KEY_WAKEUP) {
if (meson_gpio_pwrkey_is_valid(pdata)) {
pr_info("gpio keypad wakeup\n");
input_report_key(pdata->input_dev,
pdata->pwrkey_code, 1);
input_sync(pdata->input_dev);
input_report_key(pdata->input_dev,
pdata->pwrkey_code, 0);
input_sync(pdata->input_dev);
if (scpi_clr_wakeup_reason())
pr_debug("clr gpio kp wakeup reason fail.\n");
}
}
return 0;
}
static struct platform_driver meson_gpio_kp_driver = {
.probe = meson_gpio_kp_probe,
.remove = meson_gpio_kp_remove,
.suspend = meson_gpio_kp_suspend,
.resume = meson_gpio_kp_resume,
.driver = {
.name = "gpio-keypad",
.of_match_table = key_dt_match,
},
};
module_platform_driver(meson_gpio_kp_driver);
MODULE_AUTHOR("Amlogic");
MODULE_DESCRIPTION("GPIO Keypad Driver");
MODULE_LICENSE("GPL");