Message ID | 1452671352-792-1-git-send-email-jeffrey.lin@rad-ic.com (mailing list archive) |
---|---|
State | Changes Requested |
Headers | show |
Jeffrey, On Tue, Jan 12, 2016 at 11:49 PM, Jeffrey Lin <yajohn@gmail.com> wrote: > This patch is porting Raydium I2C touch driver. Developer can enable raydium touch driver by modifying define > "CONFIG_TOUCHSCREEN_RM_TS". > > Signed-off-by: jeffrey.lin<jeffrey.lin@rad-ic.com> > --- > drivers/input/touchscreen/Kconfig | 12 + > drivers/input/touchscreen/Makefile | 1 + > drivers/input/touchscreen/rm31100_ts.c | 772 +++++++++++++++++++++++++++++++++ > 3 files changed, 785 insertions(+) > create mode 100644 drivers/input/touchscreen/rm31100_ts.c Thank you for sending the full patch. Unfortunately it does not even compile: dtor@dtor-ws:~/kernel/work$ make drivers/input/touchscreen/rm31100_ts.o CHK include/config/kernel.release CHK include/generated/uapi/linux/version.h CHK include/generated/utsrelease.h CHK include/generated/bounds.h CHK include/generated/timeconst.h CHK include/generated/asm-offsets.h CALL scripts/checksyscalls.sh CC [M] drivers/input/touchscreen/rm31100_ts.o drivers/input/touchscreen/rm31100_ts.c: In function ‘rm_fw_version_show’: drivers/input/touchscreen/rm31100_ts.c:190:7: error: request for member ‘u8_version’ in something not a structure or union info.u8_version, ^ drivers/input/touchscreen/rm31100_ts.c:191:7: error: request for member ‘u8_sub_version’ in something not a structure or union info.u8_sub_version); ^ drivers/input/touchscreen/rm31100_ts.c: In function ‘rm_ts_power_on’: drivers/input/touchscreen/rm31100_ts.c:304:2: error: implicit declaration of function ‘regulator_enable’ [-Werror=implicit-function-declaration] error = regulator_enable(ts->avdd); ^ drivers/input/touchscreen/rm31100_ts.c:317:3: error: implicit declaration of function ‘regulator_disable’ [-Werror=implicit-function-declaration] regulator_disable(ts->dvdd); ^ drivers/input/touchscreen/rm31100_ts.c: In function ‘rm31100_ts_suspend’: drivers/input/touchscreen/rm31100_ts.c:392:6: warning: unused variable ‘rc’ [-Wunused-variable] int rc = 0; ^ drivers/input/touchscreen/rm31100_ts.c: In function ‘rm31100_ts_probe’: drivers/input/touchscreen/rm31100_ts.c:581:2: error: implicit declaration of function ‘devm_regulator_get’ [-Werror=implicit-function-declaration] ts->avdd = devm_regulator_get(&client->dev, "avdd"); ^ drivers/input/touchscreen/rm31100_ts.c:581:11: warning: assignment makes pointer from integer without a cast [enabled by default] ts->avdd = devm_regulator_get(&client->dev, "avdd"); ^ drivers/input/touchscreen/rm31100_ts.c:591:11: warning: assignment makes pointer from integer without a cast [enabled by default] ts->dvdd = devm_regulator_get(&client->dev, "dvdd"); ^ drivers/input/touchscreen/rm31100_ts.c:601:2: error: too few arguments to function ‘devm_gpiod_get’ ts->resout_gpio = devm_gpiod_get(&client->dev, "rm31100_resout_gpio"); ^ In file included from drivers/input/touchscreen/rm31100_ts.c:31:0: include/linux/gpio/consumer.h:73:32: note: declared here struct gpio_desc *__must_check devm_gpiod_get(struct device *dev, ^ drivers/input/touchscreen/rm31100_ts.c:664:61: error: ‘err’ undeclared (first use in this function) dev_err(&client->dev, "%s crated failed, %d\n", __func__, err); ^ drivers/input/touchscreen/rm31100_ts.c:664:61: note: each undeclared identifier is reported only once for each function it appears in drivers/input/touchscreen/rm31100_ts.c: In function ‘rm31100_ts_init’: drivers/input/touchscreen/rm31100_ts.c:754:4: error: ‘driver_attr_myAttr’ undeclared (first use in this function) &driver_attr_myAttr); ^ drivers/input/touchscreen/rm31100_ts.c: At top level: drivers/input/touchscreen/rm31100_ts.c:172:12: warning: ‘rm31100_ts_write’ defined but not used [-Wunused-function] static int rm31100_ts_write(struct i2c_client *client, u8 *buf, int num) ^ drivers/input/touchscreen/rm31100_ts.c: In function ‘rm_fw_version_show’: drivers/input/touchscreen/rm31100_ts.c:192:1: warning: control reaches end of non-void function [-Wreturn-type] } ^ cc1: some warnings being treated as errors make[1]: *** [drivers/input/touchscreen/rm31100_ts.o] Error 1 make: *** [drivers/input/touchscreen/rm31100_ts.o] Error 2 dtor@dtor-ws:~/kernel/work$ Please next time submit version that you actually tested. Thank you.
Hi Jeffrey, On Wed, Jan 13, 2016 at 03:49:12PM +0800, Jeffrey Lin wrote: > This patch is porting Raydium I2C touch driver. Developer can enable raydium touch driver by modifying define > "CONFIG_TOUCHSCREEN_RM_TS". Doing a quick pass over the driver to give you some more feedback for the next version. > > Signed-off-by: jeffrey.lin<jeffrey.lin@rad-ic.com> > --- > drivers/input/touchscreen/Kconfig | 12 + > drivers/input/touchscreen/Makefile | 1 + > drivers/input/touchscreen/rm31100_ts.c | 772 +++++++++++++++++++++++++++++++++ > 3 files changed, 785 insertions(+) > create mode 100644 drivers/input/touchscreen/rm31100_ts.c > > diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig > index 0f13e52..4790e36 100644 > --- a/drivers/input/touchscreen/Kconfig > +++ b/drivers/input/touchscreen/Kconfig > @@ -955,4 +955,16 @@ config TOUCHSCREEN_ZFORCE > To compile this driver as a module, choose M here: the > module will be called zforce_ts. > > +config TOUCHSCREEN_RM_TS > + tristate "Raydium I2C Touchscreen" > + depends on I2C > + help > + Say Y here if you have Raydium series I2C touchscreen, > + such as RM31100 , connected to your system. > + > + If unsure, say N. > + > + To compile this driver as a module, choose M here: the > + module will be called rm31100_ts. > + > endif > diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile > index 687d5a7..3220f66 100644 > --- a/drivers/input/touchscreen/Makefile > +++ b/drivers/input/touchscreen/Makefile > @@ -78,3 +78,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o > obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o > obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o > obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o > +obj-$(CONFIG_TOUCHSCREEN_RM_TS) += rm31100_ts.o > diff --git a/drivers/input/touchscreen/rm31100_ts.c b/drivers/input/touchscreen/rm31100_ts.c > new file mode 100644 > index 0000000..e024bbd > --- /dev/null > +++ b/drivers/input/touchscreen/rm31100_ts.c > @@ -0,0 +1,772 @@ > +/* > + * Raydium RM31100_ts touchscreen driver. > + * > + * Copyright (C) 2012-2014, Raydium Semiconductor Corporation. > + * > + * 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. > + * > + * Raydium reserves the right to make changes without further notice > + * to the materials described herein. Raydium does not assume any > + * liability arising out of the application described herein. > + * > + * Contact Raydium Semiconductor Corporation at www.rad-ic.com > + * > + */ > +#include <linux/async.h> > +#include <linux/init.h> > +#include <linux/module.h> > +#include <linux/i2c.h> > +#include <linux/input.h> > +#include <linux/slab.h> > +#include <linux/interrupt.h> > +#include <linux/irq.h> > +#include <linux/gpio/consumer.h> > +#include <linux/mutex.h> > +#include <linux/delay.h> > +#include <linux/pm.h> > +#include <linux/pm_runtime.h> > +#include <linux/uaccess.h> > +#include <asm/unaligned.h> > +#include <linux/input/mt.h> > + > +#define rm31100 0x0 > +#define rm3110x 0x1 > + > +#define INVALID_DATA 0xff > +#define MAX_REPORT_TOUCHED_POINTS 10 > + > +#define I2C_CLIENT_ADDR 0x39 > +#define I2C_DMA_CLIENT_ADDR 0x5A > + > +struct rm31100_ts_data { > + u8 x_index; > + u8 y_index; > + u8 z_index; > + u8 id_index; > + u8 touch_index; > + u8 data_reg; > + u8 status_reg; > + u8 data_size; > + u8 touch_bytes; > + u8 update_data; > + u8 touch_meta_data; > + u8 finger_size; > +}; > + > +struct rm3110x_ts_platform_data { > + u32 dis_min_x; /* display resoltion ABS min*/ > + u32 dis_max_x;/* display resoltion ABS max*/ > + u32 dis_min_y; > + u32 dis_max_y; > + u32 res_x; /* TS resolution unit*/ > + u32 res_y; > + u32 swap_xy; > + u8 nfingers; > + bool wakeup; > +}; > + > +static struct rm31100_ts_data devices[] = { > + [0] = { > + .x_index = 2, > + .y_index = 4, > + .z_index = 6, > + .id_index = 1, > + .data_reg = 0x1, > + .status_reg = 0, > + .update_data = 0x0, > + .touch_bytes = 6, > + .touch_meta_data = 1, > + .finger_size = 70, > + }, > + [1] = { > + .x_index = 2, > + .y_index = 4, > + .z_index = 6, > + .id_index = 1, > + .data_reg = 0x0, > + .status_reg = 0, > + .update_data = 0x0, > + .touch_bytes = 6, > + .touch_meta_data = 1, > + .finger_size = 70, > + }, > +}; > + > +struct rm31100_ts { > + struct i2c_client *client; > + struct input_dev *input; > + struct regulator *dvdd; > + struct regulator *avdd; > + struct gpio_desc *resout_gpio; > + struct rm3110x_ts_platform_data *pdata; > + struct rm31100_ts_data *dd; > + u8 *touch_data; > + u8 device_id; > + u8 prev_touches; > + bool is_suspended; > + bool int_pending; > + struct mutex access_lock; > + u32 pen_irq; > + u8 fw_version; > + u8 u8_sub_version; > +}; > + > +/* > +static inline u16 join_bytes(u8 a, u8 b) > +{ > + u16 ab = 0; > + ab = ab | a; > + ab = ab << 8 | b; > + return ab; I do not see this being used in the driver (and if you needed something like that you probably would need get_unaligned_le16() or get_unaligned-be16()). > +} > +*/ > +static s32 rm31100_ts_write_reg_u8(struct i2c_client *client, u8 reg, u8 val) Why s32? Just return "int". > +{ > + s32 data; > + > + data = i2c_smbus_write_byte_data(client, reg, val); > + if (data < 0) > + dev_err(&client->dev, "error %d in writing reg 0x%x\n", > + data, reg); > + > + return data; > +} > + > +static s32 rm31100_ts_read_reg_u8(struct i2c_client *client, u8 reg) Same here. > +{ > + s32 data; > + > + data = i2c_smbus_read_byte_data(client, reg); > + if (data < 0) > + dev_err(&client->dev, "error %d in reading reg 0x%x\n", > + data, reg); > + > + return data; > +} > + > +static int rm31100_ts_read(struct i2c_client *client, u8 reg, u8 *buf, int num) > +{ > + struct i2c_msg xfer_msg[2]; > + > + xfer_msg[0].addr = client->addr; > + xfer_msg[0].len = 1; > + xfer_msg[0].flags = 0; > + xfer_msg[0].buf = ® > + > + xfer_msg[1].addr = client->addr; > + xfer_msg[1].len = num; > + xfer_msg[1].flags = I2C_M_RD; > + xfer_msg[1].buf = buf; > + > + return i2c_transfer(client->adapter, xfer_msg, 2); ARRAY_SIZE(xfer_msg). > +} > + > +static int rm31100_ts_write(struct i2c_client *client, u8 *buf, int num) > +{ > + struct i2c_msg xfer_msg[1]; > + > + xfer_msg[0].addr = client->addr; > + xfer_msg[0].len = num; > + xfer_msg[0].flags = 0; > + xfer_msg[0].buf = buf; > + > + return i2c_transfer(client->adapter, xfer_msg, 1); > +} > + > +static ssize_t rm_fw_version_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct rm31100_ts *info = dev_get_drvdata(dev); > + return sprintf(buf, "Release V 0x%02X, Test V 0x%02X\n", > + info.u8_version, > + info.u8_sub_version); > +} We should have 1 value per sysfs attribute, so please split it on 2: firmware version and test or subversion and output simply number. This way it is much easier to process them in scripts. > + > +static DEVICE_ATTR(fw_version, S_IRUGO, rm_fw_version_show, NULL); > + > +static struct attribute *rm_ts_attributes[] = { > + &dev_attr_fw_version.attr, > + NULL > +}; > + > +static const struct attribute_group rm_ts_attr_group = { > + .attrs = rm_ts_attributes, > +}; > + > +static void report_data(struct rm31100_ts *dev, u16 x, u16 y, > + u8 pressure, u8 id) > +{ > + struct input_dev *input_dev = dev->input; > + if (dev->pdata->swap_xy) > + swap(x, y); > + > + input_mt_slot(input_dev, id); > + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, true); > + input_report_abs(input_dev, ABS_MT_POSITION_X, x); > + input_report_abs(input_dev, ABS_MT_POSITION_Y, y); > + input_report_abs(input_dev, ABS_MT_PRESSURE, pressure); > + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, dev->dd->finger_size); > +} > + > +static void process_rm31100_data(struct rm31100_ts *ts) > +{ > + u8 id, pressure, touches, i; > + u16 x, y; > + > + touches = ts->touch_data[ts->dd->touch_index]; > + > + if (touches > 0) { > + for (i = 0; i < touches; i++) { > + id = ts->touch_data[i * ts->dd->touch_bytes + > + ts->dd->id_index]; > + pressure = ts->touch_data[i * ts->dd->touch_bytes + > + ts->dd->z_index]; > + x = get_unaligned_le16(&(ts->touch_data[i * > + ts->dd->touch_bytes + ts->dd->x_index])); > + y = get_unaligned_le16(&(ts->touch_data[i * > + ts->dd->touch_bytes + ts->dd->y_index])); > + report_data(ts, x, y, pressure, id); > + } > + } else > + input_mt_sync(ts->input); > + > + ts->prev_touches = touches; Why do you need prev_touches? I do not see you reporting touch lifting the surface, do you need to also pass INPUT_MT_DROP_UNUSED flag to input_mt_init_slots()? > + > + input_mt_report_pointer_emulation(ts->input, true); > + input_sync(ts->input); > +} > + > +static void rm31100_ts_xy_worker(struct rm31100_ts *work) > +{ > + int rc; > + struct rm31100_ts *ts = work; > + > + mutex_lock(&ts->access_lock); Why do we need to take the lock here? What are we trying to protect? The interrupt thread is not reentrable. > + /* read data from DATA_REG */ > + rc = rm31100_ts_read(ts->client, ts->dd->data_reg, ts->touch_data, > + ts->dd->data_size); > + > + if (rc < 0) { > + dev_err(&ts->client->dev, "read failed\n"); > + goto schedule; > + } > + > + if (ts->touch_data[ts->dd->touch_index] == INVALID_DATA) > + goto schedule; > + > + /* write to STATUS_REG to release lock */ > + rc = rm31100_ts_write_reg_u8(ts->client, > + ts->dd->status_reg, ts->dd->update_data); > + if (rc < 0) { > + dev_err(&ts->client->dev, "write failed, try once more\n"); > + > + rc = rm31100_ts_write_reg_u8(ts->client, > + ts->dd->status_reg, ts->dd->update_data); > + if (rc < 0) > + dev_err(&ts->client->dev, "write failed, exiting\n"); > + } > + > + process_rm31100_data(ts); > +schedule: > + mutex_unlock(&ts->access_lock); > +} > + > +static irqreturn_t rm31100_ts_irq(int irq, void *dev_id) > +{ > + struct rm31100_ts *ts = dev_id; > + > + rm31100_ts_xy_worker(ts); > + return IRQ_HANDLED; I'd probably merge rm31100_ts_xy_worker() into rm31100_ts_irq(). > +} > + > +static int rm_ts_power_on(struct rm31100_ts *ts) > +{ > + int error; > + > + /* > + * If we do not have reset gpio assume platform firmware > + * controls regulators and does power them on for us. > + */ > + if (IS_ERR_OR_NULL(ts->resout_gpio)) > + return 0; > + > + gpiod_set_value_cansleep(ts->resout_gpio, 1); > + > + error = regulator_enable(ts->avdd); > + if (error) { > + dev_err(&ts->client->dev, > + "failed to enable avdd regulator: %d\n", > + error); > + goto release_reset_gpio; > + } > + > + error = regulator_enable(ts->dvdd); > + if (error) { > + dev_err(&ts->client->dev, > + "failed to enable dvdd regulator: %d\n", > + error); > + regulator_disable(ts->dvdd); > + goto release_reset_gpio; > + } > + > +release_reset_gpio: > + gpiod_set_value_cansleep(ts->resout_gpio, 0); > + if (error) > + return error; > + > + msleep(20); > + > + return 0; > +} > + > +static void rm_ts_power_off(void *_data) > +{ > + struct rm31100_ts *data = _data; > + > + if (!IS_ERR_OR_NULL(data->resout_gpio)) { > + /* > + * Activate reset gpio to prevent leakage through the > + * pin once we shut off power to the controller. > + */ > + gpiod_set_value_cansleep(data->resout_gpio, 1); > + regulator_disable(data->avdd); > + regulator_disable(data->dvdd); > + } > +} > + > +static int rm31100_ts_init_ts(struct i2c_client *client, struct rm31100_ts *ts) > +{ > + /*struct input_dev *input_device;*/ > + /*int rc = 0;*/ > + > + ts->dd = &devices[ts->device_id]; > + > + if (!ts->pdata->nfingers) { > + dev_err(&client->dev, "Touches information not specified\n"); > + return -EINVAL; > + } > + > + if (ts->device_id == rm3110x) { > + if (ts->pdata->nfingers > 2) { > + dev_err(&client->dev, "Touches >=1 & <= 2\n"); > + return -EINVAL; > + } > + ts->dd->data_size = ts->dd->touch_bytes; > + ts->dd->touch_index = 0x0; > + } else if (ts->device_id == rm31100) { > + if (ts->pdata->nfingers > 10) { > + dev_err(&client->dev, "Touches >=1 & <= 10\n"); > + return -EINVAL; > + } > + ts->dd->data_size = ts->pdata->nfingers * ts->dd->touch_bytes + > + ts->dd->touch_meta_data; > + ts->dd->touch_index = 0x0; > + } > + /* w001 */ > + else { > + ts->dd->data_size = ts->pdata->nfingers * ts->dd->touch_bytes + > + ts->dd->touch_meta_data; > + ts->dd->touch_index = 0x0; > + } > + > + ts->touch_data = kzalloc(ts->dd->data_size, GFP_KERNEL); > + if (!ts->touch_data) { > + pr_err("%s: Unable to allocate memory\n", __func__); > + return -ENOMEM; > + } > + return 0; > +} > + > +static int __maybe_unused rm31100_ts_suspend(struct device *dev) > +{ > + struct rm31100_ts *ts = dev_get_drvdata(dev); > + int rc = 0; > + > + if (device_may_wakeup(dev)) { > + /* mark suspend flag */ > + ts->is_suspended = true; > + enable_irq_wake(ts->pen_irq); > + } > + > + disable_irq(ts->pen_irq); > + > + gpiod_set_value_cansleep(ts->resout_gpio, 0); Why do we set it as "inactive" only to set it to "active" in rm_ts_power_off()? > + > + rm_ts_power_off(ts); > + > + return 0; > +} > + > +static int __maybe_unused rm31100_ts_resume(struct device *dev) > +{ > + struct rm31100_ts *ts = dev_get_drvdata(dev); > + > + int rc = 0; > + > + if (device_may_wakeup(dev)) { > + disable_irq_wake(ts->pen_irq); > + > + ts->is_suspended = false; > + > + if (ts->int_pending == true) > + ts->int_pending = false; Why do you need this? > + } else { > + rc = rm_ts_power_on(ts); > + if (rc) { > + dev_err(dev, "unable to resume\n"); > + return rc; > + } > + > + enable_irq(ts->pen_irq); > + > + /* Clear the status register of the TS controller */ > + rc = rm31100_ts_write_reg_u8(ts->client, > + ts->dd->status_reg, ts->dd->update_data); > + if (rc < 0) { > + dev_err(&ts->client->dev, > + "write failed, try once more\n"); > + > + rc = rm31100_ts_write_reg_u8(ts->client, > + ts->dd->status_reg, > + ts->dd->update_data); > + if (rc < 0) > + dev_err(&ts->client->dev, > + "write failed, exiting\n"); > + } > + } > + > + return 0; > +} > + > +static void rm_ts_remove_sysfs_group(void *_data) > +{ > + struct rm31100_ts *ts = _data; > + > + sysfs_remove_group(&ts->client->dev.kobj, &rm_ts_attr_group); > +} > + > +static SIMPLE_DEV_PM_OPS(rm31100_ts_pm_ops, > + rm31100_ts_suspend, rm31100_ts_resume); > + > +static int rm_input_dev_create(struct rm31100_ts *ts) > +{ > + struct input_dev *input_device; > + int rc = 0; > + ts->prev_touches = 0; > + > + input_device = input_allocate_device(); > + if (!input_device) { > + rc = -ENOMEM; > + goto error_alloc_dev; > + } > + ts->input = input_device; > + > + input_device->name = "raydium_ts"; > + input_device->id.bustype = BUS_I2C; > + input_device->dev.parent = &ts->client->dev; > + input_set_drvdata(input_device, ts); > + > + __set_bit(BTN_TOUCH, input_device->keybit); > + __set_bit(EV_ABS, input_device->evbit); > + __set_bit(EV_KEY, input_device->evbit); > + > + /* For single touch */ > + input_set_abs_params(input_device, ABS_X, > + ts->pdata->dis_min_x, ts->pdata->dis_max_x, 0, 0); > + input_set_abs_params(input_device, ABS_Y, > + ts->pdata->dis_min_x, ts->pdata->dis_max_y, 0, 0); > + input_set_abs_params(input_device, ABS_PRESSURE, > + 0, 255, 0, 0); > + input_abs_set_res(input_device, ABS_X, ts->pdata->res_x); > + input_abs_set_res(input_device, ABS_Y, ts->pdata->res_y); > + > + /* Multitouch input params setup */ > + rc = input_mt_init_slots(input_device, > + MAX_REPORT_TOUCHED_POINTS, INPUT_MT_DIRECT); > + if (rc) > + goto error_unreg_device; > + > + input_set_abs_params(input_device, ABS_MT_POSITION_X, > + ts->pdata->dis_min_x, ts->pdata->dis_max_x, 0, 0); > + input_set_abs_params(input_device, ABS_MT_POSITION_Y, > + ts->pdata->dis_min_y, ts->pdata->dis_max_y, 0, 0); > + input_set_abs_params(input_device, ABS_MT_PRESSURE, > + 0, 0xFF, 0, 0); > + input_set_abs_params(input_device, ABS_MT_TOUCH_MAJOR, > + 0, 0xFF, 0, 0); > + input_abs_set_res(input_device, ABS_MT_POSITION_X, ts->pdata->res_x); > + input_abs_set_res(input_device, ABS_MT_POSITION_Y, ts->pdata->res_y); > + > + rc = input_register_device(input_device); > + if (rc) > + goto error_unreg_device; > + > + return 0; > + > +error_unreg_device: > + input_free_device(input_device); > +error_alloc_dev: > + ts->input = NULL; > + return rc; > +} > + > +static int rm31100_initialize(struct i2c_client *client) > +{ > + struct rm31100_ts *ts = i2c_get_clientdata(client); > + int rc = 0, temp_reg; > + > + /* read one byte to make sure i2c device exists */ > + if (ts->device_id == rm3110x) > + temp_reg = 0x01; > + else if (ts->device_id == rm31100) > + temp_reg = 0x00; > + else > + temp_reg = 0x05; > + > + rc = rm31100_ts_read_reg_u8(client, temp_reg); > + if (rc < 0) { > + dev_err(&client->dev, "i2c sanity check failed\n"); > + return rc; > + } > + > + rc = rm31100_ts_init_ts(client, ts); > + if (rc < 0) { > + dev_err(&client->dev, "rm31100_ts init failed\n"); > + return rc; > + } > + > + return 0; > +} > + > +static int rm31100_ts_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct rm31100_ts *ts; > + struct rm3110x_ts_platform_data *pdata = client->dev.platform_data; > + int rc; > + union i2c_smbus_data dummy; > + > + ts = devm_kzalloc(&client->dev, sizeof(struct rm31100_ts), GFP_KERNEL); > + if (!ts) { > + dev_err(&client->dev, "Failed to allocate memory\n"); > + return -ENOMEM; > + } > + > + if (!i2c_check_functionality(client->adapter, > + I2C_FUNC_SMBUS_READ_WORD_DATA)) { > + dev_err(&client->dev, "I2C functionality not supported\n"); > + rc = -EIO; > + goto error_touch_data_alloc; > + } > + > + ts->client = client; > + ts->pdata = pdata; > + i2c_set_clientdata(client, ts); > + ts->device_id = id->driver_data; > + > + ts->is_suspended = false; > + ts->int_pending = false; > + > + mutex_init(&ts->access_lock); > + > + ts->avdd = devm_regulator_get(&client->dev, "avdd"); > + if (IS_ERR(ts->avdd)) { > + rc = PTR_ERR(ts->avdd); > + if (rc != -EPROBE_DEFER) > + dev_err(&client->dev, > + "Failed to get 'avdd' regulator: %d\n", > + rc); > + return rc; > + } > + > + ts->dvdd = devm_regulator_get(&client->dev, "dvdd"); > + if (IS_ERR(ts->dvdd)) { > + rc = PTR_ERR(ts->dvdd); > + if (rc != -EPROBE_DEFER) > + dev_err(&client->dev, > + "Failed to get 'dvdd' regulator: %d\n", > + rc); > + return rc; > + } > + > + ts->resout_gpio = devm_gpiod_get(&client->dev, "rm31100_resout_gpio"); > + if (IS_ERR(ts->resout_gpio)) { > + rc = PTR_ERR(ts->resout_gpio); > + > + /* > + * On Chromebooks vendors like to source touch panels from > + * different vendors, but they are connected to the same > + * regulators/GPIO pin. The drivers also use asynchronous > + * probing, which means that more than one driver will > + * attempt to claim the reset line. If we find it busy, > + * let's try again later. > + */ > + if (rc == -EBUSY) { > + dev_info(&client->dev, > + "reset gpio is busy, deferring probe\n"); > + return -EPROBE_DEFER; > + } This is Chrome OS specific and is not needed for mainline. Simply do: ts->resout_gpio = devm_gpiod_get_optional(&client->dev, "resout", GPIOF_OUT_LOW); if (IS_ERR(ts->resout_gpio)) { error = PTR_ERR(ts->resout_gpio); if (error != -EPROBE_DEFER) dev_err(...); return error; } > + > + if (rc == -EPROBE_DEFER) > + return -EPROBE_DEFER; > + > + if (rc != -ENOENT && rc != -ENOSYS) { > + dev_err(&client->dev, > + "failed to get reset gpio: %d\n", > + rc); > + return rc; > + } > + > + } else { > + rc = gpiod_direction_output(ts->resout_gpio, 0); > + if (rc) { > + dev_err(&client->dev, > + "failed to configure reset gpio as output: %d\n", > + rc); > + return rc; > + } > + } > + > + rc = rm_ts_power_on(ts); > + if (rc) > + return rc; > + > + rc = devm_add_action(&client->dev, rm_ts_power_off, ts); > + if (rc) { > + dev_err(&client->dev, > + "failed to install power off action: %d\n", rc); > + rm_ts_power_off(ts); > + return rc; > + } > + > + /* Make sure there is something at this address */ > + if (i2c_smbus_xfer(client->adapter, client->addr, 0, > + I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0) > + return -ENODEV; > + > + rc = rm31100_initialize(client); > + if (rc < 0) { > + dev_err(&client->dev, "probe failed! unbind device.\n"); > + return rc; > + } > + > + rc = rm_input_dev_create(ts); > + if (rc) { > + dev_err(&client->dev, "%s crated failed, %d\n", __func__, err); > + return rc; > + } > + > + rc = devm_request_threaded_irq(&client->dev, ts->pen_irq, > + NULL, rm31100_ts_irq, > + IRQF_TRIGGER_RISING | IRQF_ONESHOT, > + client->name, ts); > + if (rc) { > + dev_err(&client->dev, "Failed to register interrupt\n"); > + return rc; > + } > + > + device_set_wakeup_enable(&client->dev, false); Why? Let platform code decide, drop this line. > + > + rc = sysfs_create_group(&client->dev.kobj, &rm_ts_attr_group); > + if (rc) { > + dev_err(&client->dev, "failed to create sysfs attributes: %d\n", > + rc); > + return rc; > + } > + > + rc = devm_add_action(&client->dev, > + rm_ts_remove_sysfs_group, ts); > + if (rc) { > + rm_ts_remove_sysfs_group(ts); > + dev_err(&client->dev, > + "Failed to add sysfs cleanup action: %d\n", > + rc); > + return rc; > + } > + return 0; > + > +error_touch_data_alloc: > + kfree(ts); > + return rc; You can't kfree() data allocated with devm_kzalloc(), nor shoudl you - it will deallocate automatically. > +} > + > +static int rm31100_ts_remove(struct i2c_client *client) > +{ > + struct rm31100_ts *ts = i2c_get_clientdata(client); > + > + device_init_wakeup(&client->dev, 0); > + free_irq(ts->pen_irq, ts); No, it was requested with devm_* > + > + if (ts->resout_gpio >= 0) > + gpiod_set_value_cansleep(ts->resout_gpio, 0); Why do we do this here? > + > + input_unregister_device(ts->input); Manually-managed and devm-managed resources do not play well with each other, you better switch all of them to devm, including input device. > + > + /*mutex_destroy(&ts->sus_lock); JL remove*/ Why i it still here if it is marked for removal? > + mutex_destroy(&ts->access_lock); > + > + rm_ts_power_off(ts); > + > + kfree(ts->touch_data); > + kfree(ts); > + > + return 0; > +} > + > +static const struct i2c_device_id rm31100_ts_id[] = { > + {"RM31100", rm31100}, > + {"RM3110x", rm3110x}, Instead of using a constant and then doing if/else if/else to reference your devices[] array why don't you attach instance of rm31100_ts_data directly? > + {} > +}; > +MODULE_DEVICE_TABLE(i2c, rm31100_ts_id); > + > + > +static struct i2c_driver rm31100_ts_driver = { > + .driver = { > + .name = "raydium_ts", > + .owner = THIS_MODULE, No need to set owner explicitly. > +#ifdef CONFIG_PM > + .pm = &rm31100_ts_pm_ops, > +#endif No need for #ifdef here. > + }, > + .probe = rm31100_ts_probe, > + .remove = rm31100_ts_remove, > + .id_table = rm31100_ts_id, > +}; > + > +static int __init rm31100_ts_init(void) > +{ > + int rc; > + int rc2; > + > + rc = i2c_add_driver(&rm31100_ts_driver); > + > + rc2 = driver_create_file(&rm31100_ts_driver.driver, > + &driver_attr_myAttr); What is this for? I suspect you do not need this, so init/exit can be folded into module_i2c_driver() macro. > + > + return rc; > +} > +/* Making this as late init to avoid power fluctuations > + * during LCD initialization. > + */ > +module_init(rm31100_ts_init); > + > +static void __exit rm31100_ts_exit(void) > +{ > + i2c_del_driver(&rm31100_ts_driver); > +} > +module_exit(rm31100_ts_exit); > + > +MODULE_LICENSE("GPL"); > +MODULE_DESCRIPTION("rm31100-rm3110x touchscreen controller driver"); > +MODULE_AUTHOR("Raydium"); > +MODULE_ALIAS("platform:rm31100_ts"); > -- > 2.1.2 > Thanks.
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 0f13e52..4790e36 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -955,4 +955,16 @@ config TOUCHSCREEN_ZFORCE To compile this driver as a module, choose M here: the module will be called zforce_ts. +config TOUCHSCREEN_RM_TS + tristate "Raydium I2C Touchscreen" + depends on I2C + help + Say Y here if you have Raydium series I2C touchscreen, + such as RM31100 , connected to your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called rm31100_ts. + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 687d5a7..3220f66 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -78,3 +78,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o +obj-$(CONFIG_TOUCHSCREEN_RM_TS) += rm31100_ts.o diff --git a/drivers/input/touchscreen/rm31100_ts.c b/drivers/input/touchscreen/rm31100_ts.c new file mode 100644 index 0000000..e024bbd --- /dev/null +++ b/drivers/input/touchscreen/rm31100_ts.c @@ -0,0 +1,772 @@ +/* + * Raydium RM31100_ts touchscreen driver. + * + * Copyright (C) 2012-2014, Raydium Semiconductor Corporation. + * + * 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. + * + * Raydium reserves the right to make changes without further notice + * to the materials described herein. Raydium does not assume any + * liability arising out of the application described herein. + * + * Contact Raydium Semiconductor Corporation at www.rad-ic.com + * + */ +#include <linux/async.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio/consumer.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/uaccess.h> +#include <asm/unaligned.h> +#include <linux/input/mt.h> + +#define rm31100 0x0 +#define rm3110x 0x1 + +#define INVALID_DATA 0xff +#define MAX_REPORT_TOUCHED_POINTS 10 + +#define I2C_CLIENT_ADDR 0x39 +#define I2C_DMA_CLIENT_ADDR 0x5A + +struct rm31100_ts_data { + u8 x_index; + u8 y_index; + u8 z_index; + u8 id_index; + u8 touch_index; + u8 data_reg; + u8 status_reg; + u8 data_size; + u8 touch_bytes; + u8 update_data; + u8 touch_meta_data; + u8 finger_size; +}; + +struct rm3110x_ts_platform_data { + u32 dis_min_x; /* display resoltion ABS min*/ + u32 dis_max_x;/* display resoltion ABS max*/ + u32 dis_min_y; + u32 dis_max_y; + u32 res_x; /* TS resolution unit*/ + u32 res_y; + u32 swap_xy; + u8 nfingers; + bool wakeup; +}; + +static struct rm31100_ts_data devices[] = { + [0] = { + .x_index = 2, + .y_index = 4, + .z_index = 6, + .id_index = 1, + .data_reg = 0x1, + .status_reg = 0, + .update_data = 0x0, + .touch_bytes = 6, + .touch_meta_data = 1, + .finger_size = 70, + }, + [1] = { + .x_index = 2, + .y_index = 4, + .z_index = 6, + .id_index = 1, + .data_reg = 0x0, + .status_reg = 0, + .update_data = 0x0, + .touch_bytes = 6, + .touch_meta_data = 1, + .finger_size = 70, + }, +}; + +struct rm31100_ts { + struct i2c_client *client; + struct input_dev *input; + struct regulator *dvdd; + struct regulator *avdd; + struct gpio_desc *resout_gpio; + struct rm3110x_ts_platform_data *pdata; + struct rm31100_ts_data *dd; + u8 *touch_data; + u8 device_id; + u8 prev_touches; + bool is_suspended; + bool int_pending; + struct mutex access_lock; + u32 pen_irq; + u8 fw_version; + u8 u8_sub_version; +}; + +/* +static inline u16 join_bytes(u8 a, u8 b) +{ + u16 ab = 0; + ab = ab | a; + ab = ab << 8 | b; + return ab; +} +*/ +static s32 rm31100_ts_write_reg_u8(struct i2c_client *client, u8 reg, u8 val) +{ + s32 data; + + data = i2c_smbus_write_byte_data(client, reg, val); + if (data < 0) + dev_err(&client->dev, "error %d in writing reg 0x%x\n", + data, reg); + + return data; +} + +static s32 rm31100_ts_read_reg_u8(struct i2c_client *client, u8 reg) +{ + s32 data; + + data = i2c_smbus_read_byte_data(client, reg); + if (data < 0) + dev_err(&client->dev, "error %d in reading reg 0x%x\n", + data, reg); + + return data; +} + +static int rm31100_ts_read(struct i2c_client *client, u8 reg, u8 *buf, int num) +{ + struct i2c_msg xfer_msg[2]; + + xfer_msg[0].addr = client->addr; + xfer_msg[0].len = 1; + xfer_msg[0].flags = 0; + xfer_msg[0].buf = ® + + xfer_msg[1].addr = client->addr; + xfer_msg[1].len = num; + xfer_msg[1].flags = I2C_M_RD; + xfer_msg[1].buf = buf; + + return i2c_transfer(client->adapter, xfer_msg, 2); +} + +static int rm31100_ts_write(struct i2c_client *client, u8 *buf, int num) +{ + struct i2c_msg xfer_msg[1]; + + xfer_msg[0].addr = client->addr; + xfer_msg[0].len = num; + xfer_msg[0].flags = 0; + xfer_msg[0].buf = buf; + + return i2c_transfer(client->adapter, xfer_msg, 1); +} + +static ssize_t rm_fw_version_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct rm31100_ts *info = dev_get_drvdata(dev); + return sprintf(buf, "Release V 0x%02X, Test V 0x%02X\n", + info.u8_version, + info.u8_sub_version); +} + +static DEVICE_ATTR(fw_version, S_IRUGO, rm_fw_version_show, NULL); + +static struct attribute *rm_ts_attributes[] = { + &dev_attr_fw_version.attr, + NULL +}; + +static const struct attribute_group rm_ts_attr_group = { + .attrs = rm_ts_attributes, +}; + +static void report_data(struct rm31100_ts *dev, u16 x, u16 y, + u8 pressure, u8 id) +{ + struct input_dev *input_dev = dev->input; + if (dev->pdata->swap_xy) + swap(x, y); + + input_mt_slot(input_dev, id); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, true); + input_report_abs(input_dev, ABS_MT_POSITION_X, x); + input_report_abs(input_dev, ABS_MT_POSITION_Y, y); + input_report_abs(input_dev, ABS_MT_PRESSURE, pressure); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, dev->dd->finger_size); +} + +static void process_rm31100_data(struct rm31100_ts *ts) +{ + u8 id, pressure, touches, i; + u16 x, y; + + touches = ts->touch_data[ts->dd->touch_index]; + + if (touches > 0) { + for (i = 0; i < touches; i++) { + id = ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->id_index]; + pressure = ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->z_index]; + x = get_unaligned_le16(&(ts->touch_data[i * + ts->dd->touch_bytes + ts->dd->x_index])); + y = get_unaligned_le16(&(ts->touch_data[i * + ts->dd->touch_bytes + ts->dd->y_index])); + report_data(ts, x, y, pressure, id); + } + } else + input_mt_sync(ts->input); + + ts->prev_touches = touches; + + input_mt_report_pointer_emulation(ts->input, true); + input_sync(ts->input); +} + +static void rm31100_ts_xy_worker(struct rm31100_ts *work) +{ + int rc; + struct rm31100_ts *ts = work; + + mutex_lock(&ts->access_lock); + /* read data from DATA_REG */ + rc = rm31100_ts_read(ts->client, ts->dd->data_reg, ts->touch_data, + ts->dd->data_size); + + if (rc < 0) { + dev_err(&ts->client->dev, "read failed\n"); + goto schedule; + } + + if (ts->touch_data[ts->dd->touch_index] == INVALID_DATA) + goto schedule; + + /* write to STATUS_REG to release lock */ + rc = rm31100_ts_write_reg_u8(ts->client, + ts->dd->status_reg, ts->dd->update_data); + if (rc < 0) { + dev_err(&ts->client->dev, "write failed, try once more\n"); + + rc = rm31100_ts_write_reg_u8(ts->client, + ts->dd->status_reg, ts->dd->update_data); + if (rc < 0) + dev_err(&ts->client->dev, "write failed, exiting\n"); + } + + process_rm31100_data(ts); +schedule: + mutex_unlock(&ts->access_lock); +} + +static irqreturn_t rm31100_ts_irq(int irq, void *dev_id) +{ + struct rm31100_ts *ts = dev_id; + + rm31100_ts_xy_worker(ts); + return IRQ_HANDLED; +} + +static int rm_ts_power_on(struct rm31100_ts *ts) +{ + int error; + + /* + * If we do not have reset gpio assume platform firmware + * controls regulators and does power them on for us. + */ + if (IS_ERR_OR_NULL(ts->resout_gpio)) + return 0; + + gpiod_set_value_cansleep(ts->resout_gpio, 1); + + error = regulator_enable(ts->avdd); + if (error) { + dev_err(&ts->client->dev, + "failed to enable avdd regulator: %d\n", + error); + goto release_reset_gpio; + } + + error = regulator_enable(ts->dvdd); + if (error) { + dev_err(&ts->client->dev, + "failed to enable dvdd regulator: %d\n", + error); + regulator_disable(ts->dvdd); + goto release_reset_gpio; + } + +release_reset_gpio: + gpiod_set_value_cansleep(ts->resout_gpio, 0); + if (error) + return error; + + msleep(20); + + return 0; +} + +static void rm_ts_power_off(void *_data) +{ + struct rm31100_ts *data = _data; + + if (!IS_ERR_OR_NULL(data->resout_gpio)) { + /* + * Activate reset gpio to prevent leakage through the + * pin once we shut off power to the controller. + */ + gpiod_set_value_cansleep(data->resout_gpio, 1); + regulator_disable(data->avdd); + regulator_disable(data->dvdd); + } +} + +static int rm31100_ts_init_ts(struct i2c_client *client, struct rm31100_ts *ts) +{ + /*struct input_dev *input_device;*/ + /*int rc = 0;*/ + + ts->dd = &devices[ts->device_id]; + + if (!ts->pdata->nfingers) { + dev_err(&client->dev, "Touches information not specified\n"); + return -EINVAL; + } + + if (ts->device_id == rm3110x) { + if (ts->pdata->nfingers > 2) { + dev_err(&client->dev, "Touches >=1 & <= 2\n"); + return -EINVAL; + } + ts->dd->data_size = ts->dd->touch_bytes; + ts->dd->touch_index = 0x0; + } else if (ts->device_id == rm31100) { + if (ts->pdata->nfingers > 10) { + dev_err(&client->dev, "Touches >=1 & <= 10\n"); + return -EINVAL; + } + ts->dd->data_size = ts->pdata->nfingers * ts->dd->touch_bytes + + ts->dd->touch_meta_data; + ts->dd->touch_index = 0x0; + } + /* w001 */ + else { + ts->dd->data_size = ts->pdata->nfingers * ts->dd->touch_bytes + + ts->dd->touch_meta_data; + ts->dd->touch_index = 0x0; + } + + ts->touch_data = kzalloc(ts->dd->data_size, GFP_KERNEL); + if (!ts->touch_data) { + pr_err("%s: Unable to allocate memory\n", __func__); + return -ENOMEM; + } + return 0; +} + +static int __maybe_unused rm31100_ts_suspend(struct device *dev) +{ + struct rm31100_ts *ts = dev_get_drvdata(dev); + int rc = 0; + + if (device_may_wakeup(dev)) { + /* mark suspend flag */ + ts->is_suspended = true; + enable_irq_wake(ts->pen_irq); + } + + disable_irq(ts->pen_irq); + + gpiod_set_value_cansleep(ts->resout_gpio, 0); + + rm_ts_power_off(ts); + + return 0; +} + +static int __maybe_unused rm31100_ts_resume(struct device *dev) +{ + struct rm31100_ts *ts = dev_get_drvdata(dev); + + int rc = 0; + + if (device_may_wakeup(dev)) { + disable_irq_wake(ts->pen_irq); + + ts->is_suspended = false; + + if (ts->int_pending == true) + ts->int_pending = false; + } else { + rc = rm_ts_power_on(ts); + if (rc) { + dev_err(dev, "unable to resume\n"); + return rc; + } + + enable_irq(ts->pen_irq); + + /* Clear the status register of the TS controller */ + rc = rm31100_ts_write_reg_u8(ts->client, + ts->dd->status_reg, ts->dd->update_data); + if (rc < 0) { + dev_err(&ts->client->dev, + "write failed, try once more\n"); + + rc = rm31100_ts_write_reg_u8(ts->client, + ts->dd->status_reg, + ts->dd->update_data); + if (rc < 0) + dev_err(&ts->client->dev, + "write failed, exiting\n"); + } + } + + return 0; +} + +static void rm_ts_remove_sysfs_group(void *_data) +{ + struct rm31100_ts *ts = _data; + + sysfs_remove_group(&ts->client->dev.kobj, &rm_ts_attr_group); +} + +static SIMPLE_DEV_PM_OPS(rm31100_ts_pm_ops, + rm31100_ts_suspend, rm31100_ts_resume); + +static int rm_input_dev_create(struct rm31100_ts *ts) +{ + struct input_dev *input_device; + int rc = 0; + ts->prev_touches = 0; + + input_device = input_allocate_device(); + if (!input_device) { + rc = -ENOMEM; + goto error_alloc_dev; + } + ts->input = input_device; + + input_device->name = "raydium_ts"; + input_device->id.bustype = BUS_I2C; + input_device->dev.parent = &ts->client->dev; + input_set_drvdata(input_device, ts); + + __set_bit(BTN_TOUCH, input_device->keybit); + __set_bit(EV_ABS, input_device->evbit); + __set_bit(EV_KEY, input_device->evbit); + + /* For single touch */ + input_set_abs_params(input_device, ABS_X, + ts->pdata->dis_min_x, ts->pdata->dis_max_x, 0, 0); + input_set_abs_params(input_device, ABS_Y, + ts->pdata->dis_min_x, ts->pdata->dis_max_y, 0, 0); + input_set_abs_params(input_device, ABS_PRESSURE, + 0, 255, 0, 0); + input_abs_set_res(input_device, ABS_X, ts->pdata->res_x); + input_abs_set_res(input_device, ABS_Y, ts->pdata->res_y); + + /* Multitouch input params setup */ + rc = input_mt_init_slots(input_device, + MAX_REPORT_TOUCHED_POINTS, INPUT_MT_DIRECT); + if (rc) + goto error_unreg_device; + + input_set_abs_params(input_device, ABS_MT_POSITION_X, + ts->pdata->dis_min_x, ts->pdata->dis_max_x, 0, 0); + input_set_abs_params(input_device, ABS_MT_POSITION_Y, + ts->pdata->dis_min_y, ts->pdata->dis_max_y, 0, 0); + input_set_abs_params(input_device, ABS_MT_PRESSURE, + 0, 0xFF, 0, 0); + input_set_abs_params(input_device, ABS_MT_TOUCH_MAJOR, + 0, 0xFF, 0, 0); + input_abs_set_res(input_device, ABS_MT_POSITION_X, ts->pdata->res_x); + input_abs_set_res(input_device, ABS_MT_POSITION_Y, ts->pdata->res_y); + + rc = input_register_device(input_device); + if (rc) + goto error_unreg_device; + + return 0; + +error_unreg_device: + input_free_device(input_device); +error_alloc_dev: + ts->input = NULL; + return rc; +} + +static int rm31100_initialize(struct i2c_client *client) +{ + struct rm31100_ts *ts = i2c_get_clientdata(client); + int rc = 0, temp_reg; + + /* read one byte to make sure i2c device exists */ + if (ts->device_id == rm3110x) + temp_reg = 0x01; + else if (ts->device_id == rm31100) + temp_reg = 0x00; + else + temp_reg = 0x05; + + rc = rm31100_ts_read_reg_u8(client, temp_reg); + if (rc < 0) { + dev_err(&client->dev, "i2c sanity check failed\n"); + return rc; + } + + rc = rm31100_ts_init_ts(client, ts); + if (rc < 0) { + dev_err(&client->dev, "rm31100_ts init failed\n"); + return rc; + } + + return 0; +} + +static int rm31100_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct rm31100_ts *ts; + struct rm3110x_ts_platform_data *pdata = client->dev.platform_data; + int rc; + union i2c_smbus_data dummy; + + ts = devm_kzalloc(&client->dev, sizeof(struct rm31100_ts), GFP_KERNEL); + if (!ts) { + dev_err(&client->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA)) { + dev_err(&client->dev, "I2C functionality not supported\n"); + rc = -EIO; + goto error_touch_data_alloc; + } + + ts->client = client; + ts->pdata = pdata; + i2c_set_clientdata(client, ts); + ts->device_id = id->driver_data; + + ts->is_suspended = false; + ts->int_pending = false; + + mutex_init(&ts->access_lock); + + ts->avdd = devm_regulator_get(&client->dev, "avdd"); + if (IS_ERR(ts->avdd)) { + rc = PTR_ERR(ts->avdd); + if (rc != -EPROBE_DEFER) + dev_err(&client->dev, + "Failed to get 'avdd' regulator: %d\n", + rc); + return rc; + } + + ts->dvdd = devm_regulator_get(&client->dev, "dvdd"); + if (IS_ERR(ts->dvdd)) { + rc = PTR_ERR(ts->dvdd); + if (rc != -EPROBE_DEFER) + dev_err(&client->dev, + "Failed to get 'dvdd' regulator: %d\n", + rc); + return rc; + } + + ts->resout_gpio = devm_gpiod_get(&client->dev, "rm31100_resout_gpio"); + if (IS_ERR(ts->resout_gpio)) { + rc = PTR_ERR(ts->resout_gpio); + + /* + * On Chromebooks vendors like to source touch panels from + * different vendors, but they are connected to the same + * regulators/GPIO pin. The drivers also use asynchronous + * probing, which means that more than one driver will + * attempt to claim the reset line. If we find it busy, + * let's try again later. + */ + if (rc == -EBUSY) { + dev_info(&client->dev, + "reset gpio is busy, deferring probe\n"); + return -EPROBE_DEFER; + } + + if (rc == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (rc != -ENOENT && rc != -ENOSYS) { + dev_err(&client->dev, + "failed to get reset gpio: %d\n", + rc); + return rc; + } + + } else { + rc = gpiod_direction_output(ts->resout_gpio, 0); + if (rc) { + dev_err(&client->dev, + "failed to configure reset gpio as output: %d\n", + rc); + return rc; + } + } + + rc = rm_ts_power_on(ts); + if (rc) + return rc; + + rc = devm_add_action(&client->dev, rm_ts_power_off, ts); + if (rc) { + dev_err(&client->dev, + "failed to install power off action: %d\n", rc); + rm_ts_power_off(ts); + return rc; + } + + /* Make sure there is something at this address */ + if (i2c_smbus_xfer(client->adapter, client->addr, 0, + I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0) + return -ENODEV; + + rc = rm31100_initialize(client); + if (rc < 0) { + dev_err(&client->dev, "probe failed! unbind device.\n"); + return rc; + } + + rc = rm_input_dev_create(ts); + if (rc) { + dev_err(&client->dev, "%s crated failed, %d\n", __func__, err); + return rc; + } + + rc = devm_request_threaded_irq(&client->dev, ts->pen_irq, + NULL, rm31100_ts_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + client->name, ts); + if (rc) { + dev_err(&client->dev, "Failed to register interrupt\n"); + return rc; + } + + device_set_wakeup_enable(&client->dev, false); + + rc = sysfs_create_group(&client->dev.kobj, &rm_ts_attr_group); + if (rc) { + dev_err(&client->dev, "failed to create sysfs attributes: %d\n", + rc); + return rc; + } + + rc = devm_add_action(&client->dev, + rm_ts_remove_sysfs_group, ts); + if (rc) { + rm_ts_remove_sysfs_group(ts); + dev_err(&client->dev, + "Failed to add sysfs cleanup action: %d\n", + rc); + return rc; + } + return 0; + +error_touch_data_alloc: + kfree(ts); + return rc; +} + +static int rm31100_ts_remove(struct i2c_client *client) +{ + struct rm31100_ts *ts = i2c_get_clientdata(client); + + device_init_wakeup(&client->dev, 0); + free_irq(ts->pen_irq, ts); + + if (ts->resout_gpio >= 0) + gpiod_set_value_cansleep(ts->resout_gpio, 0); + + input_unregister_device(ts->input); + + /*mutex_destroy(&ts->sus_lock); JL remove*/ + mutex_destroy(&ts->access_lock); + + rm_ts_power_off(ts); + + kfree(ts->touch_data); + kfree(ts); + + return 0; +} + +static const struct i2c_device_id rm31100_ts_id[] = { + {"RM31100", rm31100}, + {"RM3110x", rm3110x}, + {} +}; +MODULE_DEVICE_TABLE(i2c, rm31100_ts_id); + + +static struct i2c_driver rm31100_ts_driver = { + .driver = { + .name = "raydium_ts", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &rm31100_ts_pm_ops, +#endif + }, + .probe = rm31100_ts_probe, + .remove = rm31100_ts_remove, + .id_table = rm31100_ts_id, +}; + +static int __init rm31100_ts_init(void) +{ + int rc; + int rc2; + + rc = i2c_add_driver(&rm31100_ts_driver); + + rc2 = driver_create_file(&rm31100_ts_driver.driver, + &driver_attr_myAttr); + + return rc; +} +/* Making this as late init to avoid power fluctuations + * during LCD initialization. + */ +module_init(rm31100_ts_init); + +static void __exit rm31100_ts_exit(void) +{ + i2c_del_driver(&rm31100_ts_driver); +} +module_exit(rm31100_ts_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("rm31100-rm3110x touchscreen controller driver"); +MODULE_AUTHOR("Raydium"); +MODULE_ALIAS("platform:rm31100_ts");
This patch is porting Raydium I2C touch driver. Developer can enable raydium touch driver by modifying define "CONFIG_TOUCHSCREEN_RM_TS". Signed-off-by: jeffrey.lin<jeffrey.lin@rad-ic.com> --- drivers/input/touchscreen/Kconfig | 12 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/rm31100_ts.c | 772 +++++++++++++++++++++++++++++++++ 3 files changed, 785 insertions(+) create mode 100644 drivers/input/touchscreen/rm31100_ts.c