Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1308132 > unrolled thread
| Started by | Jeffrey Lin <yajohn@gmail.com> |
|---|---|
| First post | 2016-01-13 08:50 +0100 |
| Last post | 2016-01-13 20:20 +0100 |
| Articles | 3 — 2 participants |
Back to article view | Back to linux.kernel
[PATCH] driver: input :touchscreen : add Raydium I2C touch driver Jeffrey Lin <yajohn@gmail.com> - 2016-01-13 08:50 +0100
Re: [PATCH] driver: input :touchscreen : add Raydium I2C touch driver Dmitry Torokhov <dmitry.torokhov@gmail.com> - 2016-01-13 09:40 +0100
Re: [PATCH] driver: input :touchscreen : add Raydium I2C touch driver Dmitry Torokhov <dmitry.torokhov@gmail.com> - 2016-01-13 20:20 +0100
| From | Jeffrey Lin <yajohn@gmail.com> |
|---|---|
| Date | 2016-01-13 08:50 +0100 |
| Subject | [PATCH] driver: input :touchscreen : add Raydium I2C touch driver |
| Message-ID | <qQolQ-4RO-9@gated-at.bofh.it> |
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
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");
--
2.1.2
[toc] | [next] | [standalone]
| From | Dmitry Torokhov <dmitry.torokhov@gmail.com> |
|---|---|
| Date | 2016-01-13 09:40 +0100 |
| Message-ID | <qQp8e-5ru-13@gated-at.bofh.it> |
| In reply to | #1308132 |
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.
--
Dmitry
[toc] | [prev] | [next] | [standalone]
| From | Dmitry Torokhov <dmitry.torokhov@gmail.com> |
|---|---|
| Date | 2016-01-13 20:20 +0100 |
| Message-ID | <qQz7A-44Y-11@gated-at.bofh.it> |
| In reply to | #1308132 |
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.
--
Dmitry
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web