root/drivers/input/touchscreen/hynitron_cstxxx.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  Driver for Hynitron cstxxx Touchscreen
 *
 *  Copyright (c) 2022 Chris Morgan <macromorgan@hotmail.com>
 *
 *  This code is based on hynitron_core.c authored by Hynitron.
 *  Note that no datasheet was available, so much of these registers
 *  are undocumented. This is essentially a cleaned-up version of the
 *  vendor driver with support removed for hardware I cannot test and
 *  device-specific functions replated with generic functions wherever
 *  possible.
 */

#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/input/touchscreen.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/unaligned.h>

/* Per chip data */
struct hynitron_ts_chip_data {
        unsigned int max_touch_num;
        u32 ic_chkcode;
        int (*firmware_info)(struct i2c_client *client);
        int (*bootloader_enter)(struct i2c_client *client);
        int (*init_input)(struct i2c_client *client);
        void (*report_touch)(struct i2c_client *client);
};

/* Data generic to all (supported and non-supported) controllers. */
struct hynitron_ts_data {
        const struct hynitron_ts_chip_data *chip;
        struct i2c_client *client;
        struct input_dev *input_dev;
        struct touchscreen_properties prop;
        struct gpio_desc *reset_gpio;
};

/*
 * Since I have no datasheet, these values are guessed and/or assumed
 * based on observation and testing.
 */
#define CST3XX_FIRMWARE_INFO_START_CMD          0x01d1
#define CST3XX_FIRMWARE_INFO_END_CMD            0x09d1
#define CST3XX_FIRMWARE_CHK_CODE_REG            0xfcd1
#define CST3XX_FIRMWARE_VERSION_REG             0x08d2
#define CST3XX_FIRMWARE_VER_INVALID_VAL         0xa5a5a5a5

#define CST3XX_BOOTLDR_PROG_CMD                 0xaa01a0
#define CST3XX_BOOTLDR_PROG_CHK_REG             0x02a0
#define CST3XX_BOOTLDR_CHK_VAL                  0xac

#define CST3XX_TOUCH_DATA_PART_REG              0x00d0
#define CST3XX_TOUCH_DATA_FULL_REG              0x07d0
#define CST3XX_TOUCH_DATA_CHK_VAL               0xab
#define CST3XX_TOUCH_DATA_TOUCH_VAL             0x03
#define CST3XX_TOUCH_DATA_STOP_CMD              0xab00d0
#define CST3XX_TOUCH_COUNT_MASK                 GENMASK(6, 0)


/*
 * Hard coded reset delay value of 20ms not IC dependent in
 * vendor driver.
 */
static void hyn_reset_proc(struct i2c_client *client, int delay)
{
        struct hynitron_ts_data *ts_data = i2c_get_clientdata(client);

        gpiod_set_value_cansleep(ts_data->reset_gpio, 1);
        msleep(20);
        gpiod_set_value_cansleep(ts_data->reset_gpio, 0);
        if (delay)
                fsleep(delay * 1000);
}

static irqreturn_t hyn_interrupt_handler(int irq, void *dev_id)
{
        struct i2c_client *client = dev_id;
        struct hynitron_ts_data *ts_data = i2c_get_clientdata(client);

        ts_data->chip->report_touch(client);

        return IRQ_HANDLED;
}

/*
 * The vendor driver would retry twice before failing to read or write
 * to the i2c device.
 */

static int cst3xx_i2c_write(struct i2c_client *client,
                            unsigned char *buf, int len)
{
        int ret;
        int retries = 0;

        while (retries < 2) {
                ret = i2c_master_send(client, buf, len);
                if (ret == len)
                        return 0;
                if (ret <= 0)
                        retries++;
                else
                        break;
        }

        return ret < 0 ? ret : -EIO;
}

static int cst3xx_i2c_read_register(struct i2c_client *client, u16 reg,
                                    u8 *val, u16 len)
{
        __le16 buf = cpu_to_le16(reg);
        struct i2c_msg msgs[] = {
                {
                        .addr = client->addr,
                        .flags = 0,
                        .len = 2,
                        .buf = (u8 *)&buf,
                },
                {
                        .addr = client->addr,
                        .flags = I2C_M_RD,
                        .len = len,
                        .buf = val,
                }
        };
        int err;
        int ret;

        ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
        if (ret == ARRAY_SIZE(msgs))
                return 0;

        err = ret < 0 ? ret : -EIO;
        dev_err(&client->dev, "Error reading %d bytes from 0x%04x: %d (%d)\n",
                len, reg, err, ret);

        return err;
}

static int cst3xx_firmware_info(struct i2c_client *client)
{
        struct hynitron_ts_data *ts_data = i2c_get_clientdata(client);
        int err;
        u32 tmp;
        unsigned char buf[4];

        /*
         * Tests suggest this command needed to read firmware regs.
         */
        put_unaligned_le16(CST3XX_FIRMWARE_INFO_START_CMD, buf);
        err = cst3xx_i2c_write(client, buf, 2);
        if (err)
                return err;

        usleep_range(10000, 11000);

        /*
         * Read register for check-code to determine if device detected
         * correctly.
         */
        err = cst3xx_i2c_read_register(client, CST3XX_FIRMWARE_CHK_CODE_REG,
                                       buf, 4);
        if (err)
                return err;

        tmp = get_unaligned_le32(buf);
        if ((tmp & 0xffff0000) != ts_data->chip->ic_chkcode) {
                dev_err(&client->dev, "%s ic mismatch, chkcode is %u\n",
                        __func__, tmp);
                return -ENODEV;
        }

        usleep_range(10000, 11000);

        /* Read firmware version and test if firmware missing. */
        err = cst3xx_i2c_read_register(client, CST3XX_FIRMWARE_VERSION_REG,
                                       buf, 4);
        if (err)
                return err;

        tmp = get_unaligned_le32(buf);
        if (tmp == CST3XX_FIRMWARE_VER_INVALID_VAL) {
                dev_err(&client->dev, "Device firmware missing\n");
                return -ENODEV;
        }

        /*
         * Tests suggest cmd required to exit reading firmware regs.
         */
        put_unaligned_le16(CST3XX_FIRMWARE_INFO_END_CMD, buf);
        err = cst3xx_i2c_write(client, buf, 2);
        if (err)
                return err;

        usleep_range(5000, 6000);

        return 0;
}

static int cst3xx_bootloader_enter(struct i2c_client *client)
{
        int err;
        u8 retry;
        u32 tmp = 0;
        unsigned char buf[3];

        for (retry = 0; retry < 5; retry++) {
                hyn_reset_proc(client, (7 + retry));
                /* set cmd to enter program mode */
                put_unaligned_le24(CST3XX_BOOTLDR_PROG_CMD, buf);
                err = cst3xx_i2c_write(client, buf, 3);
                if (err)
                        continue;

                usleep_range(2000, 2500);

                /* check whether in program mode */
                err = cst3xx_i2c_read_register(client,
                                               CST3XX_BOOTLDR_PROG_CHK_REG,
                                               buf, 1);
                if (err)
                        continue;

                tmp = get_unaligned(buf);
                if (tmp == CST3XX_BOOTLDR_CHK_VAL)
                        break;
        }

        if (tmp != CST3XX_BOOTLDR_CHK_VAL) {
                dev_err(&client->dev, "%s unable to enter bootloader mode\n",
                        __func__);
                return -ENODEV;
        }

        hyn_reset_proc(client, 40);

        return 0;
}

static void cst3xx_report_contact(struct hynitron_ts_data *ts_data,
                                  u8 id, unsigned int x, unsigned int y, u8 w)
{
        input_mt_slot(ts_data->input_dev, id);
        input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_FINGER, 1);
        touchscreen_report_pos(ts_data->input_dev, &ts_data->prop, x, y, true);
        input_report_abs(ts_data->input_dev, ABS_MT_TOUCH_MAJOR, w);
}

static int cst3xx_finish_touch_read(struct i2c_client *client)
{
        unsigned char buf[3];
        int err;

        put_unaligned_le24(CST3XX_TOUCH_DATA_STOP_CMD, buf);
        err = cst3xx_i2c_write(client, buf, 3);
        if (err) {
                dev_err(&client->dev,
                        "send read touch info ending failed: %d\n", err);
                return err;
        }

        return 0;
}

/*
 * Handle events from IRQ. Note that for cst3xx it appears that IRQ
 * fires continuously while touched, otherwise once every 1500ms
 * when not touched (assume touchscreen waking up periodically).
 * Note buffer is sized for 5 fingers, if more needed buffer must
 * be increased. The buffer contains 5 bytes for each touch point,
 * a touch count byte, a check byte, and then a second check byte after
 * all other touch points.
 *
 * For example 1 touch would look like this:
 * touch1[5]:touch_count[1]:chk_byte[1]
 *
 * 3 touches would look like this:
 * touch1[5]:touch_count[1]:chk_byte[1]:touch2[5]:touch3[5]:chk_byte[1]
 */
static void cst3xx_touch_report(struct i2c_client *client)
{
        struct hynitron_ts_data *ts_data = i2c_get_clientdata(client);
        u8 buf[28];
        u8 finger_id, sw, w;
        unsigned int x, y;
        unsigned int touch_cnt, end_byte;
        unsigned int idx = 0;
        unsigned int i;
        int err;

        /* Read and validate the first bits of input data. */
        err = cst3xx_i2c_read_register(client, CST3XX_TOUCH_DATA_PART_REG,
                                       buf, 28);
        if (err ||
            buf[6] != CST3XX_TOUCH_DATA_CHK_VAL ||
            buf[0] == CST3XX_TOUCH_DATA_CHK_VAL) {
                dev_err(&client->dev, "cst3xx touch read failure\n");
                return;
        }

        /* Report to the device we're done reading the touch data. */
        err = cst3xx_finish_touch_read(client);
        if (err)
                return;

        touch_cnt = buf[5] & CST3XX_TOUCH_COUNT_MASK;
        /*
         * Check the check bit of the last touch slot. The check bit is
         * always present after touch point 1 for valid data, and then
         * appears as the last byte after all other touch data.
         */
        if (touch_cnt > 1) {
                end_byte = touch_cnt * 5 + 2;
                if (buf[end_byte] != CST3XX_TOUCH_DATA_CHK_VAL) {
                        dev_err(&client->dev, "cst3xx touch read failure\n");
                        return;
                }
        }

        /* Parse through the buffer to capture touch data. */
        for (i = 0; i < touch_cnt; i++) {
                x = ((buf[idx + 1] << 4) | ((buf[idx + 3] >> 4) & 0x0f));
                y = ((buf[idx + 2] << 4) | (buf[idx + 3] & 0x0f));
                w = (buf[idx + 4] >> 3);
                sw = (buf[idx] & 0x0f) >> 1;
                finger_id = (buf[idx] >> 4) & 0x0f;

                /* Sanity check we don't have more fingers than we expect */
                if (ts_data->chip->max_touch_num < finger_id) {
                        dev_err(&client->dev, "cst3xx touch read failure\n");
                        break;
                }

                /* sw value of 0 means no touch, 0x03 means touch */
                if (sw == CST3XX_TOUCH_DATA_TOUCH_VAL)
                        cst3xx_report_contact(ts_data, finger_id, x, y, w);

                idx += 5;

                /* Skip the 2 bytes between point 1 and point 2 */
                if (i == 0)
                        idx += 2;
        }

        input_mt_sync_frame(ts_data->input_dev);
        input_sync(ts_data->input_dev);
}

static int cst3xx_input_dev_int(struct i2c_client *client)
{
        struct hynitron_ts_data *ts_data = i2c_get_clientdata(client);
        int err;

        ts_data->input_dev = devm_input_allocate_device(&client->dev);
        if (!ts_data->input_dev) {
                dev_err(&client->dev, "Failed to allocate input device\n");
                return -ENOMEM;
        }

        ts_data->input_dev->name = "Hynitron cst3xx Touchscreen";
        ts_data->input_dev->phys = "input/ts";
        ts_data->input_dev->id.bustype = BUS_I2C;

        input_set_drvdata(ts_data->input_dev, ts_data);

        input_set_capability(ts_data->input_dev, EV_ABS, ABS_MT_POSITION_X);
        input_set_capability(ts_data->input_dev, EV_ABS, ABS_MT_POSITION_Y);
        input_set_abs_params(ts_data->input_dev, ABS_MT_TOUCH_MAJOR,
                             0, 255, 0, 0);

        touchscreen_parse_properties(ts_data->input_dev, true, &ts_data->prop);

        if (!ts_data->prop.max_x || !ts_data->prop.max_y) {
                dev_err(&client->dev,
                        "Invalid x/y (%d, %d), using defaults\n",
                        ts_data->prop.max_x, ts_data->prop.max_y);
                ts_data->prop.max_x = 1152;
                ts_data->prop.max_y = 1920;
                input_abs_set_max(ts_data->input_dev,
                                  ABS_MT_POSITION_X, ts_data->prop.max_x);
                input_abs_set_max(ts_data->input_dev,
                                  ABS_MT_POSITION_Y, ts_data->prop.max_y);
        }

        err = input_mt_init_slots(ts_data->input_dev,
                                  ts_data->chip->max_touch_num,
                                  INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
        if (err) {
                dev_err(&client->dev,
                        "Failed to initialize input slots: %d\n", err);
                return err;
        }

        err = input_register_device(ts_data->input_dev);
        if (err) {
                dev_err(&client->dev,
                        "Input device registration failed: %d\n", err);
                return err;
        }

        return 0;
}

static int hyn_probe(struct i2c_client *client)
{
        struct hynitron_ts_data *ts_data;
        int err;

        ts_data = devm_kzalloc(&client->dev, sizeof(*ts_data), GFP_KERNEL);
        if (!ts_data)
                return -ENOMEM;

        ts_data->client = client;
        i2c_set_clientdata(client, ts_data);

        ts_data->chip = device_get_match_data(&client->dev);
        if (!ts_data->chip)
                return -EINVAL;

        ts_data->reset_gpio = devm_gpiod_get(&client->dev,
                                             "reset", GPIOD_OUT_LOW);
        err = PTR_ERR_OR_ZERO(ts_data->reset_gpio);
        if (err) {
                dev_err(&client->dev, "request reset gpio failed: %d\n", err);
                return err;
        }

        hyn_reset_proc(client, 60);

        err = ts_data->chip->bootloader_enter(client);
        if (err < 0)
                return err;

        err = ts_data->chip->init_input(client);
        if (err < 0)
                return err;

        err = ts_data->chip->firmware_info(client);
        if (err < 0)
                return err;

        err = devm_request_threaded_irq(&client->dev, client->irq,
                                        NULL, hyn_interrupt_handler,
                                        IRQF_ONESHOT,
                                        "Hynitron Touch Int", client);
        if (err) {
                dev_err(&client->dev, "failed to request IRQ: %d\n", err);
                return err;
        }

        return 0;
}

static const struct hynitron_ts_chip_data cst3xx_data = {
        .max_touch_num          = 5,
        .ic_chkcode             = 0xcaca0000,
        .firmware_info          = &cst3xx_firmware_info,
        .bootloader_enter       = &cst3xx_bootloader_enter,
        .init_input             = &cst3xx_input_dev_int,
        .report_touch           = &cst3xx_touch_report,
};

static const struct i2c_device_id hyn_tpd_id[] = {
        { .name = "hynitron_ts" },
        { /* sentinel */ },
};
MODULE_DEVICE_TABLE(i2c, hyn_tpd_id);

static const struct of_device_id hyn_dt_match[] = {
        { .compatible = "hynitron,cst340", .data = &cst3xx_data },
        { /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, hyn_dt_match);

static struct i2c_driver hynitron_i2c_driver = {
        .driver = {
                .name = "Hynitron-TS",
                .of_match_table = hyn_dt_match,
                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
        },
        .id_table = hyn_tpd_id,
        .probe = hyn_probe,
};

module_i2c_driver(hynitron_i2c_driver);

MODULE_AUTHOR("Chris Morgan");
MODULE_DESCRIPTION("Hynitron Touchscreen Driver");
MODULE_LICENSE("GPL");