@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Cypress CY8CTMA140 (TMA140) touchscreen
+ * (C) 2020 Linus Walleij <linus.walleij@linaro.org>
+ * (C) 2007 Cypress
+ * (C) 2007 Google, Inc.
+ *
+ * Inspired by the tma140_skomer.c driver in the Samsung GT-S7710 code
+ * drop. The GT-S7710 is codenamed "Skomer", the code also indicates
+ * that the same touchscreen was used in a product called "Lucas".
+ *
+ * The code drop for GT-S7710 also contains a firmware downloader and
+ * 15 (!) versions of the firmware drop from Cypress. But here we assume
+ * the firmware got downloaded to the touchscreen flash successfully and
+ * just use it to read the fingers. The shipped vendor driver does the
+ * same.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/input/mt.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/i2c.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+
+/* Used for driver name etc */
+#define CY8CTMA140_NAME "cy8ctma140"
+
+/*
+ * The datasheet claims the device supports 4 fingers but the firmware
+ * I've seen certainly only supports 2 fingers.
+ */
+#define CY8CTMA140_MAX_FINGERS 2
+#define CY8CTMA140_GET_FINGERS 0x00
+#define CY8CTMA140_GET_FW_INFO 0x19
+
+struct cy8ctma140 {
+ struct input_dev *input;
+ struct touchscreen_properties props;
+ struct device *dev;
+ struct i2c_client *client;
+ struct regulator_bulk_data regulators[2];
+ u8 prev_fingers;
+ u8 prev_f1id;
+ u8 prev_f2id;
+};
+
+static irqreturn_t cy8ctma140_irq_thread(int irq, void *d)
+{
+ struct cy8ctma140 *ts = d;
+ u8 msgbuf0[1];
+ /* This message also fits some bytes for touchkeys, if used */
+ u8 buf[31];
+ struct i2c_msg msg[] = {
+ {
+ .addr = ts->client->addr,
+ .flags = 0,
+ .len = 1,
+ .buf = msgbuf0,
+ }, {
+ .addr = ts->client->addr,
+ .flags = I2C_M_RD,
+ .len = sizeof(buf),
+ .buf = buf,
+ },
+ };
+ u8 fingers;
+ /* X, Y and Width (W) supported */
+ u16 f1x, f1y;
+ u16 f2x, f2y;
+ u8 f1w, f2w;
+ u8 f1id, f2id;
+ int ret;
+ struct input_mt_pos pos;
+
+ msgbuf0[0] = CY8CTMA140_GET_FINGERS;
+ ret = __i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret < 0) {
+ dev_err(ts->dev, "error reading message\n");
+ goto evt_out;
+ }
+ if (ret != ARRAY_SIZE(msg)) {
+ dev_err(ts->dev, "wrong number of messages\n");
+ goto evt_out;
+ }
+ if (buf[1] & 0x20) {
+ dev_info(ts->dev, "invalid event\n");
+ goto evt_out;
+ }
+
+ /* IDs are 1 and 2 so subtract 1 from ID */
+ f1id = (buf[8] >> 4) - 1;
+ f2id = (buf[8] & 0x0f) - 1;
+
+ f1x = get_unaligned_be16(&buf[3]);
+ f1y = get_unaligned_be16(&buf[5]);
+ f1w = buf[7];
+
+ f2x = get_unaligned_be16(&buf[9]);
+ f2y = get_unaligned_be16(&buf[11]);
+ f2w = buf[13];
+
+ fingers = buf[2] & 0x0f;
+
+ if (!fingers) {
+ /*
+ * For the unlikely case that both fingers reports as released
+ * by the same IRQ.
+ */
+ if (ts->prev_fingers >= 2) {
+ dev_dbg(ts->dev, "release finger 2 ID: %02x\n",
+ ts->prev_f2id);
+ input_mt_slot(ts->input, ts->prev_f2id);
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER,
+ false);
+ }
+ if (ts->prev_fingers >= 1) {
+ dev_dbg(ts->dev, "release finger 1 ID: %02x\n",
+ ts->prev_f1id);
+ input_mt_slot(ts->input, ts->prev_f1id);
+ input_mt_report_slot_state(ts->input,
+ MT_TOOL_FINGER, false);
+ }
+ goto fingers_out;
+ }
+
+ /*
+ * When just handling two fingers this code is simple and we do not
+ * need a loop increasing the complexity. If you need to modify
+ * this driver for some firmware handling more than 2 fingers,
+ * this is where you put in an array of coordinates and a loop
+ * instead.
+ */
+ if (fingers >= 1) {
+ touchscreen_set_mt_pos(&pos, &ts->props, f1x, f1y);
+ dev_dbg(ts->dev,
+ "%d fingers: finger 1 ID: %02x (%d, %d) w: %d\n",
+ fingers, f1id, pos.x, pos.y, f1w);
+ input_mt_slot(ts->input, f1id);
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true);
+ input_report_abs(ts->input, ABS_MT_POSITION_X, pos.x);
+ input_report_abs(ts->input, ABS_MT_POSITION_Y, pos.y);
+ input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, f1w);
+ }
+ if (fingers == 1 && ts->prev_fingers == 2) {
+ dev_dbg(ts->dev, "release finger 2 ID: %02x\n", ts->prev_f2id);
+ input_mt_slot(ts->input, ts->prev_f2id);
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, false);
+ goto fingers_out;
+ }
+ if (fingers >= 2) {
+ touchscreen_set_mt_pos(&pos, &ts->props, f2x, f2y);
+ dev_dbg(ts->dev,
+ "%d fingers: finger 2 ID %02x (%d, %d) w: %d\n",
+ fingers, f2id, pos.x, pos.y, f2w);
+ input_mt_slot(ts->input, f2id);
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true);
+ input_report_abs(ts->input, ABS_MT_POSITION_X, pos.x);
+ input_report_abs(ts->input, ABS_MT_POSITION_Y, pos.y);
+ input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, f2w);
+ }
+ if (fingers > 2)
+ dev_err(ts->dev, "%d fingers - unsupported!\n", fingers);
+
+fingers_out:
+ input_mt_sync_frame(ts->input);
+ input_sync(ts->input);
+ /*
+ * The firmware just resports 1 or 0 finger without any indication of
+ * the ID of the finger that just disappeared, so we need to cache
+ * the previous finger IDs.
+ */
+ ts->prev_fingers = fingers;
+ ts->prev_f1id = f1id;
+ ts->prev_f2id = f2id;
+evt_out:
+ return IRQ_HANDLED;
+}
+
+static int cy8ctma140_init(struct cy8ctma140 *ts)
+{
+ u8 addr[1];
+ u8 buf[5];
+ int ret;
+
+ addr[0] = CY8CTMA140_GET_FW_INFO;
+ ret = i2c_master_send(ts->client, addr, 1);
+ if (ret < 0) {
+ dev_err(ts->dev, "error sending FW info message\n");
+ return ret;
+ }
+ ret = i2c_master_recv(ts->client, buf, 5);
+ if (ret < 0) {
+ dev_err(ts->dev, "error recieveing FW info message\n");
+ return ret;
+ }
+ if (ret != 5) {
+ dev_err(ts->dev, "got only %d bytes\n", ret);
+ return -EIO;
+ }
+
+ dev_info(ts->dev, "vendor %c%c, HW ID %.2d, FW ver %.4d\n",
+ buf[0], buf[1], buf[3], buf[4]);
+ return 0;
+}
+
+static int cy8ctma140_power_up(struct cy8ctma140 *ts)
+{
+ int error;
+
+ error = regulator_bulk_enable(ARRAY_SIZE(ts->regulators),
+ ts->regulators);
+ if (error) {
+ dev_err(ts->dev, "failed to enable regulators\n");
+ return error;
+ }
+
+ msleep(250);
+
+ return 0;
+}
+
+static void cy8ctma140_power_down(struct cy8ctma140 *ts)
+{
+ regulator_bulk_disable(ARRAY_SIZE(ts->regulators),
+ ts->regulators);
+}
+
+/* Called from the registered devm action */
+static void cy8ctma140_power_off_action(void *d)
+{
+ struct cy8ctma140 *ts = d;
+
+ cy8ctma140_power_down(ts);
+}
+
+static int cy8ctma140_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct cy8ctma140 *ts;
+ struct input_dev *input;
+ struct device *dev = &client->dev;
+ int ret;
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ ts->dev = dev;
+ ts->client = client;
+ ts->input = input;
+
+ input_set_capability(input, EV_ABS, ABS_MT_POSITION_X);
+ input_set_capability(input, EV_ABS, ABS_MT_POSITION_Y);
+ /* One byte for width 0..255 so this is the limit */
+ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+ /*
+ * This sets up event max/min capabilities and fuzz.
+ * Some DT properties are compulsory so we do not need
+ * to provide defaults for X/Y max or pressure max.
+ *
+ * We just initialize a very simple MT touchscreen here,
+ * some devices use the capability of this touchscreen to
+ * provide touchkeys, and in that case this needs to be
+ * extended to handle touchkey input.
+ *
+ * The firmware takes care of finger tracking and dropping
+ * invalid ranges.
+ */
+ touchscreen_parse_properties(input, true, &ts->props);
+ input_abs_set_fuzz(input, ABS_MT_POSITION_X, 0);
+ input_abs_set_fuzz(input, ABS_MT_POSITION_Y, 0);
+
+ ret = input_mt_init_slots(input, CY8CTMA140_MAX_FINGERS,
+ INPUT_MT_DIRECT);
+ if (ret)
+ return ret;
+
+ input->name = CY8CTMA140_NAME;
+ input->id.bustype = BUS_I2C;
+ input_set_drvdata(input, ts);
+
+ /*
+ * VCPIN is the analog voltage supply
+ * VDD is the digital voltage supply
+ * since the voltage range of VDD overlaps that of VCPIN,
+ * many designs to just supply both with a single voltage
+ * source of ~3.3 V.
+ */
+ ts->regulators[0].supply = "vcpin";
+ ts->regulators[1].supply = "vdd";
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ts->regulators),
+ ts->regulators);
+ if (ret) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get regulators %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = cy8ctma140_power_up(ts);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, cy8ctma140_power_off_action, ts);
+ if (ret) {
+ dev_err(dev, "failed to install power off handler\n");
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ cy8ctma140_irq_thread,
+ IRQF_ONESHOT,
+ CY8CTMA140_NAME, ts);
+ if (ret) {
+ dev_err(dev, "irq %d busy? error %d\n", client->irq, ret);
+ goto err_reg_dis;
+ }
+
+ ret = cy8ctma140_init(ts);
+ if (ret)
+ return ret;
+
+ ret = input_register_device(input);
+ if (ret)
+ goto err_reg_dis;
+
+ i2c_set_clientdata(client, ts);
+
+ return 0;
+
+err_reg_dis:
+ cy8ctma140_power_down(ts);
+ return ret;
+}
+
+static int __maybe_unused cy8ctma140_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cy8ctma140 *ts = i2c_get_clientdata(client);
+
+ if (!device_may_wakeup(&client->dev))
+ cy8ctma140_power_down(ts);
+ input_mt_slot(ts->input, 0);
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, false);
+ input_mt_slot(ts->input, 1);
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, false);
+ input_mt_report_pointer_emulation(ts->input, true);
+ input_sync(ts->input);
+
+ return 0;
+}
+
+static int __maybe_unused cy8ctma140_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cy8ctma140 *ts = i2c_get_clientdata(client);
+ int ret;
+
+ if (!device_may_wakeup(&client->dev)) {
+ ret = cy8ctma140_power_up(ts);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(cy8ctma140_pm, cy8ctma140_suspend, cy8ctma140_resume);
+
+static const struct i2c_device_id cy8ctma140_idtable[] = {
+ { CY8CTMA140_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, cy8ctma140_idtable);
+
+static const struct of_device_id cy8ctma140_of_match[] = {
+ {
+ .compatible = "cypress,cy8ctma140",
+ },
+};
+MODULE_DEVICE_TABLE(of, cy8ctma140_of_match);
+
+static struct i2c_driver cy8ctma140_driver = {
+ .driver = {
+ .name = CY8CTMA140_NAME,
+ .pm = &cy8ctma140_pm,
+ .of_match_table = cy8ctma140_of_match,
+ },
+ .id_table = cy8ctma140_idtable,
+ .probe = cy8ctma140_probe,
+};
+module_i2c_driver(cy8ctma140_driver);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+MODULE_DESCRIPTION("CY8CTMA140 TouchScreen Driver");
+MODULE_LICENSE("GPL v2");