diff options
Diffstat (limited to 'drivers/input/touchscreen/ili210x.c')
| -rw-r--r-- | drivers/input/touchscreen/ili210x.c | 360 | 
1 files changed, 360 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/ili210x.c b/drivers/input/touchscreen/ili210x.c new file mode 100644 index 00000000000..2a508913981 --- /dev/null +++ b/drivers/input/touchscreen/ili210x.c @@ -0,0 +1,360 @@ +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/input/ili210x.h> + +#define MAX_TOUCHES		2 +#define DEFAULT_POLL_PERIOD	20 + +/* Touchscreen commands */ +#define REG_TOUCHDATA		0x10 +#define REG_PANEL_INFO		0x20 +#define REG_FIRMWARE_VERSION	0x40 +#define REG_CALIBRATE		0xcc + +struct finger { +	u8 x_low; +	u8 x_high; +	u8 y_low; +	u8 y_high; +} __packed; + +struct touchdata { +	u8 status; +	struct finger finger[MAX_TOUCHES]; +} __packed; + +struct panel_info { +	struct finger finger_max; +	u8 xchannel_num; +	u8 ychannel_num; +} __packed; + +struct firmware_version { +	u8 id; +	u8 major; +	u8 minor; +} __packed; + +struct ili210x { +	struct i2c_client *client; +	struct input_dev *input; +	bool (*get_pendown_state)(void); +	unsigned int poll_period; +	struct delayed_work dwork; +}; + +static int ili210x_read_reg(struct i2c_client *client, u8 reg, void *buf, +			    size_t len) +{ +	struct i2c_msg msg[2] = { +		{ +			.addr	= client->addr, +			.flags	= 0, +			.len	= 1, +			.buf	= ®, +		}, +		{ +			.addr	= client->addr, +			.flags	= I2C_M_RD, +			.len	= len, +			.buf	= buf, +		} +	}; + +	if (i2c_transfer(client->adapter, msg, 2) != 2) { +		dev_err(&client->dev, "i2c transfer failed\n"); +		return -EIO; +	} + +	return 0; +} + +static void ili210x_report_events(struct input_dev *input, +				  const struct touchdata *touchdata) +{ +	int i; +	bool touch; +	unsigned int x, y; +	const struct finger *finger; + +	for (i = 0; i < MAX_TOUCHES; i++) { +		input_mt_slot(input, i); + +		finger = &touchdata->finger[i]; + +		touch = touchdata->status & (1 << i); +		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch); +		if (touch) { +			x = finger->x_low | (finger->x_high << 8); +			y = finger->y_low | (finger->y_high << 8); + +			input_report_abs(input, ABS_MT_POSITION_X, x); +			input_report_abs(input, ABS_MT_POSITION_Y, y); +		} +	} + +	input_mt_report_pointer_emulation(input, false); +	input_sync(input); +} + +static bool get_pendown_state(const struct ili210x *priv) +{ +	bool state = false; + +	if (priv->get_pendown_state) +		state = priv->get_pendown_state(); + +	return state; +} + +static void ili210x_work(struct work_struct *work) +{ +	struct ili210x *priv = container_of(work, struct ili210x, +					    dwork.work); +	struct i2c_client *client = priv->client; +	struct touchdata touchdata; +	int error; + +	error = ili210x_read_reg(client, REG_TOUCHDATA, +				 &touchdata, sizeof(touchdata)); +	if (error) { +		dev_err(&client->dev, +			"Unable to get touchdata, err = %d\n", error); +		return; +	} + +	ili210x_report_events(priv->input, &touchdata); + +	if ((touchdata.status & 0xf3) || get_pendown_state(priv)) +		schedule_delayed_work(&priv->dwork, +				      msecs_to_jiffies(priv->poll_period)); +} + +static irqreturn_t ili210x_irq(int irq, void *irq_data) +{ +	struct ili210x *priv = irq_data; + +	schedule_delayed_work(&priv->dwork, 0); + +	return IRQ_HANDLED; +} + +static ssize_t ili210x_calibrate(struct device *dev, +				 struct device_attribute *attr, +				 const char *buf, size_t count) +{ +	struct i2c_client *client = to_i2c_client(dev); +	struct ili210x *priv = i2c_get_clientdata(client); +	unsigned long calibrate; +	int rc; +	u8 cmd = REG_CALIBRATE; + +	if (kstrtoul(buf, 10, &calibrate)) +		return -EINVAL; + +	if (calibrate > 1) +		return -EINVAL; + +	if (calibrate) { +		rc = i2c_master_send(priv->client, &cmd, sizeof(cmd)); +		if (rc != sizeof(cmd)) +			return -EIO; +	} + +	return count; +} +static DEVICE_ATTR(calibrate, 0644, NULL, ili210x_calibrate); + +static struct attribute *ili210x_attributes[] = { +	&dev_attr_calibrate.attr, +	NULL, +}; + +static const struct attribute_group ili210x_attr_group = { +	.attrs = ili210x_attributes, +}; + +static int ili210x_i2c_probe(struct i2c_client *client, +				       const struct i2c_device_id *id) +{ +	struct device *dev = &client->dev; +	const struct ili210x_platform_data *pdata = dev_get_platdata(dev); +	struct ili210x *priv; +	struct input_dev *input; +	struct panel_info panel; +	struct firmware_version firmware; +	int xmax, ymax; +	int error; + +	dev_dbg(dev, "Probing for ILI210X I2C Touschreen driver"); + +	if (!pdata) { +		dev_err(dev, "No platform data!\n"); +		return -EINVAL; +	} + +	if (client->irq <= 0) { +		dev_err(dev, "No IRQ!\n"); +		return -EINVAL; +	} + +	/* Get firmware version */ +	error = ili210x_read_reg(client, REG_FIRMWARE_VERSION, +				 &firmware, sizeof(firmware)); +	if (error) { +		dev_err(dev, "Failed to get firmware version, err: %d\n", +			error); +		return error; +	} + +	/* get panel info */ +	error = ili210x_read_reg(client, REG_PANEL_INFO, &panel, sizeof(panel)); +	if (error) { +		dev_err(dev, "Failed to get panel informations, err: %d\n", +			error); +		return error; +	} + +	xmax = panel.finger_max.x_low | (panel.finger_max.x_high << 8); +	ymax = panel.finger_max.y_low | (panel.finger_max.y_high << 8); + +	priv = kzalloc(sizeof(*priv), GFP_KERNEL); +	input = input_allocate_device(); +	if (!priv || !input) { +		error = -ENOMEM; +		goto err_free_mem; +	} + +	priv->client = client; +	priv->input = input; +	priv->get_pendown_state = pdata->get_pendown_state; +	priv->poll_period = pdata->poll_period ? : DEFAULT_POLL_PERIOD; +	INIT_DELAYED_WORK(&priv->dwork, ili210x_work); + +	/* Setup input device */ +	input->name = "ILI210x Touchscreen"; +	input->id.bustype = BUS_I2C; +	input->dev.parent = dev; + +	__set_bit(EV_SYN, input->evbit); +	__set_bit(EV_KEY, input->evbit); +	__set_bit(EV_ABS, input->evbit); +	__set_bit(BTN_TOUCH, input->keybit); + +	/* Single touch */ +	input_set_abs_params(input, ABS_X, 0, xmax, 0, 0); +	input_set_abs_params(input, ABS_Y, 0, ymax, 0, 0); + +	/* Multi touch */ +	input_mt_init_slots(input, MAX_TOUCHES, 0); +	input_set_abs_params(input, ABS_MT_POSITION_X, 0, xmax, 0, 0); +	input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ymax, 0, 0); + +	input_set_drvdata(input, priv); +	i2c_set_clientdata(client, priv); + +	error = request_irq(client->irq, ili210x_irq, pdata->irq_flags, +			    client->name, priv); +	if (error) { +		dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n", +			error); +		goto err_free_mem; +	} + +	error = sysfs_create_group(&dev->kobj, &ili210x_attr_group); +	if (error) { +		dev_err(dev, "Unable to create sysfs attributes, err: %d\n", +			error); +		goto err_free_irq; +	} + +	error = input_register_device(priv->input); +	if (error) { +		dev_err(dev, "Cannot regiser input device, err: %d\n", error); +		goto err_remove_sysfs; +	} + +	device_init_wakeup(&client->dev, 1); + +	dev_dbg(dev, +		"ILI210x initialized (IRQ: %d), firmware version %d.%d.%d", +		client->irq, firmware.id, firmware.major, firmware.minor); + +	return 0; + +err_remove_sysfs: +	sysfs_remove_group(&dev->kobj, &ili210x_attr_group); +err_free_irq: +	free_irq(client->irq, priv); +err_free_mem: +	input_free_device(input); +	kfree(priv); +	return error; +} + +static int ili210x_i2c_remove(struct i2c_client *client) +{ +	struct ili210x *priv = i2c_get_clientdata(client); + +	sysfs_remove_group(&client->dev.kobj, &ili210x_attr_group); +	free_irq(priv->client->irq, priv); +	cancel_delayed_work_sync(&priv->dwork); +	input_unregister_device(priv->input); +	kfree(priv); + +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int ili210x_i2c_suspend(struct device *dev) +{ +	struct i2c_client *client = to_i2c_client(dev); + +	if (device_may_wakeup(&client->dev)) +		enable_irq_wake(client->irq); + +	return 0; +} + +static int ili210x_i2c_resume(struct device *dev) +{ +	struct i2c_client *client = to_i2c_client(dev); + +	if (device_may_wakeup(&client->dev)) +		disable_irq_wake(client->irq); + +	return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(ili210x_i2c_pm, +			 ili210x_i2c_suspend, ili210x_i2c_resume); + +static const struct i2c_device_id ili210x_i2c_id[] = { +	{ "ili210x", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, ili210x_i2c_id); + +static struct i2c_driver ili210x_ts_driver = { +	.driver = { +		.name = "ili210x_i2c", +		.owner = THIS_MODULE, +		.pm = &ili210x_i2c_pm, +	}, +	.id_table = ili210x_i2c_id, +	.probe = ili210x_i2c_probe, +	.remove = ili210x_i2c_remove, +}; + +module_i2c_driver(ili210x_ts_driver); + +MODULE_AUTHOR("Olivier Sobrie <olivier@sobrie.be>"); +MODULE_DESCRIPTION("ILI210X I2C Touchscreen Driver"); +MODULE_LICENSE("GPL");  | 
