Re: [PATCH] Synaptics TM1217 Touchscreen Controller driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Alan,

On Fri, Jul 16, 2010 at 02:10:16PM +0100, Alan Cox wrote:
> From: Ramesh Agarwal <ramesh.agarwal@xxxxxxxxx>
> 
> Driver for the Synaptics TM1217 touchscreen controller, connected via an SPI
> and GPIO interface.
> 

Is this RMI3 or RMI4 hardware? Synaptics has posted and generic-ish
framework for RMI4 devices so if it is RMI4 we'd need to move over to
it.

> +struct cp_tm1217_device {
> +	struct i2c_client	*client;
> +	struct device		*dev;
> +	struct cp_vendor_info	vinfo;
> +	struct cp_dev_info	dinfo;
> +	struct input_dev_info {
> +		char			phys[32];
> +		char			name[128];
> +		struct input_dev	*input;
> +		struct touch_state	touch;
> +	} cp_input_info[MAX_TOUCH_SUPPORTED];

This is a non-starter. If this is a multitouch device please use
multitouch protocol for it, not register 2 separate input devices.

> +
> +	int	thread_running;
> +	struct mutex	thread_mutex;
> +
> +	int gpio;
> +};
> +
> +
> +/* The following functions are used to read/write registers on the device
> + * as per the RMI prorocol. Technically, a page select should be written
> + * before doing read/write but since the register offsets are below 0xFF
> + * we can use the default value of page which is 0x00
> + */
> +static int cp_tm1217_read(struct cp_tm1217_device *ts,
> +				u8 *req, int size)
> +{
> +	int i, retval;
> +
> +	/* Send the address */
> +	retval = i2c_master_send(ts->client, &req[0], 1);
> +	if (retval != 1) {
> +		dev_err(ts->dev, "cp_tm1217: I2C send failed\n");
> +		return retval;
> +	}
> +	msleep(WAIT_FOR_RESPONSE);
> +	for (i = 0; i < MAX_RETRIES; i++) {
> +		retval = i2c_master_recv(ts->client, &req[1], size);
> +		if (retval == size) {
> +			break;
> +		} else {
> +			msleep(INCREMENTAL_DELAY);
> +			dev_dbg(ts->dev, "cp_tm1217: Retry count is %d\n", i);
> +		}
> +	}
> +	if (retval != size)
> +		dev_err(ts->dev, "cp_tm1217: Read from device failed\n");
> +
> +	return retval;
> +}
> +
> +static int cp_tm1217_write(struct cp_tm1217_device *ts,
> +				u8 *req, int size)
> +{
> +	int retval;
> +
> +	/* Send the address and the data to be written */
> +	retval = i2c_master_send(ts->client, &req[0], size + 1);
> +	if (retval != size + 1) {
> +		dev_err(ts->dev, "cp_tm1217: I2C write  failed: %d\n", retval);
> +		return retval;
> +	}
> +	/* Wait for the write to complete. TBD why this is required */
> +	msleep(WAIT_FOR_RESPONSE);
> +
> +	return size;
> +}
> +
> +static int cp_tm1217_mask_interrupt(struct cp_tm1217_device *ts)
> +{
> +	u8 req[2];
> +	int retval;
> +
> +	req[0] = TMA1217_INTERRUPT_ENABLE;
> +	req[1] = 0x0;
> +	retval = cp_tm1217_write(ts, req, 1);
> +	if (retval != 1)
> +		return -EIO;
> +
> +	return 0;
> +}
> +
> +static int cp_tm1217_unmask_interrupt(struct cp_tm1217_device *ts)
> +{
> +	u8 req[2];
> +	int retval;
> +
> +	req[0] = TMA1217_INTERRUPT_ENABLE;
> +	req[1] = 0xa;
> +	retval = cp_tm1217_write(ts, req, 1);
> +	if (retval != 1)
> +		return -EIO;
> +
> +	return 0;
> +}
> +
> +static void process_touch(struct cp_tm1217_device *ts, int index)
> +{
> +	int retval;
> +	struct input_dev_info *input_info =
> +		(struct input_dev_info *)&ts->cp_input_info[index];
> +	u8 xy_data[6];
> +
> +	if (index == 0)
> +		xy_data[0] = TMA1217_FINGER1_X_HIGHER8;
> +	else
> +		xy_data[0] = TMA1217_FINGER2_X_HIGHER8;
> +
> +	retval = cp_tm1217_read(ts, xy_data, 5);
> +	if (retval < 5) {
> +		dev_err(ts->dev, "cp_tm1217: XY read from device failed\n");
> +		return;
> +	}
> +
> +	/* Note: Currently not using the Z values but may be requried in
> +	   the future. */
> +	input_info->touch.x = (xy_data[1] << 4)
> +					| (xy_data[3] & 0x0F);
> +	input_info->touch.y = (xy_data[2] << 4)
> +					| ((xy_data[3] & 0xF0) >> 4);
> +	input_report_abs(input_info->input, ABS_X, input_info->touch.x);
> +	input_report_abs(input_info->input, ABS_Y, input_info->touch.y);
> +	input_sync(input_info->input);
> +}
> +
> +static void cp_tm1217_get_data(struct cp_tm1217_device *ts)
> +{
> +	u8 req[2];
> +	int retval, i, finger_touched;
> +
> +	do {
> +		req[0] = TMA1217_FINGER_STATE;
> +		retval = cp_tm1217_read(ts, req, 1);
> +		if (retval != 1) {
> +			dev_err(ts->dev,
> +				"cp_tm1217: Read from device failed\n");
> +			continue;
> +		}
> +		finger_touched = 0;
> +		/* Start sampling until the pressure is below
> +		  threshold */
> +		for (i = 0; i < TOUCH_SUPPORTED; i++) {
> +			if (req[1] & 0x3) {
> +				finger_touched++;
> +				if (ts->cp_input_info[i].touch.button == 0) {
> +					/* send the button touch event */
> +					input_report_key(
> +						ts->cp_input_info[i].input,
> +						BTN_TOUCH, 1);
> +					ts->cp_input_info[i].touch.button = 1;
> +				}
> +				process_touch(ts, i);
> +			} else {
> +				if (ts->cp_input_info[i].touch.button == 1) {
> +					/* send the button release event */
> +					input_report_key(
> +						ts->cp_input_info[i].input,
> +						BTN_TOUCH, 0);
> +					ts->cp_input_info[i].touch.button = 0;
> +				}
> +			}
> +			req[1] = req[1] >> 2;
> +		}
> +		msleep(DELAY_BTWIN_SAMPLE);
> +	} while (finger_touched > 0);
> +}
> +
> +static irqreturn_t cp_tm1217_sample_thread(int irq, void *handle)
> +{
> +	struct cp_tm1217_device *ts = (struct cp_tm1217_device *) handle;
> +	u8 req[2];
> +	int retval;
> +
> +	/* Chedk if another thread is already running */

Just how exactly can it ihappen?

> +	mutex_lock(&ts->thread_mutex);
> +	if (ts->thread_running == 1) {
> +		mutex_unlock(&ts->thread_mutex);
> +		return IRQ_HANDLED;
> +	} else {
> +		ts->thread_running = 1;
> +		mutex_unlock(&ts->thread_mutex);
> +	}
> +
> +	/* Mask the interrupts */
> +	retval = cp_tm1217_mask_interrupt(ts);
> +
> +	/* Read the Interrupt Status register to find the cause of the
> +	   Interrupt */
> +	req[0] = TMA1217_INT_STATUS;
> +	retval = cp_tm1217_read(ts, req, 1);
> +	if (retval != 1)
> +		goto exit_thread;
> +
> +	if (!(req[1] & 0x8))
> +		goto exit_thread;
> +
> +	cp_tm1217_get_data(ts);
> +
> +exit_thread:
> +	/* Unmask the interrupts before going to sleep */
> +	retval = cp_tm1217_unmask_interrupt(ts);
> +
> +	mutex_lock(&ts->thread_mutex);
> +	ts->thread_running = 0;
> +	mutex_unlock(&ts->thread_mutex);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int cp_tm1217_init_data(struct cp_tm1217_device *ts)
> +{
> +	int retval;
> +	u8	req[2];
> +
> +	/* Read the vendor id/ fw revision etc. Ignoring return check as this
> +	   is non critical info  */
> +	req[0] = TMA1217_MANUFACTURER_ID;
> +	retval = cp_tm1217_read(ts, req, 1);
> +	ts->vinfo.vendor_id = req[1];
> +
> +	req[0] = TMA1217_PRODUCT_FAMILY;
> +	retval = cp_tm1217_read(ts, req, 1);
> +	ts->vinfo.product_family = req[1];
> +
> +	req[0] = TMA1217_FIRMWARE_REVISION;
> +	retval = cp_tm1217_read(ts, req, 1);
> +	ts->vinfo.firmware_rev = req[1];
> +
> +	req[0] = TMA1217_SERIAL_NO_HIGH;
> +	retval = cp_tm1217_read(ts, req, 1);
> +	ts->vinfo.serial_no = (req[1] << 8);
> +
> +	req[0] = TMA1217_SERIAL_NO_LOW;
> +	retval = cp_tm1217_read(ts, req, 1);
> +	ts->vinfo.serial_no = ts->vinfo.serial_no | req[1];
> +
> +	req[0] = TMA1217_MAX_X_HIGHER4;
> +	retval = cp_tm1217_read(ts, req, 1);
> +	ts->dinfo.maxX = (req[1] & 0xF) << 8;
> +
> +	req[0] = TMA1217_MAX_X_LOWER8;
> +	retval = cp_tm1217_read(ts, req, 1);
> +	ts->dinfo.maxX = ts->dinfo.maxX | req[1];
> +
> +	req[0] = TMA1217_MAX_Y_HIGHER4;
> +	retval = cp_tm1217_read(ts, req, 1);
> +	ts->dinfo.maxY = (req[1] & 0xF) << 8;
> +
> +	req[0] = TMA1217_MAX_Y_LOWER8;
> +	retval = cp_tm1217_read(ts, req, 1);
> +	ts->dinfo.maxY = ts->dinfo.maxY | req[1];
> +
> +	return 0;
> +
> +}
> +
> +/*
> + *	Set up a GPIO for use as the interrupt. We can't simply do this at
> + *	boot time because the GPIO drivers themselves may not be around at
> + *	boot/firmware set up time to do the work. Instead defer it to driver
> + *	detection.
> + */
> +
> +static int cp_tm1217_setup_gpio_irq(struct cp_tm1217_device *ts)
> +{
> +	int retval;
> +
> +	/* Hook up the irq handler */
> +	retval = gpio_request(ts->gpio, "cp_tm1217_touch");
> +	if (retval < 0) {
> +		dev_err(ts->dev, "cp_tm1217: GPIO request failed error %d\n",
> +								retval);
> +		return retval;
> +	}
> +
> +	retval = gpio_direction_input(ts->gpio);
> +	if (retval < 0) {
> +		dev_err(ts->dev,
> +		"cp_tm1217: GPIO direction configuration failed, error %d\n",
> +								retval);
> +		gpio_free(ts->gpio);
> +		return retval;
> +	}
> +
> +	retval = gpio_to_irq(ts->gpio);
> +	if (retval < 0) {
> +		dev_err(ts->dev, "cp_tm1217: GPIO to IRQ failedi,"
> +		" error %d\n", retval);
> +		gpio_free(ts->gpio);
> +	}
> +	dev_dbg(ts->dev,
> +		"cp_tm1217: Got IRQ number is %d for GPIO %d\n",
> +		retval, ts->gpio);
> +	return retval;
> +}
> +
> +static int cp_tm1217_probe(struct i2c_client *client,
> +			const struct i2c_device_id *id)
> +{

__devinit

> +	struct cp_tm1217_device *ts;
> +	struct input_dev *input_dev;
> +	struct input_dev_info	*input_info;
> +	struct cp_tm1217_platform_data *pdata;
> +	u8 req[2];
> +	int i, retval;
> +
> +	/* No pdata is fine - we then use "normal" IRQ mode */
> +
> +	pdata = client->dev.platform_data;
> +
> +	ts = kzalloc(sizeof(struct cp_tm1217_device), GFP_KERNEL);
> +	if (!ts) {
> +		dev_err(ts->dev,
> +			"cp_tm1217: Private Device Struct alloc failed\n");
> +		return -ENOMEM;
> +	}
> +
> +	ts->client = client;
> +	ts->dev = &client->dev;
> +	i2c_set_clientdata(client, ts);
> +
> +	ts->thread_running = 0;
> +	mutex_init(&ts->thread_mutex);
> +
> +	/* Reset the Controller */
> +	req[0] = TMA1217_DEVICE_CMD_RESET;
> +	req[1] = 0x1;
> +	retval = cp_tm1217_write(ts, req, 1);
> +	if (retval != 1) {
> +		dev_err(ts->dev, "cp_tm1217: Controller reset failed\n");
> +		kfree(ts);
> +		return -EIO;

This mixes in-line and out-of line error unwinding, very confusing. Just
goto into error path as you do in the rest of the function.

> +	}
> +
> +	/* Clear up the interrupt status from reset. */
> +	req[0] = TMA1217_INT_STATUS;
> +	retval = cp_tm1217_read(ts, req, 1);
> +
> +	/* Mask all the interrupts */
> +	retval = cp_tm1217_mask_interrupt(ts);
> +
> +	/* Read the controller information */
> +	cp_tm1217_init_data(ts);
> +
> +	/* The following code will register multiple event devices when
> +	   multi-pointer is enabled, the code has not been tested
> +	   with MPX */
> +	for (i = 0; i < TOUCH_SUPPORTED; i++) {
> +		input_dev = input_allocate_device();
> +		if (input_dev == NULL) {
> +			kfree(ts);
> +			dev_err(ts->dev,
> +				"cp_tm1217:Input Device Struct alloc failed\n");
> +			return -ENOMEM;
> +		}
> +		input_info = &ts->cp_input_info[i];
> +		snprintf(input_info->name, sizeof(input_info->name),
> +			"cp_tm1217_touchscreen_%d", i);
> +		input_dev->name = input_info->name;
> +		snprintf(input_info->phys, sizeof(input_info->phys),
> +			"%s/input%d", dev_name(&client->dev), i);
> +
> +		input_dev->phys = input_info->phys;
> +		input_dev->id.bustype = BUS_I2C;
> +
> +		input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
> +		input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
> +
> +		input_set_abs_params(input_dev, ABS_X, 0, ts->dinfo.maxX, 0, 0);
> +		input_set_abs_params(input_dev, ABS_Y, 0, ts->dinfo.maxY, 0, 0);
> +
> +		retval = input_register_device(input_dev);
> +		if (retval) {
> +			dev_err(ts->dev,
> +				"Input dev registration failed for %s\n",
> +					input_dev->name);
> +			goto fail;
> +		}
> +		input_info->input = input_dev;
> +	}
> +
> +	/* Setup the reporting mode to send an interrupt only when
> +	   finger arrives or departs. */
> +	req[0] = TMA1217_REPORT_MODE;
> +	req[1] = 0x02;
> +	retval = cp_tm1217_write(ts, req, 1);

Not acting upon retval?

> +
> +	/* Setup the device to no sleep mode for now and make it configured */
> +	req[0] = TMA1217_DEVICE_CTRL;
> +	req[1] = 0x84;
> +	retval = cp_tm1217_write(ts, req, 1);

Same...

> +
> +	/* Check for the status of the device */
> +	req[0] = TMA1217_DEV_STATUS;
> +	retval = cp_tm1217_read(ts, req, 1);

Same...

> +	if (req[1] != 0) {
> +		dev_err(ts->dev,
> +			"cp_tm1217: Device Status 0x%x != 0: config failed\n",
> +			req[1]);
> +
> +		retval = -EIO;
> +		goto fail;
> +	}
> +
> +	if (pdata && pdata->gpio) {
> +		ts->gpio = pdata->gpio;
> +		retval = cp_tm1217_setup_gpio_irq(ts);
> +	} else
> +		retval = client->irq;
> +
> +	if (retval < 0) {
> +		dev_err(ts->dev, "cp_tm1217: GPIO request failed error %d\n",
> +								retval);
> +		goto fail;
> +	}
> +
> +	client->irq = retval;
> +
> +
> +	retval = request_threaded_irq(client->irq,
> +		NULL, cp_tm1217_sample_thread,
> +		IRQF_TRIGGER_FALLING, "cp_tm1217_touch", ts);
> +	if (retval < 0) {
> +		dev_err(ts->dev, "cp_tm1217: Request IRQ error %d\n", retval);
> +		goto fail_gpio;
> +	}
> +
> +	/* Unmask the interrupts */
> +	retval = cp_tm1217_unmask_interrupt(ts);
> +	if (retval == 0)
> +		return 0;
> +
> +	free_irq(client->irq, ts);
> +fail_gpio:
> +	if (ts->gpio)
> +		gpio_free(ts->gpio);
> +fail:
> +	/* Clean up before returning failure */
> +	for (i = 0; i < TOUCH_SUPPORTED; i++) {
> +		if (ts->cp_input_info[i].input) {
> +			input_unregister_device(ts->cp_input_info[i].input);
> +			input_free_device(ts->cp_input_info[i].input);
> +		}
> +	}
> +	kfree(ts);
> +	return retval;
> +
> +}
> +
> +/*
> + * cp_tm1217 suspend
> + *
> + */

#ifdef CONFIG_PM

> +static int cp_tm1217_suspend(struct i2c_client *client, pm_message_t mesg)
> +{
> +	struct cp_tm1217_device *ts = i2c_get_clientdata(client);
> +	u8 req[2];
> +	int retval;
> +
> +	/* Put the controller to sleep */
> +	req[0] = TMA1217_DEVICE_CTRL;
> +	retval = cp_tm1217_read(ts, req, 1);
> +	req[1] = (req[1] & 0xF8) | 0x1;
> +	retval = cp_tm1217_write(ts, req, 1);
> +
> +	if (device_may_wakeup(&client->dev))
> +		enable_irq_wake(client->irq);
> +
> +	return 0;
> +}
> +
> +/*
> + * cp_tm1217_resume
> + *
> + */
> +static int cp_tm1217_resume(struct i2c_client *client)
> +{
> +	struct cp_tm1217_device *ts = i2c_get_clientdata(client);
> +	u8 req[2];
> +	int retval;
> +
> +	/* Take the controller out of sleep */
> +	req[0] = TMA1217_DEVICE_CTRL;
> +	retval = cp_tm1217_read(ts, req, 1);
> +	req[1] = (req[1] & 0xF8) | 0x4;
> +	retval = cp_tm1217_write(ts, req, 1);
> +
> +	/* Restore the register settings sinc the power to the
> +	   could have been cut off */
> +
> +	/* Setup the reporting mode to send an interrupt only when
> +	   finger arrives or departs. */
> +	req[0] = TMA1217_REPORT_MODE;
> +	req[1] = 0x02;
> +	retval = cp_tm1217_write(ts, req, 1);
> +
> +	/* Setup the device to no sleep mode for now and make it configured */
> +	req[0] = TMA1217_DEVICE_CTRL;
> +	req[1] = 0x84;
> +	retval = cp_tm1217_write(ts, req, 1);
> +
> +	/* Setup the interrupt mask */
> +	retval = cp_tm1217_unmask_interrupt(ts);
> +
> +	if (device_may_wakeup(&client->dev))
> +		disable_irq_wake(client->irq);
> +
> +	return 0;
> +}
> +
> +/*
> + * cp_tm1217_remove
> + *
> + */
> +static int cp_tm1217_remove(struct i2c_client *client)

__devexit

> +{
> +	struct cp_tm1217_device *ts = i2c_get_clientdata(client);
> +	int i;
> +
> +	free_irq(client->irq, ts);
> +	if (ts->gpio)
> +		gpio_free(ts->gpio);
> +	for (i = 0; i < TOUCH_SUPPORTED; i++)
> +		input_unregister_device(ts->cp_input_info[i].input);
> +	kfree(ts);
> +	return 0;
> +}
> +
> +static struct i2c_device_id cp_tm1217_idtable[] = {
> +	{ CPTM1217_DEVICE_NAME, 0 },
> +	{ }
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, cp_tm1217_idtable);
> +
> +static struct i2c_driver cp_tm1217_driver = {
> +	.driver = {
> +		.owner	= THIS_MODULE,
> +		.name	= CPTM1217_DRIVER_NAME,
> +	},
> +	.id_table	= cp_tm1217_idtable,
> +	.probe		= cp_tm1217_probe,
> +	.remove		= cp_tm1217_remove,

__devexit_p

> +	.suspend    = cp_tm1217_suspend,
> +	.resume     = cp_tm1217_resume,
> +};
> +
> +static int __init clearpad_tm1217_init(void)
> +{
> +	return i2c_add_driver(&cp_tm1217_driver);
> +}
> +
> +static void __exit clearpad_tm1217_exit(void)
> +{
> +	i2c_del_driver(&cp_tm1217_driver);
> +}
> +
> +module_init(clearpad_tm1217_init);
> +module_exit(clearpad_tm1217_exit);
> +
> +MODULE_AUTHOR("Ramesh Agarwal <ramesh.agarwal@xxxxxxxxx>");
> +MODULE_DESCRIPTION("Synaptics TM1217 TouchScreen Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/spi/cp_tm1217.h b/include/linux/spi/cp_tm1217.h
> new file mode 100644
> index 0000000..5ab13a8
> --- /dev/null
> +++ b/include/linux/spi/cp_tm1217.h
> @@ -0,0 +1,9 @@
> +#ifndef __LINUX_SPI_CP_TM1217_H
> +#define __LINUX_SPI_CP_TM1217_H
> +
> +struct cp_tm1217_platform_data
> +{
> +	int gpio;		/* If not set uses the IRQ resource 0 */
> +};
> +
> +#endif
> 

-- 
Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux