| /* |
| * 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"); |
| |
| |