// SPDX-License-Identifier: GPL-2.0+
/*
* Azoteq IQS550/572/525 Trackpad/Touchscreen Controller
*
* Copyright (C) 2018
* Author: 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 data sheet: http://www.azoteq.com/
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/firmware.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/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <asm/unaligned.h>
#define IQS5XX_FW_FILE_LEN 64
#define IQS5XX_NUM_RETRIES 10
#define IQS5XX_NUM_POINTS 256
#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_PROJ_NUM_A000 0
#define IQS5XX_PROJ_NUM_B000 15
#define IQS5XX_MAJOR_VER_MIN 2
#define IQS5XX_RESUME 0x00
#define IQS5XX_SUSPEND 0x01
#define IQS5XX_SW_INPUT_EVENT 0x10
#define IQS5XX_SETUP_COMPLETE 0x40
#define IQS5XX_EVENT_MODE 0x01
#define IQS5XX_TP_EVENT 0x04
#define IQS5XX_FLIP_X 0x01
#define IQS5XX_FLIP_Y 0x02
#define IQS5XX_SWITCH_XY_AXIS 0x04
#define IQS5XX_PROD_NUM 0x0000
#define IQS5XX_ABS_X 0x0016
#define IQS5XX_ABS_Y 0x0018
#define IQS5XX_SYS_CTRL0 0x0431
#define IQS5XX_SYS_CTRL1 0x0432
#define IQS5XX_SYS_CFG0 0x058E
#define IQS5XX_SYS_CFG1 0x058F
#define IQS5XX_TOTAL_RX 0x063D
#define IQS5XX_TOTAL_TX 0x063E
#define IQS5XX_XY_CFG0 0x0669
#define IQS5XX_X_RES 0x066E
#define IQS5XX_Y_RES 0x0670
#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)
#define IQS5XX_REC_HDR_LEN 4
#define IQS5XX_REC_LEN_MAX 255
#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_RESET 0x00
#define IQS5XX_BL_STATUS_AVAIL 0xA5
#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_private {
struct i2c_client *client;
struct input_dev *input;
struct gpio_desc *reset_gpio;
struct mutex lock;
u8 bl_status;
};
struct iqs5xx_dev_id_info {
__be16 prod_num;
__be16 proj_num;
u8 major_ver;
u8 minor_ver;
u8 bl_status;
} __packed;
struct iqs5xx_ihex_rec {
char start;
char len[2];
char addr[4];
char type[2];
char data[2];
} __packed;
struct iqs5xx_touch_data {
__be16 abs_x;
__be16 abs_y;
__be16 strength;
u8 area;
} __packed;
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,