diff options
Diffstat (limited to 'drivers/input/touchscreen/pixcir_i2c_ts.c')
| -rw-r--r-- | drivers/input/touchscreen/pixcir_i2c_ts.c | 284 | 
1 files changed, 248 insertions, 36 deletions
diff --git a/drivers/input/touchscreen/pixcir_i2c_ts.c b/drivers/input/touchscreen/pixcir_i2c_ts.c index 6cc6b36663f..19c6c0fdc94 100644 --- a/drivers/input/touchscreen/pixcir_i2c_ts.c +++ b/drivers/input/touchscreen/pixcir_i2c_ts.c @@ -24,12 +24,13 @@  #include <linux/i2c.h>  #include <linux/input.h>  #include <linux/input/pixcir_ts.h> +#include <linux/gpio.h>  struct pixcir_i2c_ts_data {  	struct i2c_client *client;  	struct input_dev *input;  	const struct pixcir_ts_platform_data *chip; -	bool exiting; +	bool running;  };  static void pixcir_ts_poscheck(struct pixcir_i2c_ts_data *data) @@ -87,11 +88,12 @@ static void pixcir_ts_poscheck(struct pixcir_i2c_ts_data *data)  static irqreturn_t pixcir_ts_isr(int irq, void *dev_id)  {  	struct pixcir_i2c_ts_data *tsdata = dev_id; +	const struct pixcir_ts_platform_data *pdata = tsdata->chip; -	while (!tsdata->exiting) { +	while (tsdata->running) {  		pixcir_ts_poscheck(tsdata); -		if (tsdata->chip->attb_read_val()) +		if (gpio_get_value(pdata->gpio_attb))  			break;  		msleep(20); @@ -100,25 +102,221 @@ static irqreturn_t pixcir_ts_isr(int irq, void *dev_id)  	return IRQ_HANDLED;  } +static int pixcir_set_power_mode(struct pixcir_i2c_ts_data *ts, +				 enum pixcir_power_mode mode) +{ +	struct device *dev = &ts->client->dev; +	int ret; + +	ret = i2c_smbus_read_byte_data(ts->client, PIXCIR_REG_POWER_MODE); +	if (ret < 0) { +		dev_err(dev, "%s: can't read reg 0x%x : %d\n", +			__func__, PIXCIR_REG_POWER_MODE, ret); +		return ret; +	} + +	ret &= ~PIXCIR_POWER_MODE_MASK; +	ret |= mode; + +	/* Always AUTO_IDLE */ +	ret |= PIXCIR_POWER_ALLOW_IDLE; + +	ret = i2c_smbus_write_byte_data(ts->client, PIXCIR_REG_POWER_MODE, ret); +	if (ret < 0) { +		dev_err(dev, "%s: can't write reg 0x%x : %d\n", +			__func__, PIXCIR_REG_POWER_MODE, ret); +		return ret; +	} + +	return 0; +} + +/* + * Set the interrupt mode for the device i.e. ATTB line behaviour + * + * @polarity : 1 for active high, 0 for active low. + */ +static int pixcir_set_int_mode(struct pixcir_i2c_ts_data *ts, +			       enum pixcir_int_mode mode, bool polarity) +{ +	struct device *dev = &ts->client->dev; +	int ret; + +	ret = i2c_smbus_read_byte_data(ts->client, PIXCIR_REG_INT_MODE); +	if (ret < 0) { +		dev_err(dev, "%s: can't read reg 0x%x : %d\n", +			__func__, PIXCIR_REG_INT_MODE, ret); +		return ret; +	} + +	ret &= ~PIXCIR_INT_MODE_MASK; +	ret |= mode; + +	if (polarity) +		ret |= PIXCIR_INT_POL_HIGH; +	else +		ret &= ~PIXCIR_INT_POL_HIGH; + +	ret = i2c_smbus_write_byte_data(ts->client, PIXCIR_REG_INT_MODE, ret); +	if (ret < 0) { +		dev_err(dev, "%s: can't write reg 0x%x : %d\n", +			__func__, PIXCIR_REG_INT_MODE, ret); +		return ret; +	} + +	return 0; +} + +/* + * Enable/disable interrupt generation + */ +static int pixcir_int_enable(struct pixcir_i2c_ts_data *ts, bool enable) +{ +	struct device *dev = &ts->client->dev; +	int ret; + +	ret = i2c_smbus_read_byte_data(ts->client, PIXCIR_REG_INT_MODE); +	if (ret < 0) { +		dev_err(dev, "%s: can't read reg 0x%x : %d\n", +			__func__, PIXCIR_REG_INT_MODE, ret); +		return ret; +	} + +	if (enable) +		ret |= PIXCIR_INT_ENABLE; +	else +		ret &= ~PIXCIR_INT_ENABLE; + +	ret = i2c_smbus_write_byte_data(ts->client, PIXCIR_REG_INT_MODE, ret); +	if (ret < 0) { +		dev_err(dev, "%s: can't write reg 0x%x : %d\n", +			__func__, PIXCIR_REG_INT_MODE, ret); +		return ret; +	} + +	return 0; +} + +static int pixcir_start(struct pixcir_i2c_ts_data *ts) +{ +	struct device *dev = &ts->client->dev; +	int error; + +	/* LEVEL_TOUCH interrupt with active low polarity */ +	error = pixcir_set_int_mode(ts, PIXCIR_INT_LEVEL_TOUCH, 0); +	if (error) { +		dev_err(dev, "Failed to set interrupt mode: %d\n", error); +		return error; +	} + +	ts->running = true; +	mb();	/* Update status before IRQ can fire */ + +	/* enable interrupt generation */ +	error = pixcir_int_enable(ts, true); +	if (error) { +		dev_err(dev, "Failed to enable interrupt generation: %d\n", +			error); +		return error; +	} + +	return 0; +} + +static int pixcir_stop(struct pixcir_i2c_ts_data *ts) +{ +	int error; + +	/* Disable interrupt generation */ +	error = pixcir_int_enable(ts, false); +	if (error) { +		dev_err(&ts->client->dev, +			"Failed to disable interrupt generation: %d\n", +			error); +		return error; +	} + +	/* Exit ISR if running, no more report parsing */ +	ts->running = false; +	mb();	/* update status before we synchronize irq */ + +	/* Wait till running ISR is complete */ +	synchronize_irq(ts->client->irq); + +	return 0; +} + +static int pixcir_input_open(struct input_dev *dev) +{ +	struct pixcir_i2c_ts_data *ts = input_get_drvdata(dev); + +	return pixcir_start(ts); +} + +static void pixcir_input_close(struct input_dev *dev) +{ +	struct pixcir_i2c_ts_data *ts = input_get_drvdata(dev); + +	pixcir_stop(ts); +} +  #ifdef CONFIG_PM_SLEEP  static int pixcir_i2c_ts_suspend(struct device *dev)  {  	struct i2c_client *client = to_i2c_client(dev); +	struct pixcir_i2c_ts_data *ts = i2c_get_clientdata(client); +	struct input_dev *input = ts->input; +	int ret = 0; + +	mutex_lock(&input->mutex); + +	if (device_may_wakeup(&client->dev)) { +		if (!input->users) { +			ret = pixcir_start(ts); +			if (ret) { +				dev_err(dev, "Failed to start\n"); +				goto unlock; +			} +		} -	if (device_may_wakeup(&client->dev))  		enable_irq_wake(client->irq); +	} else if (input->users) { +		ret = pixcir_stop(ts); +	} -	return 0; +unlock: +	mutex_unlock(&input->mutex); + +	return ret;  }  static int pixcir_i2c_ts_resume(struct device *dev)  {  	struct i2c_client *client = to_i2c_client(dev); +	struct pixcir_i2c_ts_data *ts = i2c_get_clientdata(client); +	struct input_dev *input = ts->input; +	int ret = 0; + +	mutex_lock(&input->mutex); -	if (device_may_wakeup(&client->dev)) +	if (device_may_wakeup(&client->dev)) {  		disable_irq_wake(client->irq); -	return 0; +		if (!input->users) { +			ret = pixcir_stop(ts); +			if (ret) { +				dev_err(dev, "Failed to stop\n"); +				goto unlock; +			} +		} +	} else if (input->users) { +		ret = pixcir_start(ts); +	} + +unlock: +	mutex_unlock(&input->mutex); + +	return ret;  }  #endif @@ -128,7 +326,9 @@ static SIMPLE_DEV_PM_OPS(pixcir_dev_pm_ops,  static int pixcir_i2c_ts_probe(struct i2c_client *client,  					 const struct i2c_device_id *id)  { -	const struct pixcir_ts_platform_data *pdata = client->dev.platform_data; +	const struct pixcir_ts_platform_data *pdata = +			dev_get_platdata(&client->dev); +	struct device *dev = &client->dev;  	struct pixcir_i2c_ts_data *tsdata;  	struct input_dev *input;  	int error; @@ -138,12 +338,19 @@ static int pixcir_i2c_ts_probe(struct i2c_client *client,  		return -EINVAL;  	} -	tsdata = kzalloc(sizeof(*tsdata), GFP_KERNEL); -	input = input_allocate_device(); -	if (!tsdata || !input) { -		dev_err(&client->dev, "Failed to allocate driver data!\n"); -		error = -ENOMEM; -		goto err_free_mem; +	if (!gpio_is_valid(pdata->gpio_attb)) { +		dev_err(dev, "Invalid gpio_attb in pdata\n"); +		return -EINVAL; +	} + +	tsdata = devm_kzalloc(dev, sizeof(*tsdata), GFP_KERNEL); +	if (!tsdata) +		return -ENOMEM; + +	input = devm_input_allocate_device(dev); +	if (!input) { +		dev_err(dev, "Failed to allocate input device\n"); +		return -ENOMEM;  	}  	tsdata->client = client; @@ -152,6 +359,8 @@ static int pixcir_i2c_ts_probe(struct i2c_client *client,  	input->name = client->name;  	input->id.bustype = BUS_I2C; +	input->open = pixcir_input_open; +	input->close = pixcir_input_close;  	input->dev.parent = &client->dev;  	__set_bit(EV_KEY, input->evbit); @@ -164,44 +373,47 @@ static int pixcir_i2c_ts_probe(struct i2c_client *client,  	input_set_drvdata(input, tsdata); -	error = request_threaded_irq(client->irq, NULL, pixcir_ts_isr, -				     IRQF_TRIGGER_FALLING | IRQF_ONESHOT, -				     client->name, tsdata); +	error = devm_gpio_request_one(dev, pdata->gpio_attb, +				      GPIOF_DIR_IN, "pixcir_i2c_attb");  	if (error) { -		dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); -		goto err_free_mem; +		dev_err(dev, "Failed to request ATTB gpio\n"); +		return error;  	} +	error = devm_request_threaded_irq(dev, client->irq, NULL, pixcir_ts_isr, +					  IRQF_TRIGGER_FALLING | IRQF_ONESHOT, +					  client->name, tsdata); +	if (error) { +		dev_err(dev, "failed to request irq %d\n", client->irq); +		return error; +	} + +	/* Always be in IDLE mode to save power, device supports auto wake */ +	error = pixcir_set_power_mode(tsdata, PIXCIR_POWER_IDLE); +	if (error) { +		dev_err(dev, "Failed to set IDLE mode\n"); +		return error; +	} + +	/* Stop device till opened */ +	error = pixcir_stop(tsdata); +	if (error) +		return error; +  	error = input_register_device(input);  	if (error) -		goto err_free_irq; +		return error;  	i2c_set_clientdata(client, tsdata);  	device_init_wakeup(&client->dev, 1);  	return 0; - -err_free_irq: -	free_irq(client->irq, tsdata); -err_free_mem: -	input_free_device(input); -	kfree(tsdata); -	return error;  }  static int pixcir_i2c_ts_remove(struct i2c_client *client)  { -	struct pixcir_i2c_ts_data *tsdata = i2c_get_clientdata(client); -  	device_init_wakeup(&client->dev, 0); -	tsdata->exiting = true; -	mb(); -	free_irq(client->irq, tsdata); - -	input_unregister_device(tsdata->input); -	kfree(tsdata); -  	return 0;  }  | 
