/*
 * pt_proximity.c
 * Parade TrueTouch(TM) Standard Product Proximity Module.
 * For use with Parade touchscreen controllers.
 * Supported parts include:
 * TMA5XX
 * TMA448
 * TMA445A
 * TT21XXX
 * TT31XXX
 * TT4XXXX
 * TT7XXX
 * TC3XXX
 *
 * Copyright (C) 2015-2020 Parade Technologies
 *
 * 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.
 *
 * Contact Parade Technologies at www.paradetech.com <ttdrivers@paradetech.com>
 *
 */

#include "pt_regs.h"

#define PT_PROXIMITY_NAME "pt_proximity"

/* Timeout value in ms. */
#define PT_PROXIMITY_REQUEST_EXCLUSIVE_TIMEOUT		1000

#define PT_PROXIMITY_ON 0
#define PT_PROXIMITY_OFF 1

/*******************************************************************************
 * FUNCTION: get_prox_data
 *
 * SUMMARY: Gets pointer of proximity data from core data structure
 *
 * RETURN:
 *	 pointer of pt_proximity_data structure in core data structure
 *
 * PARAMETERS:
 *     *dev - pointer to device structure
 ******************************************************************************/
static inline struct pt_proximity_data *get_prox_data(struct device *dev)
{
	struct pt_core_data *cd = dev_get_drvdata(dev);

	return &cd->pd;
}

/*******************************************************************************
 * FUNCTION: pt_report_proximity
 *
 * SUMMARY: Reports proximity event
 *
 * PARAMETERS:
 *     *pd  - pointer to proximity data structure
 *      on  - state of proximity(true:on; false:off)
 ******************************************************************************/
static void pt_report_proximity(struct pt_proximity_data *pd,
	bool on)
{
	int val = on ? PT_PROXIMITY_ON : PT_PROXIMITY_OFF;

	input_report_abs(pd->input, ABS_DISTANCE, val);
	input_sync(pd->input);
}

/*******************************************************************************
 * FUNCTION: pt_get_touch_axis
 *
 * SUMMARY: Calculates touch axis
 *
 * PARAMETERS:
 *     *pd      - pointer to proximity data structure
 *     *axis    - pointer to axis calculation result
 *      size    - size in bytes
 *      max     - max value of result
 *     *xy_data - pointer to input data to be parsed
 *      bofs    - bit offset
 ******************************************************************************/
static void pt_get_touch_axis(struct pt_proximity_data *pd,
	int *axis, int size, int max, u8 *xy_data, int bofs)
{
	int nbyte;
	int next;

	for (nbyte = 0, *axis = 0, next = 0; nbyte < size; nbyte++) {
		pt_debug(pd->dev, DL_INFO,
			"%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p xy_data[%d]=%02X(%d) bofs=%d\n",
			__func__, *axis, *axis, size, max, xy_data, next,
			xy_data[next], xy_data[next], bofs);
		*axis = *axis + ((xy_data[next] >> bofs) << (nbyte * 8));
		next++;
	}

	*axis &= max - 1;

	pt_debug(pd->dev, DL_INFO,
		"%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p xy_data[%d]=%02X(%d)\n",
		__func__, *axis, *axis, size, max, xy_data, next,
		xy_data[next], xy_data[next]);
}

/*******************************************************************************
 * FUNCTION: pt_get_touch_hdr
 *
 * SUMMARY: Gets header of touch report
 *
 * PARAMETERS:
 *     *pd      - pointer to proximity data structure
 *     *touch   - pointer to pt_touch structure
 *     *xy_mode - pointer to input mode data
 ******************************************************************************/
static void pt_get_touch_hdr(struct pt_proximity_data *pd,
	struct pt_touch *touch, u8 *xy_mode)
{
	struct device *dev = pd->dev;
	struct pt_sysinfo *si = pd->si;
	enum pt_tch_hdr hdr;

	for (hdr = PT_TCH_TIME; hdr < PT_TCH_NUM_HDR; hdr++) {
		if (!si->tch_hdr[hdr].report)
			continue;
		pt_get_touch_axis(pd, &touch->hdr[hdr],
			si->tch_hdr[hdr].size,
			si->tch_hdr[hdr].max,
			xy_mode + si->tch_hdr[hdr].ofs,
			si->tch_hdr[hdr].bofs);
		pt_debug(dev, DL_INFO, "%s: get %s=%04X(%d)\n",
			__func__, pt_tch_hdr_string[hdr],
			touch->hdr[hdr], touch->hdr[hdr]);
	}
}

/*******************************************************************************
 * FUNCTION: pt_get_touch
 *
 * SUMMARY: Parse proximity touch event
 *
 * PARAMETERS:
 *     *pd       - pointer to proximity data structure
 *     *touch    - pointer to touch structure
 *      xy_data  - pointer to touch data
 ******************************************************************************/
static void pt_get_touch(struct pt_proximity_data *pd,
	struct pt_touch *touch, u8 *xy_data)
{
	struct device *dev = pd->dev;
	struct pt_sysinfo *si = pd->si;
	enum pt_tch_abs abs;

	for (abs = PT_TCH_X; abs < PT_TCH_NUM_ABS; abs++) {
		if (!si->tch_abs[abs].report)
			continue;
		pt_get_touch_axis(pd, &touch->abs[abs],
			si->tch_abs[abs].size,
			si->tch_abs[abs].max,
			xy_data + si->tch_abs[abs].ofs,
			si->tch_abs[abs].bofs);
		pt_debug(dev, DL_INFO, "%s: get %s=%04X(%d)\n",
			__func__, pt_tch_abs_string[abs],
			touch->abs[abs], touch->abs[abs]);
	}

	pt_debug(dev, DL_INFO, "%s: x=%04X(%d) y=%04X(%d)\n",
		__func__, touch->abs[PT_TCH_X], touch->abs[PT_TCH_X],
		touch->abs[PT_TCH_Y], touch->abs[PT_TCH_Y]);
}

/*******************************************************************************
 * FUNCTION: pt_get_proximity_touch
 *
 * SUMMARY: Parse and report proximity touch event
 *
 * PARAMETERS:
 *     *pd      - pointer to proximity data structure
 *     *touch   - pointer to pt_touch structure
 ******************************************************************************/
static void pt_get_proximity_touch(struct pt_proximity_data *pd,
		struct pt_touch *tch, int num_cur_tch)
{
	struct pt_sysinfo *si = pd->si;
	int i;

	for (i = 0; i < num_cur_tch; i++) {
		pt_get_touch(pd, tch, si->xy_data +
			(i * si->desc.tch_record_size));

		/* Check for proximity event */
		if (tch->abs[PT_TCH_O] == PT_OBJ_PROXIMITY) {
			if (tch->abs[PT_TCH_E] == PT_EV_TOUCHDOWN)
				pt_report_proximity(pd, true);
			else if (tch->abs[PT_TCH_E] == PT_EV_LIFTOFF)
				pt_report_proximity(pd, false);
			break;
		}
	}
}

/*******************************************************************************
 * FUNCTION: pt_xy_worker
 *
 * SUMMARY: Read xy_data for all current touches
 *
 * RETURN:
 *	 0 = success
 *	!0 = failure
 *
 * PARAMETERS:
 *     *pd - pointer to proximity data structure
 ******************************************************************************/
static int pt_xy_worker(struct pt_proximity_data *pd)
{
	struct device *dev = pd->dev;
	struct pt_sysinfo *si = pd->si;
	struct pt_touch tch;
	u8 num_cur_tch;

	pt_get_touch_hdr(pd, &tch, si->xy_mode + 3);

	num_cur_tch = tch.hdr[PT_TCH_NUM];
	if (num_cur_tch > si->sensing_conf_data.max_tch) {
		pt_debug(dev, DL_ERROR, "%s: Num touch err detected (n=%d)\n",
			__func__, num_cur_tch);
		num_cur_tch = si->sensing_conf_data.max_tch;
	}

	if (tch.hdr[PT_TCH_LO])
		pt_debug(dev, DL_WARN, "%s: Large area detected\n",
		__func__);

	/* extract xy_data for all currently reported touches */
	pt_debug(dev, DL_INFO, "%s: extract data num_cur_rec=%d\n",
		__func__, num_cur_tch);
	if (num_cur_tch)
		pt_get_proximity_touch(pd, &tch, num_cur_tch);
	else
		pt_report_proximity(pd, false);

	return 0;
}

/*******************************************************************************
 * FUNCTION: pt_mt_attention
 *
 * SUMMARY: Wrapper function for pt_xy_worker() that subscribe into the TTDL
 *  attention list.
 *
 * RETURN:
 *	 0 = success
 *	!0 = failure
 *
 * PARAMETERS:
 *     *dev - pointer to device structure
 ******************************************************************************/
static int pt_proximity_attention(struct device *dev)
{
	struct pt_proximity_data *pd = get_prox_data(dev);
	int rc = 0;

	if (pd->si->xy_mode[2] != pd->si->desc.tch_report_id)
		return 0;

	mutex_lock(&pd->prox_lock);
	rc = pt_xy_worker(pd);
	mutex_unlock(&pd->prox_lock);
	if (rc < 0)
		pt_debug(dev, DL_ERROR, "%s: xy_worker error r=%d\n",
			__func__, rc);

	return rc;
}

/*******************************************************************************
 * FUNCTION: pt_startup_attention
 *
 * SUMMARY: Wrapper function for pt_report_proximity() that subcribe into the
 *  TTDL attention list.
 *
 * RETURN:
 *	 0 = success
 *
 * PARAMETERS:
 *     *dev - pointer to device structure
 ******************************************************************************/
static int pt_startup_attention(struct device *dev)
{
	struct pt_proximity_data *pd = get_prox_data(dev);

	mutex_lock(&pd->prox_lock);
	pt_report_proximity(pd, false);
	mutex_unlock(&pd->prox_lock);

	return 0;
}

/*******************************************************************************
 * FUNCTION: _pt_set_proximity_via_touchmode_enabled
 *
 * SUMMARY: Enable/Disable proximity via touchmode parameter
 *
 * RETURN:
 *	 0 = success
 *	!0 = failure
 *
 * PARAMETERS:
 *     *pd     - pointer to proximity data structure
 *      enable - enable or disable proximity(true:enable; false:disable)
 ******************************************************************************/
static int _pt_set_proximity_via_touchmode_enabled(
		struct pt_proximity_data *pd, bool enable)
{
	struct device *dev = pd->dev;
	u32 touchmode_enabled;
	int rc;

	rc = _pt_request_pip_get_param(dev, 0,
			PT_RAM_ID_TOUCHMODE_ENABLED, &touchmode_enabled);
	if (rc)
		return rc;

	if (enable)
		touchmode_enabled |= 0x80;
	else
		touchmode_enabled &= 0x7F;

	rc = _pt_request_pip_set_param(dev, 0,
			PT_RAM_ID_TOUCHMODE_ENABLED, touchmode_enabled,
			PT_RAM_ID_TOUCHMODE_ENABLED_SIZE);

	return rc;
}

/*******************************************************************************
 * FUNCTION: _pt_set_proximity_via_proximity_enable
 *
 * SUMMARY: Enable/Disable proximity via proximity parameter
 *
 * RETURN:
 *	 0 = success
 *	!0 = failure
 *
 * PARAMETERS:
 *     *pd     - pointer to proximity data structure
 *      enable - enable or disable proximity(true:enable; false:disable)
 ******************************************************************************/
static int _pt_set_proximity_via_proximity_enable(
		struct pt_proximity_data *pd, bool enable)
{
	struct device *dev = pd->dev;
	u32 proximity_enable;
	int rc;

	rc = _pt_request_pip_get_param(dev, 0,
			PT_RAM_ID_PROXIMITY_ENABLE, &proximity_enable);
	if (rc)
		return rc;

	if (enable)
		proximity_enable |= 0x01;
	else
		proximity_enable &= 0xFE;

	rc = _pt_request_pip_set_param(dev, 0,
			PT_RAM_ID_PROXIMITY_ENABLE, proximity_enable,
			PT_RAM_ID_PROXIMITY_ENABLE_SIZE);

	return rc;
}

/*******************************************************************************
 * FUNCTION: _pt_set_proximity
 *
 * SUMMARY: Set proximity mode via touchmode parameter or proximity parameter.
 *
 * RETURN:
 *	 0 = success
 *	!0 = failure
 *
 * PARAMETERS:
 *     *pd     - pointer to proximity data structure
 *      enable - enable or disable proximity(true:enable; false:disable)
 ******************************************************************************/
static int _pt_set_proximity(struct pt_proximity_data *pd,
		bool enable)
{
	if (!IS_PIP_VER_GE(pd->si, 1, 4))
		return _pt_set_proximity_via_touchmode_enabled(pd,
				enable);

	return _pt_set_proximity_via_proximity_enable(pd, enable);
}

/*******************************************************************************
 * FUNCTION: _pt_set_proximity
 *
 * SUMMARY: Enable proximity mode and subscribe into IRQ and STARTUP TTDL
 *  attention list.
 *
 * RETURN:
 *	 0 = success
 *	!0 = failure
 *
 * PARAMETERS:
 *     *pd     - pointer to proximity data structure
 ******************************************************************************/
static int _pt_proximity_enable(struct pt_proximity_data *pd)
{
	struct device *dev = pd->dev;
	int rc = 0;

	pm_runtime_get_sync(dev);

	rc = pt_request_exclusive(dev,
			PT_PROXIMITY_REQUEST_EXCLUSIVE_TIMEOUT);
	if (rc < 0) {
		pt_debug(dev, DL_ERROR, "%s: Error on request exclusive r=%d\n",
				__func__, rc);
		goto exit;
	}

	rc = _pt_set_proximity(pd, true);
	if (rc < 0) {
		pt_debug(dev, DL_ERROR, "%s: Error on request enable proximity scantype r=%d\n",
				__func__, rc);
		goto exit_release;
	}

	pt_debug(dev, DL_INFO, "%s: setup subscriptions\n", __func__);

	/* set up touch call back */
	_pt_subscribe_attention(dev, PT_ATTEN_IRQ, PT_PROXIMITY_NAME,
		pt_proximity_attention, PT_MODE_OPERATIONAL);

	/* set up startup call back */
	_pt_subscribe_attention(dev, PT_ATTEN_STARTUP,
		PT_PROXIMITY_NAME, pt_startup_attention, 0);

exit_release:
	pt_release_exclusive(dev);
exit:
	return rc;
}

/*******************************************************************************
 * FUNCTION: _pt_proximity_disable
 *
 * SUMMARY: Disable proximity mode and unsubscribe from IRQ and STARTUP TTDL
 *  attention list.
 *
 * RETURN:
 *	 0 = success
 *	!0 = failure
 *
 * PARAMETERS:
 *     *pd     - pointer to proximity data structure
 ******************************************************************************/
static int _pt_proximity_disable(struct pt_proximity_data *pd,
		bool force)
{
	struct device *dev = pd->dev;
	int rc = 0;

	rc = pt_request_exclusive(dev,
			PT_PROXIMITY_REQUEST_EXCLUSIVE_TIMEOUT);
	if (rc < 0) {
		pt_debug(dev, DL_ERROR, "%s: Error on request exclusive r=%d\n",
				__func__, rc);
		goto exit;
	}

	rc = _pt_set_proximity(pd, false);
	if (rc < 0) {
		pt_debug(dev, DL_ERROR, "%s: Error on request disable proximity scan r=%d\n",
				__func__, rc);
		goto exit_release;
	}

exit_release:
	pt_release_exclusive(dev);

exit:
	if (!rc || force) {
		_pt_unsubscribe_attention(dev, PT_ATTEN_IRQ,
			PT_PROXIMITY_NAME, pt_proximity_attention,
			PT_MODE_OPERATIONAL);

		_pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP,
			PT_PROXIMITY_NAME, pt_startup_attention, 0);
	}

	pm_runtime_put(dev);

	return rc;
}

/*******************************************************************************
 * FUNCTION: pt_proximity_enable_show
 *
 * SUMMARY: Show method for the prox_enable sysfs node that will show the
 *	enable_count of proximity
 *
 * RETURN: Size of printed buffer
 *
 * PARAMETERS:
 *	*dev  - pointer to device structure
 *	*attr - pointer to device attributes
 *	*buf  - pointer to output buffer
 ******************************************************************************/
static ssize_t pt_proximity_enable_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct pt_proximity_data *pd = get_prox_data(dev);
	int val = 0;

	mutex_lock(&pd->sysfs_lock);
	val = pd->enable_count;
	mutex_unlock(&pd->sysfs_lock);

	return scnprintf(buf, PT_MAX_PRBUF_SIZE, "%d\n", val);
}

/*******************************************************************************
 * FUNCTION: pt_proximity_enable_store
 *
 * SUMMARY: The store method for the prox_enable sysfs node that allows to
 *  enable or disable proxmity mode.
 *
 * RETURN: Size of passed in buffer
 *
 * PARAMETERS:
 *	*dev  - pointer to device structure
 *	*attr - pointer to device attributes
 *	*buf  - pointer to buffer that hold the command parameters
 *	 size - size of buf
 ******************************************************************************/
static ssize_t pt_proximity_enable_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t size)
{
	struct pt_proximity_data *pd = get_prox_data(dev);
	unsigned long value;
	int rc;

	rc = kstrtoul(buf, 10, &value);
	if (rc < 0 || (value != 0 && value != 1)) {
		pt_debug(dev, DL_ERROR, "%s: Invalid value\n", __func__);
		return -EINVAL;
	}

	mutex_lock(&pd->sysfs_lock);
	if (value) {
		if (pd->enable_count++) {
			pt_debug(dev, DL_WARN, "%s: '%s' already enabled\n",
				__func__, pd->input->name);
		} else {
			rc = _pt_proximity_enable(pd);
			if (rc)
				pd->enable_count--;
		}
	} else {
		if (--pd->enable_count) {
			if (pd->enable_count < 0) {
				pt_debug(dev, DL_ERROR, "%s: '%s' unbalanced disable\n",
					__func__, pd->input->name);
				pd->enable_count = 0;
			}
		} else {
			rc = _pt_proximity_disable(pd, false);
			if (rc)
				pd->enable_count++;
		}
	}
	mutex_unlock(&pd->sysfs_lock);

	if (rc)
		return rc;

	return size;
}

static DEVICE_ATTR(prox_enable, 0600,
		pt_proximity_enable_show,
		pt_proximity_enable_store);

/*******************************************************************************
 * FUNCTION: pt_setup_input_device_and_sysfs
 *
 * SUMMARY: Create sysnode, set event signal capabilities and register input
 *  device for proximity.
 *
 * RETURN:
 *	 0 = success
 *	!0 = failure
 *
 * PARAMETERS:
 *     *dev - pointer to device structure
 ******************************************************************************/
static int pt_setup_input_device_and_sysfs(struct device *dev)
{
	struct pt_proximity_data *pd = get_prox_data(dev);
	int signal = PT_IGNORE_VALUE;
	int i;
	int rc;

	rc = device_create_file(dev, &dev_attr_prox_enable);
	if (rc) {
		pt_debug(dev, DL_ERROR, "%s: Error, could not create enable\n",
				__func__);
		goto exit;
	}

	pt_debug(dev, DL_INFO, "%s: Initialize event signals\n",
				__func__);

	__set_bit(EV_ABS, pd->input->evbit);

	/* set event signal capabilities */
	for (i = 0; i < NUM_SIGNALS(pd->pdata->frmwrk); i++) {
		signal = PARAM_SIGNAL(pd->pdata->frmwrk, i);
		if (signal != PT_IGNORE_VALUE) {
			input_set_abs_params(pd->input, signal,
				PARAM_MIN(pd->pdata->frmwrk, i),
				PARAM_MAX(pd->pdata->frmwrk, i),
				PARAM_FUZZ(pd->pdata->frmwrk, i),
				PARAM_FLAT(pd->pdata->frmwrk, i));
		}
	}

	rc = input_register_device(pd->input);
	if (rc) {
		pt_debug(dev, DL_ERROR, "%s: Error, failed register input device r=%d\n",
			__func__, rc);
		goto unregister_enable;
	}

	pd->input_device_registered = true;
	return rc;

unregister_enable:
	device_remove_file(dev, &dev_attr_prox_enable);
exit:
	return rc;
}

/*******************************************************************************
 * FUNCTION: pt_setup_input_attention
 *
 * SUMMARY: Wrapper function for pt_setup_input_device_and_sysfs() that
 *  subscribe into TTDL attention list.
 *
 * RETURN:
 *	 0 = success
 *	!0 = failure
 *
 * PARAMETERS:
 *     *dev - pointer to device structure
 ******************************************************************************/
static int pt_setup_input_attention(struct device *dev)
{
	struct pt_proximity_data *pd = get_prox_data(dev);
	int rc;

	pd->si = _pt_request_sysinfo(dev);
	if (!pd->si)
		return -EINVAL;

	rc = pt_setup_input_device_and_sysfs(dev);
	if (!rc)
		rc = _pt_set_proximity(pd, false);

	_pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP,
		PT_PROXIMITY_NAME, pt_setup_input_attention, 0);

	return rc;
}

/*******************************************************************************
 * FUNCTION: pt_proximity_probe
 *
 * SUMMARY: The probe function for proximity input device
 *
 * RETURN:
 *	 0 = success
 *	!0 = failure
 *
 * PARAMETERS:
 *	*dev   - pointer to device structure
 ******************************************************************************/
int pt_proximity_probe(struct device *dev)
{
	struct pt_core_data *cd = dev_get_drvdata(dev);
	struct pt_proximity_data *pd = &cd->pd;
	struct pt_platform_data *pdata = dev_get_platdata(dev);
	struct pt_proximity_platform_data *prox_pdata;
	int rc = 0;

	if (!pdata ||  !pdata->prox_pdata) {
		pt_debug(dev, DL_ERROR,
			"%s: Missing platform data\n", __func__);
		rc = -ENODEV;
		goto error_no_pdata;
	}
	prox_pdata = pdata->prox_pdata;

	mutex_init(&pd->prox_lock);
	mutex_init(&pd->sysfs_lock);
	pd->dev = dev;
	pd->pdata = prox_pdata;

	/* Create the input device and register it. */
	pt_debug(dev, DL_INFO,
		"%s: Create the input device and register it\n", __func__);
	pd->input = input_allocate_device();
	if (!pd->input) {
		pt_debug(dev, DL_ERROR, "%s: Error, failed to allocate input device\n",
			__func__);
		rc = -ENODEV;
		goto error_alloc_failed;
	} else
		pd->input_device_allocated = true;

	if (pd->pdata->inp_dev_name)
		pd->input->name = pd->pdata->inp_dev_name;
	else
		pd->input->name = PT_PROXIMITY_NAME;
	scnprintf(pd->phys, sizeof(pd->phys), "%s/input%d", dev_name(dev),
			cd->phys_num++);
	pd->input->phys = pd->phys;
	pd->input->dev.parent = pd->dev;
	input_set_drvdata(pd->input, pd);

	/* get sysinfo */
	pd->si = _pt_request_sysinfo(dev);

	if (pd->si) {
		rc = pt_setup_input_device_and_sysfs(dev);
		if (rc)
			goto error_init_input;

		rc = _pt_set_proximity(pd, false);
	} else {
		pt_debug(dev, DL_ERROR, "%s: Fail get sysinfo pointer from core p=%p\n",
			__func__, pd->si);
		_pt_subscribe_attention(dev, PT_ATTEN_STARTUP,
			PT_PROXIMITY_NAME, pt_setup_input_attention,
			0);
	}

	return 0;

error_init_input:
	input_free_device(pd->input);
	pd->input_device_allocated = false;
error_alloc_failed:
error_no_pdata:
	pt_debug(dev, DL_ERROR, "%s failed.\n", __func__);
	return rc;
}

/*******************************************************************************
 * FUNCTION: pt_proximity_release
 *
 * SUMMARY: The release function for proximity input device
 *
 * RETURN:
 *	 0 = success
 *
 * PARAMETERS:
 *	*dev   - pointer to device structure
 ******************************************************************************/
int pt_proximity_release(struct device *dev)
{
	struct pt_proximity_data *pd;

	/* Ensure valid pointers before de-referencing them */
	if (dev)
		pd = get_prox_data(dev);
	else
		return 0;

	/*
	 * Second call this function may cause kernel panic if probe fail.
	 * Use input_device_registered & input_device_allocated variable to
	 * avoid unregister or free unavailable devive.
	 */
	if (pd && pd->input_device_registered) {
		/* Disable proximity sensing */
		pd->input_device_registered = false;
		mutex_lock(&pd->sysfs_lock);
		if (pd->enable_count)
			_pt_proximity_disable(pd, true);
		mutex_unlock(&pd->sysfs_lock);
		device_remove_file(dev, &dev_attr_prox_enable);
		input_unregister_device(pd->input);
		/* Unregistering device will free the device too */
		pd->input_device_allocated = false;
	} else if (pd && pd->input_device_allocated) {
		pd->input_device_allocated = false;
		input_free_device(pd->input);
		_pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP,
			PT_PROXIMITY_NAME, pt_setup_input_attention,
			0);
	}

	return 0;
}