Message ID | 1506396515-19354-1-git-send-email-anthony.kim@hideep.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Hi, Dmitry Always thank you for your comment. I resent patch file modified about your comment. Please confirm it. Incidentally, Our IC use 40bit register address I2C protocol format at flash mode. 40 bit means just 5 byte buffers. ( address data address is as below ) ------------------------------------------------------------ | 8bit | 32bit | ------------------------------------------------------------ | R/W flag and data length | register address | ------------------------------------------------------------ But I wasn't find method for use 40 bit register address at regmap, so I couldn't implement to use regmap at flash mode. (I tried to use expanding bit, It was fail that couldn't set value in expanding bit.) So I only used regmap for normal mode I2C. If I must used regmap at all of transaction, please give to advice how to use 40 bit register address at regmap. Thank you. Anthony. 2017-09-26 12:28 GMT+09:00 Anthony Kim <anthony.kim@hideep.com>: > The HiDeep touchscreen device is a capacitive multi-touch controller > mainly for multi-touch supported devices use. It use I2C interface for > communication to IC and provide axis X, Y, Z locations for ten finger > touch through input event interface to userspace. > > It support the Crimson and the Lime two type IC. They are different > the number of channel supported and FW size. But the working protocol > is same. > > Signed-off-by: Anthony Kim <anthony.kim@hideep.com> > --- > .../bindings/input/touchscreen/hideep.txt | 42 + > .../devicetree/bindings/vendor-prefixes.txt | 1 + > > Acked-by: Rob Herring <robh+dt@kernel.org> > > drivers/input/touchscreen/Kconfig | 11 + > drivers/input/touchscreen/Makefile | 1 + > drivers/input/touchscreen/hideep.c | 1176 ++++++++++++++++++++ > 5 files changed, 1231 insertions(+) > create mode 100644 Documentation/devicetree/bindings/input/touchscreen/hideep.txt > create mode 100644 drivers/input/touchscreen/hideep.c > > diff --git a/Documentation/devicetree/bindings/input/touchscreen/hideep.txt b/Documentation/devicetree/bindings/input/touchscreen/hideep.txt > new file mode 100644 > index 0000000..121d9b7 > --- /dev/null > +++ b/Documentation/devicetree/bindings/input/touchscreen/hideep.txt > @@ -0,0 +1,42 @@ > +* HiDeep Finger and Stylus touchscreen controller > + > +Required properties: > +- compatible : must be "hideep,hideep-ts" > +- reg : I2C slave address, (e.g. 0x6C). > +- interrupt-parent : Interrupt controller to which the chip is connected. > +- interrupts : Interrupt to which the chip is connected. > + > +Optional properties: > +- vdd-supply : It is the controller supply for controlling > + main voltage(3.3V) through the regulator. > +- vid-supply : It is the controller supply for controlling > + IO voltage(1.8V) through the regulator. > +- reset-gpios : Define for reset gpio pin. > + It is to use for reset IC. > +- touchscreen-size-x : X axis size of touchscreen > +- touchscreen-size-y : Y axis size of touchscreen > +- linux,keycodes : Specifies an array of numeric keycode values to > + be used for reporting button presses. The array can > + contain up to 3 entries. > + > +Example: > + > +#include "dt-bindings/input/input.h" > + > +i2c@00000000 { > + > + /* ... */ > + > + touchscreen@6c { > + compatible = "hideep,hideep-ts"; > + reg = <0x6c>; > + interrupt-parent = <&gpx1>; > + interrupts = <2 IRQ_TYPE_LEVEL_LOW>; > + vdd-supply = <&ldo15_reg>"; > + vid-supply = <&ldo18_reg>; > + reset-gpios = <&gpx1 5 0>; > + touchscreen-size-x = <1080>; > + touchscreen-size-y = <1920>; > + linux,keycodes = <KEY_HOME>, <KEY_MENU>, <KEY_BACK>; > + }; > +}; > diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt > index daf465be..9913a03 100644 > --- a/Documentation/devicetree/bindings/vendor-prefixes.txt > +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt > @@ -134,6 +134,7 @@ gw Gateworks Corporation > hannstar HannStar Display Corporation > haoyu Haoyu Microelectronic Co. Ltd. > hardkernel Hardkernel Co., Ltd > +hideep HiDeep Inc. > himax Himax Technologies, Inc. > hisilicon Hisilicon Limited. > hit Hitachi Ltd. > diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig > index 64b30fe..d0c8dafc 100644 > --- a/drivers/input/touchscreen/Kconfig > +++ b/drivers/input/touchscreen/Kconfig > @@ -344,6 +344,17 @@ config TOUCHSCREEN_GOODIX > To compile this driver as a module, choose M here: the > module will be called goodix. > > +config TOUCHSCREEN_HIDEEP > + tristate "HiDeep Touch IC" > + depends on I2C > + help > + Say Y here if you have a touchscreen using HiDeep. > + > + If unsure, say N. > + > + To compile this driver as a moudle, choose M here : the > + module will be called hideep_ts. > + > config TOUCHSCREEN_ILI210X > tristate "Ilitek ILI210X based touchscreen" > depends on I2C > diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile > index 6badce8..873b67e 100644 > --- a/drivers/input/touchscreen/Makefile > +++ b/drivers/input/touchscreen/Makefile > @@ -39,6 +39,7 @@ obj-$(CONFIG_TOUCHSCREEN_EGALAX) += egalax_ts.o > obj-$(CONFIG_TOUCHSCREEN_EGALAX_SERIAL) += egalax_ts_serial.o > obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o > obj-$(CONFIG_TOUCHSCREEN_GOODIX) += goodix.o > +obj-$(CONFIG_TOUCHSCREEN_HIDEEP) += hideep.o > obj-$(CONFIG_TOUCHSCREEN_ILI210X) += ili210x.o > obj-$(CONFIG_TOUCHSCREEN_IMX6UL_TSC) += imx6ul_tsc.o > obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o > diff --git a/drivers/input/touchscreen/hideep.c b/drivers/input/touchscreen/hideep.c > new file mode 100644 > index 0000000..186cc30 > --- /dev/null > +++ b/drivers/input/touchscreen/hideep.c > @@ -0,0 +1,1176 @@ > +/* > + * Copyright (C) 2012-2017 Hideep, Inc. > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms of the GNU General Public License version 2 > + * as published by the Free Software Foudation. > + */ > + > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/firmware.h> > +#include <linux/delay.h> > +#include <linux/gpio.h> > +#include <linux/gpio/machine.h> > +#include <linux/i2c.h> > +#include <linux/acpi.h> > +#include <linux/interrupt.h> > +#include <linux/regmap.h> > +#include <linux/sysfs.h> > +#include <linux/input.h> > +#include <linux/input/mt.h> > +#include <linux/input/touchscreen.h> > +#include <linux/regulator/consumer.h> > +#include <asm/unaligned.h> > + > +#define HIDEEP_TS_NAME "HiDeep Touchscreen" > +#define HIDEEP_I2C_NAME "hideep_ts" > + > +#define HIDEEP_MT_MAX 10 > +#define HIDEEP_KEY_MAX 3 > +/* count(2) + touch data(100) + key data(6) */ > +#define HIDEEP_MAX_EVENT 108 > +#define HIDEEP_TOUCH_EVENT_INDEX 2 > +#define HIDEEP_KEY_EVENT_INDEX 102 > + > +/* Touch & key event */ > +#define HIDEEP_EVENT_ADDR 0x240 > + > +/* command list */ > +#define HIDEEP_RESET_CMD 0x9800 > + > +/* event bit */ > +#define HIDEEP_MT_RELEASED BIT(4) > +#define HIDEEP_KEY_PRESSED BIT(7) > +#define HIDEEP_KEY_FIRST_PRESSED BIT(8) > +#define HIDEEP_KEY_PRESSED_MASK \ > + (HIDEEP_KEY_PRESSED | HIDEEP_KEY_FIRST_PRESSED) > + > +/* For NVM */ > +#define HIDEEP_YRAM_BASE 0x40000000 > +#define HIDEEP_PERIPHERAL_BASE 0x50000000 > +#define HIDEEP_ESI_BASE \ > + (HIDEEP_PERIPHERAL_BASE + 0x00000000) > +#define HIDEEP_FLASH_BASE \ > + (HIDEEP_PERIPHERAL_BASE + 0x01000000) > +#define HIDEEP_SYSCON_BASE \ > + (HIDEEP_PERIPHERAL_BASE + 0x02000000) > + > +#define HIDEEP_SYSCON_MOD_CON (HIDEEP_SYSCON_BASE + 0x0000) > +#define HIDEEP_SYSCON_SPC_CON (HIDEEP_SYSCON_BASE + 0x0004) > +#define HIDEEP_SYSCON_CLK_CON (HIDEEP_SYSCON_BASE + 0x0008) > +#define HIDEEP_SYSCON_CLK_ENA (HIDEEP_SYSCON_BASE + 0x000C) > +#define HIDEEP_SYSCON_RST_CON (HIDEEP_SYSCON_BASE + 0x0010) > +#define HIDEEP_SYSCON_WDT_CON (HIDEEP_SYSCON_BASE + 0x0014) > +#define HIDEEP_SYSCON_WDT_CNT (HIDEEP_SYSCON_BASE + 0x0018) > +#define HIDEEP_SYSCON_PWR_CON (HIDEEP_SYSCON_BASE + 0x0020) > +#define HIDEEP_SYSCON_PGM_ID (HIDEEP_SYSCON_BASE + 0x00F4) > + > +#define HIDEEP_FLASH_CON (HIDEEP_FLASH_BASE + 0x0000) > +#define HIDEEP_FLASH_STA (HIDEEP_FLASH_BASE + 0x0004) > +#define HIDEEP_FLASH_CFG (HIDEEP_FLASH_BASE + 0x0008) > +#define HIDEEP_FLASH_TIM (HIDEEP_FLASH_BASE + 0x000C) > +#define HIDEEP_FLASH_CACHE_CFG (HIDEEP_FLASH_BASE + 0x0010) > +#define HIDEEP_FLASH_PIO_SIG (HIDEEP_FLASH_BASE + 0x400000) > + > +#define HIDEEP_ESI_TX_INVALID (HIDEEP_ESI_BASE + 0x0008) > + > +#define HIDEEP_PERASE 0x00040000 > +#define HIDEEP_WRONLY 0x00100000 > + > +#define HIDEEP_NVM_MASK_OFS 0x0000000C > +#define HIDEEP_NVM_DEFAULT_PAGE 0 > +#define HIDEEP_NVM_SFR_WPAGE 1 > +#define HIDEEP_NVM_SFR_RPAGE 2 > + > +#define HIDEEP_PIO_SIG 0x00400000 > +#define HIDEEP_PROT_MODE 0x03400000 > + > +#define HIDEEP_NVM_PAGE_SIZE 128 > + > +#define HIDEEP_DWZ_INFO 0x000002C0 > + > +struct hideep_event { > + __le16 x; > + __le16 y; > + __le16 z; > + u8 w; > + u8 flag; > + u8 type; > + u8 index; > +} __packed; > + > +struct dwz_info { > + __le32 code_start; > + u8 code_crc[12]; > + > + __le32 c_code_start; > + __le16 c_code_len; > + __le16 gen_ver; > + > + __le32 vr_start; > + __le16 vr_len; > + __le16 rsv0; > + > + __le32 ft_start; > + __le16 ft_len; > + __le16 vr_version; > + > + __le16 boot_ver; > + __le16 core_ver; > + __le16 custom_ver; > + __le16 release_ver; > + > + u8 factory_id; > + u8 panel_type; > + u8 model_name[6]; > + __le16 product_code; > + __le16 extra_option; > + > + __le16 product_id; > + __le16 vendor_id; > +} __packed; > + > +struct hideep_ts { > + struct i2c_client *client; > + struct input_dev *input_dev; > + struct regmap *reg; > + > + struct touchscreen_properties prop; > + > + struct gpio_desc *reset_gpio; > + > + struct regulator *vcc_vdd; > + struct regulator *vcc_vid; > + > + struct mutex dev_mutex; > + > + u32 tch_count; > + u32 key_count; > + u32 lpm_count; > + > + u8 touch_event[HIDEEP_MT_MAX * 10]; > + u8 key_event[HIDEEP_KEY_MAX * 2]; > + > + int key_num; > + int key_codes[HIDEEP_KEY_MAX]; > + > + struct dwz_info dwz_info; > + > + int fw_size; > + int nvm_mask; > +}; > + > +struct pgm_packet { > + union { > + u8 b[8]; > + u32 w[2]; > + } header; > + > + u32 payload[HIDEEP_NVM_PAGE_SIZE / sizeof(u32)]; > +}; > + > +static int hideep_pgm_w_mem(struct hideep_ts *ts, u32 addr, > + struct pgm_packet *packet, u32 len) > +{ > + int ret; > + int i; > + struct i2c_msg msg; > + > + if ((len % sizeof(u32)) != 0) > + return -EINVAL; > + > + put_unaligned_be32((0x80 | (len / sizeof(u32) - 1)), > + &packet->header.w[0]); > + put_unaligned_be32(addr, &packet->header.w[1]); > + > + for (i = 0; i < len / sizeof(u32); i++) > + put_unaligned_be32(packet->payload[i], &packet->payload[i]); > + > + msg.addr = ts->client->addr; > + msg.flags = 0; > + msg.len = len + 5; > + msg.buf = &packet->header.b[3]; > + > + ret = i2c_transfer(ts->client->adapter, &msg, 1); > + > + return ret; > +} > + > +static int hideep_pgm_r_mem(struct hideep_ts *ts, u32 addr, > + struct pgm_packet *packet, u32 len) > +{ > + int ret; > + int i; > + u8 *buff; > + struct i2c_msg msg[2]; > + > + if ((len % sizeof(u32)) != 0) > + return -EINVAL; > + > + buff = kmalloc(len, GFP_KERNEL); > + > + if (!buff) > + return -ENOMEM; > + > + put_unaligned_be32((0x00 | (len / sizeof(u32) - 1)), > + &packet->header.w[0]); > + put_unaligned_be32(addr, &packet->header.w[1]); > + > + msg[0].addr = ts->client->addr; > + msg[0].flags = 0; > + msg[0].len = 5; > + msg[0].buf = &packet->header.b[3]; > + > + msg[1].addr = ts->client->addr; > + msg[1].flags = I2C_M_RD; > + msg[1].len = len; > + msg[1].buf = buff; > + > + ret = i2c_transfer(ts->client->adapter, msg, 2); > + > + if (ret < 0) > + return ret; > + > + for (i = 0; i < len / sizeof(u32); i++) > + packet->payload[i] = get_unaligned_be32(&buff[i * sizeof(u32)]); > + > + return ret; > +} > + > +static int hideep_pgm_r_reg(struct hideep_ts *ts, u32 addr, > + u32 *val) > +{ > + int ret; > + struct pgm_packet packet; > + > + put_unaligned_be32(0x00, &packet.header.w[0]); > + put_unaligned_be32(addr, &packet.header.w[1]); > + > + ret = hideep_pgm_r_mem(ts, addr, &packet, sizeof(u32)); > + > + if (ret < 0) > + return ret; > + > + *val = packet.payload[0]; > + > + return ret; > +} > + > +static int hideep_pgm_w_reg(struct hideep_ts *ts, u32 addr, > + u32 data) > +{ > + int ret; > + struct pgm_packet packet; > + > + put_unaligned_be32(0x80, &packet.header.w[0]); > + put_unaligned_be32(addr, &packet.header.w[1]); > + packet.payload[0] = data; > + > + ret = hideep_pgm_w_mem(ts, addr, &packet, sizeof(u32)); > + > + return ret; > +} > + > +#define SW_RESET_IN_PGM(CLK) \ > +{ \ > + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_WDT_CNT, CLK); \ > + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_WDT_CON, 0x03); \ > + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_WDT_CON, 0x01); \ > +} > + > +#define SET_FLASH_PIO(CE) \ > + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CON, 0x01 | (CE << 1)) > +#define SET_PIO_SIG(X, Y) \ > + hideep_pgm_w_reg(ts, HIDEEP_FLASH_PIO_SIG + X, Y) > +#define SET_FLASH_HWCONTROL() \ > + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CON, 0x00) > + > +#define NVM_W_SFR(x, y) \ > +{ \ > + SET_FLASH_PIO(1); \ > + SET_PIO_SIG(x, y); \ > + SET_FLASH_PIO(0); \ > +} > + > +static void hideep_pgm_set(struct hideep_ts *ts) > +{ > + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_WDT_CON, 0x00); > + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_SPC_CON, 0x00); > + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_CLK_ENA, 0xFF); > + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_CLK_CON, 0x01); > + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_PWR_CON, 0x01); > + hideep_pgm_w_reg(ts, HIDEEP_FLASH_TIM, 0x03); > + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CACHE_CFG, 0x00); > +} > + > +static int hideep_pgm_get_pattern(struct hideep_ts *ts) > +{ > + int ret; > + u32 status; > + u16 p1 = 0xAF39; > + u16 p2 = 0xDF9D; > + > + ret = regmap_bulk_write(ts->reg, p1, (void *)&p2, 1); > + > + if (ret < 0) { > + dev_err(&ts->client->dev, "%d, %08X", __LINE__, ret); > + return ret; > + } > + > + mdelay(1); > + > + /* flush invalid Tx load register */ > + ret = hideep_pgm_w_reg(ts, HIDEEP_ESI_TX_INVALID, 0x01); > + > + if (ret < 0) > + return ret; > + > + ret = hideep_pgm_r_reg(ts, HIDEEP_SYSCON_PGM_ID, &status); > + > + if (ret < 0) > + return ret; > + > + return status; > +} > + > +static int hideep_enter_pgm(struct hideep_ts *ts) > +{ > + int retry_count = 10; > + int val; > + u32 pgm_pattern = 0xDF9DAF39; > + > + while (retry_count--) { > + val = hideep_pgm_get_pattern(ts); > + > + if (pgm_pattern != get_unaligned_be32(&val)) { > + dev_err(&ts->client->dev, "enter_pgm : error(%08x):", > + get_unaligned_be32(&val)); > + } else { > + dev_dbg(&ts->client->dev, "found magic code"); > + break; > + } > + } > + > + if (retry_count < 0) { > + dev_err(&ts->client->dev, "couldn't enter pgm mode!!!"); > + SW_RESET_IN_PGM(1000); > + return -EBADMSG; > + } > + > + hideep_pgm_set(ts); > + mdelay(1); > + > + return 0; > +} > + > +static void hideep_nvm_unlock(struct hideep_ts *ts) > +{ > + u32 unmask_code = 0; > + > + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CFG, > + HIDEEP_NVM_SFR_RPAGE); > + > + hideep_pgm_r_reg(ts, 0x0000000C, &unmask_code); > + > + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CFG, > + HIDEEP_NVM_DEFAULT_PAGE); > + > + /* make it unprotected code */ > + unmask_code &= (~HIDEEP_PROT_MODE); > + > + /* compare unmask code */ > + if (unmask_code != ts->nvm_mask) > + dev_dbg(&ts->client->dev, "read mask code different 0x%x", > + unmask_code); > + > + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CFG, > + HIDEEP_NVM_SFR_WPAGE); > + SET_FLASH_PIO(0); > + > + NVM_W_SFR(HIDEEP_NVM_MASK_OFS, ts->nvm_mask); > + SET_FLASH_HWCONTROL(); > + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CFG, > + HIDEEP_NVM_DEFAULT_PAGE); > +} > + > +static int hideep_check_status(struct hideep_ts *ts) > +{ > + int ret, status; > + int time_out = 100; > + > + while (time_out--) { > + mdelay(1); > + ret = hideep_pgm_r_reg(ts, HIDEEP_FLASH_STA, > + &status); > + > + if (ret < 0) > + continue; > + > + if (status) > + return status; > + } > + > + return time_out; > +} > + > +static int hideep_program_page(struct hideep_ts *ts, > + u32 addr, struct pgm_packet *packet_w) > +{ > + int ret; > + > + > + ret = hideep_check_status(ts); > + > + if (ret < 0) > + return -EBUSY; > + > + addr = addr & ~(HIDEEP_NVM_PAGE_SIZE - 1); > + > + SET_FLASH_PIO(0); > + SET_FLASH_PIO(1); > + > + /* erase page */ > + SET_PIO_SIG((HIDEEP_PERASE | addr), 0xFFFFFFFF); > + > + SET_FLASH_PIO(0); > + > + ret = hideep_check_status(ts); > + > + if (ret < 0) > + return -EBUSY; > + > + /* write page */ > + SET_FLASH_PIO(1); > + > + SET_PIO_SIG((HIDEEP_WRONLY | addr), > + get_unaligned_be32(&packet_w->payload[0])); > + > + hideep_pgm_w_mem(ts, (HIDEEP_FLASH_PIO_SIG | HIDEEP_WRONLY), > + packet_w, HIDEEP_NVM_PAGE_SIZE); > + > + SET_PIO_SIG(124, get_unaligned_be32(&packet_w->payload[31])); > + > + SET_FLASH_PIO(0); > + > + mdelay(1); > + > + ret = hideep_check_status(ts); > + > + if (ret < 0) > + return -EBUSY; > + > + SET_FLASH_HWCONTROL(); > + > + return 0; > +} > + > +static void hideep_program_nvm(struct hideep_ts *ts, const u8 *ucode, > + int len) > +{ > + struct pgm_packet packet_w; > + struct pgm_packet packet_r; > + int i; > + int ret; > + int addr = 0; > + int len_r = len; > + int len_w = HIDEEP_NVM_PAGE_SIZE; > + u32 pages = DIV_ROUND_UP(len, HIDEEP_NVM_PAGE_SIZE); > + > + > + hideep_nvm_unlock(ts); > + > + dev_dbg(&ts->client->dev, "pages : %d", pages); > + > + for (i = 0; i < pages; i++) { > + if (len_r < HIDEEP_NVM_PAGE_SIZE) > + len_w = len_r; > + > + /* compare */ > + hideep_pgm_r_mem(ts, 0x00000000 + addr, &packet_r, > + HIDEEP_NVM_PAGE_SIZE); > + ret = memcmp(&ucode[addr], packet_r.payload, len_w); > + > + if (ret) { > + /* write page */ > + memcpy(packet_w.payload, &ucode[addr], len_w); > + ret = hideep_program_page(ts, addr, &packet_w); > + if (ret) > + dev_err(&ts->client->dev, > + "%s : error(%08x):", > + __func__, addr); > + mdelay(1); > + } > + > + addr += HIDEEP_NVM_PAGE_SIZE; > + len_r -= HIDEEP_NVM_PAGE_SIZE; > + if (len_r < 0) > + break; > + } > +} > + > +static int hideep_verify_nvm(struct hideep_ts *ts, const u8 *ucode, > + int len) > +{ > + struct pgm_packet packet_r; > + int i, j; > + int ret; > + int addr = 0; > + int len_r = len; > + int len_v = HIDEEP_NVM_PAGE_SIZE; > + u32 pages = DIV_ROUND_UP(len, HIDEEP_NVM_PAGE_SIZE); > + > + for (i = 0; i < pages; i++) { > + if (len_r < HIDEEP_NVM_PAGE_SIZE) > + len_v = len_r; > + > + hideep_pgm_r_mem(ts, 0x00000000 + addr, &packet_r, > + HIDEEP_NVM_PAGE_SIZE); > + > + ret = memcmp(&ucode[addr], packet_r.payload, len_v); > + > + if (ret) { > + u8 *read = (u8 *)packet_r.payload; > + > + for (j = 0; j < HIDEEP_NVM_PAGE_SIZE; j++) { > + if (ucode[addr + j] != read[j]) > + dev_err(&ts->client->dev, > + "verify : error([%d] %02x : %02x)", > + addr + j, ucode[addr + j], > + read[j]); > + } > + return ret; > + } > + > + addr += HIDEEP_NVM_PAGE_SIZE; > + len_r -= HIDEEP_NVM_PAGE_SIZE; > + if (len_r < 0) > + break; > + } > + > + return 0; > +} > + > +static int hideep_update_firmware(struct hideep_ts *ts, const char *fn) > +{ > + int ret; > + int retry, retry_cnt = 3; > + const struct firmware *fw_entry; > + > + dev_dbg(&ts->client->dev, "enter"); > + ret = request_firmware(&fw_entry, fn, &ts->client->dev); > + > + if (ret != 0) { > + dev_err(&ts->client->dev, "request_firmware : fail(%d)", ret); > + return ret; > + } > + > + if (fw_entry->size > ts->fw_size) { > + dev_err(&ts->client->dev, > + "file size(%zu) is big more than fw memory size(%d)", > + fw_entry->size, ts->fw_size); > + release_firmware(fw_entry); > + return -EFBIG; > + } > + > + /* chip specific code for flash fuse */ > + mutex_lock(&ts->dev_mutex); > + > + /* enter program mode */ > + ret = hideep_enter_pgm(ts); > + > + if (ret) > + return ret; > + > + /* comparing & programming each page, if the memory of specified > + * page is exactly same, no need to update. > + */ > + for (retry = 0; retry < retry_cnt; retry++) { > + hideep_program_nvm(ts, fw_entry->data, fw_entry->size); > + > + ret = hideep_verify_nvm(ts, fw_entry->data, fw_entry->size); > + if (!ret) > + break; > + } > + > + if (retry < retry_cnt) > + dev_dbg(&ts->client->dev, "update success!!!"); > + else > + dev_err(&ts->client->dev, "update failed!!!"); > + > + SW_RESET_IN_PGM(1000); > + > + mutex_unlock(&ts->dev_mutex); > + > + release_firmware(fw_entry); > + > + return ret; > +} > + > +static int hideep_load_dwz(struct hideep_ts *ts) > +{ > + int ret = 0; > + struct pgm_packet packet_r; > + > + ret = hideep_enter_pgm(ts); > + > + if (ret) > + return ret; > + > + mdelay(50); > + > + hideep_pgm_r_mem(ts, HIDEEP_DWZ_INFO, &packet_r, > + sizeof(struct dwz_info)); > + > + memcpy(&ts->dwz_info, packet_r.payload, > + sizeof(struct dwz_info)); > + > + SW_RESET_IN_PGM(10); > + > + if (get_unaligned_le16(&ts->dwz_info.product_code) & 0x60) { > + /* Lime fw size */ > + dev_dbg(&ts->client->dev, "used lime IC"); > + ts->fw_size = 1024 * 64; > + ts->nvm_mask = 0x0030027B; > + } else if (get_unaligned_le16(&ts->dwz_info.product_code) & 0x40) { > + /* Crimson IC */ > + dev_dbg(&ts->client->dev, "used crimson IC"); > + ts->fw_size = 1024 * 48; > + ts->nvm_mask = 0x00310000; > + } else { > + dev_dbg(&ts->client->dev, "product code is wrong!!!"); > + return -EINVAL; > + } > + > + dev_dbg(&ts->client->dev, "firmware release version : %04x", > + get_unaligned_le16(&ts->dwz_info.release_ver)); > + > + mdelay(50); > + > + return 0; > +} > + > +static int hideep_pwr_on(struct hideep_ts *ts) > +{ > + int ret = 0; > + u8 cmd = 0x01; > + > + if (ts->vcc_vdd) { > + ret = regulator_enable(ts->vcc_vdd); > + if (ret) > + dev_err(&ts->client->dev, > + "Regulator vdd enable failed ret=%d", ret); > + usleep_range(999, 1000); > + } > + > + if (ts->vcc_vid) { > + ret = regulator_enable(ts->vcc_vid); > + if (ret) > + dev_err(&ts->client->dev, > + "Regulator vcc_vid enable failed ret=%d", ret); > + usleep_range(2999, 3000); > + } > + > + mdelay(30); > + > + if (ts->reset_gpio) > + gpiod_set_raw_value(ts->reset_gpio, 1); > + else > + regmap_write(ts->reg, HIDEEP_RESET_CMD, cmd); > + > + mdelay(50); > + > + return ret; > +} > + > +static void hideep_pwr_off(void *data) > +{ > + struct hideep_ts *ts = data; > + > + if (ts->reset_gpio) > + gpiod_set_value(ts->reset_gpio, 0); > + > + if (ts->vcc_vid) > + regulator_disable(ts->vcc_vid); > + > + if (ts->vcc_vdd) > + regulator_disable(ts->vcc_vdd); > +} > + > +#define __GET_MT_TOOL_TYPE(X) ((X == 0x01) ? MT_TOOL_FINGER : MT_TOOL_PEN) > + > +static void push_mt(struct hideep_ts *ts) > +{ > + int id; > + int i; > + int btn_up = 0; > + int evt = 0; > + int offset = sizeof(struct hideep_event); > + struct hideep_event *event; > + > + /* load multi-touch event to input system */ > + for (i = 0; i < ts->tch_count; i++) { > + event = (struct hideep_event *)&ts->touch_event[i * offset]; > + id = event->index & 0x0F; > + btn_up = event->flag & HIDEEP_MT_RELEASED; > + > + dev_dbg(&ts->client->dev, > + "type = %d, id = %d, i = %d, x = %d, y = %d, z = %d", > + event->type, event->index, i, > + get_unaligned_le16(&event->x), > + get_unaligned_le16(&event->y), > + get_unaligned_le16(&event->z)); > + > + input_mt_slot(ts->input_dev, id); > + input_mt_report_slot_state(ts->input_dev, > + __GET_MT_TOOL_TYPE(event->type), > + (btn_up == 0)); > + > + if (btn_up == 0) { > + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, > + get_unaligned_le16(&event->x)); > + input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, > + get_unaligned_le16(&event->y)); > + input_report_abs(ts->input_dev, ABS_MT_PRESSURE, > + get_unaligned_le16(&event->z)); > + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, > + event->w); > + evt++; > + } > + } > + > + input_mt_sync_frame(ts->input_dev); > +} > + > +static void push_ky(struct hideep_ts *ts) > +{ > + int i; > + int status; > + int code; > + > + for (i = 0; i < ts->key_count; i++) { > + code = ts->key_event[i * 2] & 0x0F; > + status = ts->key_event[i * 2] & 0xF0; > + > + input_report_key(ts->input_dev, ts->key_codes[code], > + status & HIDEEP_KEY_PRESSED_MASK); > + } > +} > + > +static void hideep_put_event(struct hideep_ts *ts) > +{ > + /* mangling touch information */ > + if (ts->tch_count > 0) > + push_mt(ts); > + > + if (ts->key_count > 0) > + push_ky(ts); > + > + input_sync(ts->input_dev); > +} > + > +static int hideep_parse_event(struct hideep_ts *ts, u8 *data) > +{ > + int touch_count; > + > + ts->tch_count = data[0]; > + ts->key_count = data[1] & 0x0f; > + ts->lpm_count = data[1] & 0xf0; > + > + /* get touch event count */ > + dev_dbg(&ts->client->dev, "mt = %d, key = %d, lpm = %02x", > + ts->tch_count, ts->key_count, ts->lpm_count); > + > + /* get touch event information */ > + if (ts->tch_count < HIDEEP_MT_MAX) > + memcpy(ts->touch_event, &data[HIDEEP_TOUCH_EVENT_INDEX], > + HIDEEP_MT_MAX * sizeof(struct hideep_event)); > + else > + ts->tch_count = 0; > + > + if (ts->key_count < HIDEEP_KEY_MAX) > + memcpy(ts->key_event, &data[HIDEEP_KEY_EVENT_INDEX], > + HIDEEP_KEY_MAX * 2); > + else > + ts->key_count = 0; > + > + touch_count = ts->tch_count + ts->key_count; > + > + return touch_count; > +} > + > +static irqreturn_t hideep_irq_task(int irq, void *handle) > +{ > + u8 buff[HIDEEP_MAX_EVENT]; > + int ret; > + > + struct hideep_ts *ts = handle; > + > + ret = regmap_bulk_read(ts->reg, HIDEEP_EVENT_ADDR, > + buff, HIDEEP_MAX_EVENT / 2); > + > + if (ret < 0) > + return IRQ_HANDLED; > + > + ret = hideep_parse_event(ts, buff); > + > + if (ret > 0) > + hideep_put_event(ts); > + > + return IRQ_HANDLED; > +} > + > +static void hideep_get_axis_info(struct hideep_ts *ts) > +{ > + int ret; > + u8 val[4]; > + > + if (ts->prop.max_x == 0 || ts->prop.max_y == 0) { > + ret = regmap_bulk_read(ts->reg, 0x28, val, 2); > + > + if (ret < 0) { > + ts->prop.max_x = -1; > + ts->prop.max_y = -1; > + } else { > + ts->prop.max_x = > + get_unaligned_le16(&val[0]); > + ts->prop.max_y = > + get_unaligned_le16(&val[2]); > + } > + } > + > + dev_dbg(&ts->client->dev, "X : %d, Y : %d", > + ts->prop.max_x, ts->prop.max_y); > +} > + > +static int hideep_capability(struct hideep_ts *ts) > +{ > + int ret, i; > + > + hideep_get_axis_info(ts); > + > + if (ts->prop.max_x < 0 || ts->prop.max_y < 0) > + return -EINVAL; > + > + ts->input_dev->name = HIDEEP_TS_NAME; > + ts->input_dev->id.bustype = BUS_I2C; > + > + if (ts->key_num) { > + ts->input_dev->keycode = ts->key_codes; > + ts->input_dev->keycodesize = sizeof(ts->key_codes[0]); > + ts->input_dev->keycodemax = ts->key_num; > + for (i = 0; i < ts->key_num; i++) > + input_set_capability(ts->input_dev, EV_KEY, > + ts->key_codes[i]); > + } > + > + input_set_abs_params(ts->input_dev, > + ABS_MT_TOOL_TYPE, 0, MT_TOOL_MAX, 0, 0); > + input_set_abs_params(ts->input_dev, > + ABS_MT_POSITION_X, 0, ts->prop.max_x, 0, 0); > + input_set_abs_params(ts->input_dev, > + ABS_MT_POSITION_Y, 0, ts->prop.max_y, 0, 0); > + input_set_abs_params(ts->input_dev, > + ABS_MT_PRESSURE, 0, 65535, 0, 0); > + input_set_abs_params(ts->input_dev, > + ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); > + > + ret = input_mt_init_slots(ts->input_dev, > + HIDEEP_MT_MAX, INPUT_MT_DIRECT); > + > + return ret; > +} > + > +static ssize_t hideep_update_fw(struct device *dev, > + struct device_attribute *attr, const char *buf, size_t count) > +{ > + struct hideep_ts *ts = dev_get_drvdata(dev); > + int mode, ret; > + char *fw_name; > + > + ret = kstrtoint(buf, 8, &mode); > + if (ret) > + return ret; > + > + disable_irq(ts->client->irq); > + > + fw_name = kasprintf(GFP_KERNEL, "hideep_ts_%04x.bin", > + get_unaligned_le16(&ts->dwz_info.product_id)); > + ret = hideep_update_firmware(ts, fw_name); > + > + if (ret != 0) > + dev_err(dev, "The firmware update failed(%d)", ret); > + > + kfree(fw_name); > + > + ret = hideep_load_dwz(ts); > + > + if (ret < 0) > + dev_err(&ts->client->dev, "fail to load dwz, ret = 0x%x", ret); > + > + enable_irq(ts->client->irq); > + > + return count; > +} > + > +static ssize_t hideep_fw_version_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + int len = 0; > + struct hideep_ts *ts = dev_get_drvdata(dev); > + > + mutex_lock(&ts->dev_mutex); > + len = scnprintf(buf, PAGE_SIZE, > + "%04x\n", get_unaligned_le16(&ts->dwz_info.release_ver)); > + mutex_unlock(&ts->dev_mutex); > + > + return len; > +} > + > +static ssize_t hideep_product_id_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + int len = 0; > + struct hideep_ts *ts = dev_get_drvdata(dev); > + > + mutex_lock(&ts->dev_mutex); > + len = scnprintf(buf, PAGE_SIZE, > + "%04x\n", get_unaligned_le16(&ts->dwz_info.product_id)); > + mutex_unlock(&ts->dev_mutex); > + > + return len; > +} > + > +static DEVICE_ATTR(version, 0664, hideep_fw_version_show, NULL); > +static DEVICE_ATTR(product_id, 0664, hideep_product_id_show, NULL); > +static DEVICE_ATTR(update_fw, 0664, NULL, hideep_update_fw); > + > +static struct attribute *hideep_ts_sysfs_entries[] = { > + &dev_attr_version.attr, > + &dev_attr_product_id.attr, > + &dev_attr_update_fw.attr, > + NULL, > +}; > + > +static struct attribute_group hideep_ts_attr_group = { > + .attrs = hideep_ts_sysfs_entries, > +}; > + > +static int __maybe_unused hideep_resume(struct device *dev) > +{ > + struct hideep_ts *ts = dev_get_drvdata(dev); > + int ret; > + > + ret = hideep_pwr_on(ts); > + if (ret < 0) > + dev_err(&ts->client->dev, "power on failed"); > + else > + enable_irq(ts->client->irq); > + > + return ret; > +} > + > +static int __maybe_unused hideep_suspend(struct device *dev) > +{ > + struct hideep_ts *ts = dev_get_drvdata(dev); > + > + disable_irq(ts->client->irq); > + hideep_pwr_off(ts); > + > + return 0; > +} > + > +static int hideep_parse_dts(struct device *dev, struct hideep_ts *ts) > +{ > + int ret; > + > + ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", > + GPIOD_OUT_HIGH); > + if (IS_ERR(ts->reset_gpio)) > + return PTR_ERR(ts->reset_gpio); > + > + ts->vcc_vdd = devm_regulator_get(dev, "vdd"); > + if (IS_ERR(ts->vcc_vdd)) > + return PTR_ERR(ts->vcc_vdd); > + > + ts->vcc_vid = devm_regulator_get(dev, "vid"); > + if (IS_ERR(ts->vcc_vid)) > + return PTR_ERR(ts->vcc_vid); > + > + ts->key_num = device_property_read_u32_array(dev, "linux,keycodes", > + NULL, 0); > + > + if (ts->key_num > HIDEEP_KEY_MAX) { > + dev_err(dev, "too many support key defined(%d)!!!", > + ts->key_num); > + return -EINVAL; > + } > + > + ret = device_property_read_u32_array(dev, "linux,keycodes", > + ts->key_codes, ts->key_num); > + if (ret) { > + dev_dbg(dev, "don't support touch key"); > + ts->key_num = 0; > + } > + > + return 0; > +} > + > +const struct regmap_config hideep_regmap_config = { > + .reg_bits = 16, > + .reg_format_endian = REGMAP_ENDIAN_LITTLE, > + .val_bits = 16, > + .val_format_endian = REGMAP_ENDIAN_LITTLE, > + .max_register = 0xffff, > +}; > + > +static int hideep_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + int ret; > + struct regmap *regmap; > + struct hideep_ts *ts; > + > + /* check i2c bus */ > + if (!i2c_check_functionality(client->adapter, > + I2C_FUNC_I2C)) { > + dev_err(&client->dev, "check i2c device error"); > + return -ENODEV; > + } > + > + regmap = devm_regmap_init_i2c(client, &hideep_regmap_config); > + > + if (IS_ERR(regmap)) { > + dev_err(&client->dev, "don't init regmap"); > + return PTR_ERR(regmap); > + } > + > + /* init hideep_ts */ > + ts = devm_kzalloc(&client->dev, > + sizeof(*ts), GFP_KERNEL); > + if (!ts) > + return -ENOMEM; > + > + ret = hideep_parse_dts(&client->dev, ts); > + > + if (ret) > + return ret; > + > + ts->client = client; > + ts->reg = regmap; > + > + i2c_set_clientdata(client, ts); > + > + mutex_init(&ts->dev_mutex); > + > + /* power on */ > + ret = hideep_pwr_on(ts); > + if (ret) { > + dev_err(&ts->client->dev, "power on failed"); > + return ret; > + } > + > + ret = devm_add_action_or_reset(&ts->client->dev, hideep_pwr_off, ts); > + if (ret) { > + hideep_pwr_off(ts); > + return ret; > + } > + > + mdelay(30); > + > + /* read info */ > + ret = hideep_load_dwz(ts); > + if (ret < 0) { > + dev_err(&client->dev, "fail to load dwz, ret = 0x%x", ret); > + return ret; > + } > + > + /* init input device */ > + ts->input_dev = devm_input_allocate_device(&client->dev); > + if (!ts->input_dev) { > + dev_err(&client->dev, "can't allocate memory for input_dev"); > + return -ENOMEM; > + } > + > + touchscreen_parse_properties(ts->input_dev, true, &ts->prop); > + > + ret = hideep_capability(ts); > + if (ret) { > + dev_err(&client->dev, "can't init input properties"); > + return ret; > + } > + > + ret = input_register_device(ts->input_dev); > + if (ret) { > + dev_err(&client->dev, "can't register input_dev"); > + return ret; > + } > + > + input_set_drvdata(ts->input_dev, ts); > + > + dev_info(&ts->client->dev, "ts irq: %d", ts->client->irq); > + if (client->irq <= 0) { > + dev_err(&client->dev, "can't be assigned irq"); > + return -EINVAL; > + } > + > + ret = devm_request_threaded_irq(&client->dev, ts->client->irq, > + NULL, hideep_irq_task, IRQF_ONESHOT, > + ts->client->name, ts); > + > + if (ret < 0) { > + dev_err(&client->dev, "fail to get irq, ret = 0x%08x", > + ret); > + return ret; > + } > + > + ret = devm_device_add_group(&client->dev, &hideep_ts_attr_group); > + > + if (ret) { > + dev_err(&client->dev, "fail init sys, ret = 0x%x", ret); > + return ret; > + } > + > + return 0; > +} > + > +static SIMPLE_DEV_PM_OPS(hideep_pm_ops, hideep_suspend, hideep_resume); > + > +static const struct i2c_device_id hideep_dev_idtable[] = { > + { HIDEEP_I2C_NAME, 0 }, > + {} > +}; > +MODULE_DEVICE_TABLE(i2c, hideep_dev_idtable); > + > +#ifdef CONFIG_ACPI > +static const struct acpi_device_id hideep_acpi_id[] = { > + { "HIDP0001", 0 }, > + {} > +}; > +MODULE_DEVICE_TABLE(acpi, hideep_acpi_id); > +#endif > + > +#ifdef CONFIG_OF > +static const struct of_device_id hideep_match_table[] = { > + { .compatible = "hideep,hideep-ts" }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, hideep_match_table); > +#endif > + > +static struct i2c_driver hideep_driver = { > + .probe = hideep_probe, > + .id_table = hideep_dev_idtable, > + .driver = { > + .name = HIDEEP_I2C_NAME, > + .of_match_table = of_match_ptr(hideep_match_table), > + .acpi_match_table = ACPI_PTR(hideep_acpi_id), > + .pm = &hideep_pm_ops, > + }, > +}; > + > +module_i2c_driver(hideep_driver); > + > +MODULE_DESCRIPTION("Driver for HiDeep Touchscreen Controller"); > +MODULE_AUTHOR("anthony.kim@hideep.com"); > +MODULE_LICENSE("GPL v2"); > -- > 2.7.4 > -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/Documentation/devicetree/bindings/input/touchscreen/hideep.txt b/Documentation/devicetree/bindings/input/touchscreen/hideep.txt new file mode 100644 index 0000000..121d9b7 --- /dev/null +++ b/Documentation/devicetree/bindings/input/touchscreen/hideep.txt @@ -0,0 +1,42 @@ +* HiDeep Finger and Stylus touchscreen controller + +Required properties: +- compatible : must be "hideep,hideep-ts" +- reg : I2C slave address, (e.g. 0x6C). +- interrupt-parent : Interrupt controller to which the chip is connected. +- interrupts : Interrupt to which the chip is connected. + +Optional properties: +- vdd-supply : It is the controller supply for controlling + main voltage(3.3V) through the regulator. +- vid-supply : It is the controller supply for controlling + IO voltage(1.8V) through the regulator. +- reset-gpios : Define for reset gpio pin. + It is to use for reset IC. +- touchscreen-size-x : X axis size of touchscreen +- touchscreen-size-y : Y axis size of touchscreen +- linux,keycodes : Specifies an array of numeric keycode values to + be used for reporting button presses. The array can + contain up to 3 entries. + +Example: + +#include "dt-bindings/input/input.h" + +i2c@00000000 { + + /* ... */ + + touchscreen@6c { + compatible = "hideep,hideep-ts"; + reg = <0x6c>; + interrupt-parent = <&gpx1>; + interrupts = <2 IRQ_TYPE_LEVEL_LOW>; + vdd-supply = <&ldo15_reg>"; + vid-supply = <&ldo18_reg>; + reset-gpios = <&gpx1 5 0>; + touchscreen-size-x = <1080>; + touchscreen-size-y = <1920>; + linux,keycodes = <KEY_HOME>, <KEY_MENU>, <KEY_BACK>; + }; +}; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index daf465be..9913a03 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -134,6 +134,7 @@ gw Gateworks Corporation hannstar HannStar Display Corporation haoyu Haoyu Microelectronic Co. Ltd. hardkernel Hardkernel Co., Ltd +hideep HiDeep Inc. himax Himax Technologies, Inc. hisilicon Hisilicon Limited. hit Hitachi Ltd. diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 64b30fe..d0c8dafc 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -344,6 +344,17 @@ config TOUCHSCREEN_GOODIX To compile this driver as a module, choose M here: the module will be called goodix. +config TOUCHSCREEN_HIDEEP + tristate "HiDeep Touch IC" + depends on I2C + help + Say Y here if you have a touchscreen using HiDeep. + + If unsure, say N. + + To compile this driver as a moudle, choose M here : the + module will be called hideep_ts. + config TOUCHSCREEN_ILI210X tristate "Ilitek ILI210X based touchscreen" depends on I2C diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 6badce8..873b67e 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_TOUCHSCREEN_EGALAX) += egalax_ts.o obj-$(CONFIG_TOUCHSCREEN_EGALAX_SERIAL) += egalax_ts_serial.o obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o obj-$(CONFIG_TOUCHSCREEN_GOODIX) += goodix.o +obj-$(CONFIG_TOUCHSCREEN_HIDEEP) += hideep.o obj-$(CONFIG_TOUCHSCREEN_ILI210X) += ili210x.o obj-$(CONFIG_TOUCHSCREEN_IMX6UL_TSC) += imx6ul_tsc.o obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o diff --git a/drivers/input/touchscreen/hideep.c b/drivers/input/touchscreen/hideep.c new file mode 100644 index 0000000..186cc30 --- /dev/null +++ b/drivers/input/touchscreen/hideep.c @@ -0,0 +1,1176 @@ +/* + * Copyright (C) 2012-2017 Hideep, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 + * as published by the Free Software Foudation. + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/firmware.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/gpio/machine.h> +#include <linux/i2c.h> +#include <linux/acpi.h> +#include <linux/interrupt.h> +#include <linux/regmap.h> +#include <linux/sysfs.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/input/touchscreen.h> +#include <linux/regulator/consumer.h> +#include <asm/unaligned.h> + +#define HIDEEP_TS_NAME "HiDeep Touchscreen" +#define HIDEEP_I2C_NAME "hideep_ts" + +#define HIDEEP_MT_MAX 10 +#define HIDEEP_KEY_MAX 3 +/* count(2) + touch data(100) + key data(6) */ +#define HIDEEP_MAX_EVENT 108 +#define HIDEEP_TOUCH_EVENT_INDEX 2 +#define HIDEEP_KEY_EVENT_INDEX 102 + +/* Touch & key event */ +#define HIDEEP_EVENT_ADDR 0x240 + +/* command list */ +#define HIDEEP_RESET_CMD 0x9800 + +/* event bit */ +#define HIDEEP_MT_RELEASED BIT(4) +#define HIDEEP_KEY_PRESSED BIT(7) +#define HIDEEP_KEY_FIRST_PRESSED BIT(8) +#define HIDEEP_KEY_PRESSED_MASK \ + (HIDEEP_KEY_PRESSED | HIDEEP_KEY_FIRST_PRESSED) + +/* For NVM */ +#define HIDEEP_YRAM_BASE 0x40000000 +#define HIDEEP_PERIPHERAL_BASE 0x50000000 +#define HIDEEP_ESI_BASE \ + (HIDEEP_PERIPHERAL_BASE + 0x00000000) +#define HIDEEP_FLASH_BASE \ + (HIDEEP_PERIPHERAL_BASE + 0x01000000) +#define HIDEEP_SYSCON_BASE \ + (HIDEEP_PERIPHERAL_BASE + 0x02000000) + +#define HIDEEP_SYSCON_MOD_CON (HIDEEP_SYSCON_BASE + 0x0000) +#define HIDEEP_SYSCON_SPC_CON (HIDEEP_SYSCON_BASE + 0x0004) +#define HIDEEP_SYSCON_CLK_CON (HIDEEP_SYSCON_BASE + 0x0008) +#define HIDEEP_SYSCON_CLK_ENA (HIDEEP_SYSCON_BASE + 0x000C) +#define HIDEEP_SYSCON_RST_CON (HIDEEP_SYSCON_BASE + 0x0010) +#define HIDEEP_SYSCON_WDT_CON (HIDEEP_SYSCON_BASE + 0x0014) +#define HIDEEP_SYSCON_WDT_CNT (HIDEEP_SYSCON_BASE + 0x0018) +#define HIDEEP_SYSCON_PWR_CON (HIDEEP_SYSCON_BASE + 0x0020) +#define HIDEEP_SYSCON_PGM_ID (HIDEEP_SYSCON_BASE + 0x00F4) + +#define HIDEEP_FLASH_CON (HIDEEP_FLASH_BASE + 0x0000) +#define HIDEEP_FLASH_STA (HIDEEP_FLASH_BASE + 0x0004) +#define HIDEEP_FLASH_CFG (HIDEEP_FLASH_BASE + 0x0008) +#define HIDEEP_FLASH_TIM (HIDEEP_FLASH_BASE + 0x000C) +#define HIDEEP_FLASH_CACHE_CFG (HIDEEP_FLASH_BASE + 0x0010) +#define HIDEEP_FLASH_PIO_SIG (HIDEEP_FLASH_BASE + 0x400000) + +#define HIDEEP_ESI_TX_INVALID (HIDEEP_ESI_BASE + 0x0008) + +#define HIDEEP_PERASE 0x00040000 +#define HIDEEP_WRONLY 0x00100000 + +#define HIDEEP_NVM_MASK_OFS 0x0000000C +#define HIDEEP_NVM_DEFAULT_PAGE 0 +#define HIDEEP_NVM_SFR_WPAGE 1 +#define HIDEEP_NVM_SFR_RPAGE 2 + +#define HIDEEP_PIO_SIG 0x00400000 +#define HIDEEP_PROT_MODE 0x03400000 + +#define HIDEEP_NVM_PAGE_SIZE 128 + +#define HIDEEP_DWZ_INFO 0x000002C0 + +struct hideep_event { + __le16 x; + __le16 y; + __le16 z; + u8 w; + u8 flag; + u8 type; + u8 index; +} __packed; + +struct dwz_info { + __le32 code_start; + u8 code_crc[12]; + + __le32 c_code_start; + __le16 c_code_len; + __le16 gen_ver; + + __le32 vr_start; + __le16 vr_len; + __le16 rsv0; + + __le32 ft_start; + __le16 ft_len; + __le16 vr_version; + + __le16 boot_ver; + __le16 core_ver; + __le16 custom_ver; + __le16 release_ver; + + u8 factory_id; + u8 panel_type; + u8 model_name[6]; + __le16 product_code; + __le16 extra_option; + + __le16 product_id; + __le16 vendor_id; +} __packed; + +struct hideep_ts { + struct i2c_client *client; + struct input_dev *input_dev; + struct regmap *reg; + + struct touchscreen_properties prop; + + struct gpio_desc *reset_gpio; + + struct regulator *vcc_vdd; + struct regulator *vcc_vid; + + struct mutex dev_mutex; + + u32 tch_count; + u32 key_count; + u32 lpm_count; + + u8 touch_event[HIDEEP_MT_MAX * 10]; + u8 key_event[HIDEEP_KEY_MAX * 2]; + + int key_num; + int key_codes[HIDEEP_KEY_MAX]; + + struct dwz_info dwz_info; + + int fw_size; + int nvm_mask; +}; + +struct pgm_packet { + union { + u8 b[8]; + u32 w[2]; + } header; + + u32 payload[HIDEEP_NVM_PAGE_SIZE / sizeof(u32)]; +}; + +static int hideep_pgm_w_mem(struct hideep_ts *ts, u32 addr, + struct pgm_packet *packet, u32 len) +{ + int ret; + int i; + struct i2c_msg msg; + + if ((len % sizeof(u32)) != 0) + return -EINVAL; + + put_unaligned_be32((0x80 | (len / sizeof(u32) - 1)), + &packet->header.w[0]); + put_unaligned_be32(addr, &packet->header.w[1]); + + for (i = 0; i < len / sizeof(u32); i++) + put_unaligned_be32(packet->payload[i], &packet->payload[i]); + + msg.addr = ts->client->addr; + msg.flags = 0; + msg.len = len + 5; + msg.buf = &packet->header.b[3]; + + ret = i2c_transfer(ts->client->adapter, &msg, 1); + + return ret; +} + +static int hideep_pgm_r_mem(struct hideep_ts *ts, u32 addr, + struct pgm_packet *packet, u32 len) +{ + int ret; + int i; + u8 *buff; + struct i2c_msg msg[2]; + + if ((len % sizeof(u32)) != 0) + return -EINVAL; + + buff = kmalloc(len, GFP_KERNEL); + + if (!buff) + return -ENOMEM; + + put_unaligned_be32((0x00 | (len / sizeof(u32) - 1)), + &packet->header.w[0]); + put_unaligned_be32(addr, &packet->header.w[1]); + + msg[0].addr = ts->client->addr; + msg[0].flags = 0; + msg[0].len = 5; + msg[0].buf = &packet->header.b[3]; + + msg[1].addr = ts->client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = len; + msg[1].buf = buff; + + ret = i2c_transfer(ts->client->adapter, msg, 2); + + if (ret < 0) + return ret; + + for (i = 0; i < len / sizeof(u32); i++) + packet->payload[i] = get_unaligned_be32(&buff[i * sizeof(u32)]); + + return ret; +} + +static int hideep_pgm_r_reg(struct hideep_ts *ts, u32 addr, + u32 *val) +{ + int ret; + struct pgm_packet packet; + + put_unaligned_be32(0x00, &packet.header.w[0]); + put_unaligned_be32(addr, &packet.header.w[1]); + + ret = hideep_pgm_r_mem(ts, addr, &packet, sizeof(u32)); + + if (ret < 0) + return ret; + + *val = packet.payload[0]; + + return ret; +} + +static int hideep_pgm_w_reg(struct hideep_ts *ts, u32 addr, + u32 data) +{ + int ret; + struct pgm_packet packet; + + put_unaligned_be32(0x80, &packet.header.w[0]); + put_unaligned_be32(addr, &packet.header.w[1]); + packet.payload[0] = data; + + ret = hideep_pgm_w_mem(ts, addr, &packet, sizeof(u32)); + + return ret; +} + +#define SW_RESET_IN_PGM(CLK) \ +{ \ + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_WDT_CNT, CLK); \ + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_WDT_CON, 0x03); \ + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_WDT_CON, 0x01); \ +} + +#define SET_FLASH_PIO(CE) \ + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CON, 0x01 | (CE << 1)) +#define SET_PIO_SIG(X, Y) \ + hideep_pgm_w_reg(ts, HIDEEP_FLASH_PIO_SIG + X, Y) +#define SET_FLASH_HWCONTROL() \ + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CON, 0x00) + +#define NVM_W_SFR(x, y) \ +{ \ + SET_FLASH_PIO(1); \ + SET_PIO_SIG(x, y); \ + SET_FLASH_PIO(0); \ +} + +static void hideep_pgm_set(struct hideep_ts *ts) +{ + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_WDT_CON, 0x00); + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_SPC_CON, 0x00); + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_CLK_ENA, 0xFF); + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_CLK_CON, 0x01); + hideep_pgm_w_reg(ts, HIDEEP_SYSCON_PWR_CON, 0x01); + hideep_pgm_w_reg(ts, HIDEEP_FLASH_TIM, 0x03); + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CACHE_CFG, 0x00); +} + +static int hideep_pgm_get_pattern(struct hideep_ts *ts) +{ + int ret; + u32 status; + u16 p1 = 0xAF39; + u16 p2 = 0xDF9D; + + ret = regmap_bulk_write(ts->reg, p1, (void *)&p2, 1); + + if (ret < 0) { + dev_err(&ts->client->dev, "%d, %08X", __LINE__, ret); + return ret; + } + + mdelay(1); + + /* flush invalid Tx load register */ + ret = hideep_pgm_w_reg(ts, HIDEEP_ESI_TX_INVALID, 0x01); + + if (ret < 0) + return ret; + + ret = hideep_pgm_r_reg(ts, HIDEEP_SYSCON_PGM_ID, &status); + + if (ret < 0) + return ret; + + return status; +} + +static int hideep_enter_pgm(struct hideep_ts *ts) +{ + int retry_count = 10; + int val; + u32 pgm_pattern = 0xDF9DAF39; + + while (retry_count--) { + val = hideep_pgm_get_pattern(ts); + + if (pgm_pattern != get_unaligned_be32(&val)) { + dev_err(&ts->client->dev, "enter_pgm : error(%08x):", + get_unaligned_be32(&val)); + } else { + dev_dbg(&ts->client->dev, "found magic code"); + break; + } + } + + if (retry_count < 0) { + dev_err(&ts->client->dev, "couldn't enter pgm mode!!!"); + SW_RESET_IN_PGM(1000); + return -EBADMSG; + } + + hideep_pgm_set(ts); + mdelay(1); + + return 0; +} + +static void hideep_nvm_unlock(struct hideep_ts *ts) +{ + u32 unmask_code = 0; + + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CFG, + HIDEEP_NVM_SFR_RPAGE); + + hideep_pgm_r_reg(ts, 0x0000000C, &unmask_code); + + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CFG, + HIDEEP_NVM_DEFAULT_PAGE); + + /* make it unprotected code */ + unmask_code &= (~HIDEEP_PROT_MODE); + + /* compare unmask code */ + if (unmask_code != ts->nvm_mask) + dev_dbg(&ts->client->dev, "read mask code different 0x%x", + unmask_code); + + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CFG, + HIDEEP_NVM_SFR_WPAGE); + SET_FLASH_PIO(0); + + NVM_W_SFR(HIDEEP_NVM_MASK_OFS, ts->nvm_mask); + SET_FLASH_HWCONTROL(); + hideep_pgm_w_reg(ts, HIDEEP_FLASH_CFG, + HIDEEP_NVM_DEFAULT_PAGE); +} + +static int hideep_check_status(struct hideep_ts *ts) +{ + int ret, status; + int time_out = 100; + + while (time_out--) { + mdelay(1); + ret = hideep_pgm_r_reg(ts, HIDEEP_FLASH_STA, + &status); + + if (ret < 0) + continue; + + if (status) + return status; + } + + return time_out; +} + +static int hideep_program_page(struct hideep_ts *ts, + u32 addr, struct pgm_packet *packet_w) +{ + int ret; + + + ret = hideep_check_status(ts); + + if (ret < 0) + return -EBUSY; + + addr = addr & ~(HIDEEP_NVM_PAGE_SIZE - 1); + + SET_FLASH_PIO(0); + SET_FLASH_PIO(1); + + /* erase page */ + SET_PIO_SIG((HIDEEP_PERASE | addr), 0xFFFFFFFF); + + SET_FLASH_PIO(0); + + ret = hideep_check_status(ts); + + if (ret < 0) + return -EBUSY; + + /* write page */ + SET_FLASH_PIO(1); + + SET_PIO_SIG((HIDEEP_WRONLY | addr), + get_unaligned_be32(&packet_w->payload[0])); + + hideep_pgm_w_mem(ts, (HIDEEP_FLASH_PIO_SIG | HIDEEP_WRONLY), + packet_w, HIDEEP_NVM_PAGE_SIZE); + + SET_PIO_SIG(124, get_unaligned_be32(&packet_w->payload[31])); + + SET_FLASH_PIO(0); + + mdelay(1); + + ret = hideep_check_status(ts); + + if (ret < 0) + return -EBUSY; + + SET_FLASH_HWCONTROL(); + + return 0; +} + +static void hideep_program_nvm(struct hideep_ts *ts, const u8 *ucode, + int len) +{ + struct pgm_packet packet_w; + struct pgm_packet packet_r; + int i; + int ret; + int addr = 0; + int len_r = len; + int len_w = HIDEEP_NVM_PAGE_SIZE; + u32 pages = DIV_ROUND_UP(len, HIDEEP_NVM_PAGE_SIZE); + + + hideep_nvm_unlock(ts); + + dev_dbg(&ts->client->dev, "pages : %d", pages); + + for (i = 0; i < pages; i++) { + if (len_r < HIDEEP_NVM_PAGE_SIZE) + len_w = len_r; + + /* compare */ + hideep_pgm_r_mem(ts, 0x00000000 + addr, &packet_r, + HIDEEP_NVM_PAGE_SIZE); + ret = memcmp(&ucode[addr], packet_r.payload, len_w); + + if (ret) { + /* write page */ + memcpy(packet_w.payload, &ucode[addr], len_w); + ret = hideep_program_page(ts, addr, &packet_w); + if (ret) + dev_err(&ts->client->dev, + "%s : error(%08x):", + __func__, addr); + mdelay(1); + } + + addr += HIDEEP_NVM_PAGE_SIZE; + len_r -= HIDEEP_NVM_PAGE_SIZE; + if (len_r < 0) + break; + } +} + +static int hideep_verify_nvm(struct hideep_ts *ts, const u8 *ucode, + int len) +{ + struct pgm_packet packet_r; + int i, j; + int ret; + int addr = 0; + int len_r = len; + int len_v = HIDEEP_NVM_PAGE_SIZE; + u32 pages = DIV_ROUND_UP(len, HIDEEP_NVM_PAGE_SIZE); + + for (i = 0; i < pages; i++) { + if (len_r < HIDEEP_NVM_PAGE_SIZE) + len_v = len_r; + + hideep_pgm_r_mem(ts, 0x00000000 + addr, &packet_r, + HIDEEP_NVM_PAGE_SIZE); + + ret = memcmp(&ucode[addr], packet_r.payload, len_v); + + if (ret) { + u8 *read = (u8 *)packet_r.payload; + + for (j = 0; j < HIDEEP_NVM_PAGE_SIZE; j++) { + if (ucode[addr + j] != read[j]) + dev_err(&ts->client->dev, + "verify : error([%d] %02x : %02x)", + addr + j, ucode[addr + j], + read[j]); + } + return ret; + } + + addr += HIDEEP_NVM_PAGE_SIZE; + len_r -= HIDEEP_NVM_PAGE_SIZE; + if (len_r < 0) + break; + } + + return 0; +} + +static int hideep_update_firmware(struct hideep_ts *ts, const char *fn) +{ + int ret; + int retry, retry_cnt = 3; + const struct firmware *fw_entry; + + dev_dbg(&ts->client->dev, "enter"); + ret = request_firmware(&fw_entry, fn, &ts->client->dev); + + if (ret != 0) { + dev_err(&ts->client->dev, "request_firmware : fail(%d)", ret); + return ret; + } + + if (fw_entry->size > ts->fw_size) { + dev_err(&ts->client->dev, + "file size(%zu) is big more than fw memory size(%d)", + fw_entry->size, ts->fw_size); + release_firmware(fw_entry); + return -EFBIG; + } + + /* chip specific code for flash fuse */ + mutex_lock(&ts->dev_mutex); + + /* enter program mode */ + ret = hideep_enter_pgm(ts); + + if (ret) + return ret; + + /* comparing & programming each page, if the memory of specified + * page is exactly same, no need to update. + */ + for (retry = 0; retry < retry_cnt; retry++) { + hideep_program_nvm(ts, fw_entry->data, fw_entry->size); + + ret = hideep_verify_nvm(ts, fw_entry->data, fw_entry->size); + if (!ret) + break; + } + + if (retry < retry_cnt) + dev_dbg(&ts->client->dev, "update success!!!"); + else + dev_err(&ts->client->dev, "update failed!!!"); + + SW_RESET_IN_PGM(1000); + + mutex_unlock(&ts->dev_mutex); + + release_firmware(fw_entry); + + return ret; +} + +static int hideep_load_dwz(struct hideep_ts *ts) +{ + int ret = 0; + struct pgm_packet packet_r; + + ret = hideep_enter_pgm(ts); + + if (ret) + return ret; + + mdelay(50); + + hideep_pgm_r_mem(ts, HIDEEP_DWZ_INFO, &packet_r, + sizeof(struct dwz_info)); + + memcpy(&ts->dwz_info, packet_r.payload, + sizeof(struct dwz_info)); + + SW_RESET_IN_PGM(10); + + if (get_unaligned_le16(&ts->dwz_info.product_code) & 0x60) { + /* Lime fw size */ + dev_dbg(&ts->client->dev, "used lime IC"); + ts->fw_size = 1024 * 64; + ts->nvm_mask = 0x0030027B; + } else if (get_unaligned_le16(&ts->dwz_info.product_code) & 0x40) { + /* Crimson IC */ + dev_dbg(&ts->client->dev, "used crimson IC"); + ts->fw_size = 1024 * 48; + ts->nvm_mask = 0x00310000; + } else { + dev_dbg(&ts->client->dev, "product code is wrong!!!"); + return -EINVAL; + } + + dev_dbg(&ts->client->dev, "firmware release version : %04x", + get_unaligned_le16(&ts->dwz_info.release_ver)); + + mdelay(50); + + return 0; +} + +static int hideep_pwr_on(struct hideep_ts *ts) +{ + int ret = 0; + u8 cmd = 0x01; + + if (ts->vcc_vdd) { + ret = regulator_enable(ts->vcc_vdd); + if (ret) + dev_err(&ts->client->dev, + "Regulator vdd enable failed ret=%d", ret); + usleep_range(999, 1000); + } + + if (ts->vcc_vid) { + ret = regulator_enable(ts->vcc_vid); + if (ret) + dev_err(&ts->client->dev, + "Regulator vcc_vid enable failed ret=%d", ret); + usleep_range(2999, 3000); + } + + mdelay(30); + + if (ts->reset_gpio) + gpiod_set_raw_value(ts->reset_gpio, 1); + else + regmap_write(ts->reg, HIDEEP_RESET_CMD, cmd); + + mdelay(50); + + return ret; +} + +static void hideep_pwr_off(void *data) +{ + struct hideep_ts *ts = data; + + if (ts->reset_gpio) + gpiod_set_value(ts->reset_gpio, 0); + + if (ts->vcc_vid) + regulator_disable(ts->vcc_vid); + + if (ts->vcc_vdd) + regulator_disable(ts->vcc_vdd); +} + +#define __GET_MT_TOOL_TYPE(X) ((X == 0x01) ? MT_TOOL_FINGER : MT_TOOL_PEN) + +static void push_mt(struct hideep_ts *ts) +{ + int id; + int i; + int btn_up = 0; + int evt = 0; + int offset = sizeof(struct hideep_event); + struct hideep_event *event; + + /* load multi-touch event to input system */ + for (i = 0; i < ts->tch_count; i++) { + event = (struct hideep_event *)&ts->touch_event[i * offset]; + id = event->index & 0x0F; + btn_up = event->flag & HIDEEP_MT_RELEASED; + + dev_dbg(&ts->client->dev, + "type = %d, id = %d, i = %d, x = %d, y = %d, z = %d", + event->type, event->index, i, + get_unaligned_le16(&event->x), + get_unaligned_le16(&event->y), + get_unaligned_le16(&event->z)); + + input_mt_slot(ts->input_dev, id); + input_mt_report_slot_state(ts->input_dev, + __GET_MT_TOOL_TYPE(event->type), + (btn_up == 0)); + + if (btn_up == 0) { + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, + get_unaligned_le16(&event->x)); + input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, + get_unaligned_le16(&event->y)); + input_report_abs(ts->input_dev, ABS_MT_PRESSURE, + get_unaligned_le16(&event->z)); + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, + event->w); + evt++; + } + } + + input_mt_sync_frame(ts->input_dev); +} + +static void push_ky(struct hideep_ts *ts) +{ + int i; + int status; + int code; + + for (i = 0; i < ts->key_count; i++) { + code = ts->key_event[i * 2] & 0x0F; + status = ts->key_event[i * 2] & 0xF0; + + input_report_key(ts->input_dev, ts->key_codes[code], + status & HIDEEP_KEY_PRESSED_MASK); + } +} + +static void hideep_put_event(struct hideep_ts *ts) +{ + /* mangling touch information */ + if (ts->tch_count > 0) + push_mt(ts); + + if (ts->key_count > 0) + push_ky(ts); + + input_sync(ts->input_dev); +} + +static int hideep_parse_event(struct hideep_ts *ts, u8 *data) +{ + int touch_count; + + ts->tch_count = data[0]; + ts->key_count = data[1] & 0x0f; + ts->lpm_count = data[1] & 0xf0; + + /* get touch event count */ + dev_dbg(&ts->client->dev, "mt = %d, key = %d, lpm = %02x", + ts->tch_count, ts->key_count, ts->lpm_count); + + /* get touch event information */ + if (ts->tch_count < HIDEEP_MT_MAX) + memcpy(ts->touch_event, &data[HIDEEP_TOUCH_EVENT_INDEX], + HIDEEP_MT_MAX * sizeof(struct hideep_event)); + else + ts->tch_count = 0; + + if (ts->key_count < HIDEEP_KEY_MAX) + memcpy(ts->key_event, &data[HIDEEP_KEY_EVENT_INDEX], + HIDEEP_KEY_MAX * 2); + else + ts->key_count = 0; + + touch_count = ts->tch_count + ts->key_count; + + return touch_count; +} + +static irqreturn_t hideep_irq_task(int irq, void *handle) +{ + u8 buff[HIDEEP_MAX_EVENT]; + int ret; + + struct hideep_ts *ts = handle; + + ret = regmap_bulk_read(ts->reg, HIDEEP_EVENT_ADDR, + buff, HIDEEP_MAX_EVENT / 2); + + if (ret < 0) + return IRQ_HANDLED; + + ret = hideep_parse_event(ts, buff); + + if (ret > 0) + hideep_put_event(ts); + + return IRQ_HANDLED; +} + +static void hideep_get_axis_info(struct hideep_ts *ts) +{ + int ret; + u8 val[4]; + + if (ts->prop.max_x == 0 || ts->prop.max_y == 0) { + ret = regmap_bulk_read(ts->reg, 0x28, val, 2); + + if (ret < 0) { + ts->prop.max_x = -1; + ts->prop.max_y = -1; + } else { + ts->prop.max_x = + get_unaligned_le16(&val[0]); + ts->prop.max_y = + get_unaligned_le16(&val[2]); + } + } + + dev_dbg(&ts->client->dev, "X : %d, Y : %d", + ts->prop.max_x, ts->prop.max_y); +} + +static int hideep_capability(struct hideep_ts *ts) +{ + int ret, i; + + hideep_get_axis_info(ts); + + if (ts->prop.max_x < 0 || ts->prop.max_y < 0) + return -EINVAL; + + ts->input_dev->name = HIDEEP_TS_NAME; + ts->input_dev->id.bustype = BUS_I2C; + + if (ts->key_num) { + ts->input_dev->keycode = ts->key_codes; + ts->input_dev->keycodesize = sizeof(ts->key_codes[0]); + ts->input_dev->keycodemax = ts->key_num; + for (i = 0; i < ts->key_num; i++) + input_set_capability(ts->input_dev, EV_KEY, + ts->key_codes[i]); + } + + input_set_abs_params(ts->input_dev, + ABS_MT_TOOL_TYPE, 0, MT_TOOL_MAX, 0, 0); + input_set_abs_params(ts->input_dev, + ABS_MT_POSITION_X, 0, ts->prop.max_x, 0, 0); + input_set_abs_params(ts->input_dev, + ABS_MT_POSITION_Y, 0, ts->prop.max_y, 0, 0); + input_set_abs_params(ts->input_dev, + ABS_MT_PRESSURE, 0, 65535, 0, 0); + input_set_abs_params(ts->input_dev, + ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + + ret = input_mt_init_slots(ts->input_dev, + HIDEEP_MT_MAX, INPUT_MT_DIRECT); + + return ret; +} + +static ssize_t hideep_update_fw(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hideep_ts *ts = dev_get_drvdata(dev); + int mode, ret; + char *fw_name; + + ret = kstrtoint(buf, 8, &mode); + if (ret) + return ret; + + disable_irq(ts->client->irq); + + fw_name = kasprintf(GFP_KERNEL, "hideep_ts_%04x.bin", + get_unaligned_le16(&ts->dwz_info.product_id)); + ret = hideep_update_firmware(ts, fw_name); + + if (ret != 0) + dev_err(dev, "The firmware update failed(%d)", ret); + + kfree(fw_name); + + ret = hideep_load_dwz(ts); + + if (ret < 0) + dev_err(&ts->client->dev, "fail to load dwz, ret = 0x%x", ret); + + enable_irq(ts->client->irq); + + return count; +} + +static ssize_t hideep_fw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int len = 0; + struct hideep_ts *ts = dev_get_drvdata(dev); + + mutex_lock(&ts->dev_mutex); + len = scnprintf(buf, PAGE_SIZE, + "%04x\n", get_unaligned_le16(&ts->dwz_info.release_ver)); + mutex_unlock(&ts->dev_mutex); + + return len; +} + +static ssize_t hideep_product_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int len = 0; + struct hideep_ts *ts = dev_get_drvdata(dev); + + mutex_lock(&ts->dev_mutex); + len = scnprintf(buf, PAGE_SIZE, + "%04x\n", get_unaligned_le16(&ts->dwz_info.product_id)); + mutex_unlock(&ts->dev_mutex); + + return len; +} + +static DEVICE_ATTR(version, 0664, hideep_fw_version_show, NULL); +static DEVICE_ATTR(product_id, 0664, hideep_product_id_show, NULL); +static DEVICE_ATTR(update_fw, 0664, NULL, hideep_update_fw); + +static struct attribute *hideep_ts_sysfs_entries[] = { + &dev_attr_version.attr, + &dev_attr_product_id.attr, + &dev_attr_update_fw.attr, + NULL, +}; + +static struct attribute_group hideep_ts_attr_group = { + .attrs = hideep_ts_sysfs_entries, +}; + +static int __maybe_unused hideep_resume(struct device *dev) +{ + struct hideep_ts *ts = dev_get_drvdata(dev); + int ret; + + ret = hideep_pwr_on(ts); + if (ret < 0) + dev_err(&ts->client->dev, "power on failed"); + else + enable_irq(ts->client->irq); + + return ret; +} + +static int __maybe_unused hideep_suspend(struct device *dev) +{ + struct hideep_ts *ts = dev_get_drvdata(dev); + + disable_irq(ts->client->irq); + hideep_pwr_off(ts); + + return 0; +} + +static int hideep_parse_dts(struct device *dev, struct hideep_ts *ts) +{ + int ret; + + ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(ts->reset_gpio)) + return PTR_ERR(ts->reset_gpio); + + ts->vcc_vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(ts->vcc_vdd)) + return PTR_ERR(ts->vcc_vdd); + + ts->vcc_vid = devm_regulator_get(dev, "vid"); + if (IS_ERR(ts->vcc_vid)) + return PTR_ERR(ts->vcc_vid); + + ts->key_num = device_property_read_u32_array(dev, "linux,keycodes", + NULL, 0); + + if (ts->key_num > HIDEEP_KEY_MAX) { + dev_err(dev, "too many support key defined(%d)!!!", + ts->key_num); + return -EINVAL; + } + + ret = device_property_read_u32_array(dev, "linux,keycodes", + ts->key_codes, ts->key_num); + if (ret) { + dev_dbg(dev, "don't support touch key"); + ts->key_num = 0; + } + + return 0; +} + +const struct regmap_config hideep_regmap_config = { + .reg_bits = 16, + .reg_format_endian = REGMAP_ENDIAN_LITTLE, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .max_register = 0xffff, +}; + +static int hideep_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct regmap *regmap; + struct hideep_ts *ts; + + /* check i2c bus */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_I2C)) { + dev_err(&client->dev, "check i2c device error"); + return -ENODEV; + } + + regmap = devm_regmap_init_i2c(client, &hideep_regmap_config); + + if (IS_ERR(regmap)) { + dev_err(&client->dev, "don't init regmap"); + return PTR_ERR(regmap); + } + + /* init hideep_ts */ + ts = devm_kzalloc(&client->dev, + sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + ret = hideep_parse_dts(&client->dev, ts); + + if (ret) + return ret; + + ts->client = client; + ts->reg = regmap; + + i2c_set_clientdata(client, ts); + + mutex_init(&ts->dev_mutex); + + /* power on */ + ret = hideep_pwr_on(ts); + if (ret) { + dev_err(&ts->client->dev, "power on failed"); + return ret; + } + + ret = devm_add_action_or_reset(&ts->client->dev, hideep_pwr_off, ts); + if (ret) { + hideep_pwr_off(ts); + return ret; + } + + mdelay(30); + + /* read info */ + ret = hideep_load_dwz(ts); + if (ret < 0) { + dev_err(&client->dev, "fail to load dwz, ret = 0x%x", ret); + return ret; + } + + /* init input device */ + ts->input_dev = devm_input_allocate_device(&client->dev); + if (!ts->input_dev) { + dev_err(&client->dev, "can't allocate memory for input_dev"); + return -ENOMEM; + } + + touchscreen_parse_properties(ts->input_dev, true, &ts->prop); + + ret = hideep_capability(ts); + if (ret) { + dev_err(&client->dev, "can't init input properties"); + return ret; + } + + ret = input_register_device(ts->input_dev); + if (ret) { + dev_err(&client->dev, "can't register input_dev"); + return ret; + } + + input_set_drvdata(ts->input_dev, ts); + + dev_info(&ts->client->dev, "ts irq: %d", ts->client->irq); + if (client->irq <= 0) { + dev_err(&client->dev, "can't be assigned irq"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(&client->dev, ts->client->irq, + NULL, hideep_irq_task, IRQF_ONESHOT, + ts->client->name, ts); + + if (ret < 0) { + dev_err(&client->dev, "fail to get irq, ret = 0x%08x", + ret); + return ret; + } + + ret = devm_device_add_group(&client->dev, &hideep_ts_attr_group); + + if (ret) { + dev_err(&client->dev, "fail init sys, ret = 0x%x", ret); + return ret; + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(hideep_pm_ops, hideep_suspend, hideep_resume); + +static const struct i2c_device_id hideep_dev_idtable[] = { + { HIDEEP_I2C_NAME, 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, hideep_dev_idtable); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id hideep_acpi_id[] = { + { "HIDP0001", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, hideep_acpi_id); +#endif + +#ifdef CONFIG_OF +static const struct of_device_id hideep_match_table[] = { + { .compatible = "hideep,hideep-ts" }, + {} +}; +MODULE_DEVICE_TABLE(of, hideep_match_table); +#endif + +static struct i2c_driver hideep_driver = { + .probe = hideep_probe, + .id_table = hideep_dev_idtable, + .driver = { + .name = HIDEEP_I2C_NAME, + .of_match_table = of_match_ptr(hideep_match_table), + .acpi_match_table = ACPI_PTR(hideep_acpi_id), + .pm = &hideep_pm_ops, + }, +}; + +module_i2c_driver(hideep_driver); + +MODULE_DESCRIPTION("Driver for HiDeep Touchscreen Controller"); +MODULE_AUTHOR("anthony.kim@hideep.com"); +MODULE_LICENSE("GPL v2");