diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 442e398aed3c..3d7ca037962d 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -117,6 +117,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 26ef3d75dd27..a99ea9586234 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_INPUT_GPIO_BEEPER) += gpio-beeper.o obj-$(CONFIG_INPUT_GPIO_DECODER) += gpio_decoder.o obj-$(CONFIG_INPUT_GPIO) += gpio_event.o gpio_matrix.o gpio_input.o gpio_output.o gpio_axis.o obj-$(CONFIG_INPUT_HISI_POWERKEY) += hisi_powerkey.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..b2c0a5e0592a --- /dev/null +++ b/drivers/input/misc/hbtp_input.c @@ -0,0 +1,1615 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2020 The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../input-compat.h" +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_FB) +#include +#include +#endif + +#define HBTP_INPUT_NAME "hbtp_input" +#define DISP_COORDS_SIZE 2 +#define NUM_DEVICES 1 + +#define HBTP_PINCTRL_VALID_STATE_CNT (2) +#define HBTP_HOLD_DURATION_US (10) +#define HBTP_PINCTRL_DDIC_SEQ_NUM (4) +static DEFINE_IDA(hbtp_minor_id); + +struct hbtp_data { + struct platform_device *pdev; + struct cdev cdev; + struct device *dev; + dev_t hbtp_dev; + struct class *drv_class; + struct input_dev *input_dev; + s32 count; + struct mutex mutex; + struct mutex sensormutex; + struct hbtp_sensor_data *sensor_data; + bool touch_status[HBTP_MAX_FINGER]; +#if defined(CONFIG_FB) + struct notifier_block fb_notif; +#endif + struct pinctrl *ts_pinctrl; + struct pinctrl_state *gpio_state_active; + struct pinctrl_state *gpio_state_suspend; + bool ddic_rst_enabled; + struct pinctrl_state *ddic_rst_state_active; + struct pinctrl_state *ddic_rst_state_suspend; + u32 ts_pinctrl_seq_delay; + u32 ddic_pinctrl_seq_delay[HBTP_PINCTRL_DDIC_SEQ_NUM]; + u32 fb_resume_seq_delay; + bool lcd_on; + bool power_suspended; + bool power_sync_enabled; + bool power_sig_enabled; + struct completion power_resume_sig; + struct completion power_suspend_sig; + struct regulator *vcc_ana; + struct regulator *vcc_dig; + int afe_load_ua; + int afe_vtg_min_uv; + int afe_vtg_max_uv; + int dig_load_ua; + int dig_vtg_min_uv; + int dig_vtg_max_uv; + int disp_maxx; /* Display Max X */ + int disp_maxy; /* Display Max Y */ + int def_maxx; /* Default Max X */ + int def_maxy; /* Default Max Y */ + int des_maxx; /* Desired Max X */ + int des_maxy; /* Desired Max Y */ + bool use_scaling; + bool override_disp_coords; + bool manage_afe_power_ana; + bool manage_power_dig; + u32 power_on_delay; + u32 power_off_delay; + bool manage_pin_ctrl; + struct kobject *sysfs_kobject; + s16 ROI[MAX_ROI_SIZE]; + s16 accelBuffer[MAX_ACCEL_SIZE]; + u32 display_status; +}; + +static struct hbtp_data *hbtp; + +static struct kobject *sensor_kobject; + +#if defined(CONFIG_FB) +static int hbtp_fb_suspend(struct hbtp_data *ts); +static int hbtp_fb_early_resume(struct hbtp_data *ts); +static int hbtp_fb_resume(struct hbtp_data *ts); +#endif + +#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); + + if (evdata && evdata->data && hbtp_data && + (event == FB_EARLY_EVENT_BLANK || + event == FB_R_EARLY_EVENT_BLANK)) { + blank = *(int *)(evdata->data); + if (event == FB_EARLY_EVENT_BLANK) { + if (blank == FB_BLANK_UNBLANK) { + pr_debug("%s: receives EARLY_BLANK:UNBLANK\n", + __func__); + hbtp_data->lcd_on = true; + hbtp_fb_early_resume(hbtp_data); + } else if (blank == FB_BLANK_POWERDOWN) { + pr_debug("%s: receives EARLY_BLANK:POWERDOWN\n", + __func__); + hbtp_data->lcd_on = false; + } + } else if (event == FB_R_EARLY_EVENT_BLANK) { + if (blank == FB_BLANK_UNBLANK) { + pr_debug("%s: receives R_EARLY_BALNK:UNBLANK\n", + __func__); + hbtp_data->lcd_on = false; + hbtp_fb_suspend(hbtp_data); + } else if (blank == FB_BLANK_POWERDOWN) { + pr_debug("%s: receives R_EARLY_BALNK:POWERDOWN\n", + __func__); + hbtp_data->lcd_on = true; + } + } + } + + if (evdata && evdata->data && hbtp_data && + event == FB_EVENT_BLANK) { + blank = *(int *)(evdata->data); + if (blank == FB_BLANK_POWERDOWN) { + pr_debug("%s: receives BLANK:POWERDOWN\n", __func__); + hbtp_fb_suspend(hbtp_data); + } else if (blank == FB_BLANK_UNBLANK) { + pr_debug("%s: receives BLANK:UNBLANK\n", __func__); + hbtp_fb_resume(hbtp_data); + } + } + return 0; +} +#endif + +static ssize_t hbtp_sensor_roi_show(struct file *dev, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t pos, + size_t size) +{ + mutex_lock(&hbtp->sensormutex); + memcpy(buf, hbtp->ROI, size); + mutex_unlock(&hbtp->sensormutex); + + return size; +} + +static ssize_t hbtp_sensor_vib_show(struct file *dev, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t pos, + size_t size) +{ + mutex_lock(&hbtp->sensormutex); + memcpy(buf, hbtp->accelBuffer, size); + mutex_unlock(&hbtp->sensormutex); + + return size; +} + +static struct bin_attribute capdata_attr = { + .attr = { + .name = "capdata", + .mode = 0444, + }, + .size = 1024, + .read = hbtp_sensor_roi_show, + .write = NULL, +}; + +static struct bin_attribute vibdata_attr = { + .attr = { + .name = "vib_data", + .mode = 0444, + }, + .size = MAX_ACCEL_SIZE*sizeof(int16_t), + .read = hbtp_sensor_vib_show, + .write = NULL, +}; + +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--; + if (hbtp->power_sig_enabled) + hbtp->power_sig_enabled = false; + 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) { + if (abs->code >= 0 && abs->code < ABS_CNT) + input_set_abs_params(input_dev, abs->code, + abs->minimum, abs->maximum, 0, 0); + else + pr_err("%s: ABS code out of bound\n", __func__); + } + } + + if (hbtp->override_disp_coords) { + input_set_abs_params(input_dev, ABS_MT_POSITION_X, + 0, hbtp->disp_maxx, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, + 0, hbtp->disp_maxy, 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); + /* + * Scale up/down the X-coordinate as per + * DT property + */ + if (hbtp_data->use_scaling && + hbtp_data->def_maxx > 0 && + hbtp_data->des_maxx > 0) + tch->x = (tch->x * hbtp_data->des_maxx) + / hbtp_data->def_maxx; + + input_report_abs(hbtp_data->input_dev, + ABS_MT_POSITION_X, + tch->x); + /* + * Scale up/down the Y-coordinate as per + * DT property + */ + if (hbtp_data->use_scaling && + hbtp_data->def_maxy > 0 && + hbtp_data->des_maxy > 0) + tch->y = (tch->y * hbtp_data->des_maxy) + / hbtp_data->def_maxy; + + 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_load_check(struct regulator *reg, int load_uA) +{ + return (regulator_count_voltages(reg) > 0) ? + regulator_set_load(reg, load_uA) : 0; +} + +static int hbtp_pdev_power_on(struct hbtp_data *hbtp, bool on) +{ + int ret; + + if (!hbtp->vcc_ana) + pr_err("%s: analog regulator is not available\n", __func__); + + if (!hbtp->vcc_dig) + pr_err("%s: digital regulator is not available\n", __func__); + + if (!hbtp->vcc_ana && !hbtp->vcc_dig) { + pr_err("%s: no regulators available\n", __func__); + return -EINVAL; + } + + if (!on) + goto reg_off; + + if (hbtp->vcc_ana) { + ret = reg_set_load_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 ret; + } + + ret = regulator_enable(hbtp->vcc_ana); + if (ret) { + pr_err("%s: Regulator vcc_ana enable failed rc=%d\n", + __func__, ret); + reg_set_load_check(hbtp->vcc_ana, 0); + return ret; + } + } + + if (hbtp->power_on_delay) { + pr_debug("%s: power-on-delay = %u\n", __func__, + hbtp->power_on_delay); + usleep_range(hbtp->power_on_delay, + hbtp->power_on_delay + HBTP_HOLD_DURATION_US); + } + + if (hbtp->vcc_dig) { + ret = reg_set_load_check(hbtp->vcc_dig, + hbtp->dig_load_ua); + if (ret < 0) { + pr_err("%s: Regulator vcc_dig set_opt failed rc=%d\n", + __func__, ret); + return ret; + } + + ret = regulator_enable(hbtp->vcc_dig); + if (ret) { + pr_err("%s: Regulator vcc_dig enable failed rc=%d\n", + __func__, ret); + reg_set_load_check(hbtp->vcc_dig, 0); + return ret; + } + } + + return 0; + +reg_off: + if (hbtp->vcc_dig) { + reg_set_load_check(hbtp->vcc_dig, 0); + regulator_disable(hbtp->vcc_dig); + } + + if (hbtp->power_off_delay) { + pr_debug("%s: power-off-delay = %u\n", __func__, + hbtp->power_off_delay); + usleep_range(hbtp->power_off_delay, + hbtp->power_off_delay + HBTP_HOLD_DURATION_US); + } + + if (hbtp->vcc_ana) { + reg_set_load_check(hbtp->vcc_ana, 0); + regulator_disable(hbtp->vcc_ana); + } + return 0; +} + +static int hbtp_gpio_select(struct hbtp_data *data, bool on) +{ + struct pinctrl_state *pins_state; + int ret = 0; + + pins_state = on ? data->gpio_state_active : data->gpio_state_suspend; + if (!IS_ERR_OR_NULL(pins_state)) { + ret = pinctrl_select_state(data->ts_pinctrl, pins_state); + if (ret) { + dev_err(&data->pdev->dev, + "can not set %s pins\n", + on ? "ts_active" : "ts_suspend"); + return ret; + } + + if (on) { + if (data->ts_pinctrl_seq_delay) { + usleep_range(data->ts_pinctrl_seq_delay, + data->ts_pinctrl_seq_delay + + HBTP_HOLD_DURATION_US); + dev_dbg(&data->pdev->dev, "ts_pinctrl_seq_delay = %u\n", + data->ts_pinctrl_seq_delay); + } + } + } else { + dev_warn(&data->pdev->dev, + "not a valid '%s' pinstate\n", + on ? "ts_active" : "ts_suspend"); + return ret; + } + + return ret; +} + +static int hbtp_ddic_rst_select(struct hbtp_data *data, bool on) +{ + struct pinctrl_state *active, *suspend; + int ret = 0; + + active = data->ddic_rst_state_active; + if (IS_ERR_OR_NULL(active)) { + dev_warn(&data->pdev->dev, + "not a valid ddic_rst_active pinstate\n"); + return ret; + } + + suspend = data->ddic_rst_state_suspend; + if (IS_ERR_OR_NULL(suspend)) { + dev_warn(&data->pdev->dev, + "not a valid ddic_rst_suspend pinstate\n"); + return ret; + } + + if (on) { + if (data->ddic_pinctrl_seq_delay[0]) { + usleep_range(data->ddic_pinctrl_seq_delay[0], + data->ddic_pinctrl_seq_delay[0] + + HBTP_HOLD_DURATION_US); + dev_dbg(&data->pdev->dev, "ddic_seq_delay[0] = %u\n", + data->ddic_pinctrl_seq_delay[0]); + } + + ret = pinctrl_select_state(data->ts_pinctrl, active); + if (ret) { + dev_err(&data->pdev->dev, + "can not set ddic_rst_active pins\n"); + return ret; + } + if (data->ddic_pinctrl_seq_delay[1]) { + usleep_range(data->ddic_pinctrl_seq_delay[1], + data->ddic_pinctrl_seq_delay[1] + + HBTP_HOLD_DURATION_US); + dev_dbg(&data->pdev->dev, "ddic_seq_delay[1] = %u\n", + data->ddic_pinctrl_seq_delay[1]); + } + ret = pinctrl_select_state(data->ts_pinctrl, suspend); + if (ret) { + dev_err(&data->pdev->dev, + "can not set ddic_rst_suspend pins\n"); + return ret; + } + + if (data->ddic_pinctrl_seq_delay[2]) { + usleep_range(data->ddic_pinctrl_seq_delay[2], + data->ddic_pinctrl_seq_delay[2] + + HBTP_HOLD_DURATION_US); + dev_dbg(&data->pdev->dev, "ddic_seq_delay[2] = %u\n", + data->ddic_pinctrl_seq_delay[2]); + } + + ret = pinctrl_select_state(data->ts_pinctrl, active); + if (ret) { + dev_err(&data->pdev->dev, + "can not set ddic_rst_active pins\n"); + return ret; + } + + if (data->ddic_pinctrl_seq_delay[3]) { + usleep_range(data->ddic_pinctrl_seq_delay[3], + data->ddic_pinctrl_seq_delay[3] + + HBTP_HOLD_DURATION_US); + dev_dbg(&data->pdev->dev, "ddic_seq_delay[3] = %u\n", + data->ddic_pinctrl_seq_delay[3]); + } + } else { + ret = pinctrl_select_state(data->ts_pinctrl, suspend); + if (ret) { + dev_err(&data->pdev->dev, + "can not set ddic_rst_suspend pins\n"); + return ret; + } + } + + return ret; +} + +static int hbtp_pinctrl_enable(struct hbtp_data *ts, bool on) +{ + int rc = 0; + + if (!ts->manage_pin_ctrl) { + pr_info("%s: pinctrl info is not available\n", __func__); + return 0; + } + + if (!on) + goto pinctrl_suspend; + + rc = hbtp_gpio_select(ts, true); + if (rc < 0) + return -EINVAL; + + if (ts->ddic_rst_enabled) { + rc = hbtp_ddic_rst_select(ts, true); + if (rc < 0) + goto err_ddic_rst_pinctrl_enable; + } + + return rc; + +pinctrl_suspend: + if (ts->ddic_rst_enabled) + hbtp_ddic_rst_select(ts, false); +err_ddic_rst_pinctrl_enable: + hbtp_gpio_select(ts, false); + return rc; +} + +static long hbtp_input_ioctl_handler(struct file *file, unsigned int cmd, + unsigned long arg, void __user *p) +{ + int error = 0; + 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; + enum hbtp_afe_signal afe_signal; + enum hbtp_afe_power_ctrl afe_power_ctrl; + + 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; + + case HBTP_SET_SYNCSIGNAL: + if (!hbtp || !hbtp->input_dev) { + pr_err("%s: The input device hasn't been created\n", + __func__); + return -EFAULT; + } + + if (!hbtp->power_sig_enabled) { + pr_err("%s: power_signal is not enabled\n", __func__); + return -EPERM; + } + + if (copy_from_user(&afe_signal, (void *)arg, + sizeof(enum hbtp_afe_signal))) { + pr_err("%s: Error copying data\n", __func__); + return -EFAULT; + } + + pr_debug("%s: receives %d signal\n", __func__, afe_signal); + + switch (afe_signal) { + case HBTP_AFE_SIGNAL_ON_RESUME: + mutex_lock(&hbtp->mutex); + if (!hbtp->power_suspended) { + complete(&hbtp->power_resume_sig); + } else { + pr_err("%s: resume signal in wrong state\n", + __func__); + } + mutex_unlock(&hbtp->mutex); + break; + case HBTP_AFE_SIGNAL_ON_SUSPEND: + mutex_lock(&hbtp->mutex); + if (hbtp->power_suspended) { + complete(&hbtp->power_suspend_sig); + } else { + pr_err("%s: suspend signal in wrong state\n", + __func__); + } + mutex_unlock(&hbtp->mutex); + break; + default: + pr_err("%s: Unsupported command for afe signal, %d\n", + __func__, afe_signal); + return -EINVAL; + } + break; + case HBTP_SET_POWER_CTRL: + if (!hbtp || !hbtp->input_dev) { + pr_err("%s: The input device hasn't been created\n", + __func__); + return -EFAULT; + } + + if (copy_from_user(&afe_power_ctrl, (void *)arg, + sizeof(enum hbtp_afe_power_ctrl))) { + pr_err("%s: Error copying data\n", __func__); + return -EFAULT; + } + switch (afe_power_ctrl) { + case HBTP_AFE_POWER_ENABLE_SYNC: + pr_debug("%s: power_sync is enabled\n", __func__); + if (!hbtp->manage_pin_ctrl || !hbtp->manage_power_dig || + !hbtp->manage_afe_power_ana) { + pr_err("%s: power/pin is not available\n", + __func__); + return -EFAULT; + } + mutex_lock(&hbtp->mutex); + error = hbtp_pdev_power_on(hbtp, true); + if (error) { + mutex_unlock(&hbtp->mutex); + pr_err("%s: failed to power on\n", __func__); + return error; + } + error = hbtp_pinctrl_enable(hbtp, true); + if (error) { + mutex_unlock(&hbtp->mutex); + pr_err("%s: failed to enable pins\n", __func__); + hbtp_pdev_power_on(hbtp, false); + return error; + } + hbtp->power_sync_enabled = true; + mutex_unlock(&hbtp->mutex); + pr_debug("%s: power_sync option is enabled\n", + __func__); + break; + case HBTP_AFE_POWER_ENABLE_SYNC_SIGNAL: + if (!hbtp->power_sync_enabled) { + pr_err("%s: power_sync is not enabled\n", + __func__); + return -EFAULT; + } + mutex_lock(&hbtp->mutex); + init_completion(&hbtp->power_resume_sig); + init_completion(&hbtp->power_suspend_sig); + hbtp->power_sig_enabled = true; + mutex_unlock(&hbtp->mutex); + pr_err("%s: sync_signal option is enabled\n", __func__); + break; + default: + pr_err("%s: unsupported power ctrl, %d\n", + __func__, afe_power_ctrl); + return -EINVAL; + } + break; + + case HBTP_SET_SENSORDATA: + if (copy_from_user(hbtp->sensor_data, (void __user *)arg, + sizeof(struct hbtp_sensor_data))) { + pr_err("%s: Error copying data\n", __func__); + return -EFAULT; + } + mutex_lock(&hbtp->sensormutex); + memcpy(hbtp->ROI, hbtp->sensor_data->ROI, sizeof(hbtp->ROI)); + memcpy(hbtp->accelBuffer, hbtp->sensor_data->accelBuffer, + sizeof(hbtp->accelBuffer)); + mutex_unlock(&hbtp->sensormutex); + + error = 0; + 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 +}; + +#ifdef CONFIG_OF +static int hbtp_parse_dt(struct device *dev) +{ + int rc, size; + struct device_node *np = dev->of_node; + struct property *prop; + u32 temp_val; + u32 disp_reso[DISP_COORDS_SIZE]; + + if (of_find_property(np, "vcc_ana-supply", NULL)) + hbtp->manage_afe_power_ana = true; + if (of_find_property(np, "vcc_dig-supply", NULL)) + hbtp->manage_power_dig = true; + + if (hbtp->manage_afe_power_ana) { + 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; + } + } + if (hbtp->manage_power_dig) { + rc = of_property_read_u32(np, "qcom,dig-load", &temp_val); + if (!rc) { + hbtp->dig_load_ua = (int) temp_val; + } else { + dev_err(dev, "Unable to read digital load\n"); + return rc; + } + + rc = of_property_read_u32(np, "qcom,dig-vtg-min", &temp_val); + if (!rc) { + hbtp->dig_vtg_min_uv = (int) temp_val; + } else { + dev_err(dev, "Unable to read digital min voltage\n"); + return rc; + } + + rc = of_property_read_u32(np, "qcom,dig-vtg-max", &temp_val); + if (!rc) { + hbtp->dig_vtg_max_uv = (int) temp_val; + } else { + dev_err(dev, "Unable to read digital max voltage\n"); + return rc; + } + } + + if (hbtp->manage_power_dig && hbtp->manage_afe_power_ana) { + rc = of_property_read_u32(np, + "qcom,afe-power-on-delay-us", &temp_val); + if (!rc) + hbtp->power_on_delay = (u32)temp_val; + else + dev_info(dev, "Power-On Delay is not specified\n"); + + rc = of_property_read_u32(np, + "qcom,afe-power-off-delay-us", &temp_val); + if (!rc) + hbtp->power_off_delay = (u32)temp_val; + else + dev_info(dev, "Power-Off Delay is not specified\n"); + + dev_dbg(dev, "power-on-delay = %u, power-off-delay = %u\n", + hbtp->power_on_delay, hbtp->power_off_delay); + } + + prop = of_find_property(np, "qcom,display-resolution", NULL); + if (prop != NULL) { + if (!prop->value) + return -ENODATA; + + size = prop->length / sizeof(u32); + if (size != DISP_COORDS_SIZE) { + dev_err(dev, "invalid qcom,display-resolution DT property\n"); + return -EINVAL; + } + + rc = of_property_read_u32_array(np, "qcom,display-resolution", + disp_reso, size); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read DT property qcom,display-resolution\n"); + return rc; + } + + hbtp->disp_maxx = disp_reso[0]; + hbtp->disp_maxy = disp_reso[1]; + + hbtp->override_disp_coords = true; + } + + hbtp->use_scaling = of_property_read_bool(np, "qcom,use-scale"); + if (hbtp->use_scaling) { + rc = of_property_read_u32(np, "qcom,default-max-x", &temp_val); + if (!rc) { + hbtp->def_maxx = (int) temp_val; + } else if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read default max x\n"); + return rc; + } + + rc = of_property_read_u32(np, "qcom,desired-max-x", &temp_val); + if (!rc) { + hbtp->des_maxx = (int) temp_val; + } else if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read desired max x\n"); + return rc; + } + + /* + * Either both DT properties i.e. Default max X and + * Desired max X should be defined simultaneously, or none + * of them should be defined. + */ + if ((hbtp->def_maxx == 0 && hbtp->des_maxx != 0) || + (hbtp->def_maxx != 0 && hbtp->des_maxx == 0)) { + dev_err(dev, "default or desired max-X properties are incorrect\n"); + return -EINVAL; + } + + rc = of_property_read_u32(np, "qcom,default-max-y", &temp_val); + if (!rc) { + hbtp->def_maxy = (int) temp_val; + } else if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read default max y\n"); + return rc; + } + + rc = of_property_read_u32(np, "qcom,desired-max-y", &temp_val); + if (!rc) { + hbtp->des_maxy = (int) temp_val; + } else if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read desired max y\n"); + return rc; + } + + /* + * Either both DT properties i.e. Default max X and + * Desired max X should be defined simultaneously, or none + * of them should be defined. + */ + if ((hbtp->def_maxy == 0 && hbtp->des_maxy != 0) || + (hbtp->def_maxy != 0 && hbtp->des_maxy == 0)) { + dev_err(dev, "default or desired max-Y properties are incorrect\n"); + return -EINVAL; + } + + } + + return 0; +} +#else +static int hbtp_parse_dt(struct device *dev) +{ + return -ENODEV; +} +#endif + +static int hbtp_pinctrl_init(struct hbtp_data *data) +{ + const char *statename; + int rc; + int state_cnt, i; + struct device_node *np = data->pdev->dev.of_node; + bool pinctrl_state_act_found = false; + bool pinctrl_state_sus_found = false; + bool pinctrl_ddic_act_found = false; + bool pinctrl_ddic_sus_found = false; + int count = 0; + + data->ts_pinctrl = devm_pinctrl_get(&(data->pdev->dev)); + if (IS_ERR_OR_NULL(data->ts_pinctrl)) { + dev_err(&data->pdev->dev, + "Target does not use pinctrl\n"); + rc = PTR_ERR(data->ts_pinctrl); + data->ts_pinctrl = NULL; + return rc; + } + + state_cnt = of_property_count_strings(np, "pinctrl-names"); + if (state_cnt < HBTP_PINCTRL_VALID_STATE_CNT) { + /* + *if pinctrl names are not available then, + *power_sync can't be enabled + */ + dev_info(&data->pdev->dev, + "pinctrl names are not available\n"); + rc = -EINVAL; + goto error; + } + + for (i = 0; i < state_cnt; i++) { + rc = of_property_read_string_index(np, + "pinctrl-names", i, &statename); + if (rc) { + dev_err(&data->pdev->dev, + "failed to read pinctrl states by index\n"); + goto error; + } + + if (!strcmp(statename, "pmx_ts_active")) { + data->gpio_state_active + = pinctrl_lookup_state(data->ts_pinctrl, + statename); + if (IS_ERR_OR_NULL(data->gpio_state_active)) { + dev_err(&data->pdev->dev, + "Can not get ts default state\n"); + rc = PTR_ERR(data->gpio_state_active); + goto error; + } + pinctrl_state_act_found = true; + } else if (!strcmp(statename, "pmx_ts_suspend")) { + data->gpio_state_suspend + = pinctrl_lookup_state(data->ts_pinctrl, + statename); + if (IS_ERR_OR_NULL(data->gpio_state_suspend)) { + dev_err(&data->pdev->dev, + "Can not get ts sleep state\n"); + rc = PTR_ERR(data->gpio_state_suspend); + goto error; + } + pinctrl_state_sus_found = true; + } else if (!strcmp(statename, "ddic_rst_active")) { + data->ddic_rst_state_active + = pinctrl_lookup_state(data->ts_pinctrl, + statename); + if (IS_ERR(data->ddic_rst_state_active)) { + dev_err(&data->pdev->dev, + "Can not get DDIC rst act state\n"); + rc = PTR_ERR(data->ddic_rst_state_active); + goto error; + } + pinctrl_ddic_act_found = true; + } else if (!strcmp(statename, "ddic_rst_suspend")) { + data->ddic_rst_state_suspend + = pinctrl_lookup_state(data->ts_pinctrl, + statename); + if (IS_ERR(data->ddic_rst_state_suspend)) { + dev_err(&data->pdev->dev, + "Can not get DDIC rst sleep state\n"); + rc = PTR_ERR(data->ddic_rst_state_suspend); + goto error; + } + pinctrl_ddic_sus_found = true; + } else { + dev_err(&data->pdev->dev, "invalid pinctrl state\n"); + rc = -EINVAL; + goto error; + } + } + + if (!pinctrl_state_act_found || !pinctrl_state_sus_found) { + dev_err(&data->pdev->dev, + "missing required pinctrl states\n"); + rc = -EINVAL; + goto error; + } + + if (of_property_read_u32(np, "qcom,pmx-ts-on-seq-delay-us", + &data->ts_pinctrl_seq_delay)) { + dev_warn(&data->pdev->dev, "Can not find ts seq delay\n"); + } + + if (of_property_read_u32(np, "qcom,fb-resume-delay-us", + &data->fb_resume_seq_delay)) { + dev_warn(&data->pdev->dev, "Can not find fb resume seq delay\n"); + } + + if (pinctrl_ddic_act_found && pinctrl_ddic_sus_found) { + count = of_property_count_u32_elems(np, + "qcom,ddic-rst-on-seq-delay-us"); + if (count == HBTP_PINCTRL_DDIC_SEQ_NUM) { + of_property_read_u32_array(np, + "qcom,ddic-rst-on-seq-delay-us", + data->ddic_pinctrl_seq_delay, count); + } else { + dev_err(&data->pdev->dev, "count(%u) is not same as %u\n", + (u32)count, HBTP_PINCTRL_DDIC_SEQ_NUM); + } + + data->ddic_rst_enabled = true; + } else { + dev_warn(&data->pdev->dev, "ddic pinctrl act/sus not found\n"); + } + + data->manage_pin_ctrl = true; + return 0; + +error: + devm_pinctrl_put(data->ts_pinctrl); + data->ts_pinctrl = NULL; + return rc; +} + +static int hbtp_fb_suspend(struct hbtp_data *ts) +{ + int rc; + char *envp[2] = {HBTP_EVENT_TYPE_DISPLAY, NULL}; + + mutex_lock(&hbtp->mutex); + if (ts->pdev && ts->power_sync_enabled) { + pr_debug("%s: power_sync is enabled\n", __func__); + if (ts->power_suspended) { + mutex_unlock(&hbtp->mutex); + pr_err("%s: power is not resumed\n", __func__); + return 0; + } + rc = hbtp_pinctrl_enable(ts, false); + if (rc) { + pr_err("%s: failed to disable GPIO pins\n", __func__); + goto err_pin_disable; + } + + rc = hbtp_pdev_power_on(ts, false); + if (rc) { + pr_err("%s: failed to disable power\n", __func__); + goto err_power_disable; + } + ts->power_suspended = true; + } + + if (ts->input_dev) { + kobject_uevent_env(&ts->input_dev->dev.kobj, + KOBJ_OFFLINE, envp); + + if (ts->power_sig_enabled) { + pr_debug("%s: power_sig is enabled, wait for signal\n", + __func__); + mutex_unlock(&hbtp->mutex); + rc = wait_for_completion_interruptible( + &hbtp->power_suspend_sig); + if (rc != 0) { + pr_err("%s: wait for suspend is interrupted\n", + __func__); + } + mutex_lock(&hbtp->mutex); + pr_debug("%s: Wait is done for suspend\n", __func__); + } else { + pr_debug("%s: power_sig is NOT enabled\n", __func__); + } + } + + mutex_unlock(&hbtp->mutex); + return 0; +err_power_disable: + hbtp_pinctrl_enable(ts, true); +err_pin_disable: + mutex_unlock(&hbtp->mutex); + return rc; +} + +static int hbtp_fb_early_resume(struct hbtp_data *ts) +{ + char *envp[2] = {HBTP_EVENT_TYPE_DISPLAY, NULL}; + int rc; + + mutex_lock(&hbtp->mutex); + + pr_debug("%s: enter\n", __func__); + + if (ts->pdev && ts->power_sync_enabled) { + pr_debug("%s: power_sync is enabled\n", __func__); + if (!ts->power_suspended) { + pr_err("%s: power is not suspended\n", __func__); + mutex_unlock(&hbtp->mutex); + return 0; + } + rc = hbtp_pdev_power_on(ts, true); + if (rc) { + pr_err("%s: failed to enable panel power\n", __func__); + goto err_power_on; + } + + rc = hbtp_pinctrl_enable(ts, true); + + if (rc) { + pr_err("%s: failed to enable pin\n", __func__); + goto err_pin_enable; + } + + ts->power_suspended = false; + + if (ts->input_dev) { + + kobject_uevent_env(&ts->input_dev->dev.kobj, + KOBJ_ONLINE, envp); + + if (ts->power_sig_enabled) { + pr_err("%s: power_sig is enabled, wait for signal\n", + __func__); + mutex_unlock(&hbtp->mutex); + rc = wait_for_completion_interruptible( + &hbtp->power_resume_sig); + if (rc != 0) { + pr_err("%s: wait for resume is interrupted\n", + __func__); + } + mutex_lock(&hbtp->mutex); + pr_debug("%s: wait is done\n", __func__); + } else { + pr_debug("%s: power_sig is NOT enabled\n", + __func__); + } + + if (ts->fb_resume_seq_delay) { + usleep_range(ts->fb_resume_seq_delay, + ts->fb_resume_seq_delay + + HBTP_HOLD_DURATION_US); + pr_err("%s: fb_resume_seq_delay = %u\n", + __func__, ts->fb_resume_seq_delay); + } + } + } + mutex_unlock(&hbtp->mutex); + return 0; + +err_pin_enable: + hbtp_pdev_power_on(ts, false); +err_power_on: + mutex_unlock(&hbtp->mutex); + return rc; +} + +static int hbtp_fb_resume(struct hbtp_data *ts) +{ + char *envp[2] = {HBTP_EVENT_TYPE_DISPLAY, NULL}; + + mutex_lock(&hbtp->mutex); + if (!ts->power_sync_enabled) { + pr_debug("%s: power_sync is disabled, send uevent\n", __func__); + if (ts->input_dev) { + kobject_uevent_env(&ts->input_dev->dev.kobj, + KOBJ_ONLINE, envp); + } + } + mutex_unlock(&hbtp->mutex); + return 0; +} + +static int hbtp_pdev_probe(struct platform_device *pdev) +{ + int error; + struct regulator *vcc_ana, *vcc_dig; + + hbtp->pdev = pdev; + + 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; + } + } + + platform_set_drvdata(pdev, hbtp); + + error = hbtp_pinctrl_init(hbtp); + if (error) { + pr_info("%s: pinctrl isn't available, rc=%d\n", __func__, + error); + } + + if (hbtp->manage_afe_power_ana) { + vcc_ana = regulator_get(&pdev->dev, "vcc_ana"); + if (IS_ERR(vcc_ana)) { + error = PTR_ERR(vcc_ana); + pr_err("%s: regulator get failed vcc_ana rc=%d\n", + __func__, error); + return error; + } + + if (regulator_count_voltages(vcc_ana) > 0) { + error = regulator_set_voltage(vcc_ana, + hbtp->afe_vtg_min_uv, hbtp->afe_vtg_max_uv); + if (error) { + pr_err("%s: regulator set vtg failed vcc_ana rc=%d\n", + __func__, error); + regulator_put(vcc_ana); + return error; + } + } + hbtp->vcc_ana = vcc_ana; + } + + if (hbtp->manage_power_dig) { + vcc_dig = regulator_get(&pdev->dev, "vcc_dig"); + if (IS_ERR(vcc_dig)) { + error = PTR_ERR(vcc_dig); + pr_err("%s: regulator get failed vcc_dig rc=%d\n", + __func__, error); + return error; + } + + if (regulator_count_voltages(vcc_dig) > 0) { + error = regulator_set_voltage(vcc_dig, + hbtp->dig_vtg_min_uv, hbtp->dig_vtg_max_uv); + if (error) { + pr_err("%s: regulator set vtg failed vcc_dig rc=%d\n", + __func__, error); + regulator_put(vcc_dig); + return error; + } + } + hbtp->vcc_dig = vcc_dig; + } + + return 0; +} + +static int hbtp_pdev_remove(struct platform_device *pdev) +{ + if (hbtp->vcc_ana || hbtp->vcc_dig) { + hbtp_pdev_power_on(hbtp, false); + if (hbtp->vcc_ana) + regulator_put(hbtp->vcc_ana); + if (hbtp->vcc_dig) + regulator_put(hbtp->vcc_dig); + } + + if (hbtp->ts_pinctrl) + devm_pinctrl_put(hbtp->ts_pinctrl); + + return 0; +} + +#ifdef CONFIG_OF +static const 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", + .of_match_table = hbtp_match_table, + }, +}; + +static ssize_t hbtp_display_pwr_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + u32 status; + ssize_t ret; + char *envp[2] = {HBTP_EVENT_TYPE_DISPLAY, NULL}; + + mutex_lock(&hbtp->mutex); + ret = kstrtou32(buf, 10, &status); + if (ret) { + pr_err("hbtp: ret error: %zd\n", ret); + mutex_unlock(&hbtp->mutex); + return 0; + } + hbtp->display_status = status; + if (!hbtp->input_dev) { + pr_err("hbtp: hbtp->input_dev not ready!\n"); + mutex_unlock(&hbtp->mutex); + return ret; + } + if (status) { + pr_debug("hbtp: display power on!\n"); + kobject_uevent_env(&hbtp->input_dev->dev.kobj, + KOBJ_ONLINE, envp); + } else { + pr_debug("hbtp: display power off!\n"); + kobject_uevent_env(&hbtp->input_dev->dev.kobj, + KOBJ_OFFLINE, envp); + } + mutex_unlock(&hbtp->mutex); + return count; +} + +static ssize_t hbtp_display_pwr_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + ssize_t ret = 0; + + mutex_lock(&hbtp->mutex); + ret = scnprintf(buf, PAGE_SIZE, "%u\n", hbtp->display_status); + mutex_unlock(&hbtp->mutex); + return ret; +} + +static struct kobj_attribute hbtp_display_attribute = + __ATTR(display_pwr, 0660, hbtp_display_pwr_show, + hbtp_display_pwr_store); + +static int __init hbtp_init(void) +{ + int error = 0; + int minor; + + hbtp = kzalloc(sizeof(struct hbtp_data), GFP_KERNEL); + if (!hbtp) + return -ENOMEM; + + hbtp->sensor_data = kzalloc(sizeof(struct hbtp_sensor_data), + GFP_KERNEL); + if (!hbtp->sensor_data) + goto err_sensordata; + + mutex_init(&hbtp->mutex); + mutex_init(&hbtp->sensormutex); + hbtp->display_status = 1; + + hbtp->drv_class = class_create(THIS_MODULE, HBTP_INPUT_NAME); + error = alloc_chrdev_region(&hbtp->hbtp_dev, 0, NUM_DEVICES, + HBTP_INPUT_NAME); + if (error) { + pr_err("%s: alloc_chrdev_region failed: %d\n", __func__, error); + goto err_allocate_chrdev_region; + } + + minor = ida_simple_get(&hbtp_minor_id, 0, NUM_DEVICES, GFP_KERNEL); + if (minor < 0) { + pr_err("%s: No more minor numbers left! rc:%d\n", + __func__, minor); + goto err_out_of_minors; + } + + hbtp->dev = device_create(hbtp->drv_class, NULL, + MKDEV(MAJOR(hbtp->hbtp_dev), minor), hbtp, HBTP_INPUT_NAME); + if (IS_ERR(hbtp->dev)) { + error = PTR_ERR(hbtp->dev); + pr_err("%s: device_create failed for %s (%d)\n", __func__, + HBTP_INPUT_NAME, error); + goto err_device_create; + } + + cdev_init(&hbtp->cdev, &hbtp_input_fops); + + error = cdev_add(&hbtp->cdev, MKDEV(MAJOR(hbtp->hbtp_dev), minor), + NUM_DEVICES); + if (error < 0) { + pr_err("%s: cdev_add failed for %s (%d)\n", __func__, + HBTP_INPUT_NAME, error); + goto err_cdev_add; + } + +#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 + + sensor_kobject = kobject_create_and_add("hbtpsensor", kernel_kobj); + if (!sensor_kobject) { + pr_err("%s: Could not create hbtpsensor kobject\n", __func__); + goto err_kobject_create; + } + + error = sysfs_create_bin_file(sensor_kobject, &capdata_attr); + if (error < 0) { + pr_err("%s: hbtp capdata sysfs creation failed: %d\n", __func__, + error); + goto err_sysfs_create_capdata; + } + pr_debug("capdata sysfs creation success\n"); + + error = sysfs_create_bin_file(sensor_kobject, &vibdata_attr); + if (error < 0) { + pr_err("%s: vibdata sysfs creation failed: %d\n", __func__, + error); + goto err_sysfs_create_vibdata; + } + pr_debug("vibdata sysfs creation success\n"); + + error = platform_driver_register(&hbtp_pdev_driver); + if (error) { + pr_err("Failed to register platform driver: %d\n", error); + goto err_platform_drv_reg; + } + + hbtp->sysfs_kobject = kobject_create_and_add("hbtp", kernel_kobj); + if (!hbtp->sysfs_kobject) + pr_err("%s: Could not create sysfs kobject\n", __func__); + else { + error = sysfs_create_file(hbtp->sysfs_kobject, + &hbtp_display_attribute.attr); + if (error) + pr_err("failed to create the display_pwr sysfs\n"); + } + + return 0; + +err_platform_drv_reg: + sysfs_remove_bin_file(sensor_kobject, &vibdata_attr); +err_sysfs_create_vibdata: + sysfs_remove_bin_file(sensor_kobject, &capdata_attr); +err_sysfs_create_capdata: + kobject_put(sensor_kobject); +err_kobject_create: +#if defined(CONFIG_FB) + fb_unregister_client(&hbtp->fb_notif); +err_fb_reg: +#endif + cdev_del(&hbtp->cdev); +err_cdev_add: + device_unregister(hbtp->dev); +err_device_create: + ida_simple_remove(&hbtp_minor_id, minor); +err_out_of_minors: +err_allocate_chrdev_region: + kfree(hbtp->sensor_data); +err_sensordata: + kfree(hbtp); + + return error; +} + +static void __exit hbtp_exit(void) +{ + int minor = MINOR(hbtp->cdev.dev); + + sysfs_remove_bin_file(sensor_kobject, &vibdata_attr); + sysfs_remove_bin_file(sensor_kobject, &capdata_attr); + kobject_put(sensor_kobject); + + sysfs_remove_file(hbtp->sysfs_kobject, &hbtp_display_attribute.attr); + kobject_put(hbtp->sysfs_kobject); + + 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); + + cdev_del(&hbtp->cdev); + device_unregister(hbtp->dev); + ida_simple_remove(&hbtp_minor_id, minor); + + kfree(hbtp->sensor_data); + kfree(hbtp); +} + +MODULE_DESCRIPTION("Kernel driver to support host based touch processing"); +MODULE_LICENSE("GPL v2"); + +module_init(hbtp_init); +module_exit(hbtp_exit); diff --git a/gen_headers_arm.bp b/gen_headers_arm.bp index b61129eaa953..1446700ba42f 100644 --- a/gen_headers_arm.bp +++ b/gen_headers_arm.bp @@ -246,6 +246,7 @@ gen_headers_out_arm = [ "linux/gsmmux.h", "linux/gtp.h", "linux/hash_info.h", + "linux/hbtp_input.h", "linux/hdlc.h", "linux/hdlcdrv.h", "linux/hdreg.h", diff --git a/gen_headers_arm64.bp b/gen_headers_arm64.bp index d5c831841c06..d73e96d1d767 100644 --- a/gen_headers_arm64.bp +++ b/gen_headers_arm64.bp @@ -241,6 +241,7 @@ gen_headers_out_arm64 = [ "linux/gsmmux.h", "linux/gtp.h", "linux/hash_info.h", + "linux/hbtp_input.h", "linux/hdlc.h", "linux/hdlcdrv.h", "linux/hdreg.h", diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index dc1a320e3f21..0bb40057f484 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -4,6 +4,8 @@ ifeq ($(wildcard $(srctree)/arch/$(SRCARCH)/include/uapi/asm/a.out.h),) no-export-headers += a.out.h endif +header-y += hbtp_input.h + ifeq ($(wildcard $(srctree)/arch/$(SRCARCH)/include/uapi/asm/kvm.h),) no-export-headers += kvm.h endif diff --git a/include/uapi/linux/hbtp_input.h b/include/uapi/linux/hbtp_input.h new file mode 100644 index 000000000000..1ea70b01fccb --- /dev/null +++ b/include/uapi/linux/hbtp_input.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +#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 MAX_ROI_SIZE 144 +#define MAX_ACCEL_SIZE 128 + +#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_sensor_data { + __s16 accelBuffer[MAX_ACCEL_SIZE]; + __s16 ROI[MAX_ROI_SIZE]; +}; + +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; +}; + +enum hbtp_afe_signal { + HBTP_AFE_SIGNAL_ON_RESUME, + HBTP_AFE_SIGNAL_ON_SUSPEND, +}; + +enum hbtp_afe_power_ctrl { + HBTP_AFE_POWER_ENABLE_SYNC, + HBTP_AFE_POWER_ENABLE_SYNC_SIGNAL, +}; + + +/* 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) +#define HBTP_SET_SYNCSIGNAL _IOW(HBTP_INPUT_IOCTL_BASE, 205, \ + enum hbtp_afe_signal) +#define HBTP_SET_POWER_CTRL _IOW(HBTP_INPUT_IOCTL_BASE, 206, \ + enum hbtp_afe_power_ctrl) +#define HBTP_SET_SENSORDATA _IOW(HBTP_INPUT_IOCTL_BASE, 207, \ + struct hbtp_sensor_data) + +#endif /* _UAPI_HBTP_INPUT_H */ +