| /* | 
 |  * Core Source for: | 
 |  * Cypress TrueTouch(TM) Standard Product (TTSP) touchscreen drivers. | 
 |  * For use with Cypress Txx3xx parts. | 
 |  * Supported parts include: | 
 |  * CY8CTST341 | 
 |  * CY8CTMA340 | 
 |  * | 
 |  * Copyright (C) 2009, 2010, 2011 Cypress Semiconductor, Inc. | 
 |  * Copyright (C) 2012 Javier Martinez Canillas <javier@dowhile0.org> | 
 |  * | 
 |  * 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. | 
 |  * | 
 |  * You should have received a copy of the GNU General Public License along | 
 |  * with this program; if not, write to the Free Software Foundation, Inc., | 
 |  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 
 |  * | 
 |  * Contact Cypress Semiconductor at www.cypress.com <kev@cypress.com> | 
 |  * | 
 |  */ | 
 |  | 
 | #include <linux/delay.h> | 
 | #include <linux/input.h> | 
 | #include <linux/input/mt.h> | 
 | #include <linux/input/touchscreen.h> | 
 | #include <linux/gpio.h> | 
 | #include <linux/interrupt.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/property.h> | 
 | #include <linux/gpio/consumer.h> | 
 |  | 
 | #include "cyttsp_core.h" | 
 |  | 
 | /* Bootloader number of command keys */ | 
 | #define CY_NUM_BL_KEYS		8 | 
 |  | 
 | /* helpers */ | 
 | #define GET_NUM_TOUCHES(x)		((x) & 0x0F) | 
 | #define IS_LARGE_AREA(x)		(((x) & 0x10) >> 4) | 
 | #define IS_BAD_PKT(x)			((x) & 0x20) | 
 | #define IS_VALID_APP(x)			((x) & 0x01) | 
 | #define IS_OPERATIONAL_ERR(x)		((x) & 0x3F) | 
 | #define GET_HSTMODE(reg)		(((reg) & 0x70) >> 4) | 
 | #define GET_BOOTLOADERMODE(reg)		(((reg) & 0x10) >> 4) | 
 |  | 
 | #define CY_REG_BASE			0x00 | 
 | #define CY_REG_ACT_DIST			0x1E | 
 | #define CY_REG_ACT_INTRVL		0x1D | 
 | #define CY_REG_TCH_TMOUT		(CY_REG_ACT_INTRVL + 1) | 
 | #define CY_REG_LP_INTRVL		(CY_REG_TCH_TMOUT + 1) | 
 | #define CY_MAXZ				255 | 
 | #define CY_DELAY_DFLT			20 /* ms */ | 
 | #define CY_DELAY_MAX			500 | 
 | #define CY_ACT_DIST_DFLT		0xF8 | 
 | #define CY_ACT_DIST_MASK		0x0F | 
 | #define CY_HNDSHK_BIT			0x80 | 
 | /* device mode bits */ | 
 | #define CY_OPERATE_MODE			0x00 | 
 | #define CY_SYSINFO_MODE			0x10 | 
 | /* power mode select bits */ | 
 | #define CY_SOFT_RESET_MODE		0x01 /* return to Bootloader mode */ | 
 | #define CY_DEEP_SLEEP_MODE		0x02 | 
 | #define CY_LOW_POWER_MODE		0x04 | 
 |  | 
 | /* Slots management */ | 
 | #define CY_MAX_FINGER			4 | 
 | #define CY_MAX_ID			16 | 
 |  | 
 | static const u8 bl_command[] = { | 
 | 	0x00,			/* file offset */ | 
 | 	0xFF,			/* command */ | 
 | 	0xA5,			/* exit bootloader command */ | 
 | 	0, 1, 2, 3, 4, 5, 6, 7	/* default keys */ | 
 | }; | 
 |  | 
 | static int ttsp_read_block_data(struct cyttsp *ts, u8 command, | 
 | 				u8 length, void *buf) | 
 | { | 
 | 	int error; | 
 | 	int tries; | 
 |  | 
 | 	for (tries = 0; tries < CY_NUM_RETRY; tries++) { | 
 | 		error = ts->bus_ops->read(ts->dev, ts->xfer_buf, command, | 
 | 				length, buf); | 
 | 		if (!error) | 
 | 			return 0; | 
 |  | 
 | 		msleep(CY_DELAY_DFLT); | 
 | 	} | 
 |  | 
 | 	return -EIO; | 
 | } | 
 |  | 
 | static int ttsp_write_block_data(struct cyttsp *ts, u8 command, | 
 | 				 u8 length, void *buf) | 
 | { | 
 | 	int error; | 
 | 	int tries; | 
 |  | 
 | 	for (tries = 0; tries < CY_NUM_RETRY; tries++) { | 
 | 		error = ts->bus_ops->write(ts->dev, ts->xfer_buf, command, | 
 | 				length, buf); | 
 | 		if (!error) | 
 | 			return 0; | 
 |  | 
 | 		msleep(CY_DELAY_DFLT); | 
 | 	} | 
 |  | 
 | 	return -EIO; | 
 | } | 
 |  | 
 | static int ttsp_send_command(struct cyttsp *ts, u8 cmd) | 
 | { | 
 | 	return ttsp_write_block_data(ts, CY_REG_BASE, sizeof(cmd), &cmd); | 
 | } | 
 |  | 
 | static int cyttsp_handshake(struct cyttsp *ts) | 
 | { | 
 | 	if (ts->use_hndshk) | 
 | 		return ttsp_send_command(ts, | 
 | 				ts->xy_data.hst_mode ^ CY_HNDSHK_BIT); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int cyttsp_load_bl_regs(struct cyttsp *ts) | 
 | { | 
 | 	memset(&ts->bl_data, 0, sizeof(ts->bl_data)); | 
 | 	ts->bl_data.bl_status = 0x10; | 
 |  | 
 | 	return ttsp_read_block_data(ts, CY_REG_BASE, | 
 | 				    sizeof(ts->bl_data), &ts->bl_data); | 
 | } | 
 |  | 
 | static int cyttsp_exit_bl_mode(struct cyttsp *ts) | 
 | { | 
 | 	int error; | 
 | 	u8 bl_cmd[sizeof(bl_command)]; | 
 |  | 
 | 	memcpy(bl_cmd, bl_command, sizeof(bl_command)); | 
 | 	if (ts->bl_keys) | 
 | 		memcpy(&bl_cmd[sizeof(bl_command) - CY_NUM_BL_KEYS], | 
 | 			ts->bl_keys, CY_NUM_BL_KEYS); | 
 |  | 
 | 	error = ttsp_write_block_data(ts, CY_REG_BASE, | 
 | 				      sizeof(bl_cmd), bl_cmd); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	/* wait for TTSP Device to complete the operation */ | 
 | 	msleep(CY_DELAY_DFLT); | 
 |  | 
 | 	error = cyttsp_load_bl_regs(ts); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	if (GET_BOOTLOADERMODE(ts->bl_data.bl_status)) | 
 | 		return -EIO; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int cyttsp_set_operational_mode(struct cyttsp *ts) | 
 | { | 
 | 	int error; | 
 |  | 
 | 	error = ttsp_send_command(ts, CY_OPERATE_MODE); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	/* wait for TTSP Device to complete switch to Operational mode */ | 
 | 	error = ttsp_read_block_data(ts, CY_REG_BASE, | 
 | 				     sizeof(ts->xy_data), &ts->xy_data); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	error = cyttsp_handshake(ts); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	return ts->xy_data.act_dist == CY_ACT_DIST_DFLT ? -EIO : 0; | 
 | } | 
 |  | 
 | static int cyttsp_set_sysinfo_mode(struct cyttsp *ts) | 
 | { | 
 | 	int error; | 
 |  | 
 | 	memset(&ts->sysinfo_data, 0, sizeof(ts->sysinfo_data)); | 
 |  | 
 | 	/* switch to sysinfo mode */ | 
 | 	error = ttsp_send_command(ts, CY_SYSINFO_MODE); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	/* read sysinfo registers */ | 
 | 	msleep(CY_DELAY_DFLT); | 
 | 	error = ttsp_read_block_data(ts, CY_REG_BASE, sizeof(ts->sysinfo_data), | 
 | 				      &ts->sysinfo_data); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	error = cyttsp_handshake(ts); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	if (!ts->sysinfo_data.tts_verh && !ts->sysinfo_data.tts_verl) | 
 | 		return -EIO; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int cyttsp_set_sysinfo_regs(struct cyttsp *ts) | 
 | { | 
 | 	int retval = 0; | 
 |  | 
 | 	if (ts->act_intrvl != CY_ACT_INTRVL_DFLT || | 
 | 	    ts->tch_tmout != CY_TCH_TMOUT_DFLT || | 
 | 	    ts->lp_intrvl != CY_LP_INTRVL_DFLT) { | 
 |  | 
 | 		u8 intrvl_ray[] = { | 
 | 			ts->act_intrvl, | 
 | 			ts->tch_tmout, | 
 | 			ts->lp_intrvl | 
 | 		}; | 
 |  | 
 | 		/* set intrvl registers */ | 
 | 		retval = ttsp_write_block_data(ts, CY_REG_ACT_INTRVL, | 
 | 					sizeof(intrvl_ray), intrvl_ray); | 
 | 		msleep(CY_DELAY_DFLT); | 
 | 	} | 
 |  | 
 | 	return retval; | 
 | } | 
 |  | 
 | static void cyttsp_hard_reset(struct cyttsp *ts) | 
 | { | 
 | 	if (ts->reset_gpio) { | 
 | 		gpiod_set_value_cansleep(ts->reset_gpio, 1); | 
 | 		msleep(CY_DELAY_DFLT); | 
 | 		gpiod_set_value_cansleep(ts->reset_gpio, 0); | 
 | 		msleep(CY_DELAY_DFLT); | 
 | 	} | 
 | } | 
 |  | 
 | static int cyttsp_soft_reset(struct cyttsp *ts) | 
 | { | 
 | 	unsigned long timeout; | 
 | 	int retval; | 
 |  | 
 | 	/* wait for interrupt to set ready completion */ | 
 | 	reinit_completion(&ts->bl_ready); | 
 | 	ts->state = CY_BL_STATE; | 
 |  | 
 | 	enable_irq(ts->irq); | 
 |  | 
 | 	retval = ttsp_send_command(ts, CY_SOFT_RESET_MODE); | 
 | 	if (retval) | 
 | 		goto out; | 
 |  | 
 | 	timeout = wait_for_completion_timeout(&ts->bl_ready, | 
 | 			msecs_to_jiffies(CY_DELAY_DFLT * CY_DELAY_MAX)); | 
 | 	retval = timeout ? 0 : -EIO; | 
 |  | 
 | out: | 
 | 	ts->state = CY_IDLE_STATE; | 
 | 	disable_irq(ts->irq); | 
 | 	return retval; | 
 | } | 
 |  | 
 | static int cyttsp_act_dist_setup(struct cyttsp *ts) | 
 | { | 
 | 	u8 act_dist_setup = ts->act_dist; | 
 |  | 
 | 	/* Init gesture; active distance setup */ | 
 | 	return ttsp_write_block_data(ts, CY_REG_ACT_DIST, | 
 | 				sizeof(act_dist_setup), &act_dist_setup); | 
 | } | 
 |  | 
 | static void cyttsp_extract_track_ids(struct cyttsp_xydata *xy_data, int *ids) | 
 | { | 
 | 	ids[0] = xy_data->touch12_id >> 4; | 
 | 	ids[1] = xy_data->touch12_id & 0xF; | 
 | 	ids[2] = xy_data->touch34_id >> 4; | 
 | 	ids[3] = xy_data->touch34_id & 0xF; | 
 | } | 
 |  | 
 | static const struct cyttsp_tch *cyttsp_get_tch(struct cyttsp_xydata *xy_data, | 
 | 					       int idx) | 
 | { | 
 | 	switch (idx) { | 
 | 	case 0: | 
 | 		return &xy_data->tch1; | 
 | 	case 1: | 
 | 		return &xy_data->tch2; | 
 | 	case 2: | 
 | 		return &xy_data->tch3; | 
 | 	case 3: | 
 | 		return &xy_data->tch4; | 
 | 	default: | 
 | 		return NULL; | 
 | 	} | 
 | } | 
 |  | 
 | static void cyttsp_report_tchdata(struct cyttsp *ts) | 
 | { | 
 | 	struct cyttsp_xydata *xy_data = &ts->xy_data; | 
 | 	struct input_dev *input = ts->input; | 
 | 	int num_tch = GET_NUM_TOUCHES(xy_data->tt_stat); | 
 | 	const struct cyttsp_tch *tch; | 
 | 	int ids[CY_MAX_ID]; | 
 | 	int i; | 
 | 	DECLARE_BITMAP(used, CY_MAX_ID); | 
 |  | 
 | 	if (IS_LARGE_AREA(xy_data->tt_stat) == 1) { | 
 | 		/* terminate all active tracks */ | 
 | 		num_tch = 0; | 
 | 		dev_dbg(ts->dev, "%s: Large area detected\n", __func__); | 
 | 	} else if (num_tch > CY_MAX_FINGER) { | 
 | 		/* terminate all active tracks */ | 
 | 		num_tch = 0; | 
 | 		dev_dbg(ts->dev, "%s: Num touch error detected\n", __func__); | 
 | 	} else if (IS_BAD_PKT(xy_data->tt_mode)) { | 
 | 		/* terminate all active tracks */ | 
 | 		num_tch = 0; | 
 | 		dev_dbg(ts->dev, "%s: Invalid buffer detected\n", __func__); | 
 | 	} | 
 |  | 
 | 	cyttsp_extract_track_ids(xy_data, ids); | 
 |  | 
 | 	bitmap_zero(used, CY_MAX_ID); | 
 |  | 
 | 	for (i = 0; i < num_tch; i++) { | 
 | 		tch = cyttsp_get_tch(xy_data, i); | 
 |  | 
 | 		input_mt_slot(input, ids[i]); | 
 | 		input_mt_report_slot_state(input, MT_TOOL_FINGER, true); | 
 | 		input_report_abs(input, ABS_MT_POSITION_X, be16_to_cpu(tch->x)); | 
 | 		input_report_abs(input, ABS_MT_POSITION_Y, be16_to_cpu(tch->y)); | 
 | 		input_report_abs(input, ABS_MT_TOUCH_MAJOR, tch->z); | 
 |  | 
 | 		__set_bit(ids[i], used); | 
 | 	} | 
 |  | 
 | 	for (i = 0; i < CY_MAX_ID; i++) { | 
 | 		if (test_bit(i, used)) | 
 | 			continue; | 
 |  | 
 | 		input_mt_slot(input, i); | 
 | 		input_mt_report_slot_state(input, MT_TOOL_FINGER, false); | 
 | 	} | 
 |  | 
 | 	input_sync(input); | 
 | } | 
 |  | 
 | static irqreturn_t cyttsp_irq(int irq, void *handle) | 
 | { | 
 | 	struct cyttsp *ts = handle; | 
 | 	int error; | 
 |  | 
 | 	if (unlikely(ts->state == CY_BL_STATE)) { | 
 | 		complete(&ts->bl_ready); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	/* Get touch data from CYTTSP device */ | 
 | 	error = ttsp_read_block_data(ts, CY_REG_BASE, | 
 | 				 sizeof(struct cyttsp_xydata), &ts->xy_data); | 
 | 	if (error) | 
 | 		goto out; | 
 |  | 
 | 	/* provide flow control handshake */ | 
 | 	error = cyttsp_handshake(ts); | 
 | 	if (error) | 
 | 		goto out; | 
 |  | 
 | 	if (unlikely(ts->state == CY_IDLE_STATE)) | 
 | 		goto out; | 
 |  | 
 | 	if (GET_BOOTLOADERMODE(ts->xy_data.tt_mode)) { | 
 | 		/* | 
 | 		 * TTSP device has reset back to bootloader mode. | 
 | 		 * Restore to operational mode. | 
 | 		 */ | 
 | 		error = cyttsp_exit_bl_mode(ts); | 
 | 		if (error) { | 
 | 			dev_err(ts->dev, | 
 | 				"Could not return to operational mode, err: %d\n", | 
 | 				error); | 
 | 			ts->state = CY_IDLE_STATE; | 
 | 		} | 
 | 	} else { | 
 | 		cyttsp_report_tchdata(ts); | 
 | 	} | 
 |  | 
 | out: | 
 | 	return IRQ_HANDLED; | 
 | } | 
 |  | 
 | static int cyttsp_power_on(struct cyttsp *ts) | 
 | { | 
 | 	int error; | 
 |  | 
 | 	error = cyttsp_soft_reset(ts); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	error = cyttsp_load_bl_regs(ts); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	if (GET_BOOTLOADERMODE(ts->bl_data.bl_status) && | 
 | 	    IS_VALID_APP(ts->bl_data.bl_status)) { | 
 | 		error = cyttsp_exit_bl_mode(ts); | 
 | 		if (error) | 
 | 			return error; | 
 | 	} | 
 |  | 
 | 	if (GET_HSTMODE(ts->bl_data.bl_file) != CY_OPERATE_MODE || | 
 | 	    IS_OPERATIONAL_ERR(ts->bl_data.bl_status)) { | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	error = cyttsp_set_sysinfo_mode(ts); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	error = cyttsp_set_sysinfo_regs(ts); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	error = cyttsp_set_operational_mode(ts); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	/* init active distance */ | 
 | 	error = cyttsp_act_dist_setup(ts); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	ts->state = CY_ACTIVE_STATE; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int cyttsp_enable(struct cyttsp *ts) | 
 | { | 
 | 	int error; | 
 |  | 
 | 	/* | 
 | 	 * The device firmware can wake on an I2C or SPI memory slave | 
 | 	 * address match. So just reading a register is sufficient to | 
 | 	 * wake up the device. The first read attempt will fail but it | 
 | 	 * will wake it up making the second read attempt successful. | 
 | 	 */ | 
 | 	error = ttsp_read_block_data(ts, CY_REG_BASE, | 
 | 				     sizeof(ts->xy_data), &ts->xy_data); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	if (GET_HSTMODE(ts->xy_data.hst_mode)) | 
 | 		return -EIO; | 
 |  | 
 | 	enable_irq(ts->irq); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int cyttsp_disable(struct cyttsp *ts) | 
 | { | 
 | 	int error; | 
 |  | 
 | 	error = ttsp_send_command(ts, CY_LOW_POWER_MODE); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	disable_irq(ts->irq); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int __maybe_unused cyttsp_suspend(struct device *dev) | 
 | { | 
 | 	struct cyttsp *ts = dev_get_drvdata(dev); | 
 | 	int retval = 0; | 
 |  | 
 | 	mutex_lock(&ts->input->mutex); | 
 |  | 
 | 	if (ts->input->users) { | 
 | 		retval = cyttsp_disable(ts); | 
 | 		if (retval == 0) | 
 | 			ts->suspended = true; | 
 | 	} | 
 |  | 
 | 	mutex_unlock(&ts->input->mutex); | 
 |  | 
 | 	return retval; | 
 | } | 
 |  | 
 | static int __maybe_unused cyttsp_resume(struct device *dev) | 
 | { | 
 | 	struct cyttsp *ts = dev_get_drvdata(dev); | 
 |  | 
 | 	mutex_lock(&ts->input->mutex); | 
 |  | 
 | 	if (ts->input->users) | 
 | 		cyttsp_enable(ts); | 
 |  | 
 | 	ts->suspended = false; | 
 |  | 
 | 	mutex_unlock(&ts->input->mutex); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | SIMPLE_DEV_PM_OPS(cyttsp_pm_ops, cyttsp_suspend, cyttsp_resume); | 
 | EXPORT_SYMBOL_GPL(cyttsp_pm_ops); | 
 |  | 
 | static int cyttsp_open(struct input_dev *dev) | 
 | { | 
 | 	struct cyttsp *ts = input_get_drvdata(dev); | 
 | 	int retval = 0; | 
 |  | 
 | 	if (!ts->suspended) | 
 | 		retval = cyttsp_enable(ts); | 
 |  | 
 | 	return retval; | 
 | } | 
 |  | 
 | static void cyttsp_close(struct input_dev *dev) | 
 | { | 
 | 	struct cyttsp *ts = input_get_drvdata(dev); | 
 |  | 
 | 	if (!ts->suspended) | 
 | 		cyttsp_disable(ts); | 
 | } | 
 |  | 
 | static int cyttsp_parse_properties(struct cyttsp *ts) | 
 | { | 
 | 	struct device *dev = ts->dev; | 
 | 	u32 dt_value; | 
 | 	int ret; | 
 |  | 
 | 	ts->bl_keys = devm_kzalloc(dev, CY_NUM_BL_KEYS, GFP_KERNEL); | 
 | 	if (!ts->bl_keys) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	/* Set some default values */ | 
 | 	ts->use_hndshk = false; | 
 | 	ts->act_dist = CY_ACT_DIST_DFLT; | 
 | 	ts->act_intrvl = CY_ACT_INTRVL_DFLT; | 
 | 	ts->tch_tmout = CY_TCH_TMOUT_DFLT; | 
 | 	ts->lp_intrvl = CY_LP_INTRVL_DFLT; | 
 |  | 
 | 	ret = device_property_read_u8_array(dev, "bootloader-key", | 
 | 					    ts->bl_keys, CY_NUM_BL_KEYS); | 
 | 	if (ret) { | 
 | 		dev_err(dev, | 
 | 			"bootloader-key property could not be retrieved\n"); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	ts->use_hndshk = device_property_present(dev, "use-handshake"); | 
 |  | 
 | 	if (!device_property_read_u32(dev, "active-distance", &dt_value)) { | 
 | 		if (dt_value > 15) { | 
 | 			dev_err(dev, "active-distance (%u) must be [0-15]\n", | 
 | 				dt_value); | 
 | 			return -EINVAL; | 
 | 		} | 
 | 		ts->act_dist &= ~CY_ACT_DIST_MASK; | 
 | 		ts->act_dist |= dt_value; | 
 | 	} | 
 |  | 
 | 	if (!device_property_read_u32(dev, "active-interval-ms", &dt_value)) { | 
 | 		if (dt_value > 255) { | 
 | 			dev_err(dev, "active-interval-ms (%u) must be [0-255]\n", | 
 | 				dt_value); | 
 | 			return -EINVAL; | 
 | 		} | 
 | 		ts->act_intrvl = dt_value; | 
 | 	} | 
 |  | 
 | 	if (!device_property_read_u32(dev, "lowpower-interval-ms", &dt_value)) { | 
 | 		if (dt_value > 2550) { | 
 | 			dev_err(dev, "lowpower-interval-ms (%u) must be [0-2550]\n", | 
 | 				dt_value); | 
 | 			return -EINVAL; | 
 | 		} | 
 | 		/* Register value is expressed in 0.01s / bit */ | 
 | 		ts->lp_intrvl = dt_value / 10; | 
 | 	} | 
 |  | 
 | 	if (!device_property_read_u32(dev, "touch-timeout-ms", &dt_value)) { | 
 | 		if (dt_value > 2550) { | 
 | 			dev_err(dev, "touch-timeout-ms (%u) must be [0-2550]\n", | 
 | 				dt_value); | 
 | 			return -EINVAL; | 
 | 		} | 
 | 		/* Register value is expressed in 0.01s / bit */ | 
 | 		ts->tch_tmout = dt_value / 10; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | struct cyttsp *cyttsp_probe(const struct cyttsp_bus_ops *bus_ops, | 
 | 			    struct device *dev, int irq, size_t xfer_buf_size) | 
 | { | 
 | 	struct cyttsp *ts; | 
 | 	struct input_dev *input_dev; | 
 | 	int error; | 
 |  | 
 | 	ts = devm_kzalloc(dev, sizeof(*ts) + xfer_buf_size, GFP_KERNEL); | 
 | 	if (!ts) | 
 | 		return ERR_PTR(-ENOMEM); | 
 |  | 
 | 	input_dev = devm_input_allocate_device(dev); | 
 | 	if (!input_dev) | 
 | 		return ERR_PTR(-ENOMEM); | 
 |  | 
 | 	ts->dev = dev; | 
 | 	ts->input = input_dev; | 
 | 	ts->bus_ops = bus_ops; | 
 | 	ts->irq = irq; | 
 |  | 
 | 	ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); | 
 | 	if (IS_ERR(ts->reset_gpio)) { | 
 | 		error = PTR_ERR(ts->reset_gpio); | 
 | 		dev_err(dev, "Failed to request reset gpio, error %d\n", error); | 
 | 		return ERR_PTR(error); | 
 | 	} | 
 |  | 
 | 	error = cyttsp_parse_properties(ts); | 
 | 	if (error) | 
 | 		return ERR_PTR(error); | 
 |  | 
 | 	init_completion(&ts->bl_ready); | 
 | 	snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(dev)); | 
 |  | 
 | 	input_dev->name = "Cypress TTSP TouchScreen"; | 
 | 	input_dev->phys = ts->phys; | 
 | 	input_dev->id.bustype = bus_ops->bustype; | 
 | 	input_dev->dev.parent = ts->dev; | 
 |  | 
 | 	input_dev->open = cyttsp_open; | 
 | 	input_dev->close = cyttsp_close; | 
 |  | 
 | 	input_set_drvdata(input_dev, ts); | 
 |  | 
 | 	input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_X); | 
 | 	input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_Y); | 
 | 	touchscreen_parse_properties(input_dev, true); | 
 |  | 
 | 	error = input_mt_init_slots(input_dev, CY_MAX_ID, 0); | 
 | 	if (error) { | 
 | 		dev_err(dev, "Unable to init MT slots.\n"); | 
 | 		return ERR_PTR(error); | 
 | 	} | 
 |  | 
 | 	error = devm_request_threaded_irq(dev, ts->irq, NULL, cyttsp_irq, | 
 | 					  IRQF_TRIGGER_FALLING | IRQF_ONESHOT, | 
 | 					  "cyttsp", ts); | 
 | 	if (error) { | 
 | 		dev_err(ts->dev, "failed to request IRQ %d, err: %d\n", | 
 | 			ts->irq, error); | 
 | 		return ERR_PTR(error); | 
 | 	} | 
 |  | 
 | 	disable_irq(ts->irq); | 
 |  | 
 | 	cyttsp_hard_reset(ts); | 
 |  | 
 | 	error = cyttsp_power_on(ts); | 
 | 	if (error) | 
 | 		return ERR_PTR(error); | 
 |  | 
 | 	error = input_register_device(input_dev); | 
 | 	if (error) { | 
 | 		dev_err(ts->dev, "failed to register input device: %d\n", | 
 | 			error); | 
 | 		return ERR_PTR(error); | 
 | 	} | 
 |  | 
 | 	return ts; | 
 | } | 
 | EXPORT_SYMBOL_GPL(cyttsp_probe); | 
 |  | 
 | MODULE_LICENSE("GPL"); | 
 | MODULE_DESCRIPTION("Cypress TrueTouch(R) Standard touchscreen driver core"); | 
 | MODULE_AUTHOR("Cypress"); |