blob: 35abea93ba2e961a3d8a6b0430b4a1c353fd8cfd [file] [log] [blame]
/*
* 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/interrupt.h>
#include <linux/types.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/errno.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <asm/irq.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <uapi/linux/input.h>
#include <linux/major.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/amlogic/aml_gpio_consumer.h>
#include <linux/amlogic/gpio-amlogic.h>
#include <linux/amlogic/sd.h>
#include <linux/amlogic/iomap.h>
#include <linux/amlogic/cpu_version.h>
#include <linux/amlogic/pm.h>
#include <linux/of_address.h>
#include "../../../gpio/gpiolib.h"
#define OFFSET 24
#define MOD_NAME "gpio_key"
#undef pr_fmt
#define pr_fmt(fmt) "gpio_key: " fmt
struct gpio_key {
int code; /* input key code */
const char *name;
int pin; /*pin number*/
int status; /*0 up , 1 down*/
};
struct gpio_platform_data {
struct gpio_key *key;
int key_num;
int repeat_delay;
int repeat_period;
int irq_keyup;
int irq_keydown;
};
struct kp {
struct input_dev *input;
struct timer_list timer;
int config_major;
char config_name[20];
struct class *config_class;
struct device *config_dev;
struct gpio_key *keys;
int key_num;
struct work_struct work_update;
};
static struct kp *gp_kp;
int amlogic_gpio_name_map_num(struct platform_device *pdev, const char *value)
{
return of_property_read_string(pdev->dev.of_node, "key_pin", &value);
}
static void kp_work(struct kp *kp)
{
struct gpio_key *key;
int i;
int io_status;
key = kp->keys;
for (i = 0; i < kp->key_num; i++) {
io_status = gpiod_get_value(gpio_to_desc(key->pin));
if (io_status != key->status) {
if (io_status) {
dev_info(&kp->input->dev,
"key %d up\n", key->code);
input_report_key(kp->input, key->code, 0);
input_sync(kp->input);
} else {
dev_info(&kp->input->dev,
"key %d down\n", key->code);
input_report_key(kp->input, key->code, 1);
input_sync(kp->input);
}
key->status = io_status;
}
key++;
}
}
static void update_work_func(struct work_struct *work)
{
struct kp *kp_data = container_of(work, struct kp, work_update);
kp_work(kp_data);
}
/*
* What we do here is just for loss wakeup key when suspend.
* In suspend routine , the intr is disable.
* we need do more things to adapt the gpio change.
*/
int det_pwr_key(void)
{
/*return aml_read_reg32(P_AO_IRQ_STAT) & (1<<8);*/
return 0;
}
/*Enable gpio interrupt for AO domain interrupt*/
void set_pwr_key(void)
{
/* aml_write_reg32(P_AO_IRQ_GPIO_REG,
* aml_read_reg32(P_AO_IRQ_GPIO_REG)|(1<<18)|(1<<16)|(0x3<<0));
* aml_write_reg32(P_AO_IRQ_MASK_FIQ_SEL,
* aml_read_reg32(P_AO_IRQ_MASK_FIQ_SEL)|(1<<8));
* aml_set_reg32_mask(P_AO_IRQ_STAT_CLR, 1<<8);
*/
}
void clr_pwr_key(void)
{
/*aml_set_reg32_mask(P_AO_IRQ_STAT_CLR, 1<<8);*/
}
#ifdef USE_IRQ
static irqreturn_t kp_isr(int irq, void *data)
{
struct kp *kp_data = (struct kp *)data;
queue_work(system_freezable_wq, &(kp_data->work_update));
/* if (!deep_suspend_flag)
* clr_pwr_key();
*/
return IRQ_HANDLED;
}
#else
void kp_timer_sr(unsigned long data)
{
struct kp *kp_data = (struct kp *)data;
queue_work(system_freezable_wq, &(kp_data->work_update));
mod_timer(&kp_data->timer, jiffies+msecs_to_jiffies(25));
}
#endif
static int gpio_key_config_open(struct inode *inode, struct file *file)
{
file->private_data = gp_kp;
return 0;
}
static int gpio_key_config_release(struct inode *inode, struct file *file)
{
file->private_data = NULL;
return 0;
}
static const struct file_operations keypad_fops = {
.owner = THIS_MODULE,
.open = gpio_key_config_open,
.release = gpio_key_config_release,
};
static int register_keypad_dev(struct kp *kp)
{
int ret = 0;
char name[] = "gpio_keyboard";
strncpy(kp->config_name, name, sizeof(name));
ret = register_chrdev(0, kp->config_name, &keypad_fops);
if (ret <= 0) {
dev_info(&kp->input->dev, "register char device error\n");
return ret;
}
kp->config_major = ret;
dev_info(&kp->input->dev, "gpio keypad major:%d\n", ret);
kp->config_class = class_create(THIS_MODULE, kp->config_name);
kp->config_dev = device_create(kp->config_class, NULL,
MKDEV(kp->config_major, 0), NULL, kp->config_name);
return ret;
}
static int gpio_key_probe(struct platform_device *pdev)
{
const char *str;
struct kp *kp;
struct input_dev *input_dev;
int i, ret, key_size;
struct gpio_key *key;
struct gpio_platform_data *pdata = NULL;
struct gpio_desc *desc;
int *key_param = NULL;
int state = -EINVAL;
#ifdef USE_IRQ
int irq_keyup;
int irq_keydown;
#endif
int gpio_highz = 0;
if (!pdev->dev.of_node) {
dev_info(&pdev->dev, "gpio_key: pdev->dev.of_node == NULL!\n");
state = -EINVAL;
goto get_key_node_fail;
}
ret = of_property_read_u32(pdev->dev.of_node, "key_num", &key_size);
if (ret) {
dev_info(&pdev->dev, "gpio_key: failed to get key_num!\n");
state = -EINVAL;
goto get_key_node_fail;
}
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
state = -EINVAL;
goto get_key_node_fail;
}
ret = of_property_read_bool(pdev->dev.of_node, "gpio_high_z");
if (ret) {
gpio_highz = 1;
dev_info(&pdev->dev, "gpio request set to High-Z status\n");
}
pdata->key = kcalloc(key_size, sizeof(*(pdata->key)), GFP_KERNEL);
if (!(pdata->key)) {
dev_err(&pdev->dev, "platform key is required!\n");
goto get_key_mem_fail;
}
pdata->key_num = key_size;
for (i = 0; i < key_size; i++) {
ret = of_property_read_string_index(pdev->dev.of_node,
"key_name", i, &(pdata->key[i].name));
if (ret < 0) {
dev_info(&pdev->dev,
"gpio_key: find key_name=%d finished\n", i);
break;
}
}
key_param = kzalloc((sizeof(*key_param))*(pdata->key_num), GFP_KERNEL);
if (!key_param)
goto get_param_mem_fail;
ret = of_property_read_u32_array(pdev->dev.of_node,
"key_code", key_param, pdata->key_num);
if (ret) {
dev_info(&pdev->dev, "gpio_key: failed to get key_code!\n");
goto get_key_param_failed;
}
for (i = 0; i < pdata->key_num; i++) {
pdata->key[i].code = *(key_param+i);
pdata->key[i].status = -1;
}
#ifdef USE_IRQ
pdata->irq_keyup = irq_of_parse_and_map(pdev->dev.of_node, 0);
pdata->irq_keydown = irq_of_parse_and_map(pdev->dev.of_node, 1);
pdata->irq_keyup = irq_keyup;
pdata->irq_keydown = irq_keydown;
pr_info("irq_keyup:%d,irq_keydown:%d\n",
pdata->irq_keyup, pdata->irq_keydown);
#endif
for (i = 0; i < key_size; i++) {
ret = of_property_read_string_index(pdev->dev.of_node,
"key_pin", i, &str);
dev_info(&pdev->dev, "gpio_key: %d name(%s) pin(%s)\n",
i, (pdata->key[i].name), str);
if (ret < 0) {
dev_info(&pdev->dev,
"gpio_key: find key_name=%d finished\n", i);
break;
}
ret = amlogic_gpio_name_map_num(pdev, str);
dev_info(&pdev->dev, "amlogic_gpio_name_map_num pin %d!%s::\n",
ret, str);
if (ret < 0) {
dev_info(&pdev->dev, "gpio_key bad pin !\n");
goto get_key_param_failed;
}
desc = of_get_named_gpiod_flags(pdev->dev.of_node,
"key_pin", i, NULL);
pdata->key[i].pin = desc_to_gpio(desc);
dev_info(&pdev->dev, "gpio_key: %d %s(%d)\n", i,
(pdata->key[i].name), pdata->key[i].pin);
/*pdata->key[i].pin = ret;*/
gpio_request(pdata->key[i].pin, MOD_NAME);
if (!gpio_highz) {
gpio_direction_input(pdata->key[i].pin);
gpiod_set_pull(desc, GPIOD_PULL_UP);
}
#ifdef USE_IRQ
gpio_for_irq(pdata->key[i].pin,
AML_GPIO_IRQ(irq_keyup, FILTER_NUM7, IRQF_TRIGGER_RISING));
gpio_for_irq(pdata->key[i].pin,
AML_GPIO_IRQ(irq_keydown, FILTER_NUM7, IRQF_TRIGGER_FALLING));
#endif
}
kp = kzalloc(sizeof(struct kp), GFP_KERNEL);
input_dev = input_allocate_device();
if (!kp || !input_dev) {
kfree(kp);
input_free_device(input_dev);
state = -ENOMEM;
goto get_key_param_failed;
}
gp_kp = kp;
platform_set_drvdata(pdev, pdata);
kp->input = input_dev;
INIT_WORK(&(kp->work_update), update_work_func);
#ifdef USE_IRQ
if (request_irq(irq_keyup, kp_isr, IRQF_DISABLED,
"irq_keyup", kp)) {
dev_info(&pdev->dev, "Failed to request gpio key up irq.\n");
kfree(kp);
input_free_device(input_dev);
state = -EINVAL;
goto get_key_param_failed;
}
if (request_irq(irq_keydown, kp_isr, IRQF_DISABLED,
"irq_keydown", kp)) {
dev_info(&pdev->dev, "Failed to request gpio key down irq.\n");
kfree(kp);
input_free_device(input_dev);
state = -EINVAL;
goto get_key_param_failed;
}
#else
dev_info(&pdev->dev, "start setup_timer");
setup_timer(&kp->timer, kp_timer_sr, (unsigned long) kp);
mod_timer(&kp->timer, jiffies+msecs_to_jiffies(100));
#endif
/* setup input device */
set_bit(EV_KEY, input_dev->evbit);
set_bit(EV_REP, input_dev->evbit);
kp->keys = pdata->key;
kp->key_num = pdata->key_num;
key = pdata->key;
for (i = 0; i < kp->key_num; i++) {
set_bit(pdata->key[i].code, input_dev->keybit);
dev_info(&pdev->dev, "%s key(%d) registed.\n",
key->name, pdata->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;
ret = input_register_device(kp->input);
if (ret < 0) {
dev_err(&pdev->dev, "Unable to register keypad input device.\n");
kfree(kp);
input_free_device(input_dev);
state = -EINVAL;
goto get_key_param_failed;
}
/*set_pwr_key();*/
dev_info(&pdev->dev, "gpio keypad register input device completed.\n");
register_keypad_dev(gp_kp);
kfree(key_param);
return 0;
get_key_param_failed:
kfree(key_param);
get_param_mem_fail:
kfree(pdata->key);
get_key_mem_fail:
kfree(pdata);
get_key_node_fail:
return state;
}
static int gpio_key_remove(struct platform_device *pdev)
{
struct gpio_platform_data *pdata = platform_get_drvdata(pdev);
struct kp *kp = gp_kp;
input_unregister_device(kp->input);
input_free_device(kp->input);
unregister_chrdev(kp->config_major, kp->config_name);
if (kp->config_class) {
if (kp->config_dev)
device_destroy(kp->config_class,
MKDEV(kp->config_major, 0));
class_destroy(kp->config_class);
}
kfree(kp);
#ifdef CONFIG_OF
kfree(pdata->key);
kfree(pdata);
#endif
gp_kp = NULL;
return 0;
}
static int gpio_key_suspend(struct platform_device *dev, pm_message_t state)
{
return 0;
}
static int gpio_key_resume(struct platform_device *dev)
{
int i;
struct gpio_platform_data *pdata;
pdata = (struct gpio_platform_data *)platform_get_drvdata(dev);
if (get_resume_method() == POWER_KEY_WAKEUP) {
for (i = 0; i < pdata->key_num; i++) {
if (pdata->key[i].code == KEY_POWER) {
pr_info("gpio keypad wakeup\n");
input_report_key(gp_kp->input, KEY_POWER, 1);
input_sync(gp_kp->input);
input_report_key(gp_kp->input, KEY_POWER, 0);
input_sync(gp_kp->input);
break;
}
}
}
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id key_dt_match[] = {
{ .compatible = "amlogic, gpio_keypad", },
{},
};
#else
#define key_dt_match NULL
#endif
static struct platform_driver gpio_driver = {
.probe = gpio_key_probe,
.remove = gpio_key_remove,
.suspend = gpio_key_suspend,
.resume = gpio_key_resume,
.driver = {
.name = "gpio-key",
.of_match_table = key_dt_match,
},
};
static int __init gpio_key_init(void)
{
return platform_driver_register(&gpio_driver);
}
static void __exit gpio_key_exit(void)
{
platform_driver_unregister(&gpio_driver);
}
module_init(gpio_key_init);
module_exit(gpio_key_exit);
MODULE_AUTHOR("Frank Chen");
MODULE_DESCRIPTION("GPIO Keypad Driver");
MODULE_LICENSE("GPL");