cregit-Linux how code gets into the kernel

Release 4.7 drivers/input/touchscreen/wdt87xx_i2c.c

/*
 * Weida HiTech WDT87xx TouchScreen I2C driver
 *
 * Copyright (c) 2015  Weida Hi-Tech Co., Ltd.
 * HN Chen <hn.chen@weidahitech.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 */

#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/firmware.h>
#include <linux/input/mt.h>
#include <linux/acpi.h>
#include <asm/unaligned.h>


#define WDT87XX_NAME		"wdt87xx_i2c"

#define WDT87XX_DRV_VER		"0.9.7"

#define WDT87XX_FW_NAME		"wdt87xx_fw.bin"

#define WDT87XX_CFG_NAME	"wdt87xx_cfg.bin"


#define MODE_ACTIVE			0x01

#define MODE_READY			0x02

#define MODE_IDLE			0x03

#define MODE_SLEEP			0x04

#define MODE_STOP			0xFF


#define WDT_MAX_FINGER			10

#define WDT_RAW_BUF_COUNT		54

#define WDT_V1_RAW_BUF_COUNT		74

#define WDT_FIRMWARE_ID			0xa9e368f5


#define PG_SIZE				0x1000

#define MAX_RETRIES			3


#define MAX_UNIT_AXIS			0x7FFF


#define PKT_READ_SIZE			72

#define PKT_WRITE_SIZE			80

/* the finger definition of the report event */

#define FINGER_EV_OFFSET_ID		0

#define FINGER_EV_OFFSET_X		1

#define FINGER_EV_OFFSET_Y		3

#define FINGER_EV_SIZE			5


#define FINGER_EV_V1_OFFSET_ID		0

#define FINGER_EV_V1_OFFSET_W		1

#define FINGER_EV_V1_OFFSET_P		2

#define FINGER_EV_V1_OFFSET_X		3

#define FINGER_EV_V1_OFFSET_Y		5

#define FINGER_EV_V1_SIZE		7

/* The definition of a report packet */

#define TOUCH_PK_OFFSET_REPORT_ID	0

#define TOUCH_PK_OFFSET_EVENT		1

#define TOUCH_PK_OFFSET_SCAN_TIME	51

#define TOUCH_PK_OFFSET_FNGR_NUM	53


#define TOUCH_PK_V1_OFFSET_REPORT_ID	0

#define TOUCH_PK_V1_OFFSET_EVENT	1

#define TOUCH_PK_V1_OFFSET_SCAN_TIME	71

#define TOUCH_PK_V1_OFFSET_FNGR_NUM	73

/* The definition of the controller parameters */

#define CTL_PARAM_OFFSET_FW_ID		0

#define CTL_PARAM_OFFSET_PLAT_ID	2

#define CTL_PARAM_OFFSET_XMLS_ID1	4

#define CTL_PARAM_OFFSET_XMLS_ID2	6

#define CTL_PARAM_OFFSET_PHY_CH_X	8

#define CTL_PARAM_OFFSET_PHY_CH_Y	10

#define CTL_PARAM_OFFSET_PHY_X0		12

#define CTL_PARAM_OFFSET_PHY_X1		14

#define CTL_PARAM_OFFSET_PHY_Y0		16

#define CTL_PARAM_OFFSET_PHY_Y1		18

#define CTL_PARAM_OFFSET_PHY_W		22

#define CTL_PARAM_OFFSET_PHY_H		24

#define CTL_PARAM_OFFSET_FACTOR		32

/* The definition of the device descriptor */

#define WDT_GD_DEVICE			1

#define DEV_DESC_OFFSET_VID		8

#define DEV_DESC_OFFSET_PID		10

/* Communication commands */

#define PACKET_SIZE			56

#define VND_REQ_READ			0x06

#define VND_READ_DATA			0x07

#define VND_REQ_WRITE			0x08


#define VND_CMD_START			0x00

#define VND_CMD_STOP			0x01

#define VND_CMD_RESET			0x09


#define VND_CMD_ERASE			0x1A


#define VND_GET_CHECKSUM		0x66


#define VND_SET_DATA			0x83

#define VND_SET_COMMAND_DATA		0x84

#define VND_SET_CHECKSUM_CALC		0x86

#define VND_SET_CHECKSUM_LENGTH		0x87


#define VND_CMD_SFLCK			0xFC

#define VND_CMD_SFUNL			0xFD


#define CMD_SFLCK_KEY			0xC39B

#define CMD_SFUNL_KEY			0x95DA


#define STRIDX_PLATFORM_ID		0x80

#define STRIDX_PARAMETERS		0x81


#define CMD_BUF_SIZE			8

#define PKT_BUF_SIZE			64

/* The definition of the command packet */

#define CMD_REPORT_ID_OFFSET		0x0

#define CMD_TYPE_OFFSET			0x1

#define CMD_INDEX_OFFSET		0x2

#define CMD_KEY_OFFSET			0x3

#define CMD_LENGTH_OFFSET		0x4

#define CMD_DATA_OFFSET			0x8

/* The definition of firmware chunk tags */

#define FOURCC_ID_RIFF			0x46464952

#define FOURCC_ID_WHIF			0x46494857

#define FOURCC_ID_FRMT			0x544D5246

#define FOURCC_ID_FRWR			0x52575246

#define FOURCC_ID_CNFG			0x47464E43


#define CHUNK_ID_FRMT			FOURCC_ID_FRMT

#define CHUNK_ID_FRWR			FOURCC_ID_FRWR

#define CHUNK_ID_CNFG			FOURCC_ID_CNFG


#define FW_FOURCC1_OFFSET		0

#define FW_SIZE_OFFSET			4

#define FW_FOURCC2_OFFSET		8

#define FW_PAYLOAD_OFFSET		40


#define FW_CHUNK_ID_OFFSET		0

#define FW_CHUNK_SIZE_OFFSET		4

#define FW_CHUNK_TGT_START_OFFSET	8

#define FW_CHUNK_PAYLOAD_LEN_OFFSET	12

#define FW_CHUNK_SRC_START_OFFSET	16

#define FW_CHUNK_VERSION_OFFSET		20

#define FW_CHUNK_ATTR_OFFSET		24

#define FW_CHUNK_PAYLOAD_OFFSET		32

/* Controller requires minimum 300us between commands */

#define WDT_COMMAND_DELAY_MS		2

#define WDT_FLASH_WRITE_DELAY_MS	4

#define WDT_FW_RESET_TIME		2500


struct wdt87xx_sys_param {
	
u16	fw_id;
	
u16	plat_id;
	
u16	xmls_id1;
	
u16	xmls_id2;
	
u16	phy_ch_x;
	
u16	phy_ch_y;
	
u16	phy_w;
	
u16	phy_h;
	
u16	scaling_factor;
	
u32	max_x;
	
u32	max_y;
	
u16	vendor_id;
	
u16	product_id;
};


struct wdt87xx_data {
	
struct i2c_client		*client;
	
struct input_dev		*input;
	/* Mutex for fw update to prevent concurrent access */
	
struct mutex			fw_mutex;
	
struct wdt87xx_sys_param	param;
	
u8				phys[32];
};


static int wdt87xx_i2c_xfer(struct i2c_client *client, void *txdata, size_t txlen, void *rxdata, size_t rxlen) { struct i2c_msg msgs[] = { { .addr = client->addr, .flags = 0, .len = txlen, .buf = txdata, }, { .addr = client->addr, .flags = I2C_M_RD, .len = rxlen, .buf = rxdata, }, }; int error; int ret; ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); if (ret != ARRAY_SIZE(msgs)) { error = ret < 0 ? ret : -EIO; dev_err(&client->dev, "%s: i2c transfer failed: %d\n", __func__, error); return error; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen144100.00%1100.00%
Total144100.00%1100.00%


static int wdt87xx_get_desc(struct i2c_client *client, u8 desc_idx, u8 *buf, size_t len) { u8 tx_buf[] = { 0x22, 0x00, 0x10, 0x0E, 0x23, 0x00 }; int error; tx_buf[2] |= desc_idx & 0xF; error = wdt87xx_i2c_xfer(client, tx_buf, sizeof(tx_buf), buf, len); if (error) { dev_err(&client->dev, "get desc failed: %d\n", error); return error; } if (buf[0] != len) { dev_err(&client->dev, "unexpected response to get desc: %d\n", buf[0]); return -EINVAL; } mdelay(WDT_COMMAND_DELAY_MS); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen128100.00%1100.00%
Total128100.00%1100.00%


static int wdt87xx_get_string(struct i2c_client *client, u8 str_idx, u8 *buf, size_t len) { u8 tx_buf[] = { 0x22, 0x00, 0x13, 0x0E, str_idx, 0x23, 0x00 }; u8 rx_buf[PKT_WRITE_SIZE]; size_t rx_len = len + 2; int error; if (rx_len > sizeof(rx_buf)) return -EINVAL; error = wdt87xx_i2c_xfer(client, tx_buf, sizeof(tx_buf), rx_buf, rx_len); if (error) { dev_err(&client->dev, "get string failed: %d\n", error); return error; } if (rx_buf[1] != 0x03) { dev_err(&client->dev, "unexpected response to get string: %d\n", rx_buf[1]); return -EINVAL; } rx_len = min_t(size_t, len, rx_buf[0]); memcpy(buf, &rx_buf[2], rx_len); mdelay(WDT_COMMAND_DELAY_MS); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen174100.00%1100.00%
Total174100.00%1100.00%


static int wdt87xx_get_feature(struct i2c_client *client, u8 *buf, size_t buf_size) { u8 tx_buf[8]; u8 rx_buf[PKT_WRITE_SIZE]; size_t tx_len = 0; size_t rx_len = buf_size + 2; int error; if (rx_len > sizeof(rx_buf)) return -EINVAL; /* Get feature command packet */ tx_buf[tx_len++] = 0x22; tx_buf[tx_len++] = 0x00; if (buf[CMD_REPORT_ID_OFFSET] > 0xF) { tx_buf[tx_len++] = 0x30; tx_buf[tx_len++] = 0x02; tx_buf[tx_len++] = buf[CMD_REPORT_ID_OFFSET]; } else { tx_buf[tx_len++] = 0x30 | buf[CMD_REPORT_ID_OFFSET]; tx_buf[tx_len++] = 0x02; } tx_buf[tx_len++] = 0x23; tx_buf[tx_len++] = 0x00; error = wdt87xx_i2c_xfer(client, tx_buf, tx_len, rx_buf, rx_len); if (error) { dev_err(&client->dev, "get feature failed: %d\n", error); return error; } rx_len = min_t(size_t, buf_size, get_unaligned_le16(rx_buf)); memcpy(buf, &rx_buf[2], rx_len); mdelay(WDT_COMMAND_DELAY_MS); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen224100.00%1100.00%
Total224100.00%1100.00%


static int wdt87xx_set_feature(struct i2c_client *client, const u8 *buf, size_t buf_size) { u8 tx_buf[PKT_WRITE_SIZE]; int tx_len = 0; int error; /* Set feature command packet */ tx_buf[tx_len++] = 0x22; tx_buf[tx_len++] = 0x00; if (buf[CMD_REPORT_ID_OFFSET] > 0xF) { tx_buf[tx_len++] = 0x30; tx_buf[tx_len++] = 0x03; tx_buf[tx_len++] = buf[CMD_REPORT_ID_OFFSET]; } else { tx_buf[tx_len++] = 0x30 | buf[CMD_REPORT_ID_OFFSET]; tx_buf[tx_len++] = 0x03; } tx_buf[tx_len++] = 0x23; tx_buf[tx_len++] = 0x00; tx_buf[tx_len++] = (buf_size & 0xFF); tx_buf[tx_len++] = ((buf_size & 0xFF00) >> 8); if (tx_len + buf_size > sizeof(tx_buf)) return -EINVAL; memcpy(&tx_buf[tx_len], buf, buf_size); tx_len += buf_size; error = i2c_master_send(client, tx_buf, tx_len); if (error < 0) { dev_err(&client->dev, "set feature failed: %d\n", error); return error; } mdelay(WDT_COMMAND_DELAY_MS); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen230100.00%1100.00%
Total230100.00%1100.00%


static int wdt87xx_send_command(struct i2c_client *client, int cmd, int value) { u8 cmd_buf[CMD_BUF_SIZE]; /* Set the command packet */ cmd_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_WRITE; cmd_buf[CMD_TYPE_OFFSET] = VND_SET_COMMAND_DATA; put_unaligned_le16((u16)cmd, &cmd_buf[CMD_INDEX_OFFSET]); switch (cmd) { case VND_CMD_START: case VND_CMD_STOP: case VND_CMD_RESET: /* Mode selector */ put_unaligned_le32((value & 0xFF), &cmd_buf[CMD_LENGTH_OFFSET]); break; case VND_CMD_SFLCK: put_unaligned_le16(CMD_SFLCK_KEY, &cmd_buf[CMD_KEY_OFFSET]); break; case VND_CMD_SFUNL: put_unaligned_le16(CMD_SFUNL_KEY, &cmd_buf[CMD_KEY_OFFSET]); break; case VND_CMD_ERASE: case VND_SET_CHECKSUM_CALC: case VND_SET_CHECKSUM_LENGTH: put_unaligned_le32(value, &cmd_buf[CMD_KEY_OFFSET]); break; default: cmd_buf[CMD_REPORT_ID_OFFSET] = 0; dev_err(&client->dev, "Invalid command: %d\n", cmd); return -EINVAL; } return wdt87xx_set_feature(client, cmd_buf, sizeof(cmd_buf)); }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen172100.00%1100.00%
Total172100.00%1100.00%


static int wdt87xx_sw_reset(struct i2c_client *client) { int error; dev_dbg(&client->dev, "resetting device now\n"); error = wdt87xx_send_command(client, VND_CMD_RESET, 0); if (error) { dev_err(&client->dev, "reset failed\n"); return error; } /* Wait the device to be ready */ msleep(WDT_FW_RESET_TIME); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen63100.00%2100.00%
Total63100.00%2100.00%


static const void *wdt87xx_get_fw_chunk(const struct firmware *fw, u32 id) { size_t pos = FW_PAYLOAD_OFFSET; u32 chunk_id, chunk_size; while (pos < fw->size) { chunk_id = get_unaligned_le32(fw->data + pos + FW_CHUNK_ID_OFFSET); if (chunk_id == id) return fw->data + pos; chunk_size = get_unaligned_le32(fw->data + pos + FW_CHUNK_SIZE_OFFSET); pos += chunk_size + 2 * sizeof(u32); /* chunk ID + size */ } return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen91100.00%1100.00%
Total91100.00%1100.00%


static int wdt87xx_get_sysparam(struct i2c_client *client, struct wdt87xx_sys_param *param) { u8 buf[PKT_READ_SIZE]; int error; error = wdt87xx_get_desc(client, WDT_GD_DEVICE, buf, 18); if (error) { dev_err(&client->dev, "failed to get device desc\n"); return error; } param->vendor_id = get_unaligned_le16(buf + DEV_DESC_OFFSET_VID); param->product_id = get_unaligned_le16(buf + DEV_DESC_OFFSET_PID); error = wdt87xx_get_string(client, STRIDX_PARAMETERS, buf, 34); if (error) { dev_err(&client->dev, "failed to get parameters\n"); return error; } param->xmls_id1 = get_unaligned_le16(buf + CTL_PARAM_OFFSET_XMLS_ID1); param->xmls_id2 = get_unaligned_le16(buf + CTL_PARAM_OFFSET_XMLS_ID2); param->phy_ch_x = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_CH_X); param->phy_ch_y = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_CH_Y); param->phy_w = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_W) / 10; param->phy_h = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_H) / 10; /* Get the scaling factor of pixel to logical coordinate */ param->scaling_factor = get_unaligned_le16(buf + CTL_PARAM_OFFSET_FACTOR); param->max_x = MAX_UNIT_AXIS; param->max_y = DIV_ROUND_CLOSEST(MAX_UNIT_AXIS * param->phy_h, param->phy_w); error = wdt87xx_get_string(client, STRIDX_PLATFORM_ID, buf, 8); if (error) { dev_err(&client->dev, "failed to get platform id\n"); return error; } param->plat_id = buf[1]; buf[0] = 0xf2; error = wdt87xx_get_feature(client, buf, 16); if (error) { dev_err(&client->dev, "failed to get firmware id\n"); return error; } if (buf[0] != 0xf2) { dev_err(&client->dev, "wrong id of fw response: 0x%x\n", buf[0]); return -EINVAL; } param->fw_id = get_unaligned_le16(&buf[1]); dev_info(&client->dev, "fw_id: 0x%x, plat_id: 0x%x, xml_id1: %04x, xml_id2: %04x\n", param->fw_id, param->plat_id, param->xmls_id1, param->xmls_id2); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen36599.73%375.00%
dmitry torokhovdmitry torokhov10.27%125.00%
Total366100.00%4100.00%


static int wdt87xx_validate_firmware(struct wdt87xx_data *wdt, const struct firmware *fw) { const void *fw_chunk; u32 data1, data2; u32 size; u8 fw_chip_id; u8 chip_id; data1 = get_unaligned_le32(fw->data + FW_FOURCC1_OFFSET); data2 = get_unaligned_le32(fw->data + FW_FOURCC2_OFFSET); if (data1 != FOURCC_ID_RIFF || data2 != FOURCC_ID_WHIF) { dev_err(&wdt->client->dev, "check fw tag failed\n"); return -EINVAL; } size = get_unaligned_le32(fw->data + FW_SIZE_OFFSET); if (size != fw->size) { dev_err(&wdt->client->dev, "fw size mismatch: expected %d, actual %zu\n", size, fw->size); return -EINVAL; } /* * Get the chip_id from the firmware. Make sure that it is the * right controller to do the firmware and config update. */ fw_chunk = wdt87xx_get_fw_chunk(fw, CHUNK_ID_FRWR); if (!fw_chunk) { dev_err(&wdt->client->dev, "unable to locate firmware chunk\n"); return -EINVAL; } fw_chip_id = (get_unaligned_le32(fw_chunk + FW_CHUNK_VERSION_OFFSET) >> 12) & 0xF; chip_id = (wdt->param.fw_id >> 12) & 0xF; if (fw_chip_id != chip_id) { dev_err(&wdt->client->dev, "fw version mismatch: fw %d vs. chip %d\n", fw_chip_id, chip_id); return -ENODEV; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen22199.55%150.00%
dmitry torokhovdmitry torokhov10.45%150.00%
Total222100.00%2100.00%


static int wdt87xx_validate_fw_chunk(const void *data, int id) { if (id == CHUNK_ID_FRWR) { u32 fw_id; fw_id = get_unaligned_le32(data + FW_CHUNK_PAYLOAD_OFFSET); if (fw_id != WDT_FIRMWARE_ID) return -EINVAL; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen47100.00%1100.00%
Total47100.00%1100.00%


static int wdt87xx_write_data(struct i2c_client *client, const char *data, u32 address, int length) { u16 packet_size; int count = 0; int error; u8 pkt_buf[PKT_BUF_SIZE]; /* Address and length should be 4 bytes aligned */ if ((address & 0x3) != 0 || (length & 0x3) != 0) { dev_err(&client->dev, "addr & len must be 4 bytes aligned %x, %x\n", address, length); return -EINVAL; } while (length) { packet_size = min(length, PACKET_SIZE); pkt_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_WRITE; pkt_buf[CMD_TYPE_OFFSET] = VND_SET_DATA; put_unaligned_le16(packet_size, &pkt_buf[CMD_INDEX_OFFSET]); put_unaligned_le32(address, &pkt_buf[CMD_LENGTH_OFFSET]); memcpy(&pkt_buf[CMD_DATA_OFFSET], data, packet_size); error = wdt87xx_set_feature(client, pkt_buf, sizeof(pkt_buf)); if (error) return error; length -= packet_size; data += packet_size; address += packet_size; /* Wait for the controller to finish the write */ mdelay(WDT_FLASH_WRITE_DELAY_MS); if ((++count % 32) == 0) { /* Delay for fw to clear watch dog */ msleep(20); } } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen203100.00%1100.00%
Total203100.00%1100.00%


static u16 misr(u16 cur_value, u8 new_value) { u32 a, b; u32 bit0; u32 y; a = cur_value; b = new_value; bit0 = a ^ (b & 1); bit0 ^= a >> 1; bit0 ^= a >> 2; bit0 ^= a >> 4; bit0 ^= a >> 5; bit0 ^= a >> 7; bit0 ^= a >> 11; bit0 ^= a >> 15; y = (a << 1) ^ b; y = (y & ~1) | (bit0 & 1); return (u16)y; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen114100.00%1100.00%
Total114100.00%1100.00%


static u16 wdt87xx_calculate_checksum(const u8 *data, size_t length) { u16 checksum = 0; size_t i; for (i = 0; i < length; i++) checksum = misr(checksum, data[i]); return checksum; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen50100.00%1100.00%
Total50100.00%1100.00%


static int wdt87xx_get_checksum(struct i2c_client *client, u16 *checksum, u32 address, int length) { int error; int time_delay; u8 pkt_buf[PKT_BUF_SIZE]; u8 cmd_buf[CMD_BUF_SIZE]; error = wdt87xx_send_command(client, VND_SET_CHECKSUM_LENGTH, length); if (error) { dev_err(&client->dev, "failed to set checksum length\n"); return error; } error = wdt87xx_send_command(client, VND_SET_CHECKSUM_CALC, address); if (error) { dev_err(&client->dev, "failed to set checksum address\n"); return error; } /* Wait the operation to complete */ time_delay = DIV_ROUND_UP(length, 1024); msleep(time_delay * 30); memset(cmd_buf, 0, sizeof(cmd_buf)); cmd_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_READ; cmd_buf[CMD_TYPE_OFFSET] = VND_GET_CHECKSUM; error = wdt87xx_set_feature(client, cmd_buf, sizeof(cmd_buf)); if (error) { dev_err(&client->dev, "failed to request checksum\n"); return error; } memset(pkt_buf, 0, sizeof(pkt_buf)); pkt_buf[CMD_REPORT_ID_OFFSET] = VND_READ_DATA; error = wdt87xx_get_feature(client, pkt_buf, sizeof(pkt_buf)); if (error) { dev_err(&client->dev, "failed to read checksum\n"); return error; } *checksum = get_unaligned_le16(&pkt_buf[CMD_DATA_OFFSET]); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen242100.00%1100.00%
Total242100.00%1100.00%


static int wdt87xx_write_firmware(struct i2c_client *client, const void *chunk) { u32 start_addr = get_unaligned_le32(chunk + FW_CHUNK_TGT_START_OFFSET); u32 size = get_unaligned_le32(chunk + FW_CHUNK_PAYLOAD_LEN_OFFSET); const void *data = chunk + FW_CHUNK_PAYLOAD_OFFSET; int error; int err1; int page_size; int retry = 0; u16 device_checksum, firmware_checksum; dev_dbg(&client->dev, "start 4k page program\n"); error = wdt87xx_send_command(client, VND_CMD_STOP, MODE_STOP); if (error) { dev_err(&client->dev, "stop report mode failed\n"); return error; } error = wdt87xx_send_command(client, VND_CMD_SFUNL, 0); if (error) { dev_err(&client->dev, "unlock failed\n"); goto out_enable_reporting; } mdelay(10); while (size) { dev_dbg(&client->dev, "%s: %x, %x\n", __func__, start_addr, size); page_size = min_t(u32, size, PG_SIZE); size -= page_size; for (retry = 0; retry < MAX_RETRIES; retry++) { error = wdt87xx_send_command(client, VND_CMD_ERASE, start_addr); if (error) { dev_err(&client->dev, "erase failed at %#08x\n", start_addr); break; } msleep(50); error = wdt87xx_write_data(client, data, start_addr, page_size); if (error) { dev_err(&client->dev, "write failed at %#08x (%d bytes)\n", start_addr, page_size); break; } error = wdt87xx_get_checksum(client, &device_checksum, start_addr, page_size); if (error) { dev_err(&client->dev, "failed to retrieve checksum for %#08x (len: %d)\n", start_addr, page_size); break; } firmware_checksum = wdt87xx_calculate_checksum(data, page_size); if (device_checksum == firmware_checksum) break; dev_err(&client->dev, "checksum fail: %d vs %d, retry %d\n", device_checksum, firmware_checksum, retry); } if (retry == MAX_RETRIES) { dev_err(&client->dev, "page write failed\n"); error = -EIO; goto out_lock_device; } start_addr = start_addr + page_size; data = data + page_size; } out_lock_device: err1 = wdt87xx_send_command(client, VND_CMD_SFLCK, 0); if (err1) dev_err(&client->dev, "lock failed\n"); mdelay(10); out_enable_reporting: err1 = wdt87xx_send_command(client, VND_CMD_START, 0); if (err1) dev_err(&client->dev, "start to report failed\n"); return error ? error : err1; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen431100.00%1100.00%
Total431100.00%1100.00%


static int wdt87xx_load_chunk(struct i2c_client *client, const struct firmware *fw, u32 ck_id) { const void *chunk; int error; chunk = wdt87xx_get_fw_chunk(fw, ck_id); if (!chunk) { dev_err(&client->dev, "unable to locate chunk (type %d)\n", ck_id); return -EINVAL; } error = wdt87xx_validate_fw_chunk(chunk, ck_id); if (error) { dev_err(&client->dev, "invalid chunk (type %d): %d\n", ck_id, error); return error; } error = wdt87xx_write_firmware(client, chunk); if (error) { dev_err(&client->dev, "failed to write fw chunk (type %d): %d\n", ck_id, error); return error; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen127100.00%1100.00%
Total127100.00%1100.00%


static int wdt87xx_do_update_firmware(struct i2c_client *client, const struct firmware *fw, unsigned int chunk_id) { struct wdt87xx_data *wdt = i2c_get_clientdata(client); int error; error = wdt87xx_validate_firmware(wdt, fw); if (error) return error; error = mutex_lock_interruptible(&wdt->fw_mutex); if (error) return error; disable_irq(client->irq); error = wdt87xx_load_chunk(client, fw, chunk_id); if (error) { dev_err(&client->dev, "firmware load failed (type: %d): %d\n", chunk_id, error); goto out; } error = wdt87xx_sw_reset(client); if (error) { dev_err(&client->dev, "soft reset failed: %d\n", error); goto out; } /* Refresh the parameters */ error = wdt87xx_get_sysparam(client, &wdt->param); if (error) dev_err(&client->dev, "failed to refresh system parameters: %d\n", error); out: enable_irq(client->irq); mutex_unlock(&wdt->fw_mutex); return error ? error : 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen18899.47%150.00%
masanari iidamasanari iida10.53%150.00%
Total189100.00%2100.00%


static int wdt87xx_update_firmware(struct device *dev, const char *fw_name, unsigned int chunk_id) { struct i2c_client *client = to_i2c_client(dev); const struct firmware *fw; int error; error = request_firmware(&fw, fw_name, dev); if (error) { dev_err(&client->dev, "unable to retrieve firmware %s: %d\n", fw_name, error); return error; } error = wdt87xx_do_update_firmware(client, fw, chunk_id); release_firmware(fw); return error ? error : 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen97100.00%1100.00%
Total97100.00%1100.00%


static ssize_t config_csum_show(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct wdt87xx_data *wdt = i2c_get_clientdata(client); u32 cfg_csum; cfg_csum = wdt->param.xmls_id1; cfg_csum = (cfg_csum << 16) | wdt->param.xmls_id2; return scnprintf(buf, PAGE_SIZE, "%x\n", cfg_csum); }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen77100.00%1100.00%
Total77100.00%1100.00%


static ssize_t fw_version_show(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct wdt87xx_data *wdt = i2c_get_clientdata(client); return scnprintf(buf, PAGE_SIZE, "%x\n", wdt->param.fw_id); }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen56100.00%1100.00%
Total56100.00%1100.00%


static ssize_t plat_id_show(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct wdt87xx_data *wdt = i2c_get_clientdata(client); return scnprintf(buf, PAGE_SIZE, "%x\n", wdt->param.plat_id); }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen56100.00%1100.00%
Total56100.00%1100.00%


static ssize_t update_config_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int error; error = wdt87xx_update_firmware(dev, WDT87XX_CFG_NAME, CHUNK_ID_CNFG); return error ? error : count; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen45100.00%1100.00%
Total45100.00%1100.00%


static ssize_t update_fw_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int error; error = wdt87xx_update_firmware(dev, WDT87XX_FW_NAME, CHUNK_ID_FRWR); return error ? error : count; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen45100.00%1100.00%
Total45100.00%1100.00%

static DEVICE_ATTR_RO(config_csum); static DEVICE_ATTR_RO(fw_version); static DEVICE_ATTR_RO(plat_id); static DEVICE_ATTR_WO(update_config); static DEVICE_ATTR_WO(update_fw); static struct attribute *wdt87xx_attrs[] = { &dev_attr_config_csum.attr, &dev_attr_fw_version.attr, &dev_attr_plat_id.attr, &dev_attr_update_config.attr, &dev_attr_update_fw.attr, NULL }; static const struct attribute_group wdt87xx_attr_group = { .attrs = wdt87xx_attrs, };
static void wdt87xx_report_contact(struct input_dev *input, struct wdt87xx_sys_param *param, u8 *buf) { int finger_id; u32 x, y, w; u8 p; finger_id = (buf[FINGER_EV_V1_OFFSET_ID] >> 3) - 1; if (finger_id < 0) return; /* Check if this is an active contact */ if (!(buf[FINGER_EV_V1_OFFSET_ID] & 0x1)) return; w = buf[FINGER_EV_V1_OFFSET_W]; w *= param->scaling_factor; p = buf[FINGER_EV_V1_OFFSET_P]; x = get_unaligned_le16(buf + FINGER_EV_V1_OFFSET_X); y = get_unaligned_le16(buf + FINGER_EV_V1_OFFSET_Y); y = DIV_ROUND_CLOSEST(y * param->phy_h, param->phy_w); /* Refuse incorrect coordinates */ if (x > param->max_x || y > param->max_y) return; dev_dbg(input->dev.parent, "tip on (%d), x(%d), y(%d)\n", finger_id, x, y); input_mt_slot(input, finger_id); input_mt_report_slot_state(input, MT_TOOL_FINGER, 1); input_report_abs(input, ABS_MT_TOUCH_MAJOR, w); input_report_abs(input, ABS_MT_PRESSURE, p); input_report_abs(input, ABS_MT_POSITION_X, x); input_report_abs(input, ABS_MT_POSITION_Y, y); }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen205100.00%2100.00%
Total205100.00%2100.00%


static irqreturn_t wdt87xx_ts_interrupt(int irq, void *dev_id) { struct wdt87xx_data *wdt = dev_id; struct i2c_client *client = wdt->client; int i, fingers; int error; u8 raw_buf[WDT_V1_RAW_BUF_COUNT] = {0}; error = i2c_master_recv(client, raw_buf, WDT_V1_RAW_BUF_COUNT); if (error < 0) { dev_err(&client->dev, "read v1 raw data failed: %d\n", error); goto irq_exit; } fingers = raw_buf[TOUCH_PK_V1_OFFSET_FNGR_NUM]; if (!fingers) goto irq_exit; for (i = 0; i < WDT_MAX_FINGER; i++) wdt87xx_report_contact(wdt->input, &wdt->param, &raw_buf[TOUCH_PK_V1_OFFSET_EVENT + i * FINGER_EV_V1_SIZE]); input_mt_sync_frame(wdt->input); input_sync(wdt->input); irq_exit: return IRQ_HANDLED; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen150100.00%1100.00%
Total150100.00%1100.00%


static int wdt87xx_ts_create_input_device(struct wdt87xx_data *wdt) { struct device *dev = &wdt->client->dev; struct input_dev *input; unsigned int res = DIV_ROUND_CLOSEST(MAX_UNIT_AXIS, wdt->param.phy_w); int error; input = devm_input_allocate_device(dev); if (!input) { dev_err(dev, "failed to allocate input device\n"); return -ENOMEM; } wdt->input = input; input->name = "WDT87xx Touchscreen"; input->id.bustype = BUS_I2C; input->id.vendor = wdt->param.vendor_id; input->id.product = wdt->param.product_id; input->phys = wdt->phys; input_set_abs_params(input, ABS_MT_POSITION_X, 0, wdt->param.max_x, 0, 0); input_set_abs_params(input, ABS_MT_POSITION_Y, 0, wdt->param.max_y, 0, 0); input_abs_set_res(input, ABS_MT_POSITION_X, res); input_abs_set_res(input, ABS_MT_POSITION_Y, res); input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, wdt->param.max_x, 0, 0); input_set_abs_params(input, ABS_MT_PRESSURE, 0, 0xFF, 0, 0); input_mt_init_slots(input, WDT_MAX_FINGER, INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); error = input_register_device(input); if (error) { dev_err(dev, "failed to register input device: %d\n", error); return error; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen252100.00%3100.00%
Total252100.00%3100.00%


static int wdt87xx_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct wdt87xx_data *wdt; int error; dev_dbg(&client->dev, "adapter=%d, client irq: %d\n", client->adapter->nr, client->irq); /* Check if the I2C function is ok in this adaptor */ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) return -ENXIO; wdt = devm_kzalloc(&client->dev, sizeof(*wdt), GFP_KERNEL); if (!wdt) return -ENOMEM; wdt->client = client; mutex_init(&wdt->fw_mutex); i2c_set_clientdata(client, wdt); snprintf(wdt->phys, sizeof(wdt->phys), "i2c-%u-%04x/input0", client->adapter->nr, client->addr); error = wdt87xx_get_sysparam(client, &wdt->param); if (error) return error; error = wdt87xx_ts_create_input_device(wdt); if (error) return error; error = devm_request_threaded_irq(&client->dev, client->irq, NULL, wdt87xx_ts_interrupt, IRQF_ONESHOT, client->name, wdt); if (error) { dev_err(&client->dev, "request irq failed: %d\n", error); return error; } error = sysfs_create_group(&client->dev.kobj, &wdt87xx_attr_group); if (error) { dev_err(&client->dev, "create sysfs failed: %d\n", error); return error; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen255100.00%1100.00%
Total255100.00%1100.00%


static int wdt87xx_ts_remove(struct i2c_client *client) { sysfs_remove_group(&client->dev.kobj, &wdt87xx_attr_group); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen27100.00%1100.00%
Total27100.00%1100.00%


static int __maybe_unused wdt87xx_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); int error; disable_irq(client->irq); error = wdt87xx_send_command(client, VND_CMD_STOP, MODE_IDLE); if (error) { enable_irq(client->irq); dev_err(&client->dev, "failed to stop device when suspending: %d\n", error); return error; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen74100.00%1100.00%
Total74100.00%1100.00%


static int __maybe_unused wdt87xx_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); int error; /* * The chip may have been reset while system is resuming, * give it some time to settle. */ mdelay(100); error = wdt87xx_send_command(client, VND_CMD_START, 0); if (error) dev_err(&client->dev, "failed to start device when resuming: %d\n", error); enable_irq(client->irq); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen68100.00%1100.00%
Total68100.00%1100.00%

static SIMPLE_DEV_PM_OPS(wdt87xx_pm_ops, wdt87xx_suspend, wdt87xx_resume); static const struct i2c_device_id wdt87xx_dev_id[] = { { WDT87XX_NAME, 0 }, { } }; MODULE_DEVICE_TABLE(i2c, wdt87xx_dev_id); static const struct acpi_device_id wdt87xx_acpi_id[] = { { "WDHT0001", 0 }, { } }; MODULE_DEVICE_TABLE(acpi, wdt87xx_acpi_id); static struct i2c_driver wdt87xx_driver = { .probe = wdt87xx_ts_probe, .remove = wdt87xx_ts_remove, .id_table = wdt87xx_dev_id, .driver = { .name = WDT87XX_NAME, .pm = &wdt87xx_pm_ops, .acpi_match_table = ACPI_PTR(wdt87xx_acpi_id), }, }; module_i2c_driver(wdt87xx_driver); MODULE_AUTHOR("HN Chen <hn.chen@weidahitech.com>"); MODULE_DESCRIPTION("WeidaHiTech WDT87XX Touchscreen driver"); MODULE_VERSION(WDT87XX_DRV_VER); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
hungnien chenhungnien chen535699.94%457.14%
dmitry torokhovdmitry torokhov20.04%228.57%
masanari iidamasanari iida10.02%114.29%
Total5359100.00%7100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
{% endraw %}