Re: [PATCH V2] Add driver for GOODiX GTx5 series touchsereen

From: Wang Yafei
Date: Fri Jun 16 2017 - 03:52:20 EST


Ping for review


On 06/13/2017 02:27 PM, Wang Yafei wrote:
> V2 changes:
>
> - replace touchscreen properties according to the description in
> Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt
>
> - Droped all compat stuff for older kernels
>
> - Removed Android stuff (EARLY_SUSPEND, CONFIG_FB)
>
> - Use device_property_read_* get device properties
>
> - use get-unaligned_*() API
>
> - Use dev_err() dev_dbg() for logging
>
> - remove pinctrl functions
>
> - remove some unused functions
>
> V1 info:
> This driver is for GOODiX GTx5 series touchscreen controllers
> such as GT8589, GT7589. This driver designed with hierarchial structure,
> for that can be modified to support subsequent controllers easily.
> Some zones of the touchscreen can be set to buttons(according to the
> hardware). That is why it handles button and multitouch events.
>
> A brief description of driver structure
> - Core Layer: This layer responsible for basic input events report,
> GPIO pinctrl, Interrupt, Power resources manager and submodules
> manager.
> - Hardware Layer: This layer responsible for controllers initialization,
> irq handle as well as bus read/write.
> - External Module Layer: This layer used for support more features
> such as firmware update, debug tools and gesture wakeup.
>
> Signed-off-by: Wang Yafei <wangyafei@xxxxxxxxxx>
> ---
> drivers/input/touchscreen/Kconfig | 1 +
> drivers/input/touchscreen/Makefile | 1 +
> .../input/touchscreen/goodix-ts-sunrise/Kconfig | 36 +
> .../input/touchscreen/goodix-ts-sunrise/Makefile | 6 +
> .../touchscreen/goodix-ts-sunrise/goodix-gtx5.txt | 75 +
> .../goodix-ts-sunrise/goodix_gtx5_i2c.c | 895 ++++++++++++
> .../goodix-ts-sunrise/goodix_gtx5_update.c | 1450 ++++++++++++++++++++
> .../touchscreen/goodix-ts-sunrise/goodix_ts_core.c | 1366 ++++++++++++++++++
> .../touchscreen/goodix-ts-sunrise/goodix_ts_core.h | 553 ++++++++
> .../goodix-ts-sunrise/goodix_ts_tools.c | 542 ++++++++
> 10 files changed, 4925 insertions(+)
> create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/Kconfig
> create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/Makefile
> create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/goodix-gtx5.txt
> create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_i2c.c
> create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_update.c
> create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.c
> create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.h
> create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_tools.c
>
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index cf26ca4..f3642bb 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -15,6 +15,7 @@ config TOUCHSCREEN_PROPERTIES
> def_tristate INPUT
> depends on INPUT
>
> +source "drivers/input/touchscreen/goodix-ts-sunrise/Kconfig"
> config TOUCHSCREEN_88PM860X
> tristate "Marvell 88PM860x touchscreen"
> depends on MFD_88PM860X
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index 18e4769..d9408c0 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -6,6 +6,7 @@
>
> wm97xx-ts-y := wm97xx-core.o
>
> +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX5) += goodix-ts-sunrise/
> obj-$(CONFIG_TOUCHSCREEN_PROPERTIES) += of_touchscreen.o
> obj-$(CONFIG_TOUCHSCREEN_88PM860X) += 88pm860x-ts.o
> obj-$(CONFIG_TOUCHSCREEN_AD7877) += ad7877.o
> diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/Kconfig b/drivers/input/touchscreen/goodix-ts-sunrise/Kconfig
> new file mode 100755
> index 0000000..8e16595
> --- /dev/null
> +++ b/drivers/input/touchscreen/goodix-ts-sunrise/Kconfig
> @@ -0,0 +1,36 @@
> +#
> +# Goodix touchscreen driver configuration
> +#
> +menuconfig TOUCHSCREEN_GOODIX_GTX5
> + bool "Goodix GTx5 touchscreen"
> + depends on I2C
> + default y
> + help
> + Say Y here if you have a Goodix GTx5xx touchscreen connected
> + to your system.
> +
> + If unsure, say N.
> +
> +if TOUCHSCREEN_GOODIX_GTX5
> +
> +config TOUCHSCREEN_GOODIX_GTX5_UPDATE
> + tristate "Goodix GTx5xx firmware update module"
> + default y
> + help
> + Say Y here to enable support for doing firmware update.
> +
> + If unsure, say N.
> +
> + To compile this driver as a module, choose M here.
> +
> +config TOUCHSCREEN_GOODIX_GTX5_TOOLS
> + tristate "Goodix touch tools support"
> + default n
> + help
> + Say Y here to enable debug tools.
> +
> + If unsure, say N.
> +
> + To compile this driver as a module, choose M here.
> +
> +endif
> diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/Makefile b/drivers/input/touchscreen/goodix-ts-sunrise/Makefile
> new file mode 100755
> index 0000000..690f256
> --- /dev/null
> +++ b/drivers/input/touchscreen/goodix-ts-sunrise/Makefile
> @@ -0,0 +1,6 @@
> +# Goodix Touchscreen Makefile
> +
> +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX5) += goodix_gtx5_i2c.o
> +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX5) += goodix_ts_core.o
> +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX5_TOOLS) += goodix_ts_tools.o
> +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX5_UPDATE) += goodix_gtx5_update.o
> diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/goodix-gtx5.txt b/drivers/input/touchscreen/goodix-ts-sunrise/goodix-gtx5.txt
> new file mode 100755
> index 0000000..116c3ec
> --- /dev/null
> +++ b/drivers/input/touchscreen/goodix-ts-sunrise/goodix-gtx5.txt
> @@ -0,0 +1,75 @@
> +Device tree bindings for Goodix GTx5 series touchscreen controller
> +
> +Required properties:
> +
> +- compatible : should be "goodix,gtx5" or "goodix,gsx"
> +
> +- reg : I2C address of the chip. Should be 0x5d or 0x14
> +- interrupt-parent : Inerrupt controller to which the chip is connected
> +- interrupts : Interrrupt to which the chip is connected
> +- touchscreen-size-x : horizontal resolution of touchscreen
> + (in pixels)
> +- touchscreen-size-y : vertical resolution of touchscreen
> + (in pixels)
> +- touchscreen-max-id : panel supported max touch number.
> +- touchscreen-max-w : panel max width value.
> +
> +
> +Optional properties:
> +- reset-gpios : reset gpio.
> +- irq-gpios : interrupt gpio.
> +- irq-flags : irq trigger type config, value should be:
> + 1 - rising edge,
> + 2 - falling edge,
> + 4 - high level,
> + 5 - low level.
> +- touchscreen-swapped-x-y: swap x/y axis coordinates.
> +- touchscreen-key-map: keycode value map /*KEY_HOMEPAGE, KEY_BACK*/
> +- power-on-delay-us: delay after power on.
> +- power-off-delay-us: delay after power off.
> +- normal-cfg: touch device normal config data.
> +- vtouch-supply : power supply for the touch device.
> +Example:
> +i2c@00000000 {
> + /* ... */
> +
> + goodix-ts-i2c@14 {
> + compatible = "goodix,gtx5";
> + reg = <0x14>;
> + interrupt-parent = <&msm_gpio>;
> + interrupts = <13 0x2800>;
> + vtouch-supply = <&pm8916_l15>;
> + reset-gpios = <&msm_gpio 12 0x0>;
> + irq-gpios = <&msm_gpio 13 0x2800>;
> + irq-flags = <1>; /* 1:trigger rising, 2:trigger falling;*/
> + touchscreen-max-id = <10>;
> + touchscreen-size-x = <400>;
> + touchscreen-size-y = <400>;
> + touchscreen-max-w = <400>;
> + touchscreen-max-pressure = <255>;
> + touchscreen-swapped-x-y;
> + touchscreen-key-map = <172 158>; /*KEY_HOMEPAGE, KEY_BACK*/
> + sensor0 {
> + normal-cfg = [
> + 02 00 00 09 09 01 07 02 00 00 00 00 01 00 3C 00 07 07
> + 00 00 00 00 00 00 40 01 40 01 C8 00 96 00 F4 01 F4 01
> + F4 01 20 01 11 0A 0A 03 14 14 14 14 0A 0C 01 01 11 11
> + 11 00 14 14 14 14 14 14 14 14 14 00 00 0F 00 00 00 00
> + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
> + 00 00 00 00 00 00 00 00 00 00 11 09 10 00 31 32 33 34
> + 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 CA 64 00
> + 00 00 00 00 00 00 09 00 13 00 00 00 00 00 00 00 00 00
> + 50 B0 19 00 19 00 05 00 00 00 00 0A 05 00 00 00 00 00
> + 01 00 FF 00 0B 06 0D 02 FF 04 05 03 07 01 08 0A 0E 11
> + 0F 10 09 13 0C 16 17 14 18 12 19 15 1D 1E 1C 1F 1B 20
> + 1A 2A 29 28 25 2B 27 21 FF 24 22 2C 26 23 FF 00 00 00
> + 00 00 00 00 00 00 00 00 00 00 00 00 80 80 80 80 80 80
> + 80 80 80 80 80 80 80 80 80 80 9F 22 01 AE];
> + };
> + sensor1 {
> + normal-cfg = [ ];
> + };
> + };
> +}
> +
> +
> diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_i2c.c b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_i2c.c
> new file mode 100755
> index 0000000..5a0585a
> --- /dev/null
> +++ b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_i2c.c
> @@ -0,0 +1,895 @@
> +/*
> + * Goodix GTx5 Touchscreen Dirver
> + * Hardware interface layer of touchdriver architecture.
> + *
> + * Copyright (C) 2015 - 2016 Goodix, Inc.
> + * Authors: Wang Yafei <wangyafei@xxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be a reference
> + * to you, when you are integrating the GOODiX's CTP IC into your system,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + */
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/delay.h>
> +#include <linux/ctype.h>
> +#include <linux/slab.h>
> +#include <linux/i2c.h>
> +#include <linux/property.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include "goodix_ts_core.h"
> +
> +#define TS_DT_COMPATIBLE "goodix,gtx5"
> +#define TS_DRIVER_NAME "goodix_i2c"
> +#define I2C_MAX_TRANSFER_SIZE 256
> +#define TS_ADDR_LENGTH 2
> +
> +#define TS_REG_COORDS_BASE 0x824E
> +#define TS_REG_CMD 0x8040
> +#define TS_REG_REQUEST 0x8044
> +#define TS_REG_VERSION 0x8240
> +#define TS_REG_CFG_BASE 0x8050
> +
> +#define CFG_XMAX_OFFSET (0x8052 - 0x8050)
> +#define CFG_YMAX_OFFSET (0x8054 - 0x8050)
> +
> +#define REQUEST_HANDLED 0x00
> +#define REQUEST_CONFIG 0x01
> +#define REQUEST_BAKREF 0x02
> +#define REQUEST_RESET 0x03
> +#define REQUEST_MAINCLK 0x04
> +#define REQUEST_IDLE 0x05
> +
> +#define TS_MAX_SENSORID 5
> +#define TS_CFG_MAX_LEN 495
> +/* set defalut irq flags as Falling edge */
> +#define DEFAULT_IRQ_FLAGS 2
> +#if TS_CFG_MAX_LEN > GOODIX_CFG_MAX_SIZE
> +#error GOODIX_CFG_MAX_SIZE too small, please fix.
> +#endif
> +
> +#ifdef CONFIG_OF
> +/*
> + * goodix_parse_dt_resolution - parse resolution from dt
> + * @dev: device
> + * @board_data: pointer to board data structure
> + * return: 0 - no error, <0 error
> + */
> +static int goodix_parse_dt_resolution(struct device *dev,
> + struct goodix_ts_board_data *board_data)
> +{
> + int r, err = 0;
> +
> + r = device_property_read_u32(dev, "touchscreen-max-id",
> + &board_data->panel_max_id);
> + if (r || board_data->panel_max_id > GOODIX_MAX_TOUCH)
> + board_data->panel_max_id = GOODIX_MAX_TOUCH;
> +
> + r = device_property_read_u32(dev, "touchscreen-size-x",
> + &board_data->panel_max_x);
> + if (r)
> + err = -ENOENT;
> +
> + r = device_property_read_u32(dev, "touchscreen-size-y",
> + &board_data->panel_max_y);
> + if (r)
> + err = -ENOENT;
> +
> + r = device_property_read_u32(dev, "touchscreen-max-w",
> + &board_data->panel_max_w);
> + if (r)
> + err = -ENOENT;
> +
> + board_data->swap_axis = device_property_read_bool(dev,
> + "touchscreen-swapped-x-y");
> +
> + return err;
> +}
> +
> +/**
> + * goodix_parse_dt - parse board data from dt
> + * @dev: pointer to device
> + * @board_data: pointer to board data structure
> + * return: 0 - no error, <0 error
> + */
> +static int goodix_parse_dt(struct device *dev,
> + struct goodix_ts_board_data *board_data)
> +{
> + int r;
> +
> + if (!board_data) {
> + dev_err(dev, "Invalid board data\n");
> + return -EINVAL;
> + }
> +
> + r = device_property_read_u32(dev, "irq-flags",
> + &board_data->irq_flags);
> + if (r) {
> + dev_info(dev, "Use default irq flags:falling_edge\n");
> + board_data->irq_flags = DEFAULT_IRQ_FLAGS;
> + }
> +
> + board_data->avdd_name = "vtouch";
> + r = device_property_read_u32(dev, "power-on-delay-us",
> + &board_data->power_on_delay_us);
> + if (!r) {
> + /* 1000ms is too large, maybe you have pass a wrong value */
> + if (board_data->power_on_delay_us > 1000 * 1000) {
> + dev_warn(dev, "Power on delay time exceed 1s\n");
> + board_data->power_on_delay_us = 0;
> + }
> + }
> +
> + r = device_property_read_u32(dev, "power-off-delay-us",
> + &board_data->power_off_delay_us);
> + if (!r) {
> + /* 1000ms is too large, maybe you have pass a wrong value */
> + if (board_data->power_off_delay_us > 1000 * 1000) {
> + dev_warn(dev, "Power off delay time exceed 1s\n");
> + board_data->power_off_delay_us = 0;
> + }
> + }
> +
> + /* get xyz resolutions */
> + r = goodix_parse_dt_resolution(dev, board_data);
> + if (r < 0) {
> + dev_err(dev, "Failed to parse resolutions:%d\n", r);
> + return r;
> + }
> +
> + /* parse key map */
> + r = device_property_read_u32_array(dev, "panel-key-map",
> + NULL, GOODIX_MAX_KEY);
> + if (r > 0 && r <= GOODIX_MAX_KEY) {
> + board_data->panel_max_key = r;
> + r = device_property_read_u32_array(dev,
> + "panel-key-map",
> + &board_data->panel_key_map[0],
> + board_data->panel_max_key);
> + if (r)
> + dev_err(dev, "Failed get key map info\n");
> + } else {
> + dev_info(dev, "No key map found\n");
> + }
> +
> + dev_dbg(dev, "[DT]id:%d, x:%d, y:%d, w:%d, p:%d\n",
> + board_data->panel_max_id,
> + board_data->panel_max_x,
> + board_data->panel_max_y,
> + board_data->panel_max_w);
> + return 0;
> +}
> +
> +/**
> + * goodix_parse_dt_cfg - pares config data from devicetree dev
> + * @dev: pointer to device
> + * @cfg_type: config type such as normal_config, highsense_cfg ...
> + * @config: pointer to config data structure
> + * @sensor_id: sensor id
> + * return: 0 - no error, <0 error
> + */
> +static int goodix_parse_dt_cfg(struct goodix_ts_device *ts_dev,
> + char *cfg_type, struct goodix_ts_config *config,
> + unsigned int sensor_id)
> +{
> + int r, len;
> + char sub_node_name[24] = {0};
> + struct fwnode_handle *fwnode;
> + struct device *dev = ts_dev->dev;
> + struct goodix_ts_board_data *ts_bdata = ts_dev->board_data;
> +
> + u16 checksum;
> +
> + BUG_ON(config == NULL);
> + if (sensor_id > TS_MAX_SENSORID) {
> + dev_err(dev, "Invalid sensor id\n");
> + return -EINVAL;
> + }
> +
> + if (config->initialized) {
> + dev_dbg(dev, "Config already initialized\n");
> + return 0;
> + }
> +
> + /*
> + * config data are located in child node called
> + * 'sensorx', x is the sensor ID got from touch
> + * device.
> + */
> + snprintf(sub_node_name, sizeof(sub_node_name),
> + "sensor%u", sensor_id);
> + fwnode = device_get_named_child_node(dev, "sub_node_name");
> + if (!fwnode) {
> + dev_dbg(dev, "Child property[%s] not found\n",
> + sub_node_name);
> + return -EINVAL;
> + }
> +
> + len = fwnode_property_read_u8_array(fwnode, cfg_type,
> + NULL, TS_CFG_MAX_LEN);
> + if (len <= 0 || len % 2 != 1) {
> + dev_err(dev, "Invalid cfg type%s, size:%u\n", cfg_type, len);
> + return -EINVAL;
> + }
> +
> + config->length = len;
> +
> + mutex_init(&config->lock);
> + mutex_lock(&config->lock);
> +
> + r = fwnode_property_read_u8_array(fwnode, cfg_type,
> + config->data, TS_CFG_MAX_LEN);
> + if (r) {
> + mutex_unlock(&config->lock);
> + return r;
> + }
> +
> + /* modify max-x max-y resolution, little-endian */
> + config->data[CFG_XMAX_OFFSET] = (u8)ts_bdata->panel_max_x;
> + config->data[CFG_XMAX_OFFSET + 1] = (u8)(ts_bdata->panel_max_x >> 8);
> + config->data[CFG_YMAX_OFFSET] = (u8)ts_bdata->panel_max_y;
> + config->data[CFG_YMAX_OFFSET + 1] = (u8)(ts_bdata->panel_max_y >> 8);
> +
> + /*
> + * checksum: u16 little-endian format
> + * the last byte of config is the config update flag
> + */
> + checksum = checksum_le16(config->data, len - 3);
> + checksum = 0 - checksum;
> + config->data[len - 3] = (u8)checksum;
> + config->data[len - 2] = (u8)(checksum >> 8 & 0xff);
> + config->data[len - 1] = 0x01;
> +
> + strlcpy(config->name, cfg_type, sizeof(config->name));
> + config->reg_base = TS_REG_CFG_BASE;
> + config->delay = 0;
> + config->initialized = true;
> + mutex_unlock(&config->lock);
> +
> + dev_dbg(dev, "Config name:%s,ver:%02xh,size:%d,checksum:%04xh\n",
> + config->name, config->data[0],
> + config->length, checksum);
> + return 0;
> +}
> +#endif
> +
> +/**
> + * goodix_i2c_read - read device register through i2c bus
> + * @dev: pointer to device data
> + * @addr: register address
> + * @data: read buffer
> + * @len: bytes to read
> + * return: 0 - read ok, < 0 - i2c transter error
> + */
> +static int goodix_i2c_read(struct goodix_ts_device *dev, unsigned int reg,
> + unsigned char *data, unsigned int len)
> +{
> + struct i2c_client *client = to_i2c_client(dev->dev);
> + unsigned int transfer_length = 0;
> + unsigned int pos = 0, address = reg;
> + unsigned char get_buf[64], addr_buf[2];
> + int retry, r = 0;
> + struct i2c_msg msgs[] = {
> + {
> + .addr = client->addr,
> + .flags = !I2C_M_RD,
> + .buf = &addr_buf[0],
> + .len = TS_ADDR_LENGTH,
> + }, {
> + .addr = client->addr,
> + .flags = I2C_M_RD,
> + }
> + };
> +
> + if (likely(len < sizeof(get_buf))) {
> + /* code optimize, use stack memory */
> + msgs[1].buf = &get_buf[0];
> + } else {
> + msgs[1].buf = kzalloc(len > I2C_MAX_TRANSFER_SIZE
> + ? I2C_MAX_TRANSFER_SIZE : len, GFP_KERNEL);
> + if (!msgs[1].buf)
> + return -ENOMEM;
> + }
> +
> + while (pos != len) {
> + if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE))
> + transfer_length = I2C_MAX_TRANSFER_SIZE;
> + else
> + transfer_length = len - pos;
> +
> + msgs[0].buf[0] = (address >> 8) & 0xFF;
> + msgs[0].buf[1] = address & 0xFF;
> + msgs[1].len = transfer_length;
> +
> + for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) {
> + if (likely(i2c_transfer(client->adapter, msgs, 2) == 2)) {
> + memcpy(&data[pos], msgs[1].buf, transfer_length);
> + pos += transfer_length;
> + address += transfer_length;
> + break;
> + }
> + dev_info(&client->dev, "I2c read retry[%d]:0x%x\n",
> + retry + 1, reg);
> + msleep(20);
> + }
> + if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) {
> + dev_err(&client->dev,
> + "I2c read failed,dev:%02x,reg:%04x,size:%u\n",
> + client->addr, reg, len);
> + r = -EBUS;
> + goto read_exit;
> + }
> + }
> +
> +read_exit:
> + if (unlikely(len >= sizeof(get_buf)))
> + kfree(msgs[1].buf);
> + return r;
> +}
> +
> +/**
> + * goodix_i2c_write - write device register through i2c bus
> + * @ts_dev: pointer to goodix device data
> + * @addr: register address
> + * @data: write buffer
> + * @len: bytes to write
> + * return: 0 - write ok; < 0 - i2c transter error.
> + */
> +static int goodix_i2c_write(struct goodix_ts_device *ts_dev, unsigned int reg,
> + unsigned char *data, unsigned int len)
> +{
> + struct i2c_client *client = to_i2c_client(ts_dev->dev);
> + unsigned int pos = 0, transfer_length = 0;
> + unsigned int address = reg;
> + unsigned char put_buf[64];
> + int retry, r = 0;
> + struct i2c_msg msg = {
> + .addr = client->addr,
> + .flags = !I2C_M_RD,
> + };
> +
> + if (likely(len + TS_ADDR_LENGTH < sizeof(put_buf))) {
> + /* code optimize,use stack memory*/
> + msg.buf = &put_buf[0];
> + } else {
> + msg.buf = kmalloc(len + TS_ADDR_LENGTH > I2C_MAX_TRANSFER_SIZE
> + ? I2C_MAX_TRANSFER_SIZE : len + TS_ADDR_LENGTH, GFP_KERNEL);
> + if (!msg.buf)
> + return -ENOMEM;
> + }
> +
> + while (pos != len) {
> + if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE - TS_ADDR_LENGTH))
> + transfer_length = I2C_MAX_TRANSFER_SIZE - TS_ADDR_LENGTH;
> + else
> + transfer_length = len - pos;
> +
> + msg.buf[0] = (unsigned char)((address >> 8) & 0xFF);
> + msg.buf[1] = (unsigned char)(address & 0xFF);
> + msg.len = transfer_length + 2;
> + memcpy(&msg.buf[2], &data[pos], transfer_length);
> +
> + for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) {
> + if (likely(i2c_transfer(client->adapter, &msg, 1) == 1)) {
> + pos += transfer_length;
> + address += transfer_length;
> + break;
> + }
> + dev_info(&client->dev, "I2c write retry[%d]\n", retry + 1);
> + msleep(20);
> + }
> + if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) {
> + dev_err(&client->dev,
> + "I2c write failed,dev:%02x,reg:%04x,size:%u",
> + client->addr, reg, len);
> + r = -EBUS;
> + goto write_exit;
> + }
> + }
> +
> +write_exit:
> + if (likely(len + TS_ADDR_LENGTH >= sizeof(put_buf)))
> + kfree(msg.buf);
> + return r;
> +}
> +
> +static int goodix_read_version(struct goodix_ts_device *ts_dev,
> + struct goodix_ts_version *version)
> +{
> + u8 buffer[12];
> + int r;
> +
> + r = goodix_i2c_read(ts_dev, TS_REG_VERSION,
> + buffer, sizeof(buffer));
> + if (r < 0) {
> + dev_err(ts_dev->dev, "Read chip version failed\n");
> + if (version)
> + version->valid = false;
> + return r;
> + }
> +
> + /* if checksum is right and first 4 bytes are not invalid value */
> + if (checksum_u8(buffer, sizeof(buffer)) == 0 &&
> + isalnum(buffer[0]) && isalnum(buffer[1]) &&
> + isalnum(buffer[2]) && isalnum(buffer[3])) {
> + if (version) {
> + memcpy(&version->pid[0], buffer, 4);
> + version->pid[4] = '\0';
> + version->cid = buffer[4];
> + /* vid = main version + minor version */
> + version->vid = get_unaligned_be16(&buffer[5]);
> + version->sensor_id = buffer[10] & 0x0F;
> + version->valid = true;
> +
> + if (version->cid)
> + dev_info(ts_dev->dev,
> + "PID:%s,CID: %c,VID:%04x,SensorID:%u\n",
> + version->pid, version->cid + 'A' - 1,
> + version->vid, version->sensor_id);
> + else
> + dev_info(ts_dev->dev,
> + "PID:%s,VID:%04x,SensorID:%u\n",
> + version->pid, version->vid,
> + version->sensor_id);
> + }
> + } else {
> + dev_warn(ts_dev->dev, "Checksum error:%*ph\n",
> + (int)sizeof(buffer), buffer);
> + /* mark this version is invalid */
> + if (version)
> + version->valid = false;
> + r = -EINVAL;
> + }
> +
> + return r;
> +}
> +
> +/**
> + * goodix_send_config - send config data to device.
> + * @ts_dev: pointer to goodix device data
> + * @config: pointer to config data struct to be send
> + * @return: 0 - succeed, < 0 - failed
> + */
> +static int goodix_send_config(struct goodix_ts_device *ts_dev,
> + struct goodix_ts_config *config)
> +{
> + int r = 0;
> +
> + if (!config || !config->data) {
> + dev_warn(ts_dev->dev, "Null config data\n");
> + return -EINVAL;
> + }
> +
> + dev_dbg(ts_dev->dev, "Send %s,ver:%02xh,size:%d\n",
> + config->name, config->data[0],
> + config->length);
> +
> + mutex_lock(&config->lock);
> + r = goodix_i2c_write(ts_dev, config->reg_base,
> + config->data, config->length);
> + if (r)
> + goto exit;
> +
> + /* make sure the firmware accept the config data*/
> + if (config->delay)
> + msleep(config->delay);
> +exit:
> + mutex_unlock(&config->lock);
> + return r;
> +}
> +
> +static inline int goodix_cmds_init(struct goodix_ts_device *ts_dev)
> +{
> + /* low power mode command */
> + ts_dev->sleep_cmd.cmd_reg = TS_REG_CMD;
> + ts_dev->sleep_cmd.length = 3;
> + ts_dev->sleep_cmd.cmds[0] = 0x05;
> + ts_dev->sleep_cmd.cmds[1] = 0x0;
> + ts_dev->sleep_cmd.cmds[2] = 0 - 0x05;
> + ts_dev->sleep_cmd.initialized = true;
> +
> + return 0;
> +}
> +
> +/**
> + * goodix_hw_init - hardware initialize
> + * Called by touch core module when bootup
> + * @ts_dev: pointer to touch device
> + * return: 0 - no error, <0 error
> + */
> +static int goodix_hw_init(struct goodix_ts_device *ts_dev)
> +{
> + int r;
> +
> + BUG_ON(!ts_dev);
> + goodix_cmds_init(ts_dev);
> +
> + /* goodix_hw_init may be called many times */
> + if (!ts_dev->normal_cfg) {
> + ts_dev->normal_cfg = devm_kzalloc(ts_dev->dev,
> + sizeof(*ts_dev->normal_cfg), GFP_KERNEL);
> + if (!ts_dev->normal_cfg) {
> + dev_err(ts_dev->dev,
> + "Failed to alloc memory for normal cfg\n");
> + return -ENOMEM;
> + }
> + }
> +
> + /* read chip version: PID/VID/sensor ID,etc.*/
> + r = goodix_read_version(ts_dev, &ts_dev->chip_version);
> + if (r < 0)
> + return r;
> +
> +#ifdef CONFIG_OF
> + /* parse normal-cfg from devicetree node */
> + r = goodix_parse_dt_cfg(ts_dev, "normal-cfg",
> + ts_dev->normal_cfg,
> + ts_dev->chip_version.sensor_id);
> + if (r < 0) {
> + dev_warn(ts_dev->dev, "Failed to obtain normal-cfg\n");
> + return r;
> + }
> +#endif
> +
> + ts_dev->normal_cfg->delay = 500;
> + /* send normal-cfg to firmware */
> + r = goodix_send_config(ts_dev, ts_dev->normal_cfg);
> +
> + return r;
> +}
> +
> +/**
> + * goodix_hw_reset - reset device
> + *
> + * @dev: pointer to touch device
> + * Returns 0 - succeed,<0 - failed
> + */
> +static void goodix_hw_reset(struct goodix_ts_device *dev)
> +{
> + dev_dbg(dev->dev, "HW reset\n");
> +
> + if (!dev->board_data->reset_gpiod) {
> + msleep(80);
> + return;
> + }
> + gpiod_direction_output(dev->board_data->reset_gpiod, 0);
> + udelay(200);
> + gpiod_direction_output(dev->board_data->reset_gpiod, 1);
> + msleep(80);
> +}
> +
> +/**
> + * goodix_request_handler - handle firmware request
> + *
> + * @dev: pointer to touch device
> + * @request_data: requset information
> + * Returns 0 - succeed,<0 - failed
> + */
> +static int goodix_request_handler(struct goodix_ts_device *dev,
> + struct goodix_request_data *request_data) {
> + unsigned char buffer[1];
> + int r;
> +
> + r = goodix_i2c_read(dev, TS_REG_REQUEST, buffer, 1);
> + if (r < 0)
> + return r;
> +
> + switch (buffer[0]) {
> + case REQUEST_CONFIG:
> + dev_dbg(dev->dev, "HW request config\n");
> + goodix_send_config(dev, dev->normal_cfg);
> + goto clear_requ;
> + case REQUEST_BAKREF:
> + dev_dbg(dev->dev, "HW request bakref\n");
> + goto clear_requ;
> + case REQUEST_RESET:
> + dev_dbg(dev->dev, "HW requset reset\n");
> + goto clear_requ;
> + case REQUEST_MAINCLK:
> + dev_dbg(dev->dev, "HW request mainclk\n");
> + goto clear_requ;
> + default:
> + dev_dbg(dev->dev, "Unknown hw request:%d\n", buffer[0]);
> + return 0;
> + }
> +
> +clear_requ:
> + buffer[0] = 0x00;
> + r = goodix_i2c_write(dev, TS_REG_REQUEST, buffer, 1);
> + return r;
> +}
> +
> +/**
> + * goodix_eventt_handler - handle firmware event
> + *
> + * @dev: pointer to touch device
> + * @ts_event: pointer to touch event structure
> + * Returns 0 - succeed,<0 - failed
> + */
> +static int goodix_event_handler(struct goodix_ts_device *dev,
> + struct goodix_ts_event *ts_event)
> +{
> +#define BYTES_PER_COORD 8
> + struct goodix_touch_data *touch_data =
> + &ts_event->event_data.touch_data;
> + struct goodix_ts_coords *coords = &touch_data->coords[0];
> + int max_touch_num = dev->board_data->panel_max_id;
> + unsigned char buffer[2 + BYTES_PER_COORD * max_touch_num];
> + unsigned char coord_sta;
> + int touch_num = 0, i, r;
> + unsigned char chksum = 0;
> +
> + r = goodix_i2c_read(dev, TS_REG_COORDS_BASE,
> + buffer, 3 + BYTES_PER_COORD/* * 1*/);
> + if (unlikely(r < 0))
> + return r;
> +
> + /* buffer[0]: event state */
> + coord_sta = buffer[0];
> + if (unlikely(coord_sta == 0x00)) {
> + /* handle request event */
> + ts_event->event_type = EVENT_REQUEST;
> + goodix_request_handler(dev,
> + &ts_event->event_data.request_data);
> + goto exit_clean_sta;
> + } else if (unlikely((coord_sta & 0x80) != 0x80)) {
> + r = -EINVAL;
> + return r;
> + }
> +
> + /* bit7 of coord_sta is 1, touch data is ready */
> + /* handle touch event */
> + touch_data->key_value = (coord_sta >> 4) & 0x01;
> + touch_num = coord_sta & 0x0F;
> + if (unlikely(touch_num > max_touch_num)) {
> + touch_num = -EINVAL;
> + goto exit_clean_sta;
> + } else if (unlikely(touch_num > 1)) {
> + r = goodix_i2c_read(dev,
> + TS_REG_COORDS_BASE + 3 + BYTES_PER_COORD,
> + &buffer[3 + BYTES_PER_COORD],
> + (touch_num - 1) * BYTES_PER_COORD);
> + if (unlikely(r < 0))
> + goto exit_clean_sta;
> + }
> +
> + /* touch_num * BYTES_PER_COORD + 1(touch event state) + 1(checksum)
> + * + 1(key value)
> + */
> + chksum = checksum_u8(&buffer[0], touch_num * BYTES_PER_COORD + 3);
> + if (unlikely(chksum != 0)) {
> + dev_warn(dev->dev, "Checksum error:%X\n", chksum);
> + r = -EINVAL;
> + goto exit_clean_sta;
> + }
> +
> + memset(touch_data->coords, 0x00, sizeof(touch_data->coords));
> + for (i = 0; i < touch_num; i++) {
> + coords->id = buffer[i * BYTES_PER_COORD + 1] & 0x0f;
> + coords->x = get_unaligned_le16(&buffer[i * BYTES_PER_COORD + 2]);
> + coords->y = get_unaligned_le16(&buffer[i * BYTES_PER_COORD + 4]);
> + coords->w = get_unaligned_le16(&buffer[i * BYTES_PER_COORD + 6]);
> +
> + dev_dbg(dev->dev, "D:[%d](%d, %d)[%d]\n",
> + coords->id, coords->x, coords->y, coords->w);
> + coords++;
> + }
> +
> + touch_data->touch_num = touch_num;
> + /* mark this event as touch event */
> + ts_event->event_type = EVENT_TOUCH;
> + r = 0;
> +
> +exit_clean_sta:
> + /* handshake */
> + buffer[0] = 0x00;
> + goodix_i2c_write(dev, TS_REG_COORDS_BASE, buffer, 1);
> + return r;
> +}
> +
> +/**
> + * goodix_send_command - seng cmd to firmware
> + *
> + * @dev: pointer to device
> + * @cmd: pointer to command struct which cotain command data
> + * Returns 0 - succeed,<0 - failed
> + */
> +int goodix_send_command(struct goodix_ts_device *dev,
> + struct goodix_ts_cmd *cmd)
> +{
> + int ret;
> +
> + if (!cmd || !cmd->initialized)
> + return -EINVAL;
> + ret = goodix_i2c_write(dev, cmd->cmd_reg, cmd->cmds,
> + cmd->length);
> + return ret;
> +}
> +
> +/**
> + * goodix_hw_suspend - Let touch deivce stay in lowpower mode.
> + * @dev: pointer to goodix touch device
> + * @return: 0 - succeed, < 0 - failed
> + */
> +static int goodix_hw_suspend(struct goodix_ts_device *dev)
> +{
> + struct goodix_ts_cmd *sleep_cmd =
> + &dev->sleep_cmd;
> + int r = 0;
> +
> + if (sleep_cmd->initialized) {
> + r = goodix_send_command(dev, sleep_cmd);
> + if (!r)
> + dev_dbg(dev->dev, "Chip in sleep mode\n");
> + } else {
> + dev_dbg(dev->dev, "Uninitialized sleep command\n");
> + }
> +
> + return r;
> +}
> +
> +/**
> + * goodix_hw_resume - Let touch deivce stay in active mode.
> + * @dev: pointer to goodix touch device
> + * @return: 0 - succeed, < 0 - failed
> + */
> +static int goodix_hw_resume(struct goodix_ts_device *dev)
> +{
> + struct goodix_ts_version ver;
> + int r, retry = GOODIX_BUS_RETRY_TIMES;
> +
> + for (; retry--;) {
> + goodix_hw_reset(dev);
> + r = goodix_read_version(dev, &ver);
> + if (!r)
> + break;
> + }
> +
> + return r;
> +}
> +
> +/* hardware opeation funstions */
> +static const struct goodix_ts_hw_ops hw_i2c_ops = {
> + .init = goodix_hw_init,
> + .read = goodix_i2c_read,
> + .write = goodix_i2c_write,
> + .reset = goodix_hw_reset,
> + .event_handler = goodix_event_handler,
> + .send_config = goodix_send_config,
> + .send_cmd = goodix_send_command,
> + .read_version = goodix_read_version,
> + .suspend = goodix_hw_suspend,
> + .resume = goodix_hw_resume,
> +};
> +
> +static struct platform_device *goodix_pdev;
> +static void goodix_pdev_release(struct device *dev)
> +{
> + kfree(goodix_pdev);
> +}
> +
> +static int goodix_i2c_probe(struct i2c_client *client,
> + const struct i2c_device_id *dev_id)
> +{
> + struct goodix_ts_device *ts_device = NULL;
> + struct goodix_ts_board_data *ts_bdata = NULL;
> + int r = 0;
> +
> + r = i2c_check_functionality(client->adapter,
> + I2C_FUNC_I2C);
> + if (!r)
> + return -EIO;
> +
> + /* board data */
> + ts_bdata = devm_kzalloc(&client->dev,
> + sizeof(struct goodix_ts_board_data), GFP_KERNEL);
> + if (!ts_bdata)
> + return -ENOMEM;
> +
> +#ifdef CONFIG_OF
> + if (IS_ENABLED(CONFIG_OF) && client->dev.of_node) {
> + /* parse devicetree property */
> + r = goodix_parse_dt(client->dev, ts_bdata);
> + if (r < 0)
> + return r;
> + } else
> +#endif
> + {
> + /* use platform data */
> + dev_info(&client->dev, "use platform data\n");
> + devm_kfree(&client->dev, ts_bdata);
> + ts_bdata = client->dev.platform_data;
> + }
> +
> + if (!ts_bdata)
> + return -ENODEV;
> +
> + ts_device = devm_kzalloc(&client->dev,
> + sizeof(struct goodix_ts_device), GFP_KERNEL);
> + if (!ts_device)
> + return -ENOMEM;
> +
> + ts_bdata->irq = client->irq;
> + ts_device->name = "GTx5 TouchDevcie";
> + ts_device->dev = &client->dev;
> + ts_device->board_data = ts_bdata;
> + ts_device->hw_ops = &hw_i2c_ops;
> +
> + /* ts core device */
> + goodix_pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL);
> + if (!goodix_pdev)
> + return -ENOMEM;
> +
> + goodix_pdev->name = GOODIX_CORE_DRIVER_NAME;
> + goodix_pdev->id = 0;
> + goodix_pdev->num_resources = 0;
> + /*
> + * you could find this platform dev in
> + * /sys/devices/platform/goodix_ts.0
> + * goodix_pdev->dev.parent = &client->dev;
> + */
> + goodix_pdev->dev.platform_data = ts_device;
> + goodix_pdev->dev.release = goodix_pdev_release;
> +
> + /* register platform device, then the goodix_ts_core module will probe
> + * the touch deivce.
> + */
> + r = platform_device_register(goodix_pdev);
> + return r;
> +}
> +
> +static int goodix_i2c_remove(struct i2c_client *client)
> +{
> + platform_device_unregister(goodix_pdev);
> + return 0;
> +}
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id gtx5_of_matchs[] = {
> + {.compatible = TS_DT_COMPATIBLE,},
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, gtx5_of_matchs);
> +#endif
> +
> +static const struct i2c_device_id gtx5_id_table[] = {
> + {TS_DRIVER_NAME, 0},
> + {},
> +};
> +MODULE_DEVICE_TABLE(i2c, gtx5_id_table);
> +
> +static struct i2c_driver goodix_i2c_driver = {
> + .driver = {
> + .name = TS_DRIVER_NAME,
> + .owner = THIS_MODULE,
> + .of_match_table = of_match_ptr(gtx5_of_matchs),
> + },
> + .probe = goodix_i2c_probe,
> + .remove = goodix_i2c_remove,
> + .id_table = gtx5_id_table,
> +};
> +
> +static int __init goodix_i2c_init(void)
> +{
> + return i2c_add_driver(&goodix_i2c_driver);
> +}
> +
> +static void __exit goodix_i2c_exit(void)
> +{
> + i2c_del_driver(&goodix_i2c_driver);
> +}
> +
> +module_init(goodix_i2c_init);
> +module_exit(goodix_i2c_exit);
> +
> +MODULE_DESCRIPTION("Goodix GTx5 Touchscreen Hardware Module");
> +MODULE_AUTHOR("Goodix, Inc.");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_update.c b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_update.c
> new file mode 100755
> index 0000000..7dd0e6b
> --- /dev/null
> +++ b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_update.c
> @@ -0,0 +1,1450 @@
> +/*
> + * Goodix GTx5 Touchscreen Driver.
> + *
> + * Copyright (C) 2015 - 2016 Goodix, Inc.
> + * Authors: Wang Yafei <wangyafei@xxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be a reference
> + * to you, when you are integrating the GOODiX's CTP IC into your system,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + */
> +#include "goodix_ts_core.h"
> +
> +/* COMMON PART - START */
> +#define TS_DEFAULT_FIRMWARE "goodix_ts_fw.bin"
> +
> +#define FW_HEADER_SIZE 256
> +#define FW_SUBSYS_INFO_SIZE 8
> +#define FW_SUBSYS_INFO_OFFSET 32
> +#define FW_SUBSYS_MAX_NUM 24
> +#define FW_NAME_MAX 128
> +
> +#define ISP_MAX_BUFFERSIZE (1024 * 16)
> +
> +#define HW_REG_CPU_EN 0x4180
> +#define HW_REG_ILM_ACCESS 0x50C0
> +#define HW_REG_BANK_SELECT 0x50C4
> +#define HW_REG_ISP_ADDR 0x8000
> +#define HW_REG_ISP_STAT 0x4195
> +#define HW_REG_ISP_CMD 0x4196
> +#define HW_REG_ISP_PKT_INFO 0xFFF0
> +#define HW_REG_ISP_RESULT 0x4197
> +#define HW_REG_ISP_BUFFER 0x8000
> +#define HW_REG_BOOT_FLAG 0x434C
> +#define HW_REG_BOOT_CTRL0 0xF7CC
> +#define HW_REG_BOOT_CTRL1 0xF7EC
> +#define HW_REG_WDT 0x40B0
> +
> +#define CPU_CTRL_PENDING 0x00
> +#define CPU_CTRL_RUNNING 0x01
> +
> +#define ISP_STAT_IDLE 0xFF
> +#define ISP_STAT_READY 0xAA
> +#define ISP_STAT_WRITING 0xCC
> +#define ISP_FLASH_ERROR 0xEE
> +#define ISP_FLASH_SUCCESS 0xDD
> +#define ISP_CMD_PREPARE 0x55
> +#define ISP_CMD_FLASH 0xAA
> +
> +/**
> + * fw_subsys_info - subsytem firmware information
> + * @type: sybsystem type
> + * @size: firmware size
> + * @flash_addr: flash address
> + * @data: firmware data
> + */
> +struct fw_subsys_info {
> + u8 type;
> + u32 size;
> + u32 flash_addr;
> + const u8 *data;
> +};
> +
> +#pragma pack(1)
> +/**
> + * firmware_info
> + * @size: fw total length
> + * @checksum: checksum of fw
> + * @hw_pid: mask pid string
> + * @hw_pid: mask vid code
> + * @fw_pid: fw pid string
> + * @fw_vid: fw vid code
> + * @subsys_num: number of fw subsystem
> + * @chip_type: chip type
> + * @protocol_ver: firmware packing
> + * protocol version
> + * @subsys: sybsystem info
> + */
> +struct firmware_info {
> + u32 size;
> + u16 checksum;
> + u8 hw_pid[6];
> + u8 hw_vid[3];
> + u8 fw_pid[8];
> + u8 fw_vid[3];
> + u8 subsys_num;
> + u8 chip_type;
> + u8 protocol_ver;
> + u8 reserved[3];
> + struct fw_subsys_info subsys[FW_SUBSYS_MAX_NUM];
> +};
> +
> +/**
> + * firmware_packet - firmware packet information
> + * @packet_size: firmware packet size, max 4Kbytes.
> + * @flash_addr: device flash address
> + * @packet_checksum: checksum of the firmware in this packet
> + * @data: pointer to firmware data.
> + */
> +struct firmware_packet {
> + u32 packet_size;
> + u32 flash_addr;
> + u32 packet_checksum;
> + const u8 *data;
> +};
> +#pragma pack()
> +
> +/**
> + * firmware_data - firmware data structure
> + * @fw_info: firmware infromation
> + * @firmware: firmware data structure
> + */
> +struct firmware_data {
> + struct firmware_info fw_info;
> + const struct firmware *firmware;
> +};
> +
> +enum update_status {
> + UPSTA_NOTWORK = 0,
> + UPSTA_PREPARING,
> + UPSTA_UPDATING,
> + UPSTA_ABORT,
> + UPSTA_SUCCESS,
> + UPSTA_FAILED
> +};
> +
> +/**
> + * fw_update_ctrl - structure used to control the
> + * firmware update process
> + * @status: update status
> + * @progress: indicate the progress of update
> + * @allow_reset: control the reset callback
> + * @allow_irq: control the irq callback
> + * @allow_suspend: control the suspend callback
> + * @allow_resume: allow resume callback
> + * @fw_data: firmware data
> + * @ts_dev: touch device
> + * @fw_name: firmware name
> + * @attr_fwimage: sysfs bin attrs, for storing fw image
> + * @fw_from_sysfs: whether the firmware image is loadind
> + * from sysfs
> + */
> +struct fw_update_ctrl {
> + enum update_status status;
> + unsigned int progress;
> + bool force_update;
> +
> + bool allow_reset;
> + bool allow_irq;
> + bool allow_suspend;
> + bool allow_resume;
> +
> + struct firmware_data fw_data;
> + struct goodix_ts_device *ts_dev;
> +
> + char fw_name[FW_NAME_MAX];
> + struct bin_attribute attr_fwimage;
> + bool fw_from_sysfs;
> +};
> +
> +static struct goodix_ext_module goodix_fwu_module;
> +/**
> + * goodix_parse_firmware - parse firmware header information
> + * and subsystem information from firmware data buffer
> + *
> + * @fw_data: firmware struct, contains firmware header info
> + * and firmware data.
> + * return: 0 - OK, < 0 - error
> + */
> +static int goodix_parse_firmware(struct fw_update_ctrl *fwu_ctrl)
> +{
> + const struct firmware *firmware;
> + struct firmware_info *fw_info;
> + struct firmware_data *fw_data = &fwu_ctrl->fw_data;
> + const struct device *dev = fwu_ctrl->ts_dev->dev;
> + unsigned int i, fw_offset, info_offset;
> + u16 checksum;
> + int r = 0;
> +
> + if (!fw_data || !fw_data->firmware) {
> + dev_err(dev, "Invalid firmware data\n");
> + return -EINVAL;
> + }
> + fw_info = &fw_data->fw_info;
> +
> + /* copy firmware head info */
> + firmware = fw_data->firmware;
> + if (firmware->size < FW_SUBSYS_INFO_OFFSET) {
> + dev_err(dev, "Invalid firmware size:%zu\n", firmware->size);
> + r = -EINVAL;
> + goto err_size;
> + }
> + memcpy(fw_info, firmware->data, FW_SUBSYS_INFO_OFFSET);
> +
> + /* check firmware size */
> + fw_info->size = be32_to_cpu(fw_info->size);
> + if (firmware->size != fw_info->size + 6) {
> + dev_err(dev, "Bad firmware, size not match\n");
> + r = -EINVAL;
> + goto err_size;
> + }
> +
> + /* calculate checksum, note: sum of bytes, but check by u16 checksum */
> + for (i = 6, checksum = 0; i < firmware->size; i++)
> + checksum += firmware->data[i];
> +
> + /* byte order change, and check */
> + fw_info->checksum = be16_to_cpu(fw_info->checksum);
> + if (checksum != fw_info->checksum) {
> + dev_err(dev, "Bad firmware, cheksum error\n");
> + r = -EINVAL;
> + goto err_size;
> + }
> +
> + if (fw_info->subsys_num > FW_SUBSYS_MAX_NUM) {
> + dev_err(dev, "Bad firmware, invalid subsys num\n");
> + r = -EINVAL;
> + goto err_size;
> + }
> +
> + /* parse subsystem info */
> + fw_offset = FW_HEADER_SIZE;
> + for (i = 0; i < fw_info->subsys_num; i++) {
> + info_offset = FW_SUBSYS_INFO_OFFSET +
> + i * FW_SUBSYS_INFO_SIZE;
> +
> + fw_info->subsys[i].type = firmware->data[info_offset];
> + fw_info->subsys[i].size =
> + be32_to_cpup((__be32 *)&firmware->data[info_offset + 1]);
> + fw_info->subsys[i].flash_addr =
> + be16_to_cpup((__be16 *)&firmware->data[info_offset + 5]);
> + fw_info->subsys[i].flash_addr <<= 8; /* important! */
> +
> + if (fw_offset > firmware->size) {
> + dev_err(dev, "Sybsys offset exceed Firmware size\n");
> + goto err_size;
> + }
> +
> + fw_info->subsys[i].data = firmware->data + fw_offset;
> + fw_offset += fw_info->subsys[i].size;
> + }
> +
> + dev_info(dev, "Firmware package protocol: V%u\n", fw_info->protocol_ver);
> + dev_info(dev, "Fimware PID:GT%s\n", fw_info->fw_pid);
> + dev_info(dev, "Fimware VID:%02X%02X%02X\n", fw_info->fw_vid[0],
> + fw_info->fw_vid[1], fw_info->fw_vid[2]);
> + dev_info(dev, "Firmware chip type:%02X\n", fw_info->chip_type);
> + dev_info(dev, "Firmware size:%u\n", fw_info->size);
> + dev_info(dev, "Firmware subsystem num:%u\n", fw_info->subsys_num);
> +
> + for (i = 0; i < fw_info->subsys_num; i++) {
> + dev_dbg(dev, "Index:%d\n", i);
> + dev_dbg(dev, "Subsystem type:%02X\n", fw_info->subsys[i].type);
> + dev_dbg(dev, "Subsystem size:%u\n", fw_info->subsys[i].size);
> + dev_dbg(dev, "Subsystem flash_addr:%08X\n",
> + fw_info->subsys[i].flash_addr);
> + dev_dbg(dev, "Subsystem Ptr:%p\n", fw_info->subsys[i].data);
> + }
> +
> +err_size:
> + return r;
> +}
> +
> +/**
> + * goodix_check_update - compare the version of firmware running in
> + * touch device with the version getting from the firmware file.
> + * @fw_info: firmware information to be compared
> + * return: 0 firmware in the touch device needs to be updated
> + * < 0 no need to update firmware
> + */
> +static int goodix_check_update(struct goodix_ts_device *ts_dev,
> + const struct firmware_info *fw_info)
> +{
> + struct goodix_ts_version fw_ver = {0};
> + const struct device *dev = ts_dev->dev;
> + u16 fwimg_vid;
> + u8 fwimg_cid;
> + int r = 0;
> +
> + /* read version from chip, if we got invalid firmware version, maybe
> + * fimware in flash is incorrect, so we need to update firmware
> + */
> + r = ts_dev->hw_ops->read_version(ts_dev, &fw_ver);
> + if (r == -EBUS)
> + return r;
> +
> + if (fw_ver.valid) {
> + if (memcmp(fw_ver.pid, fw_info->fw_pid, 4)) {
> + dev_err(dev, "Product ID is not match\n");
> + return -EPERM;
> + }
> +
> + fwimg_cid = fw_info->fw_vid[0];
> + fwimg_vid = fw_info->fw_vid[1] << 8 | fw_info->fw_vid[2];
> + if (fw_ver.vid == fwimg_vid && fw_ver.cid == fwimg_cid) {
> + dev_err(dev, "FW version is equal to the IC's\n");
> + return -EPERM;
> + } else if (fw_ver.vid > fwimg_vid) {
> + dev_info(dev, "Warning: fw version is lower the IC's\n");
> + }
> + } /* else invalid firmware, update firmware */
> +
> + dev_info(dev, "Firmware needs to be updated\n");
> + return 0;
> +}
> +
> +/**
> + * goodix_reg_write_confirm - write register and confirm the value
> + * in the register.
> + * @ts_dev: pointer to touch device
> + * @addr: register address
> + * @data: pointer to data buffer
> + * @len: data length
> + * return: 0 write success and confirm ok
> + * < 0 failed
> + */
> +static int goodix_reg_write_confirm(struct goodix_ts_device *ts_dev,
> + unsigned int addr, unsigned char *data, unsigned int len)
> +{
> + u8 *cfm, cfm_buf[32];
> + int r, i;
> +
> + if (len > sizeof(cfm_buf)) {
> + cfm = kzalloc(len, GFP_KERNEL);
> + if (!cfm)
> + return -ENOMEM;
> + } else {
> + cfm = &cfm_buf[0];
> + }
> +
> + for (i = 0; i < GOODIX_BUS_RETRY_TIMES; i++) {
> + r = ts_dev->hw_ops->write(ts_dev, addr, data, len);
> + if (r < 0)
> + goto exit;
> +
> + r = ts_dev->hw_ops->read(ts_dev, addr, cfm, len);
> + if (r < 0)
> + goto exit;
> +
> + if (memcmp(data, cfm, len)) {
> + r = -EMEMCMP;
> + continue;
> + } else {
> + r = 0;
> + break;
> + }
> + }
> +
> +exit:
> + if (cfm != &cfm_buf[0])
> + kfree(cfm);
> + return r;
> +}
> +
> +static inline int goodix_reg_write(struct goodix_ts_device *ts_dev,
> + unsigned int addr, unsigned char *data, unsigned int len)
> +{
> + return ts_dev->hw_ops->write(ts_dev, addr, data, len);
> +}
> +
> +static inline int goodix_reg_read(struct goodix_ts_device *ts_dev,
> + unsigned int addr, unsigned char *data, unsigned int len)
> +{
> + return ts_dev->hw_ops->read(ts_dev, addr, data, len);
> +}
> +
> +/**
> + * goodix_cpu_ctrl - Let cpu stay in pending state or running state
> + * @ts_dev: pointer to touch device
> + * @flag: control flag, which can be:
> + * CPU_CTRL_PENDING - Pending cpu
> + * Other type of control to cpu is not support.
> + * return: 0 OK, < 0 Failed, -EAGAIN try again
> + */
> +static int goodix_cpu_ctrl(struct goodix_ts_device *ts_dev, int flag)
> +{
> + const struct device *dev = ts_dev->dev;
> + u8 ctrl;
> + int r;
> +
> + if (flag == CPU_CTRL_PENDING) {
> + dev_info(dev, "Pending CPU\n");
> + ctrl = 0x04;
> + } else if (flag == CPU_CTRL_RUNNING) {
> + dev_info(dev, "Running CPU\n");
> + ctrl = 0x00;
> + } else {
> + dev_err(dev, "Invalid cpu ctrl flag\n");
> + return -EPERM;
> + }
> +
> + /* Pending Cpu */
> + r = goodix_reg_write_confirm(ts_dev, HW_REG_CPU_EN, &ctrl, 1);
> + if (unlikely(r < 0)) {
> + dev_err(dev, "CPU ctrl failed:%d\n", r);
> + r = -EAGAIN; /* hw reset and try again */
> + }
> +
> + return r;
> +}
> +
> +/**
> + * goodix_isp_wait_stat - waitting ISP state
> + * @ts_dev: pointer to touch device
> + * @state: state to wait
> + * return: 0 - ok, < 0 error, -ETIMEOUT timeout
> + */
> +static int goodix_isp_wait_stat(struct goodix_ts_device *ts_dev, u16 state)
> +{
> + const struct device *dev = ts_dev->dev;
> + static u8 last_state;
> + u8 isp_state;
> + int i, r, err_cnt = 0;
> +
> + for (i = 0; i < 200; i++) {
> + /* read isp state */
> + r = goodix_reg_read(ts_dev, HW_REG_ISP_STAT,
> + &isp_state, 1);
> + if (r < 0) {
> + dev_err(dev, "Failed to read ISP state\n");
> + if (++err_cnt > GOODIX_BUS_RETRY_TIMES)
> + return r;
> + continue;
> + }
> + err_cnt = 0;
> +
> + if (isp_state != last_state) {
> + switch (isp_state) {
> + case ISP_STAT_IDLE:
> + dev_info(dev, "ISP state: Idle\n");
> + break;
> + case ISP_STAT_WRITING:
> + dev_info(dev, "ISP state: Writing...\n");
> + break;
> + case ISP_STAT_READY:
> + dev_info(dev, "ISP state: Ready to write\n");
> + break;
> + default:
> + dev_err(dev, "ISP state: Unknown\n");
> + break;
> + }
> + }
> +
> + last_state = isp_state;
> + r = -ETIMEOUT;
> + if (isp_state == state) {
> + r = 0;
> + break;
> + }
> +
> + usleep_range(5000, 5010);
> + }
> +
> + return r;
> +}
> +
> +/**
> + * goodix_isp_flash_done - check whether flash is successful
> + * @ts_dev: pointer to touch device
> + * return: 0 - ok, < 0 error
> + */
> +static int goodix_isp_flash_done(struct goodix_ts_device *ts_dev)
> +{
> + u8 isp_result;
> + int r, i;
> +
> + for (i = 0; i < 2; i++) {
> + r = goodix_reg_read(ts_dev, HW_REG_ISP_RESULT,
> + &isp_result, 1);
> + if (r < 0) {
> + /* bus error */
> + break;
> + } else if (isp_result == ISP_FLASH_SUCCESS) {
> + dev_info(ts_dev->dev, "ISP result: OK!\n");
> + r = 0;
> + break;
> + } else if (isp_result == ISP_FLASH_ERROR) {
> + dev_err(ts_dev->dev, "ISP result: ERROR!\n");
> + r = -EAGAIN;
> + }
> + }
> + return r;
> +}
> +
> +/**
> + * goodix_isp_command - communication with ISP.
> + * @cmd: ISP command.
> + * return: 0 ok, <0 error
> + */
> +static int goodix_isp_command(struct goodix_ts_device *ts_dev, u8 cmd)
> +{
> + switch (cmd) {
> + case ISP_CMD_PREPARE:
> + break;
> + case ISP_CMD_FLASH:
> + break;
> + default:
> + dev_err(ts_dev->dev, "Invalid ISP cmd\n");
> + return -EINVAL;
> + }
> +
> + return goodix_reg_write(ts_dev, HW_REG_ISP_CMD, &cmd, 1);
> +}
> +
> +/**
> + * goodix_load_isp - load ISP program to deivce ram
> + * @ts_dev: pointer to touch device
> + * @fw_data: firmware data
> + * return 0 ok, <0 error
> + */
> +static inline int goodix_load_isp(struct goodix_ts_device *ts_dev,
> + struct firmware_data *fw_data)
> +{
> + struct fw_subsys_info *fw_isp;
> + int r;
> +
> + fw_isp = &fw_data->fw_info.subsys[0];
> +
> + dev_info(ts_dev->dev, "Loading ISP program\n");
> + r = goodix_reg_write_confirm(ts_dev, HW_REG_ISP_ADDR,
> + (u8 *)fw_isp->data, fw_isp->size);
> + if (r < 0)
> + dev_err(ts_dev->dev, "Loading ISP error\n");
> +
> + return r;
> +}
> +
> +/**
> + * goodix_enter_update - update prepare, loading ISP program
> + * and make sure the ISP is running.
> + * @fwu_ctrl: pointer to fimrware control structure
> + * return: 0 ok, <0 error
> + */
> +static int goodix_update_prepare(struct fw_update_ctrl *fwu_ctrl)
> +{
> + struct goodix_ts_device *ts_dev = fwu_ctrl->ts_dev;
> + const struct device *dev = fwu_ctrl->ts_dev->dev;
> + u8 boot_val0[4] = {0xb8, 0x3f, 0x35, 0x56};
> + u8 boot_val1[4] = {0xb9, 0x3e, 0xb5, 0x54};
> + u8 reg_val[4] = {0x00};
> + int r;
> +
> + fwu_ctrl->allow_reset = true;
> + ts_dev->hw_ops->reset(ts_dev);
> + fwu_ctrl->allow_reset = false;
> +
> + /* enable ILM access */
> + reg_val[0] = 0x06;
> + r = goodix_reg_write_confirm(ts_dev, HW_REG_ILM_ACCESS,
> + reg_val, 1);
> + if (r < 0) {
> + dev_err(dev, "Failed to enable ILM access\n");
> + return r;
> + }
> +
> + /* Pending CPU */
> + r = goodix_cpu_ctrl(ts_dev, CPU_CTRL_PENDING);
> + if (r < 0)
> + return r;
> +
> + /* disable watchdog timer */
> + reg_val[0] = 0x00;
> + r = goodix_reg_write_confirm(ts_dev, HW_REG_WDT,
> + reg_val, 1);
> + if (r < 0) {
> + dev_err(dev, "Failed to disable watchdog\n");
> + return r;
> + }
> +
> + /* select bank 2 */
> + reg_val[0] = 0x02;
> + r = goodix_reg_write_confirm(ts_dev, HW_REG_BANK_SELECT,
> + reg_val, 1);
> + if (r < 0) {
> + dev_err(dev, "Failed to select bank2\n");
> + return r;
> + }
> +
> + /* load ISP code */
> + r = goodix_load_isp(ts_dev, &fwu_ctrl->fw_data);
> + if (r < 0)
> + return r;
> +
> + /* Clear ISP state */
> + reg_val[0] = reg_val[1] = 0x00;
> + r = goodix_reg_write_confirm(ts_dev, HW_REG_ISP_STAT,
> + reg_val, 2);
> + if (r < 0) {
> + dev_err(dev, "Failed to clear ISP state\n");
> + return r;
> + }
> +
> + /* set boot flag */
> + reg_val[0] = 0;
> + r = goodix_reg_write_confirm(ts_dev, HW_REG_BOOT_FLAG,
> + reg_val, 1);
> + if (r < 0) {
> + dev_err(dev, "Failed to set boot flag\n");
> + return r;
> + }
> +
> + /* set boot from sRam */
> + r = goodix_reg_write_confirm(ts_dev, HW_REG_BOOT_CTRL0,
> + boot_val0, sizeof(boot_val0));
> + if (r < 0) {
> + dev_err(dev, "Failed to set boot flag\n");
> + return r;
> + }
> +
> + /* set boot from sRam */
> + r = goodix_reg_write_confirm(ts_dev, HW_REG_BOOT_CTRL1,
> + boot_val1, sizeof(boot_val1));
> + if (r < 0) {
> + dev_err(dev, "Failed to set boot flag\n");
> + return r;
> + }
> +
> + /* disbale ILM access */
> + reg_val[0] = 0x00;
> + r = goodix_reg_write_confirm(ts_dev, HW_REG_ILM_ACCESS,
> + reg_val, 1);
> + if (r < 0) {
> + dev_err(dev, "Failed to disable ILM access\n");
> + return r;
> + }
> +
> + /* Release CPU */
> + r = goodix_cpu_ctrl(ts_dev, CPU_CTRL_RUNNING);
> + if (r < 0)
> + return r;
> +
> + /* wait isp idel */
> + r = goodix_isp_wait_stat(ts_dev, ISP_STAT_IDLE);
> + if (r < 0) {
> + dev_err(dev, "Wait ISP IDLE timeout\n");
> + return r;
> + }
> +
> + return r;
> +}
> +
> +/**
> + * goodix_write_fwdata - write firmware data to ISP buffer
> + * @ts_dev: pointer to touch device
> + * @fw_data: firmware data
> + * @size: size of data, size can not exceed ISP_MAX_BUFFERSIZE
> + * + checksum size{2},
> + * return: 0 ok, <0 error
> + */
> +static int goodix_write_fwdata(struct goodix_ts_device *ts_dev,
> + const u8 *fw_data, u32 size)
> +{
> + if (!fw_data || size > ISP_MAX_BUFFERSIZE)
> + return -EINVAL;
> +
> + return goodix_reg_write(ts_dev, HW_REG_ISP_BUFFER,
> + (u8 *)fw_data, size);
> +}
> +
> +/**
> + * goodix_format_fw_packet - formate one flash packet
> + * @pkt: target firmware packet
> + * @flash_addr: flash address
> + * @size: packet size
> + * @data: packet data
> + */
> +static int goodix_format_fw_packet(struct firmware_packet *pkt,
> + u32 flash_addr, u32 size, const u8 *data)
> +{
> + if (!pkt || !data || size % 4)
> + return -EINVAL;
> +
> + /*
> + * checksum rule:sum of data in one format is equal to zero
> + * data format: byte/le16/be16/le32/be32/le64/be64
> + */
> + pkt->flash_addr = cpu_to_le32(flash_addr);
> + pkt->packet_size = cpu_to_le32(size);
> + pkt->packet_checksum = checksum_le32((u8 *)data, size);
> + pkt->data = data;
> + return 0;
> +}
> +
> +/**
> + * goodix_send_fw_packet - send one firmware packet to ISP
> + * @ts_dev: target touch device
> + * @pkt: firmware packet
> + * returnï0 ok, <0 error
> + */
> +static int goodix_send_fw_packet(struct goodix_ts_device *ts_dev,
> + struct firmware_packet *pkt)
> +{
> + u8 pkt_info[12];
> + int r;
> +
> + if (!pkt)
> + return -EINVAL;
> +
> + /* 1: wait ISP idle */
> + r = goodix_isp_wait_stat(ts_dev, ISP_STAT_IDLE);
> + if (r < 0)
> + return r;
> +
> + /* 2: write packet information */
> + memcpy(pkt_info, pkt, sizeof(pkt_info));
> + r = goodix_reg_write(ts_dev, HW_REG_ISP_PKT_INFO,
> + pkt_info, sizeof(pkt_info));
> + if (r < 0) {
> + dev_err(ts_dev->dev, "Failed to write packet info\n");
> + return r;
> + }
> +
> + /* 3: Make ISP ready to flash */
> + r = goodix_isp_command(ts_dev, ISP_CMD_PREPARE);
> + if (r < 0) {
> + dev_err(ts_dev->dev, "Failed to make ISP ready\n");
> + return r;
> + }
> +
> + /* 4: write packet data(firmware block) to ISP buffer */
> + r = goodix_write_fwdata(ts_dev, pkt->data, pkt->packet_size);
> + if (r < 0) {
> + dev_err(ts_dev->dev, "Failed to write firmware packet\n");
> + return r;
> + }
> +
> + /* 5: wait ISP ready */
> + r = goodix_isp_wait_stat(ts_dev, ISP_STAT_READY);
> + if (r < 0) {
> + dev_err(ts_dev->dev, "Failed to wait ISP ready\n");
> + return r;
> + }
> +
> + /* 6: start writting to flash */
> + r = goodix_isp_command(ts_dev, ISP_CMD_FLASH);
> + if (r < 0) {
> + dev_err(ts_dev->dev, "Failed to start flash\n");
> + return r;
> + }
> +
> + /* 7: wait idle */
> + r = goodix_isp_wait_stat(ts_dev, ISP_STAT_IDLE);
> + if (r < 0) {
> + dev_err(ts_dev->dev, "Error occurred when wait ISP idle\n");
> + return r;
> + };
> +
> + /* check ISP result */
> + r = goodix_isp_flash_done(ts_dev);
> + if (r < 0) {
> + dev_err(ts_dev->dev, "Flash fw packet failed:%d\n", r);
> + return r;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * goodix_flash_subsystem - flash subsystem firmware,
> + * Main flow of flashing firmware.
> + * Each firmware subsystem is divided into several
> + * packets, the max size of packet is limited to
> + * @{ISP_MAX_BUFFERSIZE}
> + * @ts_dev: pointer to touch device
> + * @subsys: subsystem information
> + * return: 0 ok, < 0 error
> + */
> +static int goodix_flash_subsystem(struct goodix_ts_device *ts_dev,
> + struct fw_subsys_info *subsys)
> +{
> + struct firmware_packet fw_pkt;
> + u32 data_size, total_size, offset;
> + int r = 0;
> +
> + /*
> + * if bus(i2c/spi) error occued, then exit, we will do
> + * hardware reset and re-prepare ISP and then retry
> + * flashing
> + */
> + total_size = subsys->size;
> + offset = 0;
> + while (total_size > 0) {
> + data_size = total_size > ISP_MAX_BUFFERSIZE ?
> + ISP_MAX_BUFFERSIZE : total_size;
> + dev_info(ts_dev->dev, "Flash firmware to %08x,size:%u bytes\n",
> + subsys->flash_addr + offset, data_size);
> +
> + /* format one firmware packet */
> + r = goodix_format_fw_packet(&fw_pkt, subsys->flash_addr + offset,
> + data_size, &subsys->data[offset]);
> + if (r < 0) {
> + dev_err(ts_dev->dev, "Invalid packet params\n");
> + goto exit;
> + }
> +
> + /* send one firmware packet */
> + r = goodix_send_fw_packet(ts_dev, &fw_pkt);
> + if (r < 0) {
> + dev_err(ts_dev->dev,
> + "Failed to send firmware packet,err:%d\n", r);
> + goto exit;
> + }
> +
> + offset += data_size;
> + total_size -= data_size;
> + } /* end while */
> +
> +exit:
> + return r;
> +}
> +
> +/**
> + * goodix_flash_firmware - flash firmware
> + * @ts_dev: pointer to touch device
> + * @fw_data: firmware data
> + * return: 0 ok, < 0 error
> + */
> +static int goodix_flash_firmware(struct goodix_ts_device *ts_dev,
> + struct firmware_data *fw_data)
> +{
> + struct fw_update_ctrl *fw_ctrl;
> + struct firmware_info *fw_info;
> + struct fw_subsys_info *fw_x;
> + int retry = GOODIX_BUS_RETRY_TIMES;
> + int i, r = 0, fw_num, prog_step;
> +
> + /* start from subsystem 1, subsystem 0 is the ISP program */
> + fw_ctrl = container_of(fw_data, struct fw_update_ctrl, fw_data);
> + fw_info = &fw_data->fw_info;
> + fw_num = fw_info->subsys_num;
> +
> + /* we have 80% work here */
> + prog_step = 80 / (fw_num - 1);
> +
> + for (i = 1; i < fw_num && retry;) {
> + dev_info(ts_dev->dev,
> + "--- Start to flash subsystem[%d] ---", i);
> + fw_x = &fw_info->subsys[i];
> + r = goodix_flash_subsystem(ts_dev, fw_x);
> + if (r == 0) {
> + dev_info(ts_dev->dev,
> + "--- End flash subsystem[%d]: OK ---", i);
> + fw_ctrl->progress += prog_step;
> + i++;
> + } else if (r == -EAGAIN) {
> + retry--;
> + dev_err(ts_dev->dev,
> + "--- End flash subsystem%d: Fail, errno:%d, retry:%d ---",
> + i, r, GOODIX_BUS_RETRY_TIMES - retry);
> + } else if (r < 0) { /* bus error */
> + dev_err(ts_dev->dev,
> + "--- End flash subsystem%d: Fatal error:%d exit ---",
> + i, r);
> + goto exit_flash;
> + }
> + }
> +
> +exit_flash:
> + return r;
> +}
> +
> +/**
> + * goodix_update_finish - update finished, free resource
> + * and reset flags---
> + * @fwu_ctrl: pointer to fw_update_ctrl structrue
> + * return: 0 ok, < 0 error
> + */
> +static int goodix_update_finish(struct fw_update_ctrl *fwu_ctrl)
> +{
> + struct goodix_ts_version ver;
> + int r = 0;
> +
> + fwu_ctrl->ts_dev->hw_ops->reset(fwu_ctrl->ts_dev);
> + r = fwu_ctrl->ts_dev->hw_ops->read_version(fwu_ctrl->ts_dev, &ver);
> + return r;
> +}
> +
> +/**
> + * goodix_fw_update_proc - firmware update process, the entry of
> + * firmware update flow
> + * @fwu_ctrl: firmware control
> + * return: 0 ok, < 0 error
> + */
> +int goodix_fw_update_proc(struct fw_update_ctrl *fwu_ctrl)
> +{
> +#define FW_UPDATE_RETRY 2
> + const struct device *dev = fwu_ctrl->ts_dev->dev;
> + int retry0 = FW_UPDATE_RETRY, retry1 = FW_UPDATE_RETRY;
> + int r = 0;
> +
> + if (fwu_ctrl->status == UPSTA_PREPARING ||
> + fwu_ctrl->status == UPSTA_UPDATING) {
> + dev_err(dev, "Firmware update already in progress\n");
> + return -EBUSY;
> + }
> + fwu_ctrl->progress = 0;
> + fwu_ctrl->status = UPSTA_PREPARING;
> + r = goodix_parse_firmware(fwu_ctrl);
> + if (r < 0) {
> + fwu_ctrl->status = UPSTA_ABORT;
> + goto err_parse_fw;
> + }
> + fwu_ctrl->progress = 10;
> + if (fwu_ctrl->force_update == false) {
> + r = goodix_check_update(fwu_ctrl->ts_dev,
> + &fwu_ctrl->fw_data.fw_info);
> + if (r < 0) {
> + fwu_ctrl->status = UPSTA_ABORT;
> + goto err_check_update;
> + }
> + }
> +start_update:
> + fwu_ctrl->progress = 20;
> + fwu_ctrl->status = UPSTA_UPDATING; /* show upgrading status */
> + r = goodix_update_prepare(fwu_ctrl);
> + if ((r == -EBUS || r == -EAGAIN) && --retry0 > 0) {
> + dev_err(dev, "Bus error, retry prepare ISP:%d\n",
> + FW_UPDATE_RETRY - retry0);
> + goto start_update;
> + } else if (r < 0) {
> + dev_err(dev, "Failed to prepare ISP, exit update:%d\n", r);
> + fwu_ctrl->status = UPSTA_FAILED;
> + goto err_fw_prepare;
> + }
> + /* progress: 20%~100% */
> + r = goodix_flash_firmware(fwu_ctrl->ts_dev, &fwu_ctrl->fw_data);
> + if ((r == -EBUS || r == -ETIMEOUT) && --retry1 > 0) {
> + /* we will retry[twice] if returns bus error[i2c/spi]
> + * we will do hardware reset and re-prepare ISP and then retry
> + * flashing
> + */
> + dev_err(dev, "Bus error, retry firmware update:%d\n",
> + FW_UPDATE_RETRY - retry1);
> + goto start_update;
> + } else if (r < 0) {
> + dev_err(dev, "Fatal error, exit update:%d\n", r);
> + fwu_ctrl->status = UPSTA_FAILED;
> + goto err_fw_flash;
> + }
> + fwu_ctrl->status = UPSTA_SUCCESS;
> +err_fw_flash:
> +err_fw_prepare:
> + goodix_update_finish(fwu_ctrl);
> +err_check_update:
> +err_parse_fw:
> + if (fwu_ctrl->status == UPSTA_SUCCESS)
> + dev_info(dev, "Firmware update successfully\n");
> + else if (fwu_ctrl->status == UPSTA_FAILED)
> + dev_err(dev, "Firmware update failed\n");
> + fwu_ctrl->progress = 100; /* 100% */
> + return r;
> +}
> +/* COMMON PART - END */
> +
> +/**
> + * goodix_request_firmware - request firmware data from user space
> + *
> + * @fw_data: firmware struct, contains firmware header info
> + * and firmware data pointer.
> + * return: 0 - OK, < 0 - error
> + */
> +static int goodix_request_firmware(struct firmware_data *fw_data,
> + const char *name)
> +{
> + struct fw_update_ctrl *fw_ctrl =
> + container_of(fw_data, struct fw_update_ctrl, fw_data);
> + struct device *dev = fw_ctrl->ts_dev->dev;
> + int r;
> +
> + r = request_firmware(&fw_data->firmware, name, dev);
> + if (r < 0)
> + dev_err(dev,
> + "Firmware image [%s] not available,errno:%d\n", name, r);
> + else
> + dev_info(dev, "Firmware image [%s] is ready\n", name);
> + return r;
> +}
> +
> +/**
> + * relase firmware resources
> + *
> + */
> +static inline void goodix_release_firmware(struct firmware_data *fw_data)
> +{
> + if (fw_data->firmware) {
> + release_firmware(fw_data->firmware);
> + fw_data->firmware = NULL;
> + }
> +}
> +
> +static int goodix_fw_update_thread(void *data)
> +{
> + struct fw_update_ctrl *fwu_ctrl = data;
> + static DEFINE_MUTEX(fwu_lock);
> + int r = -EINVAL;
> +
> + if (!fwu_ctrl)
> + return r;
> +
> + if (goodix_register_ext_module(&goodix_fwu_module))
> + return -EIO;
> +
> + mutex_lock(&fwu_lock);
> + /* judge where to get firmware data */
> + if (!fwu_ctrl->fw_from_sysfs) {
> + r = goodix_request_firmware(&fwu_ctrl->fw_data,
> + fwu_ctrl->fw_name);
> + if (r < 0) {
> + fwu_ctrl->status = UPSTA_ABORT;
> + fwu_ctrl->progress = 100;
> + goto out;
> + }
> + } else {
> + if (!fwu_ctrl->fw_data.firmware) {
> + fwu_ctrl->status = UPSTA_ABORT;
> + fwu_ctrl->progress = 100;
> + r = -EINVAL;
> + goto out;
> + }
> + }
> +
> + /* DONT allow reset/irq/suspend/resume during update */
> + fwu_ctrl->allow_irq = false;
> + fwu_ctrl->allow_suspend = false;
> + fwu_ctrl->allow_resume = false;
> + goodix_ts_blocking_notify(NOTIFY_FWUPDATE_START, NULL);
> +
> + /* ready to update */
> + r = goodix_fw_update_proc(fwu_ctrl);
> +
> + goodix_ts_blocking_notify(NOTIFY_FWUPDATE_END, NULL);
> + fwu_ctrl->allow_reset = true;
> + fwu_ctrl->allow_irq = true;
> + fwu_ctrl->allow_suspend = true;
> + fwu_ctrl->allow_resume = true;
> +
> + /* clean */
> + if (!fwu_ctrl->fw_from_sysfs) {
> + goodix_release_firmware(&fwu_ctrl->fw_data);
> + } else {
> + fwu_ctrl->fw_from_sysfs = false;
> + vfree(fwu_ctrl->fw_data.firmware);
> + fwu_ctrl->fw_data.firmware = NULL;
> + }
> +
> +out:
> + goodix_unregister_ext_module(&goodix_fwu_module);
> + mutex_unlock(&fwu_lock);
> + return r;
> +}
> +
> +/* sysfs attributes */
> +static ssize_t goodix_sysfs_update_fw_store(
> + struct goodix_ext_module *module,
> + const char *buf, size_t count)
> +{
> + int ret;
> +
> + ret = goodix_fw_update_thread(module->priv_data);
> + if (ret)
> + count = ret;
> +
> + return count;
> +}
> +
> +static ssize_t goodix_sysfs_update_progress_show(
> + struct goodix_ext_module *module,
> + char *buf)
> +{
> + struct fw_update_ctrl *fw_ctrl = module->priv_data;
> +
> + return scnprintf(buf, PAGE_SIZE, "%d\n", fw_ctrl->progress);
> +}
> +
> +static ssize_t goodix_sysfs_update_result_show(
> + struct goodix_ext_module *module,
> + char *buf)
> +{
> + char *result = NULL;
> + struct fw_update_ctrl *fw_ctrl = module->priv_data;
> +
> + switch (fw_ctrl->status) {
> + case UPSTA_NOTWORK:
> + result = "notwork";
> + break;
> + case UPSTA_PREPARING:
> + result = "preparing";
> + break;
> + case UPSTA_UPDATING:
> + result = "upgrading";
> + break;
> + case UPSTA_ABORT:
> + result = "abort";
> + break;
> + case UPSTA_SUCCESS:
> + result = "success";
> + break;
> + case UPSTA_FAILED:
> + result = "failed";
> + break;
> + default:
> + break;
> + }
> +
> + return scnprintf(buf, PAGE_SIZE, "%s\n", result);
> +}
> +
> +static ssize_t goodix_sysfs_update_fwversion_show(
> + struct goodix_ext_module *module,
> + char *buf)
> +{
> + struct goodix_ts_version fw_ver;
> + struct fw_update_ctrl *fw_ctrl = module->priv_data;
> + int r = 0;
> + char str[5];
> +
> + /* read version from chip */
> + r = fw_ctrl->ts_dev->hw_ops->read_version(fw_ctrl->ts_dev,
> + &fw_ver);
> + if (!r) {
> + memcpy(str, fw_ver.pid, 4);
> + str[4] = '\0';
> + return scnprintf(buf, PAGE_SIZE, "PID:%s VID:%04x SENSOR_ID:%d\n",
> + str, fw_ver.vid, fw_ver.sensor_id);
> + }
> + return 0;
> +}
> +
> +static ssize_t goodix_sysfs_fwsize_show(struct goodix_ext_module *module,
> + char *buf)
> +{
> + struct fw_update_ctrl *fw_ctrl = module->priv_data;
> + int r = -EINVAL;
> +
> + if (fw_ctrl && fw_ctrl->fw_data.firmware)
> + r = snprintf(buf, PAGE_SIZE, "%zu\n",
> + fw_ctrl->fw_data.firmware->size);
> + return r;
> +}
> +
> +static ssize_t goodix_sysfs_fwsize_store(struct goodix_ext_module *module,
> + const char *buf, size_t count)
> +{
> + struct fw_update_ctrl *fw_ctrl = module->priv_data;
> + struct firmware *fw;
> + u8 **data;
> + size_t size = 0;
> +
> + if (!fw_ctrl)
> + return -EINVAL;
> +
> + if (sscanf(buf, "%zu", &size) < 0 || !size) {
> + dev_err(fw_ctrl->ts_dev->dev, "Failed to get fwsize");
> + return -EFAULT;
> + }
> +
> + fw = vmalloc(sizeof(*fw) + size);
> + if (!fw)
> + return -ENOMEM;
> +
> + memset(fw, 0x00, sizeof(*fw) + size);
> + data = (u8 **)&fw->data;
> + *data = (u8 *)fw + sizeof(struct firmware);
> + fw->size = size;
> + fw_ctrl->fw_data.firmware = fw;
> + fw_ctrl->fw_from_sysfs = true;
> +
> + return count;
> +}
> +
> +static ssize_t goodix_sysfs_fwimage_store(struct file *file,
> + struct kobject *kobj, struct bin_attribute *attr,
> + char *buf, loff_t pos, size_t count)
> +{
> + struct fw_update_ctrl *fw_ctrl;
> + struct firmware_data *fw_data;
> +
> + fw_ctrl = container_of(attr, struct fw_update_ctrl,
> + attr_fwimage);
> + fw_data = &fw_ctrl->fw_data;
> +
> + if (!fw_data->firmware) {
> + dev_err(fw_ctrl->ts_dev->dev, "Need set fw image size first");
> + return -ENOMEM;
> + }
> +
> + if (fw_data->firmware->size == 0) {
> + dev_err(fw_ctrl->ts_dev->dev, "Invalid firmware size");
> + return -EINVAL;
> + }
> +
> + if (pos + count > fw_data->firmware->size)
> + return -EFAULT;
> +
> + memcpy((u8 *)&fw_data->firmware->data[pos], buf, count);
> + fw_ctrl->force_update = true;
> +
> + return count;
> +}
> +
> +static ssize_t goodix_sysfs_force_update_store(
> + struct goodix_ext_module *module,
> + const char *buf, size_t count)
> +{
> + struct fw_update_ctrl *fw_ctrl = module->priv_data;
> + int val;
> +
> + if (kstrtoint(buf, 10, &val))
> + return -EINVAL;
> +
> + if (val)
> + fw_ctrl->force_update = true;
> + else
> + fw_ctrl->force_update = false;
> +
> + return count;
> +}
> +
> +
> +static ssize_t goodix_sysfs_update_hwversion_show(
> + struct goodix_ext_module *module,
> + char *buf)
> +{
> + struct goodix_ts_version fw_ver;
> + struct fw_update_ctrl *fw_ctrl = module->priv_data;
> + int r = 0;
> + char str[5];
> +
> + /* read version from chip */
> + r = fw_ctrl->ts_dev->hw_ops->read_version(fw_ctrl->ts_dev,
> + &fw_ver);
> + if (!r) {
> + memcpy(str, fw_ver.pid, 4);
> + str[4] = '\0';
> + return scnprintf(buf, PAGE_SIZE, "%s\n", str);
> + }
> + return 0;
> +}
> +
> +static ssize_t goodix_sysfs_update_fw_version_show(
> + struct goodix_ext_module *module,
> + char *buf)
> +{
> + struct goodix_ts_version fw_ver;
> + struct fw_update_ctrl *fw_ctrl = module->priv_data;
> + int r = 0;
> +
> + /* read version from chip */
> + r = fw_ctrl->ts_dev->hw_ops->read_version(fw_ctrl->ts_dev,
> + &fw_ver);
> + if (!r) {
> + /* firmversion major+minor store formate is 2byte compress BCD */
> + return scnprintf(buf, PAGE_SIZE, "%2x.%2x\n",
> + fw_ver.vid >> 8, fw_ver.vid & 0xff);
> +
> + }
> + return 0;
> +}
> +
> +static ssize_t goodix_sysfs_fw_name_store(
> + struct goodix_ext_module *module,
> + const char *buf, size_t count)
> +{
> + struct fw_update_ctrl *fwu_ctrl;
> +
> + if (!module || !module->priv_data)
> + return -ENOMEM;
> +
> + fwu_ctrl = module->priv_data;
> + if (count > FW_NAME_MAX) {
> + dev_err(fwu_ctrl->ts_dev->dev, "Firmware name too long");
> + return -EINVAL;
> + }
> + memset(fwu_ctrl->fw_name, 0, FW_NAME_MAX);
> + memcpy(fwu_ctrl->fw_name, buf, count);
> +
> + return count;
> +}
> +
> +static struct goodix_ext_attribute goodix_fwu_attrs[] = {
> + __EXTMOD_ATTR(progress, 0444, goodix_sysfs_update_progress_show, NULL),
> + __EXTMOD_ATTR(result, 0444, goodix_sysfs_update_result_show, NULL),
> + __EXTMOD_ATTR(fwversion, 0444, goodix_sysfs_update_fwversion_show, NULL),
> + __EXTMOD_ATTR(fwsize, 0644, goodix_sysfs_fwsize_show,
> + goodix_sysfs_fwsize_store),
> + __EXTMOD_ATTR(force_update, 0200, NULL, goodix_sysfs_force_update_store),
> + __EXTMOD_ATTR(update_fw, 0200, NULL, goodix_sysfs_update_fw_store),
> + __EXTMOD_ATTR(fw_version, 0444, goodix_sysfs_update_fw_version_show, NULL),
> + __EXTMOD_ATTR(fw_name, 0200, NULL, goodix_sysfs_fw_name_store),
> + __EXTMOD_ATTR(hw_version, 0444, goodix_sysfs_update_hwversion_show, NULL),
> +};
> +
> +static int goodix_syfs_init(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module)
> +{
> + struct fw_update_ctrl *fw_ctrl = module->priv_data;
> + const struct device *dev = &core_data->pdev->dev;
> + struct kobj_type *ktype;
> + int ret = 0, i;
> +
> + ktype = goodix_get_default_ktype();
> + ret = kobject_init_and_add(&module->kobj,
> + ktype,
> + &core_data->pdev->dev.kobj,
> + "fwupdate");
> + if (ret) {
> + dev_err(dev, "Create fwupdate sysfs node error!\n");
> + goto exit_sysfs_init;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(goodix_fwu_attrs); i++) {
> + if (sysfs_create_file(&module->kobj,
> + &goodix_fwu_attrs[i].attr)) {
> + dev_warn(dev, "Create sysfs attr file error\n");
> + kobject_put(&module->kobj);
> + ret = -EINVAL;
> + goto exit_sysfs_init;
> + }
> + }
> +
> + fw_ctrl->attr_fwimage.attr.name = "fwimage";
> + fw_ctrl->attr_fwimage.attr.mode = 0200;
> + fw_ctrl->attr_fwimage.size = 0;
> + fw_ctrl->attr_fwimage.write = goodix_sysfs_fwimage_store;
> + ret = sysfs_create_bin_file(&module->kobj,
> + &fw_ctrl->attr_fwimage);
> +
> +exit_sysfs_init:
> + return ret;
> +}
> +
> +static int goodix_fw_update_init(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module)
> +{
> + struct goodix_ts_board_data *ts_bdata = board_data(core_data);
> + struct fw_update_ctrl *fwu_ctrl;
> + static bool init_sysfs = true;
> +
> + if (!core_data->ts_dev)
> + return -ENODEV;
> +
> + if (!module->priv_data) {
> + module->priv_data = kzalloc(sizeof(struct fw_update_ctrl),
> + GFP_KERNEL);
> + if (!module->priv_data)
> + return -ENOMEM;
> + }
> + fwu_ctrl = module->priv_data;
> + fwu_ctrl->ts_dev = core_data->ts_dev;
> +
> + /* find a valid firmware image name */
> + if (strlen(fwu_ctrl->fw_name) == 0) {
> + if (ts_bdata && ts_bdata->fw_name)
> + strlcpy(fwu_ctrl->fw_name, ts_bdata->fw_name,
> + sizeof(fwu_ctrl->fw_name));
> + else
> + strlcpy(fwu_ctrl->fw_name, TS_DEFAULT_FIRMWARE,
> + sizeof(fwu_ctrl->fw_name));
> + }
> +
> + /* create sysfs interface */
> + if (init_sysfs) {
> + if (!goodix_syfs_init(core_data, module))
> + init_sysfs = false;
> + }
> +
> + return 0;
> +}
> +
> +static int goodix_fw_update_exit(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module)
> +{
> + return 0;
> +}
> +
> +static int goodix_fw_before_suspend(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module)
> +{
> + struct fw_update_ctrl *fwu_ctrl = module->priv_data;
> +
> + return fwu_ctrl->allow_suspend ?
> + EVT_HANDLED : EVT_CANCEL_SUSPEND;
> +}
> +
> +static int goodix_fw_before_resume(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module)
> +{
> + struct fw_update_ctrl *fwu_ctrl = module->priv_data;
> +
> + return fwu_ctrl->allow_resume ?
> + EVT_HANDLED : EVT_CANCEL_RESUME;
> +}
> +
> +static int goodix_fw_irq_event(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module)
> +{
> + struct fw_update_ctrl *fwu_ctrl = module->priv_data;
> +
> + return fwu_ctrl->allow_irq ?
> + EVT_HANDLED : EVT_CANCEL_IRQEVT;
> +}
> +
> +static int goodix_fw_before_reset(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module)
> +{
> + struct fw_update_ctrl *fwu_ctrl = module->priv_data;
> +
> + return fwu_ctrl->allow_reset ?
> + EVT_HANDLED : EVT_CANCEL_RESET;
> +}
> +
> +static const struct goodix_ext_module_funcs goodix_ext_funcs = {
> + .init = goodix_fw_update_init,
> + .exit = goodix_fw_update_exit,
> + .before_reset = goodix_fw_before_reset,
> + .after_reset = NULL,
> + .before_suspend = goodix_fw_before_suspend,
> + .after_suspend = NULL,
> + .before_resume = goodix_fw_before_resume,
> + .after_resume = NULL,
> + .irq_event = goodix_fw_irq_event,
> +};
> +
> +static struct goodix_ext_module goodix_fwu_module = {
> + .name = "goodix-fwu",
> + .funcs = &goodix_ext_funcs,
> + .priority = EXTMOD_PRIO_FWUPDATE,
> +};
> +
> +static int __init goodix_fwu_module_init(void)
> +{
> + return goodix_register_ext_module(&goodix_fwu_module);
> +}
> +
> +static void __exit goodix_fwu_module_exit(void)
> +{
> +}
> +
> +module_init(goodix_fwu_module_init);
> +module_exit(goodix_fwu_module_exit);
> +
> +MODULE_DESCRIPTION("Goodix FWU Module");
> +MODULE_AUTHOR("Goodix, Inc.");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.c b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.c
> new file mode 100755
> index 0000000..f562b36
> --- /dev/null
> +++ b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.c
> @@ -0,0 +1,1366 @@
> + /*
> + * Goodix GTx5 Touchscreen Driver
> + * Core layer of touchdriver architecture.
> + *
> + * Copyright (C) 2015 - 2016 Goodix, Inc.
> + * Authors: Wang Yafei <wangyafei@xxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be a reference
> + * to you, when you are integrating the GOODiX's CTP IC into your system,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + */
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/interrupt.h>
> +#include <linux/of_platform.h>
> +#include <linux/completion.h>
> +#include <linux/debugfs.h>
> +#include <linux/of_irq.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/input/mt.h>
> +#include "goodix_ts_core.h"
> +
> +#define INPUT_TYPE_B_PROTOCOL
> +
> +#define GOOIDX_INPUT_PHYS "goodix_ts/input0"
> +#define PINCTRL_STATE_ACTIVE "pmx_ts_active"
> +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend"
> +
> +/*
> + * struct goodix_modules - external modules container
> + * @head: external modules list
> + * @initilized: whether this struct is initilized
> + * @mutex: mutex lock
> + * @count: current number of registered external module
> + * @wq: workqueue to do register work
> + * @core_exit: if goodix touch core exit, then no
> + * registration is allowed.
> + * @core_data: core_data pointer
> + */
> +struct goodix_modules {
> + struct list_head head;
> + bool initilized;
> + struct mutex mutex;
> + unsigned int count;
> + struct workqueue_struct *wq;
> + bool core_exit;
> + struct completion core_comp;
> + struct goodix_ts_core *core_data;
> +};
> +static struct goodix_modules goodix_modules;
> +
> +/**
> + * __do_register_ext_module - register external module
> + * to register into touch core modules structure
> + */
> +static void __do_register_ext_module(struct work_struct *work)
> +{
> + struct goodix_ext_module *module =
> + container_of(work, struct goodix_ext_module, work);
> + struct goodix_ext_module *ext_module;
> + struct list_head *insert_point = &goodix_modules.head;
> +
> + /* waitting for core layer */
> + if (!wait_for_completion_timeout(&goodix_modules.core_comp, 5 * HZ))
> + return;
> +
> + /* driver probe failed */
> + if (goodix_modules.core_exit)
> + return;
> +
> + /* prority level *must* be set */
> + if (module->priority == EXTMOD_PRIO_RESERVED)
> + return;
> +
> + mutex_lock(&goodix_modules.mutex);
> + if (!list_empty(&goodix_modules.head)) {
> + list_for_each_entry(ext_module, &goodix_modules.head, list) {
> + if (ext_module == module) {
> + mutex_unlock(&goodix_modules.mutex);
> + return;
> + }
> + }
> +
> + list_for_each_entry(ext_module, &goodix_modules.head, list) {
> + /* small value of priority have higher priority level */
> + if (ext_module->priority >= module->priority) {
> + insert_point = &ext_module->list;
> + break;
> + }
> + }
> + /* else module will be inserted to goodix_modules->head */
> + }
> +
> + if (module->funcs && module->funcs->init) {
> + if (module->funcs->init(goodix_modules.core_data,
> + module) < 0) {
> + mutex_unlock(&goodix_modules.mutex);
> + return;
> + }
> + }
> +
> + list_add(&module->list, insert_point->prev);
> + goodix_modules.count++;
> + mutex_unlock(&goodix_modules.mutex);
> +}
> +
> +/**
> + * goodix_register_ext_module - interface for external module
> + * to register into touch core modules structure
> + *
> + * @module: pointer to external module to be register
> + * return: 0 ok, <0 failed
> + */
> +int goodix_register_ext_module(struct goodix_ext_module *module)
> +{
> + if (!module)
> + return -EINVAL;
> +
> + if (!goodix_modules.initilized) {
> + goodix_modules.initilized = true;
> + INIT_LIST_HEAD(&goodix_modules.head);
> + mutex_init(&goodix_modules.mutex);
> + init_completion(&goodix_modules.core_comp);
> + }
> +
> + if (goodix_modules.core_exit)
> + return -EFAULT;
> +
> + INIT_WORK(&module->work, __do_register_ext_module);
> + schedule_work(&module->work);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(goodix_register_ext_module);
> +
> +/**
> + * goodix_unregister_ext_module - interface for external module
> + * to unregister external modules
> + *
> + * @module: pointer to external module
> + * return: 0 ok, <0 failed
> + */
> +int goodix_unregister_ext_module(struct goodix_ext_module *module)
> +{
> + struct goodix_ext_module *ext_module;
> + bool found = false;
> +
> + if (!module)
> + return -EINVAL;
> +
> + if (!goodix_modules.initilized)
> + return -EINVAL;
> +
> + if (!goodix_modules.core_data)
> + return -ENODEV;
> +
> + mutex_lock(&goodix_modules.mutex);
> + if (!list_empty(&goodix_modules.head)) {
> + list_for_each_entry(ext_module, &goodix_modules.head, list) {
> + if (ext_module == module) {
> + found = true;
> + break;
> + }
> + }
> + } else {
> + mutex_unlock(&goodix_modules.mutex);
> + return -EFAULT;
> + }
> +
> + if (!found) {
> + mutex_unlock(&goodix_modules.mutex);
> + return -EFAULT;
> + }
> +
> + list_del(&module->list);
> + mutex_unlock(&goodix_modules.mutex);
> +
> + if (module->funcs && module->funcs->exit)
> + module->funcs->exit(goodix_modules.core_data, module);
> + goodix_modules.count--;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(goodix_unregister_ext_module);
> +
> +static void goodix_ext_sysfs_release(struct kobject *kobj)
> +{
> + return;
> +}
> +
> +#define to_ext_module(kobj) container_of(kobj,\
> + struct goodix_ext_module, kobj)
> +#define to_ext_attr(attr) container_of(attr,\
> + struct goodix_ext_attribute, attr)
> +
> +static ssize_t goodix_ext_sysfs_show(struct kobject *kobj,
> + struct attribute *attr, char *buf)
> +{
> + struct goodix_ext_module *module = to_ext_module(kobj);
> + struct goodix_ext_attribute *ext_attr = to_ext_attr(attr);
> +
> + if (ext_attr->show)
> + return ext_attr->show(module, buf);
> +
> + return -EIO;
> +}
> +
> +static ssize_t goodix_ext_sysfs_store(struct kobject *kobj,
> + struct attribute *attr, const char *buf, size_t count)
> +{
> + struct goodix_ext_module *module = to_ext_module(kobj);
> + struct goodix_ext_attribute *ext_attr = to_ext_attr(attr);
> +
> + if (ext_attr->store)
> + return ext_attr->store(module, buf, count);
> +
> + return -EIO;
> +}
> +
> +static const struct sysfs_ops goodix_ext_ops = {
> + .show = goodix_ext_sysfs_show,
> + .store = goodix_ext_sysfs_store
> +};
> +
> +static struct kobj_type goodix_ext_ktype = {
> + .release = goodix_ext_sysfs_release,
> + .sysfs_ops = &goodix_ext_ops,
> +};
> +
> +struct kobj_type *goodix_get_default_ktype(void)
> +{
> + return &goodix_ext_ktype;
> +}
> +EXPORT_SYMBOL_GPL(goodix_get_default_ktype);
> +
> +struct kobject *goodix_get_default_kobj(void)
> +{
> + struct kobject *kobj = NULL;
> +
> + if (goodix_modules.core_data &&
> + goodix_modules.core_data->pdev)
> + kobj = &goodix_modules.core_data->pdev->dev.kobj;
> + return kobj;
> +}
> +EXPORT_SYMBOL_GPL(goodix_get_default_kobj);
> +
> +/* show external module information */
> +static ssize_t goodix_ts_extmod_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct goodix_ext_module *module;
> + size_t offset = 0;
> + int r;
> +
> + mutex_lock(&goodix_modules.mutex);
> + if (!list_empty(&goodix_modules.head)) {
> + list_for_each_entry(module, &goodix_modules.head, list) {
> + r = snprintf(&buf[offset], PAGE_SIZE,
> + "priority:%u module:%s\n",
> + module->priority, module->name);
> + if (r < 0) {
> + mutex_unlock(&goodix_modules.mutex);
> + return -EINVAL;
> + }
> + offset += r;
> + }
> + }
> +
> + mutex_unlock(&goodix_modules.mutex);
> + return offset;
> +}
> +
> +/* show driver information */
> +static ssize_t goodix_ts_driver_info_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + return snprintf(buf, PAGE_SIZE, "DriverVersion:%s\n",
> + GOODIX_DRIVER_VERSION);
> +}
> +
> +/* show chip infoamtion */
> +static ssize_t goodix_ts_chip_info_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct goodix_ts_core *core_data =
> + dev_get_drvdata(dev);
> + struct goodix_ts_device *ts_dev = core_data->ts_dev;
> + struct goodix_ts_version chip_ver;
> + int r, cnt = 0;
> +
> + cnt += snprintf(buf, PAGE_SIZE,
> + "TouchDeviceName:%s\n", ts_dev->name);
> + if (ts_dev->hw_ops->read_version) {
> + r = ts_dev->hw_ops->read_version(ts_dev, &chip_ver);
> + if (!r && chip_ver.valid) {
> + cnt += snprintf(&buf[cnt], PAGE_SIZE,
> + "PID:%s\nVID:%04x\nSensorID:%02x\n",
> + chip_ver.pid, chip_ver.vid,
> + chip_ver.sensor_id);
> + }
> + }
> +
> + return cnt;
> +}
> +
> +/* show chip configuration data */
> +static ssize_t goodix_ts_config_data_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct goodix_ts_core *core_data =
> + dev_get_drvdata(dev);
> + struct goodix_ts_device *ts_dev = core_data->ts_dev;
> + struct goodix_ts_config *ncfg = ts_dev->normal_cfg;
> + u8 *data;
> + int i, r, offset = 0;
> +
> + if (ncfg && ncfg->initialized && ncfg->length < PAGE_SIZE) {
> + data = kmalloc(ncfg->length, GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + r = ts_dev->hw_ops->read(ts_dev, ncfg->reg_base,
> + &data[0], ncfg->length);
> + if (r < 0) {
> + kfree(data);
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < ncfg->length; i++) {
> + if (i != 0 && i % 20 == 0)
> + buf[offset++] = '\n';
> + offset += snprintf(&buf[offset], PAGE_SIZE - offset,
> + "%02x ", data[i]);
> + }
> + buf[offset++] = '\n';
> + buf[offset++] = '\0';
> + kfree(data);
> + return offset;
> + }
> +
> + return -EINVAL;
> +}
> +
> +/* reset chip */
> +static ssize_t goodix_ts_reset_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf,
> + size_t count)
> +{
> + struct goodix_ts_core *core_data =
> + dev_get_drvdata(dev);
> + struct goodix_ts_device *ts_dev = core_data->ts_dev;
> + int en;
> +
> + if (kstrtoint(buf, 10, &en))
> + return -EINVAL;
> +
> + if (en != 1)
> + return -EINVAL;
> +
> + if (ts_dev->hw_ops->reset)
> + ts_dev->hw_ops->reset(ts_dev);
> + return count;
> +
> +}
> +
> +/* show irq information */
> +static ssize_t goodix_ts_irq_info_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct goodix_ts_core *core_data =
> + dev_get_drvdata(dev);
> + struct irq_desc *desc;
> + size_t offset = 0;
> + int r;
> +
> + r = snprintf(&buf[offset], PAGE_SIZE, "irq:%u\n",
> + core_data->irq);
> + if (r < 0)
> + return -EINVAL;
> +
> + offset += r;
> + r = snprintf(&buf[offset], PAGE_SIZE - offset, "state:%s\n",
> + atomic_read(&core_data->irq_enabled) ?
> + "enabled" : "disabled");
> + if (r < 0)
> + return -EINVAL;
> +
> + desc = irq_to_desc(core_data->irq);
> + offset += r;
> + r = snprintf(&buf[offset], PAGE_SIZE - offset, "disable-depth:%d\n",
> + desc->depth);
> + if (r < 0)
> + return -EINVAL;
> +
> + offset += r;
> + r = snprintf(&buf[offset], PAGE_SIZE - offset, "trigger-count:%zu\n",
> + core_data->irq_trig_cnt);
> + if (r < 0)
> + return -EINVAL;
> +
> + offset += r;
> + r = snprintf(&buf[offset], PAGE_SIZE - offset,
> + "echo 0/1 > irq_info to disable/enable irq");
> + if (r < 0)
> + return -EINVAL;
> +
> + offset += r;
> + return offset;
> +}
> +
> +/* enable/disable irq */
> +static ssize_t goodix_ts_irq_info_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf,
> + size_t count)
> +{
> + struct goodix_ts_core *core_data =
> + dev_get_drvdata(dev);
> + int en;
> +
> + if (kstrtoint(buf, 10, &en))
> + return -EINVAL;
> +
> + goodix_ts_irq_enable(core_data, en);
> + return count;
> +}
> +
> +static DEVICE_ATTR(extmod_info, 0444, goodix_ts_extmod_show, NULL);
> +static DEVICE_ATTR(driver_info, 0444, goodix_ts_driver_info_show, NULL);
> +static DEVICE_ATTR(chip_info, 0444, goodix_ts_chip_info_show, NULL);
> +static DEVICE_ATTR(config_data, 0444, goodix_ts_config_data_show, NULL);
> +static DEVICE_ATTR(reset, 0200, NULL, goodix_ts_reset_store);
> +static DEVICE_ATTR(irq_info, 0644,
> + goodix_ts_irq_info_show, goodix_ts_irq_info_store);
> +
> +static struct attribute *sysfs_attrs[] = {
> + &dev_attr_extmod_info.attr,
> + &dev_attr_driver_info.attr,
> + &dev_attr_chip_info.attr,
> + &dev_attr_config_data.attr,
> + &dev_attr_reset.attr,
> + &dev_attr_irq_info.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group sysfs_group = {
> + .attrs = sysfs_attrs,
> +};
> +
> +static int goodix_ts_sysfs_init(struct goodix_ts_core *core_data)
> +{
> + return sysfs_create_group(&core_data->pdev->dev.kobj, &sysfs_group);
> +}
> +
> +static void goodix_ts_sysfs_exit(struct goodix_ts_core *core_data)
> +{
> + sysfs_remove_group(&core_data->pdev->dev.kobj, &sysfs_group);
> +}
> +
> +/* event notifier */
> +static BLOCKING_NOTIFIER_HEAD(ts_notifier_list);
> +/**
> + * goodix_ts_register_client - register a client notifier
> + * @nb: notifier block to callback on events
> + * see enum ts_notify_event in goodix_ts_core.h
> + */
> +int goodix_ts_register_notifier(struct notifier_block *nb)
> +{
> + return blocking_notifier_chain_register(&ts_notifier_list, nb);
> +}
> +EXPORT_SYMBOL(goodix_ts_register_notifier);
> +
> +/**
> + * goodix_ts_unregister_client - unregister a client notifier
> + * @nb: notifier block to callback on events
> + * see enum ts_notify_event in goodix_ts_core.h
> + */
> +int goodix_ts_unregister_notifier(struct notifier_block *nb)
> +{
> + return blocking_notifier_chain_unregister(&ts_notifier_list, nb);
> +}
> +EXPORT_SYMBOL(goodix_ts_unregister_notifier);
> +
> +/**
> + * goodix_ts_blocking_notify - notify clients of certain events
> + * see enum ts_notify_event in goodix_ts_core.h
> + */
> +int goodix_ts_blocking_notify(enum ts_notify_event evt, void *v)
> +{
> + return blocking_notifier_call_chain(&ts_notifier_list,
> + (unsigned long)evt, v);
> +}
> +EXPORT_SYMBOL_GPL(goodix_ts_blocking_notify);
> +
> +/**
> + * goodix_ts_input_report - report touch event to input subsystem
> + *
> + * @dev: input device pointer
> + * @touch_data: touch data pointer
> + * return: 0 ok, <0 failed
> + */
> +static int goodix_ts_input_report(struct input_dev *dev,
> + struct goodix_touch_data *touch_data)
> +{
> + struct goodix_ts_coords *coords = &touch_data->coords[0];
> + struct goodix_ts_core *core_data = input_get_drvdata(dev);
> + struct goodix_ts_board_data *ts_bdata = board_data(core_data);
> + unsigned int touch_num = touch_data->touch_num, x, y;
> + static u16 pre_fin;
> + int i, id;
> +
> + /* report touch-key */
> + if (unlikely(touch_data->key_value)) {
> + for (i = 0; i < ts_bdata->panel_max_key; i++) {
> + input_report_key(dev, ts_bdata->panel_key_map[i],
> + touch_data->key_value & (1 << i));
> + }
> + }
> +
> + /* first touch down and last touch up condition */
> + if (touch_num != 0 && pre_fin == 0x0000) {
> + /* first touch down event */
> + input_report_key(dev, BTN_TOUCH, 1);
> + input_report_key(dev, BTN_TOOL_FINGER, 1);
> + } else if (touch_num == 0 && pre_fin != 0x0000) {
> + /* no finger exist */
> + input_report_key(dev, BTN_TOUCH, 0);
> + input_report_key(dev, BTN_TOOL_FINGER, 0);
> + } else if (touch_num == 0 && pre_fin == 0x0000) {
> + return 0;
> + }
> +
> + /* report abs */
> + id = coords->id;
> + for (i = 0; i < ts_bdata->panel_max_id; i++) {
> + if (touch_num && i == id) {
> + /* this is a valid touch down event */
> +#ifdef INPUT_TYPE_B_PROTOCOL
> + input_mt_slot(dev, id);
> + input_mt_report_slot_state(dev, MT_TOOL_FINGER, true);
> +#else
> + input_report_abs(dev, ABS_MT_TRACKING_ID, id);
> +#endif
> + if (unlikely(ts_bdata->swap_axis)) {
> + x = coords->y;
> + y = coords->x;
> + } else {
> + x = coords->x;
> + y = coords->y;
> + }
> + input_report_abs(dev, ABS_MT_POSITION_X, x);
> + input_report_abs(dev, ABS_MT_POSITION_Y, y);
> + input_report_abs(dev, ABS_MT_TOUCH_MAJOR, coords->w);
> + pre_fin |= 1 << i;
> + id = (++coords)->id;
> +#ifndef INPUT_TYPE_B_PROTOCOL
> + input_mt_sync(dev);
> +#endif
> + } else {
> + if (pre_fin & (1 << i)) {/* release touch */
> +#ifdef INPUT_TYPE_B_PROTOCOL
> + input_mt_slot(dev, i);
> + input_mt_report_slot_state(dev, MT_TOOL_FINGER,
> + false);
> +#endif
> + pre_fin &= ~(1 << i);
> + }
> + }
> + }
> +
> +#ifndef INPUT_TYPE_B_PROTOCOL
> + if (!pre_fin)
> + input_mt_sync(dev);
> +#endif
> + input_sync(dev);
> + return 0;
> +}
> +
> +/**
> + * goodix_ts_threadirq_func - Bottom half of interrupt
> + * This functions is excuted in thread context,
> + * sleep in this function is permit.
> + *
> + * @core_data: pointer to touch core data
> + * return: 0 ok, <0 failed
> + */
> +static irqreturn_t goodix_ts_threadirq_func(int irq, void *data)
> +{
> + struct goodix_ts_core *core_data = data;
> + struct goodix_ts_device *ts_dev = core_data->ts_dev;
> + struct goodix_ext_module *ext_module;
> + struct goodix_ts_event *ts_event = &core_data->ts_event;
> + int r;
> +
> + core_data->irq_trig_cnt++;
> + /* inform external module */
> + list_for_each_entry(ext_module, &goodix_modules.head, list) {
> + if (!ext_module->funcs->irq_event)
> + continue;
> + r = ext_module->funcs->irq_event(core_data, ext_module);
> + if (r == EVT_CANCEL_IRQEVT)
> + return IRQ_HANDLED;
> + }
> +
> + /* read touch data from touch device */
> + r = ts_dev->hw_ops->event_handler(ts_dev, ts_event);
> + if (likely(r >= 0)) {
> + if (ts_event->event_type == EVENT_TOUCH) {
> + /* report touch */
> + goodix_ts_input_report(core_data->input_dev,
> + &ts_event->event_data.touch_data);
> + }
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +/**
> + * goodix_ts_init_irq - Requset interrupt line from system
> + * @core_data: pointer to touch core data
> + * return: 0 ok, <0 failed
> + */
> +static int goodix_ts_irq_setup(struct goodix_ts_core *core_data)
> +{
> + const struct goodix_ts_board_data *ts_bdata =
> + board_data(core_data);
> + const struct device *dev = &core_data->pdev->dev;
> + int r;
> +
> + /* if ts_bdata->irq is invalid get it from irq-gpio */
> + if (ts_bdata->irq <= 0)
> + core_data->irq = gpiod_to_irq(ts_bdata->irq_gpiod);
> + else
> + core_data->irq = ts_bdata->irq;
> +
> + dev_info(dev, "IRQ:%u,flags:%d\n", core_data->irq, (int)ts_bdata->irq_flags);
> + r = devm_request_threaded_irq(&core_data->pdev->dev,
> + core_data->irq, NULL,
> + goodix_ts_threadirq_func,
> + ts_bdata->irq_flags | IRQF_ONESHOT,
> + GOODIX_CORE_DRIVER_NAME,
> + core_data);
> + if (r < 0)
> + dev_err(dev, "Failed to requeset threaded irq:%d\n", r);
> + else
> + atomic_set(&core_data->irq_enabled, 1);
> +
> + return r;
> +}
> +
> +/**
> + * goodix_ts_irq_enable - Enable/Disable a irq
> + * @core_data: pointer to touch core data
> + * enable: enable or disable irq
> + * return: 0 ok, <0 failed
> + */
> +int goodix_ts_irq_enable(struct goodix_ts_core *core_data,
> + bool enable)
> +{
> + const struct device *dev = &core_data->pdev->dev;
> +
> + if (enable) {
> + if (!atomic_cmpxchg(&core_data->irq_enabled, 0, 1)) {
> + enable_irq(core_data->irq);
> + dev_dbg(dev, "Irq enabled\n");
> + }
> + } else {
> + if (atomic_cmpxchg(&core_data->irq_enabled, 1, 0)) {
> + disable_irq(core_data->irq);
> + dev_dbg(dev, "Irq disabled\n");
> + }
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(goodix_ts_irq_enable);
> +/**
> + * goodix_ts_power_init - Get regulator for touch device
> + * @core_data: pointer to touch core data
> + * return: 0 ok, <0 failed
> + */
> +static int goodix_ts_power_init(struct goodix_ts_core *core_data)
> +{
> + struct device *dev = NULL;
> + struct goodix_ts_board_data *ts_bdata;
> +
> + /* dev:i2c client device or spi slave device*/
> + dev = core_data->ts_dev->dev;
> + ts_bdata = board_data(core_data);
> +
> + if (ts_bdata->avdd_name) {
> + core_data->avdd = devm_regulator_get(dev, ts_bdata->avdd_name);
> + if (IS_ERR_OR_NULL(core_data->avdd)) {
> + core_data->avdd = NULL;
> + return -ENOENT;
> + }
> + } else {
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * goodix_ts_power_on - Turn on power to the touch device
> + * @core_data: pointer to touch core data
> + * return: 0 ok, <0 failed
> + */
> +static int goodix_ts_power_on(struct goodix_ts_core *core_data)
> +{
> + struct goodix_ts_board_data *ts_bdata =
> + board_data(core_data);
> + const struct device *dev = &core_data->pdev->dev;
> + int r;
> +
> + dev_info(dev, "Device power on\n");
> + if (core_data->power_on)
> + return 0;
> +
> + if (core_data->avdd) {
> + r = regulator_enable(core_data->avdd);
> + if (!r) {
> + if (ts_bdata->power_on_delay_us)
> + usleep_range(ts_bdata->power_on_delay_us,
> + ts_bdata->power_on_delay_us);
> + } else {
> + dev_err(dev, "Failed to enable analog power:%d\n", r);
> + return r;
> + }
> + }
> +
> + core_data->power_on = 1;
> + return 0;
> +}
> +
> +/**
> + * goodix_ts_power_off - Turn off power to the touch device
> + * @core_data: pointer to touch core data
> + * return: 0 ok, <0 failed
> + */
> +static int goodix_ts_power_off(struct goodix_ts_core *core_data)
> +{
> + struct goodix_ts_board_data *ts_bdata =
> + board_data(core_data);
> + const struct device *dev = &core_data->pdev->dev;
> + int r;
> +
> + dev_info(dev, "Device power off\n");
> + if (!core_data->power_on)
> + return 0;
> +
> + if (core_data->avdd) {
> + r = regulator_disable(core_data->avdd);
> + if (!r) {
> + if (ts_bdata->power_off_delay_us)
> + usleep_range(ts_bdata->power_off_delay_us,
> + ts_bdata->power_off_delay_us);
> + } else {
> + dev_err(dev, "Failed to disable analog power:%d\n", r);
> + return r;
> + }
> + }
> +
> + core_data->power_on = 0;
> + return 0;
> +}
> +
> +/**
> + * goodix_ts_gpio_setup - Request gpio resources from GPIO subsysten
> + * reset_gpio and irq_gpio number are obtained from goodix_ts_device
> + * which created in hardware layer driver. e.g.goodix_xx_i2c.c
> + * A goodix_ts_device should set those two fileds to right value
> + * before registed to touch core driver.
> + * @core_data: pointer to touch core data
> + * return: 0 ok, <0 failed
> + */
> +static void goodix_ts_gpio_setup(struct goodix_ts_core *core_data)
> +{
> + struct goodix_ts_board_data *ts_bdata = board_data(core_data);
> + struct goodix_ts_device *ts_dev = core_data->ts_dev;
> + const struct device *dev = &core_data->pdev->dev;
> +
> + ts_bdata->reset_gpiod = devm_gpiod_get_optional(ts_dev->dev,
> + "reset", GPIOD_OUT_LOW);
> + if (!ts_bdata->reset_gpiod)
> + dev_info(dev, "No reset gpio found\n");
> +
> + ts_bdata->irq_gpiod = devm_gpiod_get_optional(ts_dev->dev,
> + "irq", GPIOD_IN);
> + if (!ts_bdata->irq_gpiod)
> + dev_info(dev, "No irq gpio found\n");
> +
> +}
> +
> +/**
> + * goodix_input_set_params - set input parameters
> + */
> +static void goodix_ts_set_input_params(struct input_dev *input_dev,
> + struct goodix_ts_board_data *ts_bdata)
> +{
> + int i;
> +
> + if (ts_bdata->swap_axis)
> + swap(ts_bdata->panel_max_x, ts_bdata->panel_max_y);
> +
> + input_set_abs_params(input_dev, ABS_MT_TRACKING_ID,
> + 0, ts_bdata->panel_max_id, 0, 0);
> + input_set_abs_params(input_dev, ABS_MT_POSITION_X,
> + 0, ts_bdata->panel_max_x, 0, 0);
> +
> + input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
> + 0, ts_bdata->panel_max_y, 0, 0);
> +
> + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,
> + 0, ts_bdata->panel_max_w, 0, 0);
> + if (ts_bdata->panel_max_key) {
> + for (i = 0; i < ts_bdata->panel_max_key; i++)
> + input_set_capability(input_dev, EV_KEY,
> + ts_bdata->panel_key_map[i]);
> + }
> +}
> +
> +/**
> + * goodix_ts_input_dev_config - Requset and config a input device
> + * then register it to input sybsystem.
> + * NOTE that some hardware layer may provide a input device
> + * (ts_dev->input_dev not NULL).
> + * @core_data: pointer to touch core data
> + * return: 0 ok, <0 failed
> + */
> +static int goodix_ts_input_dev_config(struct goodix_ts_core *core_data)
> +{
> + struct goodix_ts_board_data *ts_bdata = board_data(core_data);
> + struct device *dev = &core_data->pdev->dev;
> + struct input_dev *input_dev = NULL;
> + int r;
> +
> + input_dev = devm_input_allocate_device(dev);
> + if (!input_dev) {
> + dev_err(dev, "Failed to allocated input device\n");
> + return -ENOMEM;
> + }
> +
> + core_data->input_dev = input_dev;
> + input_set_drvdata(input_dev, core_data);
> +
> + input_dev->name = GOODIX_CORE_DRIVER_NAME;
> + input_dev->phys = GOOIDX_INPUT_PHYS;
> + input_dev->id.product = 0xDEAD;
> + input_dev->id.vendor = 0xBEEF;
> + input_dev->id.version = 10427;
> +
> + __set_bit(EV_SYN, input_dev->evbit);
> + __set_bit(EV_KEY, input_dev->evbit);
> + __set_bit(EV_ABS, input_dev->evbit);
> + __set_bit(BTN_TOUCH, input_dev->keybit);
> + __set_bit(BTN_TOOL_FINGER, input_dev->keybit);
> +
> +#ifdef INPUT_PROP_DIRECT
> + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
> +#endif
> +
> + /* set input parameters */
> + goodix_ts_set_input_params(input_dev, ts_bdata);
> +
> +#ifdef INPUT_TYPE_B_PROTOCOL
> + input_mt_init_slots(input_dev, ts_bdata->panel_max_id,
> + INPUT_MT_DIRECT);
> +#endif
> +
> + input_set_capability(input_dev, EV_KEY, KEY_POWER);
> + r = input_register_device(input_dev);
> + if (r < 0) {
> + dev_err(dev, "Unable to register input device\n");
> + return r;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * goodix_ts_hw_init - Hardware initialize
> + * poweron - hardware reset - sendconfig
> + * @core_data: pointer to touch core data
> + * return: 0 intilize ok, <0 failed
> + */
> +static int goodix_ts_hw_init(struct goodix_ts_core *core_data)
> +{
> + const struct goodix_ts_hw_ops *hw_ops =
> + ts_hw_ops(core_data);
> + int r;
> +
> + r = goodix_ts_power_on(core_data);
> + if (r < 0)
> + goto exit;
> +
> + /* reset touch device */
> + if (hw_ops->reset)
> + hw_ops->reset(core_data->ts_dev);
> +
> + /* init */
> + if (hw_ops->init) {
> + r = hw_ops->init(core_data->ts_dev);
> + if (r < 0) {
> + core_data->hw_err = true;
> + goto exit;
> + }
> + }
> +
> +exit:
> + /* if bus communication error occurred then exit driver binding, other
> + * errors will be ignored
> + */
> + if (r != -EBUS)
> + r = 0;
> + return r;
> +}
> +
> +/**
> + * goodix_ts_esd_work - check hardware status and recovery
> + * the hardware if needed.
> + */
> +static void goodix_ts_esd_work(struct work_struct *work)
> +{
> + struct delayed_work *dwork = to_delayed_work(work);
> + struct goodix_ts_esd *ts_esd = container_of(dwork,
> + struct goodix_ts_esd, esd_work);
> + struct goodix_ts_core *core = container_of(ts_esd,
> + struct goodix_ts_core, ts_esd);
> + const struct goodix_ts_hw_ops *hw_ops = ts_hw_ops(core);
> + int r = 0;
> +
> + if (ts_esd->esd_on == false)
> + return;
> +
> + if (hw_ops->check_hw)
> + r = hw_ops->check_hw(core->ts_dev);
> + if (r < 0) {
> + goodix_ts_power_off(core);
> + goodix_ts_power_on(core);
> + if (hw_ops->reset)
> + hw_ops->reset(core->ts_dev);
> + }
> +
> + mutex_lock(&ts_esd->esd_mutex);
> + if (ts_esd->esd_on)
> + schedule_delayed_work(&ts_esd->esd_work, 2 * HZ);
> + mutex_unlock(&ts_esd->esd_mutex);
> +}
> +
> +/**
> + * goodix_ts_esd_on - turn on esd protection
> + */
> +static void goodix_ts_esd_on(struct goodix_ts_core *core_data)
> +{
> + struct goodix_ts_esd *ts_esd = &core_data->ts_esd;
> + const struct device *dev = &core_data->pdev->dev;
> +
> + mutex_lock(&ts_esd->esd_mutex);
> + if (ts_esd->esd_on == false) {
> + ts_esd->esd_on = true;
> + schedule_delayed_work(&ts_esd->esd_work, 2 * HZ);
> + mutex_unlock(&ts_esd->esd_mutex);
> + dev_info(dev, "Esd on\n");
> + return;
> + }
> + mutex_unlock(&ts_esd->esd_mutex);
> +}
> +
> +/**
> + * goodix_ts_esd_off - turn off esd protection
> + */
> +static void goodix_ts_esd_off(struct goodix_ts_core *core_data)
> +{
> + struct goodix_ts_esd *ts_esd = &core_data->ts_esd;
> + const struct device *dev = &core_data->pdev->dev;
> +
> + mutex_lock(&ts_esd->esd_mutex);
> + if (ts_esd->esd_on == true) {
> + ts_esd->esd_on = false;
> + cancel_delayed_work(&ts_esd->esd_work);
> + mutex_unlock(&ts_esd->esd_mutex);
> + dev_info(dev, "Esd off\n");
> + return;
> + }
> + mutex_unlock(&ts_esd->esd_mutex);
> +}
> +
> +/**
> + * goodix_esd_notifier_callback - notification callback
> + * under certain condition, we need to turn off/on the esd
> + * protector, we use kernel notify call chain to achieve this.
> + *
> + * for example: before firmware update we need to turn off the
> + * esd protector and after firmware update finished, we should
> + * turn on the esd protector.
> + */
> +static int goodix_esd_notifier_callback(struct notifier_block *nb,
> + unsigned long action, void *data)
> +{
> + struct goodix_ts_esd *ts_esd = container_of(nb,
> + struct goodix_ts_esd, esd_notifier);
> +
> + switch (action) {
> + case NOTIFY_FWUPDATE_START:
> + case NOTIFY_SUSPEND:
> + goodix_ts_esd_off(ts_esd->ts_core);
> + break;
> + case NOTIFY_FWUPDATE_END:
> + case NOTIFY_RESUME:
> + goodix_ts_esd_on(ts_esd->ts_core);
> + break;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * goodix_ts_esd_init - initialize esd protection
> + */
> +static int goodix_ts_esd_init(struct goodix_ts_core *core)
> +{
> + struct goodix_ts_esd *ts_esd = &core->ts_esd;
> +
> + INIT_DELAYED_WORK(&ts_esd->esd_work, goodix_ts_esd_work);
> + mutex_init(&ts_esd->esd_mutex);
> + ts_esd->ts_core = core;
> + ts_esd->esd_on = false;
> + ts_esd->esd_notifier.notifier_call = goodix_esd_notifier_callback;
> + goodix_ts_register_notifier(&ts_esd->esd_notifier);
> +
> + if (core->ts_dev->board_data->esd_default_on == true
> + && core->ts_dev->hw_ops->check_hw)
> + goodix_ts_esd_on(core);
> + return 0;
> +}
> +
> +/**
> + * goodix_ts_suspend - Touchscreen suspend function
> + */
> +static int goodix_ts_suspend(struct goodix_ts_core *core_data)
> +{
> + struct goodix_ext_module *ext_module;
> + struct goodix_ts_device *ts_dev = core_data->ts_dev;
> + const struct device *dev = &core_data->pdev->dev;
> + int r;
> +
> + dev_dbg(dev, "Suspend start\n");
> + /*
> + * notify suspend event, inform the esd protector
> + * and charger detector to turn off the work
> + */
> + goodix_ts_blocking_notify(NOTIFY_SUSPEND, NULL);
> +
> + /* inform external module */
> + mutex_lock(&goodix_modules.mutex);
> + if (!list_empty(&goodix_modules.head)) {
> + list_for_each_entry(ext_module, &goodix_modules.head, list) {
> + if (!ext_module->funcs->before_suspend)
> + continue;
> +
> + r = ext_module->funcs->before_suspend(core_data, ext_module);
> + if (r == EVT_CANCEL_SUSPEND) {
> + mutex_unlock(&goodix_modules.mutex);
> + dev_dbg(dev, "Canceled by module:%s\n",
> + ext_module->name);
> + goto out;
> + }
> + }
> + }
> + mutex_unlock(&goodix_modules.mutex);
> +
> + /* disable irq */
> + goodix_ts_irq_enable(core_data, false);
> +
> + /* let touch ic work in sleep mode */
> + if (ts_dev && ts_dev->hw_ops->suspend)
> + ts_dev->hw_ops->suspend(ts_dev);
> + atomic_set(&core_data->suspended, 1);
> +
> + /* inform exteranl modules */
> + mutex_lock(&goodix_modules.mutex);
> + if (!list_empty(&goodix_modules.head)) {
> + list_for_each_entry(ext_module, &goodix_modules.head, list) {
> + if (!ext_module->funcs->after_suspend)
> + continue;
> +
> + r = ext_module->funcs->after_suspend(core_data, ext_module);
> + if (r == EVT_CANCEL_SUSPEND) {
> + mutex_unlock(&goodix_modules.mutex);
> + dev_dbg(dev, "Canceled by module:%s\n",
> + ext_module->name);
> + goto out;
> + }
> + }
> + }
> + mutex_unlock(&goodix_modules.mutex);
> +
> +out:
> + /* release all the touch IDs */
> + core_data->ts_event.event_data.touch_data.touch_num = 0;
> + goodix_ts_input_report(core_data->input_dev,
> + &core_data->ts_event.event_data.touch_data);
> + dev_dbg(dev, "Suspend end\n");
> + return 0;
> +}
> +
> +/**
> + * goodix_ts_resume - Touchscreen resume function
> + * Called by PM/FB/EARLYSUSPEN module to wakeup device
> + */
> +static int goodix_ts_resume(struct goodix_ts_core *core_data)
> +{
> + struct goodix_ext_module *ext_module;
> + struct goodix_ts_device *ts_dev = core_data->ts_dev;
> + const struct device *dev = &core_data->pdev->dev;
> + int r;
> +
> + dev_dbg(dev, "Resume start\n");
> + mutex_lock(&goodix_modules.mutex);
> + if (!list_empty(&goodix_modules.head)) {
> + list_for_each_entry(ext_module, &goodix_modules.head, list) {
> + if (!ext_module->funcs->before_resume)
> + continue;
> +
> + r = ext_module->funcs->before_resume(core_data, ext_module);
> + if (r == EVT_CANCEL_RESUME) {
> + mutex_unlock(&goodix_modules.mutex);
> + dev_dbg(dev, "Canceled by module:%s\n",
> + ext_module->name);
> + goto out;
> + }
> + }
> + }
> + mutex_unlock(&goodix_modules.mutex);
> +
> + atomic_set(&core_data->suspended, 0);
> + /* resume device */
> + if (ts_dev && ts_dev->hw_ops->resume)
> + ts_dev->hw_ops->resume(ts_dev);
> +
> + goodix_ts_irq_enable(core_data, true);
> +
> + mutex_lock(&goodix_modules.mutex);
> + if (!list_empty(&goodix_modules.head)) {
> + list_for_each_entry(ext_module, &goodix_modules.head, list) {
> + if (!ext_module->funcs->after_resume)
> + continue;
> +
> + r = ext_module->funcs->after_resume(core_data, ext_module);
> + if (r == EVT_CANCEL_RESUME) {
> + mutex_unlock(&goodix_modules.mutex);
> + dev_dbg(dev, "Canceled by module:%s\n",
> + ext_module->name);
> + goto out;
> + }
> + }
> + }
> + mutex_unlock(&goodix_modules.mutex);
> +
> +out:
> + /*
> + * notify resume event, inform the esd protector
> + * and charger detector to turn on the work
> + */
> + goodix_ts_blocking_notify(NOTIFY_RESUME, NULL);
> + dev_dbg(dev, "Resume end\n");
> + return 0;
> +}
> +
> +/**
> + * goodix_ts_pm_suspend - PM suspend function
> + * Called by kernel during system suspend phrase
> + */
> +static int __maybe_unused goodix_ts_pm_suspend(struct device *dev)
> +{
> + struct goodix_ts_core *core_data =
> + dev_get_drvdata(dev);
> +
> + return goodix_ts_suspend(core_data);
> +}
> +/**
> + * goodix_ts_pm_resume - PM resume function
> + * Called by kernel during system wakeup
> + */
> +static int __maybe_unused goodix_ts_pm_resume(struct device *dev)
> +{
> + struct goodix_ts_core *core_data =
> + dev_get_drvdata(dev);
> +
> + return goodix_ts_resume(core_data);
> +}
> +
> +/**
> + * goodix_generic_noti_callback - generic notifier callback
> + * for goodix touch notification event.
> + */
> +static int goodix_generic_noti_callback(struct notifier_block *self,
> + unsigned long action, void *data)
> +{
> + struct goodix_ts_core *ts_core = container_of(self,
> + struct goodix_ts_core, ts_notifier);
> + const struct goodix_ts_hw_ops *hw_ops = ts_hw_ops(ts_core);
> + int r;
> +
> + switch (action) {
> + case NOTIFY_FWUPDATE_END:
> + if (ts_core->hw_err && hw_ops->init) {
> + /* Firmware has been updated, we need to reinit
> + * the chip, read the sensor ID and send the
> + * correct config data based on sensor ID.
> + * The input parameters also needs to be updated.
> + */
> + r = hw_ops->init(ts_core->ts_dev);
> + if (r < 0)
> + goto exit;
> +
> + goodix_ts_set_input_params(ts_core->input_dev,
> + ts_core->ts_dev->board_data);
> + ts_core->hw_err = false;
> + }
> + break;
> + }
> +
> +exit:
> + return 0;
> +}
> +
> +/**
> + * goodix_ts_probe - called by kernel when a Goodix touch
> + * platform driver is added.
> + */
> +static int goodix_ts_probe(struct platform_device *pdev)
> +{
> + struct goodix_ts_core *core_data = NULL;
> + struct goodix_ts_device *ts_device;
> + int r;
> +
> + ts_device = pdev->dev.platform_data;
> + if (!ts_device || !ts_device->hw_ops || !ts_device->board_data) {
> + dev_err(&pdev->dev, "Invalid touch device\n");
> + return -ENODEV;
> + }
> +
> + core_data = devm_kzalloc(&pdev->dev, sizeof(struct goodix_ts_core),
> + GFP_KERNEL);
> + if (!core_data)
> + return -ENOMEM;
> +
> + /* touch core layer is a platform driver */
> + core_data->pdev = pdev;
> + core_data->ts_dev = ts_device;
> + platform_set_drvdata(pdev, core_data);
> +
> + r = goodix_ts_power_init(core_data);
> + if (r < 0)
> + dev_err(&pdev->dev, "Failed power init\n");
> +
> + /* get GPIO resource if have */
> + goodix_ts_gpio_setup(core_data);
> +
> + /* initialize firmware */
> + r = goodix_ts_hw_init(core_data);
> + if (r < 0)
> + goto out;
> +
> + /* alloc/config/register input device */
> + r = goodix_ts_input_dev_config(core_data);
> + if (r < 0)
> + goto out;
> +
> + /* request irq line */
> + r = goodix_ts_irq_setup(core_data);
> + if (r < 0)
> + goto out;
> +
> + /* inform the external module manager that
> + * touch core layer is ready now
> + */
> + goodix_modules.core_data = core_data;
> + complete_all(&goodix_modules.core_comp);
> +
> + /* create sysfs files */
> + goodix_ts_sysfs_init(core_data);
> +
> + /* esd protector */
> + goodix_ts_esd_init(core_data);
> +
> + /* generic notifier callback */
> + core_data->ts_notifier.notifier_call = goodix_generic_noti_callback;
> + goodix_ts_register_notifier(&core_data->ts_notifier);
> +
> + return 0;
> + /* we use resource managed api(devm_), no need to free resource */
> +out:
> + goodix_modules.core_exit = true;
> + complete_all(&goodix_modules.core_comp);
> + dev_err(&pdev->dev, "Core layer probe failed");
> + return r;
> +}
> +
> +static int goodix_ts_remove(struct platform_device *pdev)
> +{
> + struct goodix_ts_core *core_data =
> + platform_get_drvdata(pdev);
> +
> + goodix_ts_power_off(core_data);
> + goodix_ts_sysfs_exit(core_data);
> + return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(dev_pm_ops, goodix_ts_pm_suspend, goodix_ts_pm_resume);
> +
> +static const struct platform_device_id ts_core_ids[] = {
> + {.name = GOODIX_CORE_DRIVER_NAME},
> + {}
> +};
> +MODULE_DEVICE_TABLE(platform, ts_core_ids);
> +
> +static struct platform_driver goodix_ts_driver = {
> + .driver = {
> + .name = GOODIX_CORE_DRIVER_NAME,
> + .owner = THIS_MODULE,
> + .pm = &dev_pm_ops,
> + },
> + .probe = goodix_ts_probe,
> + .remove = goodix_ts_remove,
> + .id_table = ts_core_ids,
> +};
> +
> +static int __init goodix_ts_core_init(void)
> +{
> + if (!goodix_modules.initilized) {
> + goodix_modules.initilized = true;
> + INIT_LIST_HEAD(&goodix_modules.head);
> + mutex_init(&goodix_modules.mutex);
> + init_completion(&goodix_modules.core_comp);
> + }
> +
> + return platform_driver_register(&goodix_ts_driver);
> +}
> +
> +
> +static void __exit goodix_ts_core_exit(void)
> +{
> + platform_driver_unregister(&goodix_ts_driver);
> +}
> +
> +module_init(goodix_ts_core_init);
> +module_exit(goodix_ts_core_exit);
> +
> +MODULE_DESCRIPTION("Goodix Touchscreen Core Module");
> +MODULE_AUTHOR("Goodix, Inc.");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.h b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.h
> new file mode 100755
> index 0000000..400746e
> --- /dev/null
> +++ b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.h
> @@ -0,0 +1,553 @@
> +/*
> + * Goodix GTx5 Touchscreen Driver
> + * Core layer of touchdriver architecture.
> + *
> + * Copyright (C) 2015 - 2016 Goodix, Inc.
> + * Authors: Wang Yafei <wangyafei@xxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be a reference
> + * to you, when you are integrating the GOODiX's CTP IC into your system,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + */
> +#ifndef _GOODIX_TS_CORE_H_
> +#define _GOODIX_TS_CORE_H_
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/firmware.h>
> +#include <linux/slab.h>
> +#include <linux/vmalloc.h>
> +#include <linux/kthread.h>
> +#include <linux/version.h>
> +#include <linux/delay.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/input.h>
> +#include <asm/unaligned.h>
> +#ifdef CONFIG_OF
> +#include <linux/of_gpio.h>
> +#include <linux/regulator/consumer.h>
> +#endif
> +
> +#include <linux/gpio/consumer.h>
> +
> +/* macros definition */
> +#define GOODIX_CORE_DRIVER_NAME "goodix_ts"
> +#define GOODIX_DRIVER_VERSION "v0.8"
> +#define GOODIX_BUS_RETRY_TIMES 3
> +#define GOODIX_MAX_TOUCH 10
> +#define GOODIX_MAX_KEY 3
> +#define GOODIX_CFG_MAX_SIZE 1024
> +
> +/*
> + * struct goodix_ts_board_data - board data
> + * @avdd_name: name of analoy regulator
> + * @reset_gpio: reset gpio number
> + * @irq_gpio: interrupt gpio number
> + * @irq_flag: irq trigger type
> + * @power_on_delay_us: power on delay time (us)
> + * @power_off_delay_us: power off delay time (us)
> + * @swap_axis: whether swaw x y axis
> + * @panel_max_id: max supported fingers
> + * @panel_max_x/y/w/p: resolution and size
> + * @panel_max_key: max supported keys
> + * @pannel_key_map: key map
> + * @fw_name: name of the firmware image
> + */
> +struct goodix_ts_board_data {
> + const char *avdd_name;
> + struct gpio_desc *reset_gpiod;
> + struct gpio_desc *irq_gpiod;
> + int irq;
> + unsigned int irq_flags;
> +
> + unsigned int power_on_delay_us;
> + unsigned int power_off_delay_us;
> +
> + unsigned int swap_axis;
> + unsigned int panel_max_id; /*max touch id*/
> + unsigned int panel_max_x;
> + unsigned int panel_max_y;
> + unsigned int panel_max_w; /*major and minor*/
> + unsigned int panel_max_key;
> + unsigned int panel_key_map[GOODIX_MAX_KEY];
> +
> + const char *fw_name;
> + bool esd_default_on;
> +};
> +
> +/*
> + * struct goodix_ts_config - chip config data
> + * @initialized: whether intialized
> + * @name: name of this config
> + * @lock: mutex
> + * @reg_base: register base of config data
> + * @length: bytes of the config
> + * @delay: delay time after sending config
> + * @data: config data buffer
> + */
> +struct goodix_ts_config {
> + bool initialized;
> + char name[24];
> + struct mutex lock;
> + unsigned int reg_base;
> + unsigned int length;
> + unsigned int delay; /*ms*/
> + unsigned char data[GOODIX_CFG_MAX_SIZE];
> +};
> +
> +/*
> + * struct goodix_ts_cmd - command package
> + * @initialized: whether initialized
> + * @cmd_reg: command register
> + * @length: command length in bytes
> + * @cmds: command data
> + */
> +#pragma pack(4)
> +struct goodix_ts_cmd {
> + u32 initialized;
> + u32 cmd_reg;
> + u32 length;
> + u8 cmds[3];
> +};
> +#pragma pack()
> +
> +/* interrupt event type */
> +enum ts_event_type {
> + EVENT_INVALID,
> + EVENT_TOUCH,
> + EVENT_REQUEST,
> +};
> +
> +/* requset event type */
> +enum ts_request_type {
> + REQUEST_INVALID,
> + REQUEST_CONFIG,
> + REQUEST_BAKREF,
> + REQUEST_RESET,
> + REQUEST_MAINCLK,
> +};
> +
> +/* notifier event */
> +
> +enum ts_notify_event {
> + NOTIFY_FWUPDATE_START,
> + NOTIFY_FWUPDATE_END,
> + NOTIFY_SUSPEND,
> + NOTIFY_RESUME,
> +};
> +
> +/* coordinate package */
> +struct goodix_ts_coords {
> + int id;
> + unsigned int x, y, w, p;
> +};
> +
> +/* touch event data */
> +struct goodix_touch_data {
> + /* finger */
> + int touch_num;
> + struct goodix_ts_coords coords[GOODIX_MAX_TOUCH];
> + /* key */
> + u16 key_value;
> +};
> +
> +/* request event data */
> +struct goodix_request_data {
> + enum ts_request_type request_type;
> +};
> +
> +/*
> + * struct goodix_ts_event - touch event struct
> + * @event_type: touch event type, touch data or
> + * request event
> + * @event_data: event data
> + */
> +struct goodix_ts_event {
> + enum ts_event_type event_type;
> + union {
> + struct goodix_touch_data touch_data;
> + struct goodix_request_data request_data;
> + } event_data;
> +};
> +
> +/*
> + * struct goodix_ts_version - firmware version
> + * @valid: whether these infomation is valid
> + * @pid: product id string
> + * @vid: firmware version code
> + * @cid: customer id code
> + * @sensor_id: sendor id
> + */
> +struct goodix_ts_version {
> + bool valid;
> + char pid[5];
> + u16 vid;
> + u8 cid;
> + u8 sensor_id;
> +};
> +
> +/*
> + * struct goodix_ts_device - ts device data
> + * @name: device name
> + * @version: reserved
> + * @bus_type: i2c or spi
> + * @board_data: board data obtained from dts
> + * @normal_cfg: normal config data
> + * @highsense_cfg: high sense config data
> + * @hw_ops: hardware operations
> + * @chip_version: firmware version infomation
> + * @sleep_cmd: sleep commang
> + * @gesture_cmd: gesture command
> + * @dev: device pointer,may be a i2c or spi device
> + * @of_node: device node
> + */
> +struct goodix_ts_device {
> + char *name;
> + int version;
> + int bus_type;
> +
> + struct goodix_ts_board_data *board_data;
> + struct goodix_ts_config *normal_cfg;
> + struct goodix_ts_config *highsense_cfg;
> + const struct goodix_ts_hw_ops *hw_ops;
> +
> + struct goodix_ts_version chip_version;
> + struct goodix_ts_cmd sleep_cmd;
> + struct goodix_ts_cmd gesture_cmd;
> +
> + struct device *dev;
> +};
> +
> +/*
> + * struct goodix_ts_hw_ops - hardware opeartions
> + * @init: hardware initialization
> + * @reset: hardware reset
> + * @read: read data from touch device
> + * @write: write data to touch device
> + * @send_cmd: send command to touch device
> + * @send_config: send configuration data
> + * @read_version: read firmware version
> + * @event_handler: touch event handler
> + * @suspend: put touch device into low power mode
> + * @resume: put touch device into working mode
> + */
> +struct goodix_ts_hw_ops {
> +
> + int (*init)(struct goodix_ts_device *dev);
> + void (*reset)(struct goodix_ts_device *dev);
> + int (*read)(struct goodix_ts_device *dev, unsigned int addr,
> + unsigned char *data, unsigned int len);
> + int (*write)(struct goodix_ts_device *dev, unsigned int addr,
> + unsigned char *data, unsigned int len);
> + int (*send_cmd)(struct goodix_ts_device *dev,
> + struct goodix_ts_cmd *cmd);
> + int (*send_config)(struct goodix_ts_device *dev,
> + struct goodix_ts_config *config);
> + int (*read_version)(struct goodix_ts_device *dev,
> + struct goodix_ts_version *version);
> + int (*event_handler)(struct goodix_ts_device *dev,
> + struct goodix_ts_event *ts_event);
> + int (*check_hw)(struct goodix_ts_device *dev);
> + int (*suspend)(struct goodix_ts_device *dev);
> + int (*resume)(struct goodix_ts_device *dev);
> +};
> +
> +/*
> + * struct goodix_ts_esd - esd protector structure
> + * @esd_work: esd delayed work
> + * @esd_on: true - turn on esd protection, false - turn
> + * off esd protection
> + * @esd_mutex: protect @esd_on flag
> + */
> +struct goodix_ts_esd {
> + struct delayed_work esd_work;
> + struct mutex esd_mutex;
> + struct notifier_block esd_notifier;
> + struct goodix_ts_core *ts_core;
> + bool esd_on;
> +};
> +
> +/*
> + * struct godix_ts_core - core layer data struct
> + * @pdev: core layer platform device
> + * @ts_dev: hardware layer touch device
> + * @input_dev: input device
> + * @avdd: analog regulator
> + * @pinctrl: pinctrl handler
> + * @pin_sta_active: active/normal pin state
> + * @pin_sta_suspend: suspend/sleep pin state
> + * @ts_event: touch event data struct
> + * @power_on: power on/off flag
> + * @irq: irq number
> + * @irq_enabled: irq enabled/disabled flag
> + * @suspended: suspend/resume flag
> + * @hw_err: indicate that hw_ops->init() failed
> + * @ts_notifier: generic notifier
> + * @ts_esd: esd protector structure
> + * @fb_notifier: framebuffer notifier
> + * @early_suspend: early suspend
> + */
> +struct goodix_ts_core {
> + struct platform_device *pdev;
> + struct goodix_ts_device *ts_dev;
> + struct input_dev *input_dev;
> +
> + struct regulator *avdd;
> + struct goodix_ts_event ts_event;
> + int power_on;
> + int irq;
> + size_t irq_trig_cnt;
> +
> + atomic_t irq_enabled;
> + atomic_t suspended;
> + bool hw_err;
> +
> + struct notifier_block ts_notifier;
> + struct goodix_ts_esd ts_esd;
> +
> +#ifdef CONFIG_FB
> + struct notifier_block fb_notifier;
> +#endif
> +};
> +
> +/* external module structures */
> +enum goodix_ext_priority {
> + EXTMOD_PRIO_RESERVED = 0,
> + EXTMOD_PRIO_FWUPDATE,
> + EXTMOD_PRIO_GESTURE,
> + EXTMOD_PRIO_HOTKNOT,
> + EXTMOD_PRIO_DBGTOOL,
> + EXTMOD_PRIO_DEFAULT,
> +};
> +
> +struct goodix_ext_module;
> +/* external module's operations callback */
> +struct goodix_ext_module_funcs {
> + int (*init)(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module);
> + int (*exit)(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module);
> +
> + int (*before_reset)(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module);
> + int (*after_reset)(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module);
> +
> + int (*before_suspend)(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module);
> + int (*after_suspend)(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module);
> +
> + int (*before_resume)(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module);
> + int (*after_resume)(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module);
> +
> + int (*irq_event)(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module);
> +};
> +
> +/*
> + * struct goodix_ext_module - external module struct
> + * @list: list used to link into modules manager
> + * @name: name of external module
> + * @priority: module priority vlaue, zero is invalid
> + * @funcs: operations callback
> + * @priv_data: private data region
> + * @kobj: kobject
> + * @work: used to queue one work to do registration
> + */
> +struct goodix_ext_module {
> + struct list_head list;
> + char *name;
> + enum goodix_ext_priority priority;
> + const struct goodix_ext_module_funcs *funcs;
> + void *priv_data;
> + struct kobject kobj;
> + struct work_struct work;
> +};
> +
> +/*
> + * struct goodix_ext_attribute - exteranl attribute struct
> + * @attr: attribute
> + * @show: show interface of external attribute
> + * @store: store interface of external attribute
> + */
> +struct goodix_ext_attribute {
> + struct attribute attr;
> + ssize_t (*show)(struct goodix_ext_module *, char *);
> + ssize_t (*store)(struct goodix_ext_module *, const char *, size_t);
> +};
> +
> +/* external attrs helper macro */
> +#define __EXTMOD_ATTR(_name, _mode, _show, _store) { \
> + .attr = {.name = __stringify(_name), .mode = _mode }, \
> + .show = _show, \
> + .store = _store, \
> +}
> +
> +/* external attrs helper macro, used to define external attrs */
> +#define DEFINE_EXTMOD_ATTR(_name, _mode, _show, _store) \
> +static struct goodix_ext_attribute ext_attr_##_name = \
> + __EXTMOD_ATTR(_name, _mode, _show, _store)
> +
> +/*
> + * get board data pointer
> + */
> +static inline struct goodix_ts_board_data *board_data(
> + struct goodix_ts_core *core)
> +{
> + return core->ts_dev->board_data;
> +}
> +
> +/*
> + * get touch hardware operations pointer
> + */
> +static inline const struct goodix_ts_hw_ops *ts_hw_ops(
> + struct goodix_ts_core *core)
> +{
> + return core->ts_dev->hw_ops;
> +}
> +
> +/*
> + * checksum helper functions
> + * checksum can be u8/le16/be16/le32/be32 format
> + * NOTE: the caller shoule be responsible for the
> + * legality of @data and @size parameters, so be
> + * careful when call these functions.
> + */
> +static inline u8 checksum_u8(u8 *data, u32 size)
> +{
> + u8 checksum = 0;
> + u32 i;
> +
> + for (i = 0; i < size; i++)
> + checksum += data[i];
> + return checksum;
> +}
> +
> +static inline u16 checksum_le16(u8 *data, u32 size)
> +{
> + u16 checksum = 0;
> + u32 i;
> +
> + for (i = 0; i < size; i += 2)
> + checksum += le16_to_cpup((__le16 *)(data + i));
> + return checksum;
> +}
> +
> +static inline u16 checksum_be16(u8 *data, u32 size)
> +{
> + u16 checksum = 0;
> + u32 i;
> +
> + for (i = 0; i < size; i += 2)
> + checksum += be16_to_cpup((__be16 *)(data + i));
> + return checksum;
> +}
> +
> +static inline u32 checksum_le32(u8 *data, u32 size)
> +{
> + u32 checksum = 0;
> + u32 i;
> +
> + for (i = 0; i < size; i += 4)
> + checksum += le32_to_cpup((__le32 *)(data + i));
> + return checksum;
> +}
> +
> +static inline u32 checksum_be32(u8 *data, u32 size)
> +{
> + u32 checksum = 0;
> + u32 i;
> +
> + for (i = 0; i < size; i += 4)
> + checksum += be32_to_cpup((__be32 *)(data + i));
> + return checksum;
> +}
> +
> +/*
> + * define event action
> + * EVT_xxx macros are used in opeartions callback
> + * defined in @goodix_ext_module_funcs to control
> + * the behaviors of event such as suspend/resume/
> + * irq_event.
> + *
> + * generally there are two types of behaviors:
> + * 1. you want the flow of this event be canceled,
> + * in this condition, you should return EVT_CANCEL_XXX
> + * in the operations callback.
> + * e.g. the firmware update module is updating
> + * the firmware, you want to cancel suspend flow,
> + * so you need to return EVT_CANCEL_SUSPEND in
> + * suspend callback function.
> + * 2. you want the flow of this event continue, in
> + * this condition, you should return EVT_HANDLED in
> + * the callback function.
> + */
> +#define EVT_HANDLED 0
> +#define EVT_CONTINUE 0
> +#define EVT_CANCEL 1
> +#define EVT_CANCEL_IRQEVT 1
> +#define EVT_CANCEL_SUSPEND 1
> +#define EVT_CANCEL_RESUME 1
> +#define EVT_CANCEL_RESET 1
> +
> +/*
> + * errno define
> + * Note:
> + * 1. bus read/write functions defined in hardware
> + * layer code(e.g. goodix_xxx_i2c.c) *must* return
> + * -EBUS if failed to transfer data on bus.
> + */
> +#define EBUS 1000
> +#define ETIMEOUT 1001
> +#define ECHKSUM 1002
> +#define EMEMCMP 1003
> +
> +/**
> + * goodix_register_ext_module - interface for external module
> + * to register into touch core modules structure
> + *
> + * @module: pointer to external module to be register
> + * return: 0 ok, <0 failed
> + */
> +int goodix_register_ext_module(struct goodix_ext_module *module);
> +
> +/**
> + * goodix_unregister_ext_module - interface for external module
> + * to unregister external modules
> + *
> + * @module: pointer to external module
> + * return: 0 ok, <0 failed
> + */
> +int goodix_unregister_ext_module(struct goodix_ext_module *module);
> +
> +/**
> + * goodix_ts_irq_enable - Enable/Disable a irq
> +
> + * @core_data: pointer to touch core data
> + * enable: enable or disable irq
> + * return: 0 ok, <0 failed
> + */
> +int goodix_ts_irq_enable(struct goodix_ts_core *core_data, bool enable);
> +
> +struct kobj_type *goodix_get_default_ktype(void);
> +
> +/**
> + * goodix_ts_blocking_notify - notify clients of certain events
> + * see enum ts_notify_event in goodix_ts_core.h
> + */
> +int goodix_ts_blocking_notify(enum ts_notify_event evt, void *v);
> +
> +#endif
> diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_tools.c b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_tools.c
> new file mode 100755
> index 0000000..b0dd121
> --- /dev/null
> +++ b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_tools.c
> @@ -0,0 +1,542 @@
> +/*
> + * Goodix GTx5 Touchscreen Dirver
> + *
> + * Copyright (C) 2015 - 2016 Goodix, Inc.
> + * Authors: Wang Yafei <wangyafei@xxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be a reference
> + * to you, when you are integrating the GOODiX's CTP IC into your system,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/compat.h>
> +#include <linux/kernel.h>
> +#include <linux/atomic.h>
> +#include <linux/miscdevice.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +#include <linux/fs.h>
> +#include <linux/list.h>
> +#include <linux/ioctl.h>
> +#include <linux/wait.h>
> +#include "goodix_ts_core.h"
> +
> +#define GOODIX_TOOLS_NAME "gtp_tools"
> +#define GOODIX_TS_IOC_MAGIC 'G'
> +#define NEGLECT_SIZE_MASK (~(_IOC_SIZEMASK << _IOC_SIZESHIFT))
> +
> +#define GTP_IRQ_ENABLE _IO(GOODIX_TS_IOC_MAGIC, 0)
> +#define GTP_DEV_RESET _IO(GOODIX_TS_IOC_MAGIC, 1)
> +#define GTP_SEND_COMMAND (_IOW(GOODIX_TS_IOC_MAGIC, 2, u8) & NEGLECT_SIZE_MASK)
> +#define GTP_SEND_CONFIG (_IOW(GOODIX_TS_IOC_MAGIC, 3, u8) & NEGLECT_SIZE_MASK)
> +#define GTP_ASYNC_READ (_IOR(GOODIX_TS_IOC_MAGIC, 4, u8) & NEGLECT_SIZE_MASK)
> +#define GTP_SYNC_READ (_IOR(GOODIX_TS_IOC_MAGIC, 5, u8) & NEGLECT_SIZE_MASK)
> +#define GTP_ASYNC_WRITE (_IOW(GOODIX_TS_IOC_MAGIC, 6, u8) & NEGLECT_SIZE_MASK)
> +
> +#define GOODIX_TS_IOC_MAXNR 6
> +
> +#define IRQ_FALG (0x01 << 2)
> +
> +#define I2C_MSG_HEAD_LEN 20
> +#define MAX_DATA_LEN 4096
> +#define TS_REG_COORDS_BASE 0x824E
> +/*
> + * struct goodix_tools_data - goodix tools data message used in sync read
> + * @data: The buffer into which data is written
> + * @reg_addr: Slave device register start address to start read data
> + * @length: Number of data bytes in @data being read from slave device
> + * @filled: When buffer @data be filled will set this flag with 1, outhrwise 0
> + * @list_head:Eonnet every goodix_tools_data struct into a list
> + */
> +
> +struct goodix_tools_data {
> + u32 reg_addr;
> + u32 length;
> + u8 *data;
> + bool filled;
> + struct list_head list;
> +};
> +
> +
> +/*
> + * struct goodix_tools_dev - goodix tools device struct
> + * @ts_core: The core data struct of ts driver
> + * @ops_mode: represent device work mode
> + * @rawdiffcmd: Set slave device into rawdata mode
> + * @normalcmd: Set slave device into normal mode
> + * @wq: Wait queue struct use in synchronous data read
> + * @mutex: Protect goodix_tools_dev
> + * @ref_count: reference count
> + * @ref_mutex: Protect ref_count
> + */
> +struct goodix_tools_dev {
> + struct goodix_ts_core *ts_core;
> + struct list_head head;
> + unsigned int ops_mode;
> + struct goodix_ts_cmd rawdiffcmd, normalcmd;
> + wait_queue_head_t wq;
> + struct mutex mutex;
> + int ref_count;
> + struct mutex ref_mutex;
> + struct goodix_ext_module module;
> +} *goodix_tools_dev;
> +
> +static int goodix_tools_open(struct inode *inode, struct file *filp);
> +static int goodix_tools_release(struct inode *inode, struct file *filp);
> +static long goodix_tools_ioctl(struct file *filp, unsigned int cmd,
> + unsigned long arg);
> +#ifdef CONFIG_COMPAT
> +static long goodix_tools_compat_ioctl(struct file *file, unsigned int cmd,
> + unsigned long arg);
> +#endif
> +
> +static const struct file_operations goodix_tools_fops = {
> + .owner = THIS_MODULE,
> + .open = goodix_tools_open,
> + .release = goodix_tools_release,
> + .unlocked_ioctl = goodix_tools_ioctl,
> +#ifdef CONFIG_COMPAT
> + .compat_ioctl = goodix_tools_compat_ioctl,
> +#endif
> +};
> +
> +static struct miscdevice goodix_tools_miscdev = {
> + .minor = MISC_DYNAMIC_MINOR,
> + .name = GOODIX_TOOLS_NAME,
> + .fops = &goodix_tools_fops,
> +};
> +/* read data from i2c asynchronous,
> + * success return bytes read, else return <= 0
> + */
> +static int async_read(struct goodix_tools_dev *dev, void __user *arg)
> +{
> + u8 *databuf = NULL;
> + int ret = 0;
> + u32 reg_addr, length;
> + u8 i2c_msg_head[I2C_MSG_HEAD_LEN];
> + struct goodix_ts_device *ts_dev = dev->ts_core->ts_dev;
> + const struct goodix_ts_hw_ops *hw_ops = ts_dev->hw_ops;
> +
> + ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN);
> + if (ret)
> + return ret;
> +
> + reg_addr = le32_to_cpup((__le32 *)&i2c_msg_head[0]);
> + length = le32_to_cpup((__le32 *)&i2c_msg_head[4]);
> + if (length > MAX_DATA_LEN)
> + length = MAX_DATA_LEN;
> +
> + databuf = kzalloc(length, GFP_KERNEL);
> + if (!databuf)
> + return -ENOMEM;
> +
> + if (!hw_ops->read(ts_dev, reg_addr, databuf, length)) {
> + if (copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN,
> + databuf, length))
> + ret = -EFAULT;
> + else
> + ret = length;
> + } else {
> + ret = -EBUSY;
> + }
> +
> + kfree(databuf);
> + return ret;
> +}
> +
> +/* read data from i2c synchronous,
> + * success return bytes read, else return <= 0
> + */
> +static int sync_read(struct goodix_tools_dev *dev, void __user *arg)
> +{
> + int ret = 0;
> + u8 i2c_msg_head[I2C_MSG_HEAD_LEN];
> + struct goodix_tools_data tools_data;
> +
> + ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN);
> + if (ret)
> + return ret;
> +
> + tools_data.reg_addr = le32_to_cpup((__le32 *)&i2c_msg_head[0]);
> + tools_data.length = le32_to_cpup((__le32 *)&i2c_msg_head[4]);
> + tools_data.filled = 0;
> + if (tools_data.length > MAX_DATA_LEN)
> + tools_data.length = MAX_DATA_LEN;
> +
> + tools_data.data = kzalloc(tools_data.length, GFP_KERNEL);
> + if (!tools_data.data)
> + return -ENOMEM;
> +
> + mutex_lock(&dev->mutex);
> + list_add_tail(&tools_data.list, &dev->head);
> + mutex_unlock(&dev->mutex);
> + /* wait queue will timeout after 1 seconds */
> + wait_event_interruptible_timeout(dev->wq, tools_data.filled == 1, HZ);
> +
> + mutex_lock(&dev->mutex);
> + list_del(&tools_data.list);
> + mutex_unlock(&dev->mutex);
> + if (tools_data.filled == 1) {
> + if (copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN, tools_data.data,
> + tools_data.length))
> + ret = -EFAULT;
> + else
> + ret = tools_data.length;
> + } else {
> + ret = -EAGAIN;
> + dev_dbg(goodix_tools_miscdev.this_device,
> + "Wait queue timeout\n");
> + }
> +
> + kfree(tools_data.data);
> + return ret;
> +}
> +
> +/* write data to i2c asynchronous,
> + * success return bytes write, else return <= 0
> + */
> +static int async_write(struct goodix_tools_dev *dev, void __user *arg)
> +{
> + u8 *databuf;
> + int ret = 0;
> + u32 reg_addr, length;
> + u8 i2c_msg_head[I2C_MSG_HEAD_LEN];
> + struct goodix_ts_device *ts_dev = dev->ts_core->ts_dev;
> + const struct goodix_ts_hw_ops *hw_ops = ts_dev->hw_ops;
> +
> + ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN);
> + if (ret)
> + return -EFAULT;
> +
> + reg_addr = le32_to_cpup((__le32 *)&i2c_msg_head[0]);
> + length = le32_to_cpup((__le32 *)&i2c_msg_head[4]);
> + if (length > MAX_DATA_LEN)
> + length = MAX_DATA_LEN;
> +
> + databuf = kzalloc(length, GFP_KERNEL);
> + if (!databuf)
> + return -ENOMEM;
> +
> + ret = copy_from_user(databuf, (u8 *)arg + I2C_MSG_HEAD_LEN, length);
> + if (ret) {
> + ret = -EFAULT;
> + goto err_out;
> + }
> +
> + if (hw_ops->write(ts_dev, reg_addr, databuf, length))
> + ret = -EBUSY;
> + else
> + ret = length;
> +
> +err_out:
> + kfree(databuf);
> + return ret;
> +}
> +
> +static int init_cfg_data(struct goodix_ts_config *cfg, void __user *arg)
> +{
> + int ret = 0;
> + u32 reg_addr, length;
> + u8 i2c_msg_head[I2C_MSG_HEAD_LEN];
> +
> + cfg->initialized = 0;
> + mutex_init(&cfg->lock);
> + ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN);
> + if (ret)
> + return -EFAULT;
> +
> + reg_addr = le32_to_cpup((__le32 *)&i2c_msg_head[0]);
> + length = le32_to_cpup((__le32 *)&i2c_msg_head[4]);
> + if (length > MAX_DATA_LEN)
> + length = MAX_DATA_LEN;
> +
> + ret = copy_from_user(cfg->data, (u8 *)arg + I2C_MSG_HEAD_LEN, length);
> + if (ret) {
> + ret = -EFAULT;
> + dev_dbg(goodix_tools_miscdev.this_device,
> + "Copy data from user failed\n");
> + goto err_out;
> + }
> + cfg->reg_base = reg_addr;
> + cfg->length = length;
> + strlcpy(cfg->name, "tools-send-cfg", sizeof(cfg->name));
> + cfg->delay = 50;
> + cfg->initialized = true;
> + return 0;
> +
> +err_out:
> + return ret;
> +}
> +/**
> + * goodix_tools_ioctl - ioctl implementation
> + *
> + * @filp: Pointer to file opened
> + * @cmd: Ioctl opertion command
> + * @arg: Command data
> + * Returns >=0 - succeed, else failed
> + */
> +static long goodix_tools_ioctl(struct file *filp, unsigned int cmd,
> + unsigned long arg)
> +{
> + int ret = 0;
> + struct goodix_tools_dev *dev = filp->private_data;
> + struct goodix_ts_device *ts_dev;
> + const struct goodix_ts_hw_ops *hw_ops;
> + struct goodix_ts_cmd temp_cmd;
> + struct goodix_ts_config *temp_cfg;
> +
> + if (dev->ts_core == NULL) {
> + dev_err(goodix_tools_miscdev.this_device,
> + "Tools module not register\n");
> + return -EINVAL;
> + }
> + ts_dev = dev->ts_core->ts_dev;
> + hw_ops = ts_dev->hw_ops;
> +
> + if (_IOC_TYPE(cmd) != GOODIX_TS_IOC_MAGIC)
> + return -ENOTTY;
> + if (_IOC_NR(cmd) > GOODIX_TS_IOC_MAXNR)
> + return -ENOTTY;
> +
> +
> + switch (cmd & NEGLECT_SIZE_MASK) {
> + case GTP_IRQ_ENABLE:
> + if (arg == 1) {
> + goodix_ts_irq_enable(dev->ts_core, true);
> + mutex_lock(&dev->mutex);
> + dev->ops_mode |= IRQ_FALG;
> + mutex_unlock(&dev->mutex);
> + dev_dbg(goodix_tools_miscdev.this_device,
> + "IRQ enabled\n");
> + } else if (arg == 0) {
> + goodix_ts_irq_enable(dev->ts_core, false);
> + mutex_lock(&dev->mutex);
> + dev->ops_mode &= ~IRQ_FALG;
> + mutex_unlock(&dev->mutex);
> + dev_dbg(goodix_tools_miscdev.this_device,
> + "IRQ disabled\n");
> + } else {
> + dev_dbg(goodix_tools_miscdev.this_device,
> + "Irq already set with, arg = %ld\n", arg);
> + }
> + ret = 0;
> + break;
> + case GTP_DEV_RESET:
> + hw_ops->reset(ts_dev);
> + break;
> + case GTP_SEND_COMMAND:
> + ret = copy_from_user(&temp_cmd, (void __user *)arg,
> + sizeof(struct goodix_ts_cmd));
> + if (ret) {
> + ret = -EINVAL;
> + goto err_out;
> + }
> +
> + ret = hw_ops->send_cmd(ts_dev, &temp_cmd);
> + if (ret) {
> + dev_warn(goodix_tools_miscdev.this_device,
> + "Send command failed\n");
> + ret = -EAGAIN;
> + }
> + break;
> + case GTP_SEND_CONFIG:
> + temp_cfg = kzalloc(sizeof(struct goodix_ts_config), GFP_KERNEL);
> + if (!temp_cfg) {
> + ret = -ENOMEM;
> + goto err_out;
> + }
> + ret = init_cfg_data(temp_cfg, (void __user *)arg);
> +
> + if (!ret) {
> + ret = hw_ops->send_config(ts_dev, temp_cfg);
> + if (ret) {
> + dev_warn(goodix_tools_miscdev.this_device,
> + "Failed send config\n");
> + ret = -EAGAIN;
> + }
> + }
> + kfree(temp_cfg);
> + break;
> + case GTP_ASYNC_READ:
> + ret = async_read(dev, (void __user *)arg);
> + if (ret < 0)
> + dev_warn(goodix_tools_miscdev.this_device,
> + "Async data read failed");
> + break;
> + case GTP_SYNC_READ:
> + if (filp->f_flags & O_NONBLOCK) {
> + dev_dbg(goodix_tools_miscdev.this_device,
> + "Goodix tools now worked in sync_bus mode\n");
> + ret = -EAGAIN;
> + goto err_out;
> + }
> + ret = sync_read(dev, (void __user *)arg);
> + if (ret < 0)
> + dev_warn(goodix_tools_miscdev.this_device,
> + "Sync data read failed\n");
> + break;
> + case GTP_ASYNC_WRITE:
> + ret = async_write(dev, (void __user *)arg);
> + if (ret < 0)
> + dev_warn(goodix_tools_miscdev.this_device,
> + "Async data write failed\n");
> + break;
> + default:
> + dev_info(goodix_tools_miscdev.this_device, "Invalid cmd\n");
> + ret = -ENOTTY;
> + break;
> + }
> +
> +err_out:
> + return ret;
> +}
> +
> +#ifdef CONFIG_COMPAT
> +static long goodix_tools_compat_ioctl(struct file *file, unsigned int cmd,
> + unsigned long arg)
> +{
> + if (!file->f_op || !file->f_op->unlocked_ioctl)
> + return -ENOTTY;
> +
> + return goodix_tools_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
> +}
> +#endif
> +
> +static int goodix_tools_open(struct inode *inode, struct file *filp)
> +{
> + int ret = 0;
> +
> + filp->private_data = goodix_tools_dev;
> +
> + mutex_lock(&goodix_tools_dev->ref_mutex);
> +
> + /* Only the first time open device need to register module */
> + if (goodix_tools_dev->ref_count == 0)
> + ret = goodix_register_ext_module(&goodix_tools_dev->module);
> + if (!ret)
> + goodix_tools_dev->ref_count++;
> +
> + mutex_unlock(&goodix_tools_dev->ref_mutex);
> + return ret;
> +}
> +
> +static int goodix_tools_release(struct inode *inode, struct file *filp)
> +{
> + int ret = 0;
> +
> + mutex_lock(&goodix_tools_dev->ref_mutex);
> +
> + goodix_tools_dev->ref_count--;
> + /* when the last close this dev node unregister the module */
> + if (goodix_tools_dev->ref_count == 0)
> + ret = goodix_unregister_ext_module(&goodix_tools_dev->module);
> +
> + mutex_unlock(&goodix_tools_dev->ref_mutex);
> + return ret;
> +}
> +
> +/**
> + * goodix_tools_module_irq - goodix tools Irq handle
> + * This functions is excuted when interrupt happened
> + *
> + * @core_data: pointer to touch core data
> + * @module: pointer to goodix_ext_module struct
> + * return: EVT_CONTINUE let other module handle this irq
> + */
> +static int goodix_tools_module_irq(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module)
> +{
> + struct goodix_tools_dev *dev = module->priv_data;
> + struct goodix_ts_device *ts_dev = dev->ts_core->ts_dev;
> + const struct goodix_ts_hw_ops *hw_ops = ts_dev->hw_ops;
> + struct goodix_tools_data *tools_data;
> + int r = 0;
> + u8 evt_sta = 0;
> +
> + if (!list_empty(&dev->head)) {
> + r = hw_ops->read(ts_dev, TS_REG_COORDS_BASE, &evt_sta, 1);
> + if (r < 0 || ((evt_sta & 0x80) == 0))
> + return EVT_CONTINUE;
> +
> + mutex_lock(&dev->mutex);
> + list_for_each_entry(tools_data, &dev->head, list) {
> + if (!hw_ops->read(ts_dev, tools_data->reg_addr,
> + tools_data->data, tools_data->length)) {
> + tools_data->filled = 1;
> + }
> + }
> + mutex_unlock(&dev->mutex);
> + wake_up(&dev->wq);
> + }
> + return EVT_CONTINUE;
> +}
> +
> +static int goodix_tools_module_init(struct goodix_ts_core *core_data,
> + struct goodix_ext_module *module)
> +{
> + struct goodix_tools_dev *dev = module->priv_data;
> +
> + if (core_data) {
> + dev->ts_core = core_data;
> + return 0;
> + } else {
> + return -ENODEV;
> + }
> +}
> +
> +static struct goodix_ext_module_funcs goodix_tools_module_funcs = {
> + .irq_event = goodix_tools_module_irq,
> + .init = goodix_tools_module_init,
> +};
> +
> +/**
> + * goodix_tools_init - init goodix tools device and register a miscdevice
> + *
> + * return: 0 success, else failed
> + */
> +static int __init goodix_tools_init(void)
> +{
> + int ret;
> +
> + goodix_tools_dev = kzalloc(sizeof(struct goodix_tools_dev), GFP_KERNEL);
> + if (!goodix_tools_dev)
> + return -ENOMEM;
> +
> + INIT_LIST_HEAD(&goodix_tools_dev->head);
> + goodix_tools_dev->ops_mode = 0;
> + goodix_tools_dev->ops_mode |= IRQ_FALG;
> + init_waitqueue_head(&goodix_tools_dev->wq);
> + mutex_init(&goodix_tools_dev->mutex);
> + goodix_tools_dev->ref_count = 0;
> + mutex_init(&goodix_tools_dev->ref_mutex);
> +
> + goodix_tools_dev->module.funcs = &goodix_tools_module_funcs;
> + goodix_tools_dev->module.name = GOODIX_TOOLS_NAME;
> + goodix_tools_dev->module.priv_data = goodix_tools_dev;
> + goodix_tools_dev->module.priority = EXTMOD_PRIO_DBGTOOL;
> +
> + ret = misc_register(&goodix_tools_miscdev);
> +
> + return ret;
> +}
> +
> +static void __exit goodix_tools_exit(void)
> +{
> + misc_deregister(&goodix_tools_miscdev);
> + kfree(goodix_tools_dev);
> +}
> +
> +module_init(goodix_tools_init);
> +module_exit(goodix_tools_exit);
> +
> +MODULE_DESCRIPTION("Goodix tools Module");
> +MODULE_AUTHOR("Goodix, Inc.");
> +MODULE_LICENSE("GPL v2");