| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Azoteq IQS550/572/525 Trackpad/Touchscreen Controller | |
| * | |
| * Copyright (C) 2018 Jeff LaBundy <jeff@labundy.com> | |
| * | |
| * These devices require firmware exported from a PC-based configuration tool | |
| * made available by the vendor. Firmware files may be pushed to the device's | |
| * nonvolatile memory by writing the filename to the 'fw_file' sysfs control. | |
| * | |
| * Link to PC-based configuration tool and datasheet: https://www.azoteq.com/ | |
| */ | |
| #include <linux/bits.h> | |
| #include <linux/delay.h> | |
| #include <linux/device.h> | |
| #include <linux/err.h> | |
| #include <linux/firmware.h> | |
| #include <linux/gpio/consumer.h> | |
| #include <linux/hex.h> | |
| #include <linux/i2c.h> | |
| #include <linux/input.h> | |
| #include <linux/input/mt.h> | |
| #include <linux/input/touchscreen.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/slab.h> | |
| #include <linux/unaligned.h> | |
| #define IQS5XX_FW_FILE_LEN 64 | |
| #define IQS5XX_NUM_RETRIES 10 | |
| #define IQS5XX_NUM_CONTACTS 5 | |
| #define IQS5XX_WR_BYTES_MAX 2 | |
| #define IQS5XX_PROD_NUM_IQS550 40 | |
| #define IQS5XX_PROD_NUM_IQS572 58 | |
| #define IQS5XX_PROD_NUM_IQS525 52 | |
| #define IQS5XX_SHOW_RESET BIT(7) | |
| #define IQS5XX_ACK_RESET BIT(7) | |
| #define IQS5XX_SUSPEND BIT(0) | |
| #define IQS5XX_RESUME 0 | |
| #define IQS5XX_SETUP_COMPLETE BIT(6) | |
| #define IQS5XX_WDT BIT(5) | |
| #define IQS5XX_ALP_REATI BIT(3) | |
| #define IQS5XX_REATI BIT(2) | |
| #define IQS5XX_TP_EVENT BIT(2) | |
| #define IQS5XX_EVENT_MODE BIT(0) | |
| #define IQS5XX_PROD_NUM 0x0000 | |
| #define IQS5XX_SYS_INFO0 0x000F | |
| #define IQS5XX_SYS_INFO1 0x0010 | |
| #define IQS5XX_SYS_CTRL0 0x0431 | |
| #define IQS5XX_SYS_CTRL1 0x0432 | |
| #define IQS5XX_SYS_CFG0 0x058E | |
| #define IQS5XX_SYS_CFG1 0x058F | |
| #define IQS5XX_X_RES 0x066E | |
| #define IQS5XX_Y_RES 0x0670 | |
| #define IQS5XX_EXP_FILE 0x0677 | |
| #define IQS5XX_CHKSM 0x83C0 | |
| #define IQS5XX_APP 0x8400 | |
| #define IQS5XX_CSTM 0xBE00 | |
| #define IQS5XX_PMAP_END 0xBFFF | |
| #define IQS5XX_END_COMM 0xEEEE | |
| #define IQS5XX_CHKSM_LEN (IQS5XX_APP - IQS5XX_CHKSM) | |
| #define IQS5XX_APP_LEN (IQS5XX_CSTM - IQS5XX_APP) | |
| #define IQS5XX_CSTM_LEN (IQS5XX_PMAP_END + 1 - IQS5XX_CSTM) | |
| #define IQS5XX_PMAP_LEN (IQS5XX_PMAP_END + 1 - IQS5XX_CHKSM) | |
| /* Length of firmware header in hexadecimal characters */ | |
| #define IQS5XX_REC_HDR_LEN_HEX (1 /* start */ + 2 /* size */ + \ | |
| 4 /* addr */ + 2 /* type */) | |
| #define IQS5XX_REC_HDR_SIZE 4 /* size + addr (2 bytes) + type, in bytes*/ | |
| #define IQS5XX_REC_DATA_SIZE 255 /* maximum size of the data portion */ | |
| #define IQS5XX_REC_TYPE_DATA 0x00 | |
| #define IQS5XX_REC_TYPE_EOF 0x01 | |
| #define IQS5XX_BL_ADDR_MASK 0x40 | |
| #define IQS5XX_BL_CMD_VER 0x00 | |
| #define IQS5XX_BL_CMD_READ 0x01 | |
| #define IQS5XX_BL_CMD_EXEC 0x02 | |
| #define IQS5XX_BL_CMD_CRC 0x03 | |
| #define IQS5XX_BL_BLK_LEN_MAX 64 | |
| #define IQS5XX_BL_ID 0x0200 | |
| #define IQS5XX_BL_STATUS_NONE 0xEE | |
| #define IQS5XX_BL_CRC_PASS 0x00 | |
| #define IQS5XX_BL_CRC_FAIL 0x01 | |
| #define IQS5XX_BL_ATTEMPTS 3 | |
| struct iqs5xx_dev_id_info { | |
| __be16 prod_num; | |
| __be16 proj_num; | |
| u8 major_ver; | |
| u8 minor_ver; | |
| u8 bl_status; | |
| } __packed; | |
| struct iqs5xx_touch_data { | |
| __be16 abs_x; | |
| __be16 abs_y; | |
| __be16 strength; | |
| u8 area; | |
| } __packed; | |
| struct iqs5xx_status { | |
| u8 sys_info[2]; | |
| u8 num_active; | |
| __be16 rel_x; | |
| __be16 rel_y; | |
| struct iqs5xx_touch_data touch_data[IQS5XX_NUM_CONTACTS]; | |
| } __packed; | |
| struct iqs5xx_private { | |
| struct i2c_client *client; | |
| struct input_dev *input; | |
| struct gpio_desc *reset_gpio; | |
| struct touchscreen_properties prop; | |
| struct mutex lock; | |
| struct iqs5xx_dev_id_info dev_id_info; | |
| u8 exp_file[2]; | |
| }; | |
| static int iqs5xx_read_burst(struct i2c_client *client, | |
| u16 reg, void *val, u16 len) | |
| { | |
| __be16 reg_buf = cpu_to_be16(reg); | |
| int ret, i; | |
| struct i2c_msg msg[] = { | |
| { | |
| .addr = client->addr, | |
| .flags = 0, | |
| .len = sizeof(reg_buf), | |
| .buf = (u8 *)®_buf, | |
| }, | |
| { | |
| .addr = client->addr, | |
| .flags = I2C_M_RD, | |
| .len = len, | |
| .buf = (u8 *)val, | |
| }, | |
| }; | |
| /* | |
| * The first addressing attempt outside of a communication window fails | |
| * and must be retried, after which the device clock stretches until it | |
| * is available. | |
| */ | |
| for (i = 0; i < IQS5XX_NUM_RETRIES; i++) { | |
| ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); | |
| if (ret == ARRAY_SIZE(msg)) | |
| return 0; | |
| usleep_range(200, 300); | |
| } | |
| if (ret >= 0) | |
| ret = -EIO; | |
| dev_err(&client->dev, "Failed to read from address 0x%04X: %d\n", | |
| reg, ret); | |
| return ret; | |
| } | |
| static int iqs5xx_read_word(struct i2c_client *client, u16 reg, u16 *val) | |
| { | |
| __be16 val_buf; | |
| int error; | |
| error = iqs5xx_read_burst(client, reg, &val_buf, sizeof(val_buf)); | |
| if (error) | |
| return error; | |
| *val = be16_to_cpu(val_buf); | |
| return 0; | |
| } | |
| static int iqs5xx_write_burst(struct i2c_client *client, | |
| u16 reg, const void *val, u16 len) | |
| { | |
| int ret, i; | |
| u16 mlen = sizeof(reg) + len; | |
| u8 mbuf[sizeof(reg) + IQS5XX_WR_BYTES_MAX]; | |
| if (len > IQS5XX_WR_BYTES_MAX) | |
| return -EINVAL; | |
| put_unaligned_be16(reg, mbuf); | |
| memcpy(mbuf + sizeof(reg), val, len); | |
| /* | |
| * The first addressing attempt outside of a communication window fails | |
| * and must be retried, after which the device clock stretches until it | |
| * is available. | |
| */ | |
| for (i = 0; i < IQS5XX_NUM_RETRIES; i++) { | |
| ret = i2c_master_send(client, mbuf, mlen); | |
| if (ret == mlen) | |
| return 0; | |
| usleep_range(200, 300); | |
| } | |
| if (ret >= 0) | |
| ret = -EIO; | |
| dev_err(&client->dev, "Failed to write to address 0x%04X: %d\n", | |
| reg, ret); | |
| return ret; | |
| } | |
| static int iqs5xx_write_word(struct i2c_client *client, u16 reg, u16 val) | |
| { | |
| __be16 val_buf = cpu_to_be16(val); | |
| return iqs5xx_write_burst(client, reg, &val_buf, sizeof(val_buf)); | |
| } | |
| static int iqs5xx_write_byte(struct i2c_client *client, u16 reg, u8 val) | |
| { | |
| return iqs5xx_write_burst(client, reg, &val, sizeof(val)); | |
| } | |
| static void iqs5xx_reset(struct i2c_client *client) | |
| { | |
| struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client); | |
| gpiod_set_value_cansleep(iqs5xx->reset_gpio, 1); | |
| usleep_range(200, 300); | |
| gpiod_set_value_cansleep(iqs5xx->reset_gpio, 0); | |
| } | |
| static int iqs5xx_bl_cmd(struct i2c_client *client, u8 bl_cmd, u16 bl_addr) | |
| { | |
| struct i2c_msg msg; | |
| int ret; | |
| u8 mbuf[sizeof(bl_cmd) + sizeof(bl_addr)]; | |
| msg.addr = client->addr ^ IQS5XX_BL_ADDR_MASK; | |
| msg.flags = 0; | |
| msg.len = sizeof(bl_cmd); | |
| msg.buf = mbuf; | |
| *mbuf = bl_cmd; | |
| switch (bl_cmd) { | |
| case IQS5XX_BL_CMD_VER: | |
| case IQS5XX_BL_CMD_CRC: | |
| case IQS5XX_BL_CMD_EXEC: | |
| break; | |
| case IQS5XX_BL_CMD_READ: | |
| msg.len += sizeof(bl_addr); | |
| put_unaligned_be16(bl_addr, mbuf + sizeof(bl_cmd)); | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| ret = i2c_transfer(client->adapter, &msg, 1); | |
| if (ret != 1) | |
| goto msg_fail; | |
| switch (bl_cmd) { | |
| case IQS5XX_BL_CMD_VER: | |
| msg.len = sizeof(u16); | |
| break; | |
| case IQS5XX_BL_CMD_CRC: | |
| msg.len = sizeof(u8); | |
| /* | |
| * This delay saves the bus controller the trouble of having to | |
| * tolerate a relatively long clock-stretching period while the | |
| * CRC is calculated. | |
| */ | |
| msleep(50); | |
| break; | |
| case IQS5XX_BL_CMD_EXEC: | |
| usleep_range(10000, 10100); | |
| fallthrough; | |
| default: | |
| return 0; | |
| } | |
| msg.flags = I2C_M_RD; | |
| ret = i2c_transfer(client->adapter, &msg, 1); | |
| if (ret != 1) | |
| goto msg_fail; | |
| if (bl_cmd == IQS5XX_BL_CMD_VER && | |
| get_unaligned_be16(mbuf) != IQS5XX_BL_ID) { | |
| dev_err(&client->dev, "Unrecognized bootloader ID: 0x%04X\n", | |
| get_unaligned_be16(mbuf)); | |
| return -EINVAL; | |
| } | |
| if (bl_cmd == IQS5XX_BL_CMD_CRC && *mbuf != IQS5XX_BL_CRC_PASS) { | |
| dev_err(&client->dev, "Bootloader CRC failed\n"); | |
| return -EIO; | |
| } | |
| return 0; | |
| msg_fail: | |
| if (ret >= 0) | |
| ret = -EIO; | |
| if (bl_cmd != IQS5XX_BL_CMD_VER) | |
| dev_err(&client->dev, | |
| "Unsuccessful bootloader command 0x%02X: %d\n", | |
| bl_cmd, ret); | |
| return ret; | |
| } | |
| static int iqs5xx_bl_open(struct i2c_client *client) | |
| { | |
| int error, i, j; | |
| /* | |
| * The device opens a bootloader polling window for 2 ms following the | |
| * release of reset. If the host cannot establish communication during | |
| * this time frame, it must cycle reset again. | |
| */ | |
| for (i = 0; i < IQS5XX_BL_ATTEMPTS; i++) { | |
| iqs5xx_reset(client); | |
| usleep_range(350, 400); | |
| for (j = 0; j < IQS5XX_NUM_RETRIES; j++) { | |
| error = iqs5xx_bl_cmd(client, IQS5XX_BL_CMD_VER, 0); | |
| if (!error) | |
| usleep_range(10000, 10100); | |
| else if (error != -EINVAL) | |
| continue; | |
| return error; | |
| } | |
| } | |
| dev_err(&client->dev, "Failed to open bootloader: %d\n", error); | |
| return error; | |
| } | |
| static int iqs5xx_bl_write(struct i2c_client *client, | |
| u16 bl_addr, const u8 *pmap_data, u16 pmap_len) | |
| { | |
| struct i2c_msg msg; | |
| int ret, i; | |
| u8 mbuf[sizeof(bl_addr) + IQS5XX_BL_BLK_LEN_MAX]; | |
| if (pmap_len % IQS5XX_BL_BLK_LEN_MAX) | |
| return -EINVAL; | |
| msg.addr = client->addr ^ IQS5XX_BL_ADDR_MASK; | |
| msg.flags = 0; | |
| msg.len = sizeof(mbuf); | |
| msg.buf = mbuf; | |
| for (i = 0; i < pmap_len; i += IQS5XX_BL_BLK_LEN_MAX) { | |
| put_unaligned_be16(bl_addr + i, mbuf); | |
| memcpy(mbuf + sizeof(bl_addr), pmap_data + i, | |
| sizeof(mbuf) - sizeof(bl_addr)); | |
| ret = i2c_transfer(client->adapter, &msg, 1); | |
| if (ret != 1) | |
| goto msg_fail; | |
| usleep_range(10000, 10100); | |
| } | |
| return 0; | |
| msg_fail: | |
| if (ret >= 0) | |
| ret = -EIO; | |
| dev_err(&client->dev, "Failed to write block at address 0x%04X: %d\n", | |
| bl_addr + i, ret); | |
| return ret; | |
| } | |
| static int iqs5xx_bl_verify(struct i2c_client *client, | |
| u16 bl_addr, const u8 *pmap_data, u16 pmap_len) | |
| { | |
| struct i2c_msg msg; | |
| int ret, i; | |
| u8 bl_data[IQS5XX_BL_BLK_LEN_MAX]; | |
| if (pmap_len % IQS5XX_BL_BLK_LEN_MAX) | |
| return -EINVAL; | |
| msg.addr = client->addr ^ IQS5XX_BL_ADDR_MASK; | |
| msg.flags = I2C_M_RD; | |
| msg.len = sizeof(bl_data); | |
| msg.buf = bl_data; | |
| for (i = 0; i < pmap_len; i += IQS5XX_BL_BLK_LEN_MAX) { | |
| ret = iqs5xx_bl_cmd(client, IQS5XX_BL_CMD_READ, bl_addr + i); | |
| if (ret) | |
| return ret; | |
| ret = i2c_transfer(client->adapter, &msg, 1); | |
| if (ret != 1) | |
| goto msg_fail; | |
| if (memcmp(bl_data, pmap_data + i, sizeof(bl_data))) { | |
| dev_err(&client->dev, | |
| "Failed to verify block at address 0x%04X\n", | |
| bl_addr + i); | |
| return -EIO; | |
| } | |
| } | |
| return 0; | |
| msg_fail: | |
| if (ret >= 0) | |
| ret = -EIO; | |
| dev_err(&client->dev, "Failed to read block at address 0x%04X: %d\n", | |
| bl_addr + i, ret); | |
| return ret; | |
| } | |
| static int iqs5xx_set_state(struct i2c_client *client, u8 state) | |
| { | |
| struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client); | |
| int error1, error2; | |
| if (!iqs5xx->dev_id_info.bl_status) | |
| return 0; | |
| guard(mutex)(&iqs5xx->lock); | |
| /* | |
| * Addressing the device outside of a communication window prompts it | |
| * to assert the RDY output, so disable the interrupt line to prevent | |
| * the handler from servicing a false interrupt. | |
| */ | |
| guard(disable_irq)(&client->irq); | |
| error1 = iqs5xx_write_byte(client, IQS5XX_SYS_CTRL1, state); | |
| error2 = iqs5xx_write_byte(client, IQS5XX_END_COMM, 0); | |
| usleep_range(50, 100); | |
| return error1 ?: error2; | |
| } | |
| static int iqs5xx_open(struct input_dev *input) | |
| { | |
| struct iqs5xx_private *iqs5xx = input_get_drvdata(input); | |
| return iqs5xx_set_state(iqs5xx->client, IQS5XX_RESUME); | |
| } | |
| static void iqs5xx_close(struct input_dev *input) | |
| { | |
| struct iqs5xx_private *iqs5xx = input_get_drvdata(input); | |
| iqs5xx_set_state(iqs5xx->client, IQS5XX_SUSPEND); | |
| } | |
| static int iqs5xx_axis_init(struct i2c_client *client) | |
| { | |
| struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client); | |
| struct touchscreen_properties *prop = &iqs5xx->prop; | |
| struct input_dev *input = iqs5xx->input; | |
| u16 max_x, max_y; | |
| int error; | |
| if (!input) { | |
| input = devm_input_allocate_device(&client->dev); | |
| if (!input) | |
| return -ENOMEM; | |
| input->name = client->name; | |
| input->id.bustype = BUS_I2C; | |
| input->open = iqs5xx_open; | |
| input->close = iqs5xx_close; | |
| input_set_drvdata(input, iqs5xx); | |
| iqs5xx->input = input; | |
| } | |
| error = iqs5xx_read_word(client, IQS5XX_X_RES, &max_x); | |
| if (error) | |
| return error; | |
| error = iqs5xx_read_word(client, IQS5XX_Y_RES, &max_y); | |
| if (error) | |
| return error; | |
| input_set_abs_params(input, ABS_MT_POSITION_X, 0, max_x, 0, 0); | |
| input_set_abs_params(input, ABS_MT_POSITION_Y, 0, max_y, 0, 0); | |
| input_set_abs_params(input, ABS_MT_PRESSURE, 0, U16_MAX, 0, 0); | |
| touchscreen_parse_properties(input, true, prop); | |
| /* | |
| * The device reserves 0xFFFF for coordinates that correspond to slots | |
| * which are not in a state of touch. | |
| */ | |
| if (prop->max_x >= U16_MAX || prop->max_y >= U16_MAX) { | |
| dev_err(&client->dev, "Invalid touchscreen size: %u*%u\n", | |
| prop->max_x, prop->max_y); | |
| return -EINVAL; | |
| } | |
| if (prop->max_x != max_x) { | |
| error = iqs5xx_write_word(client, IQS5XX_X_RES, prop->max_x); | |
| if (error) | |
| return error; | |
| } | |
| if (prop->max_y != max_y) { | |
| error = iqs5xx_write_word(client, IQS5XX_Y_RES, prop->max_y); | |
| if (error) | |
| return error; | |
| } | |
| error = input_mt_init_slots(input, IQS5XX_NUM_CONTACTS, | |
| INPUT_MT_DIRECT); | |
| if (error) | |
| dev_err(&client->dev, "Failed to initialize slots: %d\n", | |
| error); | |
| return error; | |
| } | |
| static int iqs5xx_dev_init(struct i2c_client *client) | |
| { | |
| struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client); | |
| struct iqs5xx_dev_id_info *dev_id_info; | |
| int error; | |
| u8 buf[sizeof(*dev_id_info) + 1]; | |
| error = iqs5xx_read_burst(client, IQS5XX_PROD_NUM, | |
| &buf[1], sizeof(*dev_id_info)); | |
| if (error) | |
| return iqs5xx_bl_open(client); | |
| /* | |
| * A000 and B000 devices use 8-bit and 16-bit addressing, respectively. | |
| * Querying an A000 device's version information with 16-bit addressing | |
| * gives the appearance that the data is shifted by one byte; a nonzero | |
| * leading array element suggests this could be the case (in which case | |
| * the missing zero is prepended). | |
| */ | |
| buf[0] = 0; | |
| dev_id_info = (struct iqs5xx_dev_id_info *)&buf[buf[1] ? 0 : 1]; | |
| switch (be16_to_cpu(dev_id_info->prod_num)) { | |
| case IQS5XX_PROD_NUM_IQS550: | |
| case IQS5XX_PROD_NUM_IQS572: | |
| case IQS5XX_PROD_NUM_IQS525: | |
| break; | |
| default: | |
| dev_err(&client->dev, "Unrecognized product number: %u\n", | |
| be16_to_cpu(dev_id_info->prod_num)); | |
| return -EINVAL; | |
| } | |
| /* | |
| * With the product number recognized yet shifted by one byte, open the | |
| * bootloader and wait for user space to convert the A000 device into a | |
| * B000 device via new firmware. | |
| */ | |
| if (buf[1]) { | |
| dev_err(&client->dev, "Opening bootloader for A000 device\n"); | |
| return iqs5xx_bl_open(client); | |
| } | |
| error = iqs5xx_read_burst(client, IQS5XX_EXP_FILE, | |
| iqs5xx->exp_file, sizeof(iqs5xx->exp_file)); | |
| if (error) | |
| return error; | |
| error = iqs5xx_axis_init(client); | |
| if (error) | |
| return error; | |
| error = iqs5xx_write_byte(client, IQS5XX_SYS_CTRL0, IQS5XX_ACK_RESET); | |
| if (error) | |
| return error; | |
| error = iqs5xx_write_byte(client, IQS5XX_SYS_CFG0, | |
| IQS5XX_SETUP_COMPLETE | IQS5XX_WDT | | |
| IQS5XX_ALP_REATI | IQS5XX_REATI); | |
| if (error) | |
| return error; | |
| error = iqs5xx_write_byte(client, IQS5XX_SYS_CFG1, | |
| IQS5XX_TP_EVENT | IQS5XX_EVENT_MODE); | |
| if (error) | |
| return error; | |
| error = iqs5xx_write_byte(client, IQS5XX_END_COMM, 0); | |
| if (error) | |
| return error; | |
| iqs5xx->dev_id_info = *dev_id_info; | |
| /* | |
| * The following delay allows ATI to complete before the open and close | |
| * callbacks are free to elicit I2C communication. Any attempts to read | |
| * from or write to the device during this time may face extended clock | |
| * stretching and prompt the I2C controller to report an error. | |
| */ | |
| msleep(250); | |
| return 0; | |
| } | |
| static irqreturn_t iqs5xx_irq(int irq, void *data) | |
| { | |
| struct iqs5xx_private *iqs5xx = data; | |
| struct iqs5xx_status status; | |
| struct i2c_client *client = iqs5xx->client; | |
| struct input_dev *input = iqs5xx->input; | |
| int error, i; | |
| /* | |
| * This check is purely a precaution, as the device does not assert the | |
| * RDY output during bootloader mode. If the device operates outside of | |
| * bootloader mode, the input device is guaranteed to be allocated. | |
| */ | |
| if (!iqs5xx->dev_id_info.bl_status) | |
| return IRQ_NONE; | |
| error = iqs5xx_read_burst(client, IQS5XX_SYS_INFO0, | |
| &status, sizeof(status)); | |
| if (error) | |
| return IRQ_NONE; | |
| if (status.sys_info[0] & IQS5XX_SHOW_RESET) { | |
| dev_err(&client->dev, "Unexpected device reset\n"); | |
| error = iqs5xx_dev_init(client); | |
| if (error) { | |
| dev_err(&client->dev, | |
| "Failed to re-initialize device: %d\n", error); | |
| return IRQ_NONE; | |
| } | |
| return IRQ_HANDLED; | |
| } | |
| for (i = 0; i < ARRAY_SIZE(status.touch_data); i++) { | |
| struct iqs5xx_touch_data *touch_data = &status.touch_data[i]; | |
| u16 pressure = be16_to_cpu(touch_data->strength); | |
| input_mt_slot(input, i); | |
| if (input_mt_report_slot_state(input, MT_TOOL_FINGER, | |
| pressure != 0)) { | |
| touchscreen_report_pos(input, &iqs5xx->prop, | |
| be16_to_cpu(touch_data->abs_x), | |
| be16_to_cpu(touch_data->abs_y), | |
| true); | |
| input_report_abs(input, ABS_MT_PRESSURE, pressure); | |
| } | |
| } | |
| input_mt_sync_frame(input); | |
| input_sync(input); | |
| error = iqs5xx_write_byte(client, IQS5XX_END_COMM, 0); | |
| if (error) | |
| return IRQ_NONE; | |
| /* | |
| * Once the communication window is closed, a small delay is added to | |
| * ensure the device's RDY output has been deasserted by the time the | |
| * interrupt handler returns. | |
| */ | |
| usleep_range(50, 100); | |
| return IRQ_HANDLED; | |
| } | |
| static int iqs5xx_fw_file_parse(struct i2c_client *client, | |
| const char *fw_file, u8 *pmap) | |
| { | |
| size_t pos = 0; | |
| int error, i; | |
| u16 rec_num = 1; | |
| u16 rec_addr; | |
| u8 rec_len, rec_type, rec_chksm, chksm; | |
| u8 rec_hdr[IQS5XX_REC_HDR_SIZE]; | |
| u8 rec_data[IQS5XX_REC_DATA_SIZE]; | |
| /* | |
| * Firmware exported from the vendor's configuration tool deviates from | |
| * standard ihex as follows: (1) the checksum for records corresponding | |
| * to user-exported settings is not recalculated, and (2) an address of | |
| * 0xFFFF is used for the EOF record. | |
| * | |
| * Because the ihex2fw tool tolerates neither (1) nor (2), the slightly | |
| * nonstandard ihex firmware is parsed directly by the driver. | |
| */ | |
| const struct firmware *fw __free(firmware) = NULL; | |
| error = request_firmware(&fw, fw_file, &client->dev); | |
| if (error) { | |
| dev_err(&client->dev, "Failed to request firmware %s: %d\n", | |
| fw_file, error); | |
| return error; | |
| } | |
| do { | |
| if (pos + IQS5XX_REC_HDR_LEN_HEX > fw->size) { | |
| dev_err(&client->dev, "Insufficient firmware size\n"); | |
| return -EINVAL; | |
| } | |
| if (fw->data[pos] != ':') { | |
| dev_err(&client->dev, "Invalid start at record %u\n", | |
| rec_num); | |
| return -EINVAL; | |
| } | |
| /* Convert all 3 fields (length, address, and type) in one go */ | |
| error = hex2bin(rec_hdr, &fw->data[pos + 1], sizeof(rec_hdr)); | |
| if (error) { | |
| dev_err(&client->dev, "Invalid header at record %u\n", | |
| rec_num); | |
| return error; | |
| } | |
| pos += IQS5XX_REC_HDR_LEN_HEX; | |
| rec_len = *rec_hdr; | |
| rec_addr = get_unaligned_be16(rec_hdr + sizeof(rec_len)); | |
| rec_type = *(rec_hdr + sizeof(rec_len) + sizeof(rec_addr)); | |
| /* | |
| * Check if we have enough data for the data portion of the | |
| * record, as well as the checksum byte. Everything is doubled | |
| * because data is in ASCII HEX and not binary format. | |
| */ | |
| if (pos + (rec_len + sizeof(rec_chksm)) * 2 > fw->size) { | |
| dev_err(&client->dev, "Insufficient firmware size\n"); | |
| return -EINVAL; | |
| } | |
| error = hex2bin(rec_data, &fw->data[pos], rec_len); | |
| if (error) { | |
| dev_err(&client->dev, "Invalid data at record %u\n", | |
| rec_num); | |
| return error; | |
| } | |
| pos += rec_len * 2; | |
| error = hex2bin(&rec_chksm, &fw->data[pos], sizeof(rec_chksm)); | |
| if (error) { | |
| dev_err(&client->dev, "Invalid checksum at record %u\n", | |
| rec_num); | |
| return error; | |
| } | |
| pos += 2; | |
| chksm = 0; | |
| for (i = 0; i < sizeof(rec_hdr); i++) | |
| chksm += rec_hdr[i]; | |
| for (i = 0; i < rec_len; i++) | |
| chksm += rec_data[i]; | |
| chksm = ~chksm + 1; | |
| if (chksm != rec_chksm && rec_addr < IQS5XX_CSTM) { | |
| dev_err(&client->dev, | |
| "Incorrect checksum at record %u\n", | |
| rec_num); | |
| error = -EINVAL; | |
| break; | |
| } | |
| switch (rec_type) { | |
| case IQS5XX_REC_TYPE_DATA: | |
| if (rec_addr < IQS5XX_CHKSM || | |
| rec_addr > IQS5XX_PMAP_END || | |
| rec_len > IQS5XX_PMAP_END + 1 - rec_addr) { | |
| dev_err(&client->dev, | |
| "Invalid address at record %u\n", | |
| rec_num); | |
| return -EINVAL; | |
| } | |
| memcpy(pmap + rec_addr - IQS5XX_CHKSM, | |
| rec_data, rec_len); | |
| break; | |
| case IQS5XX_REC_TYPE_EOF: | |
| break; | |
| default: | |
| dev_err(&client->dev, "Invalid type at record %u\n", | |
| rec_num); | |
| return -EINVAL; | |
| } | |
| rec_num++; | |
| while (pos < fw->size) { | |
| if (*(fw->data + pos) == ':') | |
| break; | |
| pos++; | |
| } | |
| } while (rec_type != IQS5XX_REC_TYPE_EOF); | |
| return 0; | |
| } | |
| static int iqs5xx_update_firmware(struct iqs5xx_private *iqs5xx, const u8 *pmap) | |
| { | |
| struct i2c_client *client = iqs5xx->client; | |
| int error; | |
| iqs5xx->dev_id_info.bl_status = 0; | |
| error = iqs5xx_bl_cmd(client, IQS5XX_BL_CMD_VER, 0); | |
| if (error) { | |
| error = iqs5xx_bl_open(client); | |
| if (error) | |
| return error; | |
| } | |
| error = iqs5xx_bl_write(client, IQS5XX_CHKSM, pmap, IQS5XX_PMAP_LEN); | |
| if (error) | |
| return error; | |
| error = iqs5xx_bl_cmd(client, IQS5XX_BL_CMD_CRC, 0); | |
| if (error) | |
| return error; | |
| error = iqs5xx_bl_verify(client, IQS5XX_CSTM, | |
| pmap + IQS5XX_CHKSM_LEN + IQS5XX_APP_LEN, | |
| IQS5XX_CSTM_LEN); | |
| if (error) | |
| return error; | |
| return 0; | |
| } | |
| static int iqs5xx_fw_file_write(struct i2c_client *client, const char *fw_file) | |
| { | |
| struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client); | |
| int error, error_init = 0; | |
| u8 *pmap __free(kfree) = kzalloc(IQS5XX_PMAP_LEN, GFP_KERNEL); | |
| if (!pmap) | |
| return -ENOMEM; | |
| error = iqs5xx_fw_file_parse(client, fw_file, pmap); | |
| if (error) | |
| return error; | |
| guard(mutex)(&iqs5xx->lock); | |
| /* | |
| * Disable the interrupt line in case the first attempt(s) to enter the | |
| * bootloader don't happen quickly enough, in which case the device may | |
| * assert the RDY output until the next attempt. | |
| */ | |
| guard(disable_irq)(&client->irq); | |
| error = iqs5xx_update_firmware(iqs5xx, pmap); | |
| iqs5xx_reset(client); | |
| usleep_range(15000, 15100); | |
| error_init = iqs5xx_dev_init(client); | |
| if (!iqs5xx->dev_id_info.bl_status) | |
| error_init = error_init ? : -EINVAL; | |
| return error ?: error_init; | |
| } | |
| static ssize_t fw_file_store(struct device *dev, | |
| struct device_attribute *attr, const char *buf, | |
| size_t count) | |
| { | |
| struct iqs5xx_private *iqs5xx = dev_get_drvdata(dev); | |
| struct i2c_client *client = iqs5xx->client; | |
| size_t len = count; | |
| bool input_reg = !iqs5xx->input; | |
| char fw_file[IQS5XX_FW_FILE_LEN + 1]; | |
| int error; | |
| if (!len) | |
| return -EINVAL; | |
| if (buf[len - 1] == '\n') | |
| len--; | |
| if (len > IQS5XX_FW_FILE_LEN) | |
| return -ENAMETOOLONG; | |
| memcpy(fw_file, buf, len); | |
| fw_file[len] = '\0'; | |
| error = iqs5xx_fw_file_write(client, fw_file); | |
| if (error) | |
| return error; | |
| /* | |
| * If the input device was not allocated already, it is guaranteed to | |
| * be allocated by this point and can finally be registered. | |
| */ | |
| if (input_reg) { | |
| error = input_register_device(iqs5xx->input); | |
| if (error) { | |
| dev_err(&client->dev, | |
| "Failed to register device: %d\n", | |
| error); | |
| return error; | |
| } | |
| } | |
| return count; | |
| } | |
| static ssize_t fw_info_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct iqs5xx_private *iqs5xx = dev_get_drvdata(dev); | |
| if (!iqs5xx->dev_id_info.bl_status) | |
| return -ENODATA; | |
| return sysfs_emit(buf, "%u.%u.%u.%u:%u.%u\n", | |
| be16_to_cpu(iqs5xx->dev_id_info.prod_num), | |
| be16_to_cpu(iqs5xx->dev_id_info.proj_num), | |
| iqs5xx->dev_id_info.major_ver, | |
| iqs5xx->dev_id_info.minor_ver, | |
| iqs5xx->exp_file[0], iqs5xx->exp_file[1]); | |
| } | |
| static DEVICE_ATTR_WO(fw_file); | |
| static DEVICE_ATTR_RO(fw_info); | |
| static struct attribute *iqs5xx_attrs[] = { | |
| &dev_attr_fw_file.attr, | |
| &dev_attr_fw_info.attr, | |
| NULL, | |
| }; | |
| static umode_t iqs5xx_attr_is_visible(struct kobject *kobj, | |
| struct attribute *attr, int i) | |
| { | |
| struct device *dev = kobj_to_dev(kobj); | |
| struct iqs5xx_private *iqs5xx = dev_get_drvdata(dev); | |
| if (attr == &dev_attr_fw_file.attr && | |
| (iqs5xx->dev_id_info.bl_status == IQS5XX_BL_STATUS_NONE || | |
| !iqs5xx->reset_gpio)) | |
| return 0; | |
| return attr->mode; | |
| } | |
| static const struct attribute_group iqs5xx_group = { | |
| .is_visible = iqs5xx_attr_is_visible, | |
| .attrs = iqs5xx_attrs, | |
| }; | |
| __ATTRIBUTE_GROUPS(iqs5xx); | |
| static int iqs5xx_suspend(struct device *dev) | |
| { | |
| struct iqs5xx_private *iqs5xx = dev_get_drvdata(dev); | |
| struct input_dev *input = iqs5xx->input; | |
| if (!input || device_may_wakeup(dev)) | |
| return 0; | |
| guard(mutex)(&input->mutex); | |
| if (input_device_enabled(input)) | |
| return iqs5xx_set_state(iqs5xx->client, IQS5XX_SUSPEND); | |
| return 0; | |
| } | |
| static int iqs5xx_resume(struct device *dev) | |
| { | |
| struct iqs5xx_private *iqs5xx = dev_get_drvdata(dev); | |
| struct input_dev *input = iqs5xx->input; | |
| if (!input || device_may_wakeup(dev)) | |
| return 0; | |
| guard(mutex)(&input->mutex); | |
| if (input_device_enabled(input)) | |
| return iqs5xx_set_state(iqs5xx->client, IQS5XX_RESUME); | |
| return 0; | |
| } | |
| static DEFINE_SIMPLE_DEV_PM_OPS(iqs5xx_pm, iqs5xx_suspend, iqs5xx_resume); | |
| static int iqs5xx_probe(struct i2c_client *client) | |
| { | |
| struct iqs5xx_private *iqs5xx; | |
| int error; | |
| iqs5xx = devm_kzalloc(&client->dev, sizeof(*iqs5xx), GFP_KERNEL); | |
| if (!iqs5xx) | |
| return -ENOMEM; | |
| i2c_set_clientdata(client, iqs5xx); | |
| iqs5xx->client = client; | |
| iqs5xx->reset_gpio = devm_gpiod_get_optional(&client->dev, | |
| "reset", GPIOD_OUT_LOW); | |
| if (IS_ERR(iqs5xx->reset_gpio)) { | |
| error = PTR_ERR(iqs5xx->reset_gpio); | |
| dev_err(&client->dev, "Failed to request GPIO: %d\n", error); | |
| return error; | |
| } | |
| mutex_init(&iqs5xx->lock); | |
| error = iqs5xx_dev_init(client); | |
| if (error) | |
| return error; | |
| error = devm_request_threaded_irq(&client->dev, client->irq, | |
| NULL, iqs5xx_irq, IRQF_ONESHOT, | |
| client->name, iqs5xx); | |
| if (error) { | |
| dev_err(&client->dev, "Failed to request IRQ: %d\n", error); | |
| return error; | |
| } | |
| if (iqs5xx->input) { | |
| error = input_register_device(iqs5xx->input); | |
| if (error) | |
| dev_err(&client->dev, | |
| "Failed to register device: %d\n", | |
| error); | |
| } | |
| return error; | |
| } | |
| static const struct i2c_device_id iqs5xx_id[] = { | |
| { .name = "iqs550" }, | |
| { .name = "iqs572" }, | |
| { .name = "iqs525" }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(i2c, iqs5xx_id); | |
| static const struct of_device_id iqs5xx_of_match[] = { | |
| { .compatible = "azoteq,iqs550" }, | |
| { .compatible = "azoteq,iqs572" }, | |
| { .compatible = "azoteq,iqs525" }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(of, iqs5xx_of_match); | |
| static struct i2c_driver iqs5xx_i2c_driver = { | |
| .driver = { | |
| .name = "iqs5xx", | |
| .dev_groups = iqs5xx_groups, | |
| .of_match_table = iqs5xx_of_match, | |
| .pm = pm_sleep_ptr(&iqs5xx_pm), | |
| }, | |
| .id_table = iqs5xx_id, | |
| .probe = iqs5xx_probe, | |
| }; | |
| module_i2c_driver(iqs5xx_i2c_driver); | |
| MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>"); | |
| MODULE_DESCRIPTION("Azoteq IQS550/572/525 Trackpad/Touchscreen Controller"); | |
| MODULE_LICENSE("GPL"); |