Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c554ce60 authored by Himanshu Aggarwal's avatar Himanshu Aggarwal Committed by Jigarkumar Kishorkumar Zala
Browse files

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: default avatarJing Lin <jinglin@codeaurora.org>
Signed-off-by: default avatarChun Zhang <chunz@codeaurora.org>
Signed-off-by: default avatarHimanshu Aggarwal <haggarwa@codeaurora.org>
parent b9ae0269
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
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>;
		};
	};
+11 −0
Original line number Diff line number Diff line
@@ -93,6 +93,17 @@ config INPUT_BMA150
	  To compile this driver as a module, choose M here: the
	  module will be called bma150.

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
+1 −0
Original line number Diff line number Diff line
@@ -32,6 +32,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
+566 −0
Original line number Diff line number Diff line

/* 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 <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/input/mt.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <uapi/linux/hbtp_input.h>
#include "../input-compat.h"

#if defined(CONFIG_FB)
#include <linux/notifier.h>
#include <linux/fb.h>
#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);
+1 −0
Original line number Diff line number Diff line
@@ -142,6 +142,7 @@ header-y += gen_stats.h
header-y += genetlink.h
header-y += gfs2_ondisk.h
header-y += gigaset_dev.h
header-y += hbtp_input.h
header-y += hdlc.h
header-y += hdlcdrv.h
header-y += hdreg.h
Loading