From ac8c3d57e76745416dc21d6cba34953a14050ef6 Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Sun, 7 Feb 2021 13:41:57 -0800 Subject: [PATCH] rM2: touchscreen: Copy the rM2 wacom changes Copy the rM2 zero-sugar branch (https://github.com/reMarkable/linux/tree/zero-sugar) to the new kernel. Signed-off-by: Alistair Francis --- drivers/input/touchscreen/wacom_i2c.c | 400 ++++++++++++++++++++++---- 1 file changed, 347 insertions(+), 53 deletions(-) diff --git a/drivers/input/touchscreen/wacom_i2c.c b/drivers/input/touchscreen/wacom_i2c.c index f017af8c2aa3..54ed6391b25f 100644 --- a/drivers/input/touchscreen/wacom_i2c.c +++ b/drivers/input/touchscreen/wacom_i2c.c @@ -13,114 +13,336 @@ #include #include #include +#include +#include #include +#include +#include +#include +#include +#include -#define WACOM_CMD_QUERY0 0x04 -#define WACOM_CMD_QUERY1 0x00 -#define WACOM_CMD_QUERY2 0x33 -#define WACOM_CMD_QUERY3 0x02 -#define WACOM_CMD_THROW0 0x05 -#define WACOM_CMD_THROW1 0x00 -#define WACOM_QUERY_SIZE 19 +// Bitmasks (for data[3]) +#define WACOM_TIP_SWITCH_bm (1 << 0) +#define WACOM_BARREL_SWITCH_bm (1 << 1) +#define WACOM_ERASER_bm (1 << 2) +#define WACOM_INVERT_bm (1 << 3) +#define WACOM_BARREL_SWITCH_2_bm (1 << 4) +#define WACOM_IN_RANGE_bm (1 << 5) + +// Registers +#define WACOM_COMMAND_LSB 0x04 +#define WACOM_COMMAND_MSB 0x00 + +#define WACOM_DATA_LSB 0x05 +#define WACOM_DATA_MSB 0x00 + +// Report types +#define REPORT_INPUT 0x10 +#define REPORT_OUTPUT 0x20 +#define REPORT_FEATURE 0x30 + +// Requests / operations +#define OPCODE_RESET 0x01 +#define OPCODE_GET_REPORT 0x02 +#define OPCODE_SET_REPORT 0x03 +#define OPCODE_SET_POWER 0x08 + +// Power settings +#define POWER_ON 0x00 +#define POWER_SLEEP 0x01 + +// Input report ids +#define WACOM_PEN_DATA_REPORT 2 +#define WACOM_SHINONOME_REPORT 26 + +// Feature report ids +#define WACOM_DEVICE_MODE_REPORT 2 +#define WACOM_QUERY_REPORT 3 +#define WACOM_PEN_INPUT_FORMAT_REPORT 9 +#define WACOM_POSITION_RATE_REPORT 25 +#define WACOM_ORIGIN_OFFSET_REPORT 22 +#define WACOM_SIDE_SWITCH_REPORT 21 + +#define WACOM_MAX_DATA_SIZE 22 struct wacom_features { int x_max; int y_max; int pressure_max; + int distance_max; + int distance_physical_max; + int tilt_x_max; + int tilt_y_max; char fw_version; }; struct wacom_i2c { struct i2c_client *client; struct input_dev *input; - u8 data[WACOM_QUERY_SIZE]; + struct wacom_features features; + struct regulator *vdd; + u8 data[WACOM_MAX_DATA_SIZE]; bool prox; int tool; + + bool flip_tilt_x; + bool flip_tilt_y; + bool flip_pos_x; + bool flip_pos_y; + bool flip_distance; + bool flip_pressure; +}; + +u8 reset_cmd[] = { + WACOM_COMMAND_LSB, + WACOM_COMMAND_MSB, + 0x00, + OPCODE_RESET, +}; + +u8 wakeup_cmd[] = { + WACOM_COMMAND_LSB, + WACOM_COMMAND_MSB, + POWER_ON, + OPCODE_SET_POWER, +}; + +u8 sleep_cmd[] = { + WACOM_COMMAND_LSB, + WACOM_COMMAND_MSB, + POWER_SLEEP, + OPCODE_SET_POWER, }; static int wacom_query_device(struct i2c_client *client, - struct wacom_features *features) + struct wacom_features *features) { int ret; - u8 cmd1[] = { WACOM_CMD_QUERY0, WACOM_CMD_QUERY1, - WACOM_CMD_QUERY2, WACOM_CMD_QUERY3 }; - u8 cmd2[] = { WACOM_CMD_THROW0, WACOM_CMD_THROW1 }; - u8 data[WACOM_QUERY_SIZE]; + u8 data[WACOM_MAX_DATA_SIZE]; + struct reset_control *rstc; + + u8 get_query_data_cmd[] = { + WACOM_COMMAND_LSB, + WACOM_COMMAND_MSB, + REPORT_FEATURE | WACOM_QUERY_REPORT, + OPCODE_GET_REPORT, + WACOM_DATA_LSB, + WACOM_DATA_MSB, + }; + struct i2c_msg msgs[] = { + // Request reading of feature ReportID: 3 (Pen Query Data) { .addr = client->addr, .flags = 0, - .len = sizeof(cmd1), - .buf = cmd1, - }, - { - .addr = client->addr, - .flags = 0, - .len = sizeof(cmd2), - .buf = cmd2, + .len = sizeof(get_query_data_cmd), + .buf = get_query_data_cmd, }, + // Read 21 bytes { .addr = client->addr, .flags = I2C_M_RD, - .len = sizeof(data), + .len = 21, .buf = data, }, }; + rstc = devm_reset_control_get_optional_exclusive(&client->dev, NULL); + if (IS_ERR(rstc)) { + dev_err(&client->dev, "Failed to get reset control before init\n"); + } + else { + reset_control_reset(rstc); + } + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); - if (ret < 0) + if (ret < 0) { return ret; - if (ret != ARRAY_SIZE(msgs)) + } + if (ret != ARRAY_SIZE(msgs)) { return -EIO; + } features->x_max = get_unaligned_le16(&data[3]); features->y_max = get_unaligned_le16(&data[5]); features->pressure_max = get_unaligned_le16(&data[11]); features->fw_version = get_unaligned_le16(&data[13]); + features->distance_max = data[15]; + features->distance_physical_max = data[16]; + features->tilt_x_max = get_unaligned_le16(&data[17]); + features->tilt_y_max = get_unaligned_le16(&data[19]); dev_dbg(&client->dev, - "x_max:%d, y_max:%d, pressure:%d, fw:%d\n", + "x_max:%d, y_max:%d, pressure:%d, fw:%d, " + "distance: %d, phys distance: %d" + "tilt_x_max: %d, tilt_y_max: %d\n", features->x_max, features->y_max, - features->pressure_max, features->fw_version); + features->pressure_max, features->fw_version, + features->distance_max, features->distance_physical_max, + features->tilt_x_max, features->tilt_y_max); + + return 0; +} + +#ifdef CONFIG_OF +static void wacom_of_read(struct wacom_i2c *wac_i2c) +{ + struct i2c_client *client = wac_i2c->client; + + wac_i2c->flip_tilt_x = of_property_read_bool(client->dev.of_node, "flip-tilt-x"); + wac_i2c->flip_tilt_y = of_property_read_bool(client->dev.of_node, "flip-tilt-y"); + wac_i2c->flip_pos_x = of_property_read_bool(client->dev.of_node, "flip-pos-x"); + wac_i2c->flip_pos_y = of_property_read_bool(client->dev.of_node, "flip-pos-y"); + wac_i2c->flip_distance = of_property_read_bool(client->dev.of_node, "flip-distance"); + wac_i2c->flip_pressure = of_property_read_bool(client->dev.of_node, "flip-pressure"); +} +#endif + +static int wacom_setup_device(struct i2c_client *client) +{ + int ret; + u8 data[WACOM_MAX_DATA_SIZE]; + + u8 get_sample_rate_cmd[] = { + WACOM_COMMAND_LSB, + WACOM_COMMAND_MSB, + REPORT_FEATURE | 0xF, + OPCODE_GET_REPORT, + WACOM_POSITION_RATE_REPORT, + WACOM_DATA_LSB, + WACOM_DATA_MSB, + }; + + u8 set_sample_rate_cmd[] = { + WACOM_COMMAND_LSB, + WACOM_COMMAND_MSB, + REPORT_FEATURE | 0xF, + OPCODE_SET_REPORT, + WACOM_POSITION_RATE_REPORT, + WACOM_DATA_LSB, + WACOM_DATA_MSB, + 0x04, // LENGTH LSB + 0x00, // LENGTH MSB + WACOM_POSITION_RATE_REPORT, + 0x06, // Selected rate (1=240pps, 3=360pps (def), 5=480pps, 6=540pps) + }; + + struct i2c_msg msgs[] = { + // Request writing of feature, ReportID 25 / Position Report Rate + { + .addr = client->addr, + .flags = 0, + .len = sizeof(set_sample_rate_cmd), + .buf = set_sample_rate_cmd, + }, + }; + + struct i2c_msg msgs_readback[] = { + // Request reading of feature, ReportID 25 / Position Report Rate + { + .addr = client->addr, + .flags = 0, + .len = sizeof(get_sample_rate_cmd), + .buf = get_sample_rate_cmd, + }, + // Read 4 bytes + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 4, + .buf = data, + }, + }; + + // Write position rate report + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) { + return ret; + } + if (ret != ARRAY_SIZE(msgs)) { + return -EIO; + } + + // Read position rate report + ret = i2c_transfer(client->adapter, msgs_readback, ARRAY_SIZE(msgs_readback)); + if (ret < 0) { + return ret; + } + if (ret != ARRAY_SIZE(msgs_readback)) { + return -EIO; + } return 0; } static irqreturn_t wacom_i2c_irq(int irq, void *dev_id) { + ktime_t timestamp = ktime_get(); + struct wacom_i2c *wac_i2c = dev_id; struct input_dev *input = wac_i2c->input; + struct wacom_features *features = &wac_i2c->features; u8 *data = wac_i2c->data; unsigned int x, y, pressure; - unsigned char tsw, f1, f2, ers; + unsigned char tip, f1, f2, eraser, transducer = 0; + short tilt_x, tilt_y, distance; int error; error = i2c_master_recv(wac_i2c->client, - wac_i2c->data, sizeof(wac_i2c->data)); - if (error < 0) + wac_i2c->data, 17); + if (error < 0) { goto out; + } - tsw = data[3] & 0x01; - ers = data[3] & 0x04; - f1 = data[3] & 0x02; - f2 = data[3] & 0x10; + // data[0] == Length LSB + // data[1] == Length MSB + // data[2] == ReportID (2 | 26) + tip = data[3] & WACOM_TIP_SWITCH_bm; + eraser = data[3] & WACOM_ERASER_bm; + f1 = data[3] & WACOM_BARREL_SWITCH_bm; + f2 = data[3] & WACOM_BARREL_SWITCH_2_bm; x = le16_to_cpup((__le16 *)&data[4]); y = le16_to_cpup((__le16 *)&data[6]); pressure = le16_to_cpup((__le16 *)&data[8]); + // Shinonome Refill has a transducer index field: + if (data[2] == WACOM_SHINONOME_REPORT) { + transducer = (data[3] >> 6); + } + + // Tilt (signed) + tilt_x = le16_to_cpup((__le16 *)&data[11]); + tilt_y = le16_to_cpup((__le16 *)&data[13]); + + // Hover height + distance = le16_to_cpup((__le16 *)&data[15]); + if (!wac_i2c->prox) wac_i2c->tool = (data[3] & 0x0c) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN; - wac_i2c->prox = data[3] & 0x20; + wac_i2c->prox = (data[3] & 0x20) ? true : false; - input_report_key(input, BTN_TOUCH, tsw || ers); + // Flippin' + pressure = wac_i2c->flip_pressure ? (features->pressure_max - pressure) : pressure; + distance = wac_i2c->flip_distance ? -distance : distance; + x = wac_i2c->flip_pos_x ? (features->x_max - x) : x; + y = wac_i2c->flip_pos_y ? (features->y_max - y) : y; + tilt_x = wac_i2c->flip_tilt_x ? -tilt_x : tilt_x; + tilt_y = wac_i2c->flip_tilt_y ? -tilt_y : tilt_y; + + input_report_key(input, BTN_TOUCH, tip || eraser); input_report_key(input, wac_i2c->tool, wac_i2c->prox); input_report_key(input, BTN_STYLUS, f1); input_report_key(input, BTN_STYLUS2, f2); input_report_abs(input, ABS_X, x); input_report_abs(input, ABS_Y, y); input_report_abs(input, ABS_PRESSURE, pressure); + input_report_abs(input, ABS_DISTANCE, distance); + input_report_abs(input, ABS_TILT_X, tilt_x); + input_report_abs(input, ABS_TILT_Y, tilt_y); + input_set_timestamp(input, timestamp); input_sync(input); out: @@ -146,36 +368,55 @@ static void wacom_i2c_close(struct input_dev *dev) } static int wacom_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) + const struct i2c_device_id *id) { struct wacom_i2c *wac_i2c; + struct wacom_features *features; struct input_dev *input; - struct wacom_features features = { 0 }; int error; + wac_i2c = kzalloc(sizeof(*wac_i2c), GFP_KERNEL); + if (!wac_i2c) { + return -ENOMEM; + } + features = &wac_i2c->features; + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { dev_err(&client->dev, "i2c_check_functionality error\n"); return -EIO; } - error = wacom_query_device(client, &features); - if (error) - return error; - - wac_i2c = kzalloc(sizeof(*wac_i2c), GFP_KERNEL); - input = input_allocate_device(); - if (!wac_i2c || !input) { - error = -ENOMEM; + wac_i2c->vdd = regulator_get(&client->dev, "vdd"); + if (IS_ERR(wac_i2c->vdd)) { + error = PTR_ERR(wac_i2c->vdd); goto err_free_mem; } + error = regulator_enable(wac_i2c->vdd); + if (error) + goto err_put_vdd; + + error = wacom_query_device(client, features); + if (error) + goto err_disable_vdd; + + error = wacom_setup_device(client); + if (error) + goto err_disable_vdd; + + input = input_allocate_device(); + if (!input) { + error = -ENOMEM; + goto err_disable_vdd; + } + wac_i2c->client = client; wac_i2c->input = input; input->name = "Wacom I2C Digitizer"; input->id.bustype = BUS_I2C; input->id.vendor = 0x56a; - input->id.version = features.fw_version; + input->id.version = features->fw_version; input->dev.parent = &client->dev; input->open = wacom_i2c_open; input->close = wacom_i2c_close; @@ -188,20 +429,24 @@ static int wacom_i2c_probe(struct i2c_client *client, __set_bit(BTN_STYLUS2, input->keybit); __set_bit(BTN_TOUCH, input->keybit); - input_set_abs_params(input, ABS_X, 0, features.x_max, 0, 0); - input_set_abs_params(input, ABS_Y, 0, features.y_max, 0, 0); + input_set_abs_params(input, ABS_X, 0, features->x_max, 0, 0); + input_set_abs_params(input, ABS_Y, 0, features->y_max, 0, 0); input_set_abs_params(input, ABS_PRESSURE, - 0, features.pressure_max, 0, 0); + 0, features->pressure_max, 0, 0); + + input_set_abs_params(input, ABS_DISTANCE, 0, features->distance_max, 0, 0); + input_set_abs_params(input, ABS_TILT_X, -features->tilt_x_max, features->tilt_x_max, 0, 0); + input_set_abs_params(input, ABS_TILT_Y, -features->tilt_y_max, features->tilt_y_max, 0, 0); input_set_drvdata(input, wac_i2c); error = request_threaded_irq(client->irq, NULL, wacom_i2c_irq, - IRQF_TRIGGER_LOW | IRQF_ONESHOT, - "wacom_i2c", wac_i2c); + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "wacom_i2c", wac_i2c); if (error) { dev_err(&client->dev, "Failed to enable IRQ, error: %d\n", error); - goto err_free_mem; + goto err_free_moremem; } /* Disable the IRQ, we'll enable it in wac_i2c_open() */ @@ -215,12 +460,24 @@ static int wacom_i2c_probe(struct i2c_client *client, } i2c_set_clientdata(client, wac_i2c); + +#ifdef CONFIG_OF + wacom_of_read(wac_i2c); +#endif + + device_init_wakeup(&client->dev, true); + return 0; err_free_irq: free_irq(client->irq, wac_i2c); -err_free_mem: +err_free_moremem: input_free_device(input); +err_disable_vdd: + regulator_disable(wac_i2c->vdd); +err_put_vdd: + regulator_put(wac_i2c->vdd); +err_free_mem: kfree(wac_i2c); return error; @@ -232,6 +489,8 @@ static int wacom_i2c_remove(struct i2c_client *client) free_irq(client->irq, wac_i2c); input_unregister_device(wac_i2c->input); + regulator_disable(wac_i2c->vdd); + regulator_put(wac_i2c->vdd); kfree(wac_i2c); return 0; @@ -240,7 +499,15 @@ static int wacom_i2c_remove(struct i2c_client *client) static int __maybe_unused wacom_i2c_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); + struct wacom_i2c *wac_i2c = i2c_get_clientdata(client); + if (pm_suspend_target_state == PM_SUSPEND_MEM) { + regulator_disable(wac_i2c->vdd); + pinctrl_pm_select_sleep_state(dev); + } + + if (device_may_wakeup(dev)) + enable_irq_wake(client->irq); disable_irq(client->irq); return 0; @@ -249,8 +516,24 @@ static int __maybe_unused wacom_i2c_suspend(struct device *dev) static int __maybe_unused wacom_i2c_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); + struct wacom_i2c *wac_i2c = i2c_get_clientdata(client); + int ret; enable_irq(client->irq); + if (device_may_wakeup(dev)) + disable_irq_wake(client->irq); + + if (pm_suspend_target_state == PM_SUSPEND_MEM) { + pinctrl_pm_select_default_state(dev); + + ret = regulator_enable(wac_i2c->vdd); + if (ret) + return ret; + + ret = wacom_setup_device(client); + if (ret) + return ret; + } return 0; } @@ -263,10 +546,21 @@ static const struct i2c_device_id wacom_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, wacom_i2c_id); +#ifdef CONFIG_OF +static const struct of_device_id wacom_i2c_of_match_table[] = { + { .compatible = "wacom,wacom-i2c" }, + {} +}; +MODULE_DEVICE_TABLE(of, wacom_i2c_of_match_table); +#endif + static struct i2c_driver wacom_i2c_driver = { .driver = { .name = "wacom_i2c", .pm = &wacom_i2c_pm, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(wacom_i2c_of_match_table), +#endif }, .probe = wacom_i2c_probe,