blob: 87b804b903ded3eb192aa7ef63d5699cfd1af901 [file] [log] [blame]
/*
* drivers/amlogic/input/keyboard/pca9557_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/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/kobject.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/pm.h>
#include <linux/uaccess.h>
#include <linux/pm_runtime.h>
#include <linux/input.h>
#include <linux/of_platform.h>
#define PCA9557_INPUT_REG 0x00
#define PCA9557_OUTPUT_REG 0x01
#define PCA9557_POL_INV_REG 0x02
#define PCA9557_CONFIG_REG 0x03
#define DEFAULT_DELAY 200
#define DEVICE_NAME "pca9557_keypad"
#define MAX_NAME_LEN 50
static struct pca9557_keypad {
struct input_dev *input_dev;
struct delayed_work work;
int delay;
int key_num;
int key_tmp_val;
int key_last_val;
int major;
int key_input_mask;
struct i2c_client *i2c_client;
} *keypad_desc;
struct _key_des {
char name[MAX_NAME_LEN];
unsigned int key_val;
int key_index_mask;
} *exp_key;
static void delay_work_func(
struct work_struct *work)
{
int i;
int key_val = 0;
unsigned long delay = msecs_to_jiffies(keypad_desc->delay);
key_val = i2c_smbus_read_byte_data(keypad_desc->i2c_client,
PCA9557_INPUT_REG) & keypad_desc->key_input_mask;
if (keypad_desc->key_tmp_val != key_val) {
keypad_desc->key_tmp_val = key_val;
if (key_val != 0)
keypad_desc->key_last_val = key_val;
for (i = 0; i < keypad_desc->key_num; i++) {
if (keypad_desc->key_last_val ==
exp_key[i].key_index_mask) {
if (key_val != 0) {
pr_info("key \"%s\" down\n",
exp_key[i].name);
input_event(keypad_desc->input_dev,
EV_KEY, exp_key[i].key_val, 1);
} else {
pr_info("key \"%s\" up\n",
exp_key[i].name);
input_event(keypad_desc->input_dev,
EV_KEY, exp_key[i].key_val, 0);
}
input_event(keypad_desc->input_dev,
EV_SYN, 0, 0);
break;
}
}
}
schedule_delayed_work(&keypad_desc->work, delay);
}
static int pca9557_init_reg(struct i2c_client *client)
{
int ret;
ret = i2c_smbus_write_byte_data(client,
PCA9557_POL_INV_REG, keypad_desc->key_input_mask);
if (ret < 0) {
pr_err("pca9557 init POL_INV reg fail!\n");
return -1;
}
return 0;
}
static long pca9557_ioctl(struct file *file,
unsigned int cmd,
unsigned long args)
{
return 0;
}
static ssize_t pca9557_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
return count;
}
static const struct file_operations pca9557_fops = {
.owner = THIS_MODULE,
.read = pca9557_read,
.compat_ioctl = pca9557_ioctl,
.unlocked_ioctl = pca9557_ioctl,
};
static int pca9557_parse_child_dt(const struct device *dev)
{
int ret, cnt;
const char *uname;
if (dev->of_node) {
ret = of_property_read_u32(dev->of_node, "key_num",
&keypad_desc->key_num);
if (ret) {
pr_err("failed to get key_num!\n");
return -EINVAL;
}
exp_key = kcalloc(keypad_desc->key_num,
sizeof(struct _key_des), GFP_KERNEL);
for (cnt = 0; cnt < keypad_desc->key_num; cnt++) {
ret = of_property_read_string_index(dev->of_node,
"key_name", cnt, &uname);
if (ret < 0) {
pr_err("invalid key name index[%d]\n", cnt);
return -EINVAL;
}
strncpy(exp_key[cnt].name, uname, MAX_NAME_LEN);
ret = of_property_read_u32_index(dev->of_node,
"key_value", cnt, &exp_key[cnt].key_val);
if (ret < 0) {
pr_err("invalid key value index[%d]\n", cnt);
return -EINVAL;
}
ret = of_property_read_u32_index(dev->of_node,
"key_index_mask", cnt,
&exp_key[cnt].key_index_mask);
if (ret < 0) {
pr_err("invalid key index mask index[%d]\n",
cnt);
return -EINVAL;
}
}
ret = of_property_read_u32(dev->of_node, "key_input_mask",
&keypad_desc->key_input_mask);
if (ret) {
pr_err("failed to get key_input_mask!\n");
return -EINVAL;
}
return 0;
}
return -EINVAL;
}
static ssize_t delay_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret = 0;
ret = sprintf(buf, "pca9557 schedule delay: %d\n", keypad_desc->delay);
return ret;
}
static ssize_t delay_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
ret = kstrtouint(buf, 10, &keypad_desc->delay);
if (ret != 0) {
pr_info("set delay level fail, use default delay!!\n");
keypad_desc->delay = DEFAULT_DELAY;
}
schedule_delayed_work(&keypad_desc->work,
msecs_to_jiffies(keypad_desc->delay));
return count;
}
DEVICE_ATTR_RW(delay);
static struct attribute *pca9557_attrs[] = {
&dev_attr_delay.attr,
NULL,
};
ATTRIBUTE_GROUPS(pca9557);
static struct class pca9557_class = {
.name = "pca9557_keypad",
.owner = THIS_MODULE,
.dev_groups = pca9557_groups,
};
static int pca9557_probe(struct i2c_client *client,
const struct i2c_device_id *i2c_id)
{
int ret, i;
struct device *dev = &client->dev;
pr_info("%s\n", __func__);
keypad_desc = devm_kzalloc(dev, sizeof(struct pca9557_keypad),
GFP_KERNEL);
if (!keypad_desc)
return -ENOMEM;
keypad_desc->i2c_client = client;
ret = pca9557_parse_child_dt(dev);
if (ret < 0) {
pr_err("%s,pca9557_parse_child_dt fail!\n", __func__);
return -EINVAL;
}
ret = pca9557_init_reg(client);
keypad_desc->input_dev = input_allocate_device();
if (keypad_desc->input_dev == NULL) {
pr_err("input_allocate_device err!\n");
return -1;
}
set_bit(EV_SYN, keypad_desc->input_dev->evbit);
set_bit(EV_KEY, keypad_desc->input_dev->evbit);
for (i = 0; i < keypad_desc->key_num; i++)
set_bit(exp_key[i].key_val, keypad_desc->input_dev->keybit);
keypad_desc->input_dev->name = DEVICE_NAME;
ret = input_register_device(keypad_desc->input_dev);
if (ret != 0) {
pr_err("input_register_device err!\n");
return -1;
}
keypad_desc->delay = DEFAULT_DELAY;
keypad_desc->key_tmp_val = 0;
keypad_desc->major = register_chrdev(0, DEVICE_NAME, &pca9557_fops);
class_register(&pca9557_class);
device_create(&pca9557_class, NULL, MKDEV(keypad_desc->major, 0),
NULL, DEVICE_NAME);
INIT_DELAYED_WORK(&keypad_desc->work, delay_work_func);
schedule_delayed_work(&keypad_desc->work, DEFAULT_DELAY);
return 0;
}
static int pca9557_remove(struct i2c_client *client)
{
device_destroy(&pca9557_class, MKDEV(keypad_desc->major, 0));
cancel_delayed_work(&keypad_desc->work);
input_unregister_device(keypad_desc->input_dev);
input_free_device(keypad_desc->input_dev);
return 0;
}
static const struct i2c_device_id pca9557_id[] = {
{"pca9557_keypad", 0 },
{}
};
static const struct of_device_id pca9557_dt_id[] = {
{ .compatible = "amlogic,pca9557_keypad",},
{},
};
static struct i2c_driver pca9557_drv = {
.driver = {
.name = "pca9557_keypad",
.owner = THIS_MODULE,
.of_match_table = pca9557_dt_id,
},
.probe = pca9557_probe,
.remove = pca9557_remove,
.id_table = pca9557_id,
};
static int __init pca9557_init(void)
{
return i2c_add_driver(&pca9557_drv);
}
static void __exit pca9557_exit(void)
{
i2c_del_driver(&pca9557_drv);
}
module_init(pca9557_init);
module_exit(pca9557_exit);
MODULE_AUTHOR("www.amlogic.com");
MODULE_DESCRIPTION("pca9557 driver for keypad");
MODULE_LICENSE("GPL");