From 8366ca71813ba9a97bfb3141863fab9a62e181bf Mon Sep 17 00:00:00 2001 From: Himanshu Aggarwal Date: Mon, 20 Jul 2015 19:32:49 +0530 Subject: input: hbtp_input: Port and add snapshot of changes from msm-3.10 Port the HBTP Input driver and apply the following driver changes taken from msm-3.10 kernel branch as of msm-3.10 commit. 31773451 input: misc: add input driver for HBTP 20bf9c5f input: hbtp_input: bypass alphabetical and numeric key config e424ee72 input: misc: hbtp-input: add event type in uevents Change-Id: I122d22cf738c747bcd43ec7f90c33a239288fd9d Signed-off-by: Jing Lin Signed-off-by: Chun Zhang Signed-off-by: Himanshu Aggarwal --- .../devicetree/bindings/input/hbtp-input.txt | 28 + drivers/input/misc/Kconfig | 11 + drivers/input/misc/Makefile | 1 + drivers/input/misc/hbtp_input.c | 566 +++++++++++++++++++++ include/uapi/linux/Kbuild | 1 + include/uapi/linux/hbtp_input.h | 58 +++ 6 files changed, 665 insertions(+) create mode 100644 Documentation/devicetree/bindings/input/hbtp-input.txt create mode 100644 drivers/input/misc/hbtp_input.c create mode 100644 include/uapi/linux/hbtp_input.h diff --git a/Documentation/devicetree/bindings/input/hbtp-input.txt b/Documentation/devicetree/bindings/input/hbtp-input.txt new file mode 100644 index 000000000000..d4a248bac085 --- /dev/null +++ b/Documentation/devicetree/bindings/input/hbtp-input.txt @@ -0,0 +1,28 @@ +Platform device for Host Based Touch Processing (HBTP) + +hbtp_input is a kernel driver that provides functionality needed by +Host Based Touch Processing (HBTP) from the kernel. One of the +functionality is to manage the power source for touch Analog Front +End (AFE). + +Required properties: + + - compatible : should be "qcom,hbtp-input" + +Optional properties: + + - vcc_ana-supply : Analog power supply needed to power device + - qcom,afe-load : AFE load in uA + - qcom,afe-vtg-min : AFE minimum voltage in uV + - qcom,afe-vtg-max : AFE maximum voltage in uV + +Example: + &soc { + hbtp { + compatible = "qcom,hbtp-input"; + vcc_ana-supply = <&pm8941_l18>; + qcom,afe-load = <150000>; + qcom,afe-vtg-min = <2700000>; + qcom,afe-vtg-max = <3300000>; + }; + }; diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 25ac47b9a180..31369d8c0ef3 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -103,6 +103,17 @@ config INPUT_E3X0_BUTTON To compile this driver as a module, choose M here: the module will be called e3x0_button. +config INPUT_HBTP_INPUT + tristate "HBTP input driver support" + help + This option enables an input driver for the host based touch + processing. + + Say Y to enable HBTP input driver. + + To compile this driver as a module, choose M here: the + module will be called hbtp_input. + config INPUT_PCSPKR tristate "PC Speaker support" depends on PCSPKR_PLATFORM diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 66c3cc9f181c..f6606c1526ab 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -35,6 +35,7 @@ obj-$(CONFIG_INPUT_GP2A) += gp2ap002a00f.o obj-$(CONFIG_INPUT_GPIO_BEEPER) += gpio-beeper.o obj-$(CONFIG_INPUT_GPIO_TILT_POLLED) += gpio_tilt_polled.o obj-$(CONFIG_INPUT_GPIO) += gpio_event.o gpio_matrix.o gpio_input.o gpio_output.o gpio_axis.o +obj-$(CONFIG_INPUT_HBTP_INPUT) += hbtp_input.o obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o obj-$(CONFIG_INPUT_IMS_PCU) += ims-pcu.o obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o diff --git a/drivers/input/misc/hbtp_input.c b/drivers/input/misc/hbtp_input.c new file mode 100644 index 000000000000..74637c4d0428 --- /dev/null +++ b/drivers/input/misc/hbtp_input.c @@ -0,0 +1,566 @@ + +/* Copyright (c) 2014-2015, The Linux Foundation. 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 version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../input-compat.h" + +#if defined(CONFIG_FB) +#include +#include +#endif + +#define HBTP_INPUT_NAME "hbtp_input" + +struct hbtp_data { + struct platform_device *pdev; + struct input_dev *input_dev; + s32 count; + struct mutex mutex; + bool touch_status[HBTP_MAX_FINGER]; +#if defined(CONFIG_FB) + struct notifier_block fb_notif; +#endif + struct regulator *vcc_ana; + int afe_load_ua; + int afe_vtg_min_uv; + int afe_vtg_max_uv; + bool manage_afe_power; +}; + +static struct hbtp_data *hbtp; + +#if defined(CONFIG_FB) +static int fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + int blank; + struct fb_event *evdata = data; + struct hbtp_data *hbtp_data = + container_of(self, struct hbtp_data, fb_notif); + char *envp[2] = {HBTP_EVENT_TYPE_DISPLAY, NULL}; + + if (evdata && evdata->data && event == FB_EVENT_BLANK && + hbtp_data && hbtp_data->input_dev) { + blank = *(int *)(evdata->data); + if (blank == FB_BLANK_UNBLANK) + kobject_uevent_env(&hbtp_data->input_dev->dev.kobj, + KOBJ_ONLINE, envp); + else if (blank == FB_BLANK_POWERDOWN) + kobject_uevent_env(&hbtp_data->input_dev->dev.kobj, + KOBJ_OFFLINE, envp); + } + + return 0; +} +#endif + +static int hbtp_input_open(struct inode *inode, struct file *file) +{ + mutex_lock(&hbtp->mutex); + if (hbtp->count) { + pr_err("%s is busy\n", HBTP_INPUT_NAME); + mutex_unlock(&hbtp->mutex); + return -EBUSY; + } + hbtp->count++; + mutex_unlock(&hbtp->mutex); + + return 0; +} + +static int hbtp_input_release(struct inode *inode, struct file *file) +{ + mutex_lock(&hbtp->mutex); + if (!hbtp->count) { + pr_err("%s wasn't opened\n", HBTP_INPUT_NAME); + mutex_unlock(&hbtp->mutex); + return -ENOTTY; + } + hbtp->count--; + mutex_unlock(&hbtp->mutex); + + return 0; +} + +static int hbtp_input_create_input_dev(struct hbtp_input_absinfo *absinfo) +{ + struct input_dev *input_dev; + struct hbtp_input_absinfo *abs; + int error; + int i; + + input_dev = input_allocate_device(); + if (!input_dev) { + pr_err("%s: input_allocate_device failed\n", __func__); + return -ENOMEM; + } + + kfree(input_dev->name); + input_dev->name = kstrndup(HBTP_INPUT_NAME, sizeof(HBTP_INPUT_NAME), + GFP_KERNEL); + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(BTN_TOUCH, input_dev->keybit); + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + + for (i = KEY_HOME; i <= KEY_MICMUTE; i++) + __set_bit(i, input_dev->keybit); + + /* For multi touch */ + input_mt_init_slots(input_dev, HBTP_MAX_FINGER, 0); + for (i = 0; i <= ABS_MT_LAST - ABS_MT_FIRST; i++) { + abs = absinfo + i; + if (abs->active) + input_set_abs_params(input_dev, abs->code, + abs->minimum, abs->maximum, 0, 0); + } + + error = input_register_device(input_dev); + if (error) { + pr_err("%s: input_register_device failed\n", __func__); + goto err_input_reg_dev; + } + + hbtp->input_dev = input_dev; + return 0; + +err_input_reg_dev: + input_free_device(input_dev); + + return error; +} + +static int hbtp_input_report_events(struct hbtp_data *hbtp_data, + struct hbtp_input_mt *mt_data) +{ + int i; + struct hbtp_input_touch *tch; + + for (i = 0; i < HBTP_MAX_FINGER; i++) { + tch = &(mt_data->touches[i]); + if (tch->active || hbtp_data->touch_status[i]) { + input_mt_slot(hbtp_data->input_dev, i); + input_mt_report_slot_state(hbtp_data->input_dev, + MT_TOOL_FINGER, tch->active); + + if (tch->active) { + input_report_abs(hbtp_data->input_dev, + ABS_MT_TOOL_TYPE, + tch->tool); + input_report_abs(hbtp_data->input_dev, + ABS_MT_TOUCH_MAJOR, + tch->major); + input_report_abs(hbtp_data->input_dev, + ABS_MT_TOUCH_MINOR, + tch->minor); + input_report_abs(hbtp_data->input_dev, + ABS_MT_ORIENTATION, + tch->orientation); + input_report_abs(hbtp_data->input_dev, + ABS_MT_PRESSURE, + tch->pressure); + input_report_abs(hbtp_data->input_dev, + ABS_MT_POSITION_X, + tch->x); + input_report_abs(hbtp_data->input_dev, + ABS_MT_POSITION_Y, + tch->y); + } + hbtp_data->touch_status[i] = tch->active; + } + } + + input_report_key(hbtp->input_dev, BTN_TOUCH, mt_data->num_touches > 0); + input_sync(hbtp->input_dev); + + return 0; +} + +static int reg_set_optimum_mode_check(struct regulator *reg, int load_uA) +{ + return (regulator_count_voltages(reg) > 0) ? + regulator_set_optimum_mode(reg, load_uA) : 0; +} + +static int hbtp_pdev_power_on(struct hbtp_data *hbtp, bool on) +{ + int ret, error; + + if (!hbtp->vcc_ana) { + pr_err("%s: regulator is not available\n", __func__); + return -EINVAL; + } + + if (!on) + goto reg_off; + + ret = reg_set_optimum_mode_check(hbtp->vcc_ana, hbtp->afe_load_ua); + if (ret < 0) { + pr_err("%s: Regulator vcc_ana set_opt failed rc=%d\n", + __func__, ret); + return -EINVAL; + } + + ret = regulator_enable(hbtp->vcc_ana); + if (ret) { + pr_err("%s: Regulator vcc_ana enable failed rc=%d\n", + __func__, ret); + error = -EINVAL; + goto error_reg_en_vcc_ana; + } + + return 0; + +error_reg_en_vcc_ana: + reg_set_optimum_mode_check(hbtp->vcc_ana, 0); + return error; + +reg_off: + reg_set_optimum_mode_check(hbtp->vcc_ana, 0); + regulator_disable(hbtp->vcc_ana); + return 0; +} + +static long hbtp_input_ioctl_handler(struct file *file, unsigned int cmd, + unsigned long arg, void __user *p) +{ + int error; + struct hbtp_input_mt mt_data; + struct hbtp_input_absinfo absinfo[ABS_MT_LAST - ABS_MT_FIRST + 1]; + struct hbtp_input_key key_data; + enum hbtp_afe_power_cmd power_cmd; + + switch (cmd) { + case HBTP_SET_ABSPARAM: + if (hbtp && hbtp->input_dev) { + pr_err("%s: The input device is already created\n", + __func__); + return 0; + } + + if (copy_from_user(absinfo, (void *)arg, + sizeof(struct hbtp_input_absinfo) * + (ABS_MT_LAST - ABS_MT_FIRST + 1))) { + pr_err("%s: Error copying data for ABS param\n", + __func__); + return -EFAULT; + } + + error = hbtp_input_create_input_dev(absinfo); + if (error) + pr_err("%s, hbtp_input_create_input_dev failed (%d)\n", + __func__, error); + break; + + case HBTP_SET_TOUCHDATA: + if (!hbtp || !hbtp->input_dev) { + pr_err("%s: The input device hasn't been created\n", + __func__); + return -EFAULT; + } + + if (copy_from_user(&mt_data, (void *)arg, + sizeof(struct hbtp_input_mt))) { + pr_err("%s: Error copying data\n", __func__); + return -EFAULT; + } + + hbtp_input_report_events(hbtp, &mt_data); + error = 0; + break; + + case HBTP_SET_POWERSTATE: + if (!hbtp || !hbtp->input_dev) { + pr_err("%s: The input device hasn't been created\n", + __func__); + return -EFAULT; + } + + if (copy_from_user(&power_cmd, (void *)arg, + sizeof(enum hbtp_afe_power_cmd))) { + pr_err("%s: Error copying data\n", __func__); + return -EFAULT; + } + + switch (power_cmd) { + case HBTP_AFE_POWER_ON: + error = hbtp_pdev_power_on(hbtp, true); + if (error) + pr_err("%s: failed to power on\n", __func__); + break; + case HBTP_AFE_POWER_OFF: + error = hbtp_pdev_power_on(hbtp, false); + if (error) + pr_err("%s: failed to power off\n", __func__); + break; + default: + pr_err("%s: Unsupported command for power state, %d\n", + __func__, power_cmd); + return -EINVAL; + } + break; + + case HBTP_SET_KEYDATA: + if (!hbtp || !hbtp->input_dev) { + pr_err("%s: The input device hasn't been created\n", + __func__); + return -EFAULT; + } + + if (copy_from_user(&key_data, (void *)arg, + sizeof(struct hbtp_input_key))) { + pr_err("%s: Error copying data for key info\n", + __func__); + return -EFAULT; + } + + input_report_key(hbtp->input_dev, key_data.code, + key_data.value); + input_sync(hbtp->input_dev); + break; + + default: + pr_err("%s: Unsupported ioctl command %u\n", __func__, cmd); + error = -EINVAL; + break; + } + + return error; +} + +static long hbtp_input_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return hbtp_input_ioctl_handler(file, cmd, arg, (void __user *)arg); +} + +#ifdef CONFIG_COMPAT +static long hbtp_input_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return hbtp_input_ioctl_handler(file, cmd, arg, compat_ptr(arg)); +} +#endif + +static const struct file_operations hbtp_input_fops = { + .owner = THIS_MODULE, + .open = hbtp_input_open, + .release = hbtp_input_release, + .unlocked_ioctl = hbtp_input_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = hbtp_input_compat_ioctl, +#endif +}; + +static struct miscdevice hbtp_input_misc = { + .fops = &hbtp_input_fops, + .minor = MISC_DYNAMIC_MINOR, + .name = HBTP_INPUT_NAME, +}; +MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR); +MODULE_ALIAS("devname:" HBTP_INPUT_NAME); + +#ifdef CONFIG_OF +static int hbtp_parse_dt(struct device *dev) +{ + int rc; + struct device_node *np = dev->of_node; + u32 temp_val; + + if (of_find_property(np, "vcc_ana-supply", NULL)) { + hbtp->manage_afe_power = true; + + rc = of_property_read_u32(np, "qcom,afe-load", &temp_val); + if (!rc) { + hbtp->afe_load_ua = (int) temp_val; + } else { + dev_err(dev, "Unable to read AFE load\n"); + return rc; + } + + rc = of_property_read_u32(np, "qcom,afe-vtg-min", &temp_val); + if (!rc) { + hbtp->afe_vtg_min_uv = (int) temp_val; + } else { + dev_err(dev, "Unable to read AFE min voltage\n"); + return rc; + } + + rc = of_property_read_u32(np, "qcom,afe-vtg-max", &temp_val); + if (!rc) { + hbtp->afe_vtg_max_uv = (int) temp_val; + } else { + dev_err(dev, "Unable to read AFE max voltage\n"); + return rc; + } + } + + return 0; +} +#else +static int hbtp_parse_dt(struct device *dev) +{ + return -ENODEV; +} +#endif + +static int hbtp_pdev_probe(struct platform_device *pdev) +{ + int error, ret; + struct regulator *vcc_ana; + + if (pdev->dev.of_node) { + error = hbtp_parse_dt(&pdev->dev); + if (error) { + pr_err("%s: parse dt failed, rc=%d\n", __func__, error); + return error; + } + } + + if (hbtp->manage_afe_power) { + vcc_ana = regulator_get(&pdev->dev, "vcc_ana"); + if (IS_ERR(vcc_ana)) { + ret = PTR_ERR(vcc_ana); + pr_err("%s: regulator get failed vcc_ana rc=%d\n", + __func__, ret); + return -EINVAL; + } + + if (regulator_count_voltages(vcc_ana) > 0) { + ret = regulator_set_voltage(vcc_ana, + hbtp->afe_vtg_min_uv, hbtp->afe_vtg_max_uv); + if (ret) { + pr_err("%s: regulator set vtg failed rc=%d\n", + __func__, ret); + error = -EINVAL; + goto error_set_vtg_vcc_ana; + } + } + hbtp->vcc_ana = vcc_ana; + } + + hbtp->pdev = pdev; + + return 0; + +error_set_vtg_vcc_ana: + regulator_put(vcc_ana); + + return error; +}; + +static int hbtp_pdev_remove(struct platform_device *pdev) +{ + if (hbtp->vcc_ana) { + hbtp_pdev_power_on(hbtp, false); + regulator_put(hbtp->vcc_ana); + } + + return 0; +} + +#ifdef CONFIG_OF +static struct of_device_id hbtp_match_table[] = { + { .compatible = "qcom,hbtp-input",}, + { }, +}; +#else +#define hbtp_match_table NULL +#endif + +static struct platform_driver hbtp_pdev_driver = { + .probe = hbtp_pdev_probe, + .remove = hbtp_pdev_remove, + .driver = { + .name = "hbtp", + .owner = THIS_MODULE, + .of_match_table = hbtp_match_table, + }, +}; + +static int __init hbtp_init(void) +{ + int error; + + hbtp = kzalloc(sizeof(struct hbtp_data), GFP_KERNEL); + if (!hbtp) + return -ENOMEM; + + mutex_init(&hbtp->mutex); + + error = misc_register(&hbtp_input_misc); + if (error) { + pr_err("%s: misc_register failed\n", HBTP_INPUT_NAME); + goto err_misc_reg; + } + +#if defined(CONFIG_FB) + hbtp->fb_notif.notifier_call = fb_notifier_callback; + error = fb_register_client(&hbtp->fb_notif); + if (error) { + pr_err("%s: Unable to register fb_notifier: %d\n", + HBTP_INPUT_NAME, error); + goto err_fb_reg; + } +#endif + + error = platform_driver_register(&hbtp_pdev_driver); + if (error) { + pr_err("Failed to register platform driver: %d\n", error); + goto err_platform_drv_reg; + } + + return 0; + +err_platform_drv_reg: +#if defined(CONFIG_FB) + fb_unregister_client(&hbtp->fb_notif); +err_fb_reg: +#endif + misc_deregister(&hbtp_input_misc); +err_misc_reg: + kfree(hbtp); + + return error; +} + +static void __exit hbtp_exit(void) +{ + misc_deregister(&hbtp_input_misc); + if (hbtp->input_dev) + input_unregister_device(hbtp->input_dev); + +#if defined(CONFIG_FB) + fb_unregister_client(&hbtp->fb_notif); +#endif + + platform_driver_unregister(&hbtp_pdev_driver); + + kfree(hbtp); +} + +MODULE_DESCRIPTION("Kernel driver to support host based touch processing"); +MODULE_LICENSE("GPLv2"); + +module_init(hbtp_init); +module_exit(hbtp_exit); diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index 671e98bf2496..3589e9d08ffb 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -145,6 +145,7 @@ header-y += gen_stats.h header-y += gfs2_ondisk.h header-y += gigaset_dev.h header-y += gsmmux.h +header-y += hbtp_input.h header-y += hdlcdrv.h header-y += hdlc.h header-y += hdreg.h diff --git a/include/uapi/linux/hbtp_input.h b/include/uapi/linux/hbtp_input.h new file mode 100644 index 000000000000..67692ed8e3b8 --- /dev/null +++ b/include/uapi/linux/hbtp_input.h @@ -0,0 +1,58 @@ +#ifndef _UAPI_HBTP_INPUT_H +#define _UAPI_HBTP_INPUT_H + +#include + +#define HBTP_MAX_FINGER 20 +#define HBTP_ABS_MT_FIRST ABS_MT_TOUCH_MAJOR +#define HBTP_ABS_MT_LAST ABS_MT_TOOL_Y + +#define HBTP_EVENT_TYPE_DISPLAY "EVENT_TYPE=HBTP_DISPLAY" + +struct hbtp_input_touch { + bool active; + __s32 tool; + __s32 x; + __s32 y; + __s32 pressure; + __s32 major; + __s32 minor; + __s32 orientation; +}; + +struct hbtp_input_mt { + __s32 num_touches; + struct hbtp_input_touch touches[HBTP_MAX_FINGER]; + struct timeval time_val; +}; + +struct hbtp_input_absinfo { + bool active; + __u16 code; + __s32 minimum; + __s32 maximum; +}; + +enum hbtp_afe_power_cmd { + HBTP_AFE_POWER_ON, + HBTP_AFE_POWER_OFF, +}; + +struct hbtp_input_key { + __u32 code; + __s32 value; +}; + +/* ioctl */ +#define HBTP_INPUT_IOCTL_BASE 'T' +#define HBTP_SET_ABSPARAM _IOW(HBTP_INPUT_IOCTL_BASE, 201, \ + struct hbtp_input_absinfo *) +#define HBTP_SET_TOUCHDATA _IOW(HBTP_INPUT_IOCTL_BASE, 202, \ + struct hbtp_input_mt) +#define HBTP_SET_POWERSTATE _IOW(HBTP_INPUT_IOCTL_BASE, 203, \ + enum hbtp_afe_power_cmd) +#define HBTP_SET_KEYDATA _IOW(HBTP_INPUT_IOCTL_BASE, 204, \ + struct hbtp_input_key) + +#endif /* _UAPI_HBTP_INPUT_H */ + -- cgit v1.2.3