From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MCC45TR Date: Tue, 25 Aug 2026 17:30:52 +0300 Subject: [PATCH 06/21] input: power: restore Nabu touch and fuel-gauge drivers --- drivers/input/touchscreen/Kconfig | 2 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/nt36523/Kconfig | 11 + drivers/input/touchscreen/nt36523/Makefile | 8 + drivers/input/touchscreen/nt36523/nt36xxx.c | 1921 +++++++++++++++++ drivers/input/touchscreen/nt36523/nt36xxx.h | 244 +++ .../touchscreen/nt36523/nt36xxx_fw_update.c | 841 ++++++++ .../touchscreen/nt36523/nt36xxx_mem_map.h | 390 ++++ drivers/power/supply/Kconfig | 8 + drivers/power/supply/Makefile | 1 + drivers/power/supply/qcom_fg.c | 1386 ++++++++++++ senemos/configs/nabu-minimal.config | 4 + 12 files changed, 4817 insertions(+) create mode 100644 drivers/input/touchscreen/nt36523/Kconfig create mode 100644 drivers/input/touchscreen/nt36523/Makefile create mode 100644 drivers/input/touchscreen/nt36523/nt36xxx.c create mode 100644 drivers/input/touchscreen/nt36523/nt36xxx.h create mode 100644 drivers/input/touchscreen/nt36523/nt36xxx_fw_update.c create mode 100644 drivers/input/touchscreen/nt36523/nt36xxx_mem_map.h create mode 100644 drivers/power/supply/qcom_fg.c diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 9b9ae8ac3f7f..5c3415aafa40 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -12,6 +12,8 @@ menuconfig INPUT_TOUCHSCREEN if INPUT_TOUCHSCREEN +source "drivers/input/touchscreen/nt36523/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 bfd9de83389d..fe8357a38e50 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -119,3 +119,4 @@ obj-$(CONFIG_TOUCHSCREEN_IQS5XX) += iqs5xx.o obj-$(CONFIG_TOUCHSCREEN_IQS7211) += iqs7211.o obj-$(CONFIG_TOUCHSCREEN_ZINITIX) += zinitix.o obj-$(CONFIG_TOUCHSCREEN_HIMAX_HX83112B) += himax_hx83112b.o +obj-$(CONFIG_TOUCHSCREEN_NT36523_SPI) += nt36523/ diff --git a/drivers/input/touchscreen/nt36523/Kconfig b/drivers/input/touchscreen/nt36523/Kconfig new file mode 100644 index 000000000000..5f4ef5abfd6a --- /dev/null +++ b/drivers/input/touchscreen/nt36523/Kconfig @@ -0,0 +1,11 @@ +# +# Novatek NT36523 touchscreen driver configuration +# +config TOUCHSCREEN_NT36523_SPI + tristate "Novatek NT36523 no flash SPI driver" + default n + help + Say Y here if you have a Novatek NT36523 no flash touchscreen connected + to your system by SPI bus. + + If unsure, say N. diff --git a/drivers/input/touchscreen/nt36523/Makefile b/drivers/input/touchscreen/nt36523/Makefile new file mode 100644 index 000000000000..d16afc8f127f --- /dev/null +++ b/drivers/input/touchscreen/nt36523/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the Novatek NT36523 touchscreen driver. +# + +# Each configuration option enables a list of files. +obj-$(CONFIG_TOUCHSCREEN_NT36523_SPI) += nt36523_ts.o +nt36523_ts-y := nt36xxx.o \ + nt36xxx_fw_update.o diff --git a/drivers/input/touchscreen/nt36523/nt36xxx.c b/drivers/input/touchscreen/nt36523/nt36xxx.c new file mode 100644 index 000000000000..46cc0dd60c88 --- /dev/null +++ b/drivers/input/touchscreen/nt36523/nt36xxx.c @@ -0,0 +1,1921 @@ +/* + * Copyright (C) 2010 - 2018 Novatek, Inc. + * Copyright (C) 2021 XiaoMi, Inc. + * + * $Revision: 73033 $ + * $Date: 2020-11-26 10:09:14 +0800 (週四, 26 十一月 2020) $ + * + * 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 useful, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_DRM +#include +#endif + +#include "nt36xxx.h" + +#if NVT_TOUCH_ESD_PROTECT +#include +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + +#if NVT_TOUCH_ESD_PROTECT +static struct delayed_work nvt_esd_check_work; +static struct workqueue_struct *nvt_esd_check_wq; +static unsigned long irq_timer = 0; +uint8_t esd_check = false; +uint8_t esd_retry = 0; +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + +struct nvt_ts_data *ts; + +#ifdef CONFIG_DRM +static int nvt_drm_notifier_callback(struct notifier_block *self, unsigned long event, void *data); +#endif + +static int32_t nvt_ts_suspend(struct device *dev); +static int32_t nvt_ts_resume(struct device *dev); + +uint32_t ENG_RST_ADDR = 0x7FFF80; +uint32_t SWRST_N8_ADDR = 0; //read from dtsi +uint32_t SPI_RD_FAST_ADDR = 0; //read from dtsi + +#if TOUCH_KEY_NUM > 0 +const uint16_t touch_key_array[TOUCH_KEY_NUM] = { + KEY_BACK, + KEY_HOME, + KEY_MENU +}; +#endif + +static uint8_t bTouchIsAwake = 0; + +/******************************************************* +Description: + Novatek touchscreen irq enable/disable function. + +return: + n.a. +*******************************************************/ +static void nvt_irq_enable(bool enable) +{ + if (enable) { + if (!ts->irq_enabled) { + enable_irq(ts->client->irq); + ts->irq_enabled = true; + } + } else { + if (ts->irq_enabled) { + disable_irq(ts->client->irq); + ts->irq_enabled = false; + } + } +} + +static inline int32_t spi_read_write(struct spi_device *client, uint8_t *buf, size_t len , NVT_SPI_RW rw) +{ + struct spi_message m; + struct spi_transfer t = { + .len = len, + }; + + memset(ts->xbuf, 0, len + DUMMY_BYTES); + memcpy(ts->xbuf, buf, len); + + switch (rw) { + case NVTREAD: + t.tx_buf = ts->xbuf; + t.rx_buf = ts->rbuf; + t.len = (len + DUMMY_BYTES); + break; + + case NVTWRITE: + t.tx_buf = ts->xbuf; + break; + } + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + return spi_sync(client, &m); +} + +/******************************************************* +Description: + Novatek touchscreen spi read function. + +return: + Executive outcomes. 2---succeed. -5---I/O error +*******************************************************/ +int32_t CTP_SPI_READ(struct spi_device *client, uint8_t *buf, uint16_t len) +{ + int32_t ret = -1; + int32_t retries = 0; + + mutex_lock(&ts->xbuf_lock); + + buf[0] = SPI_READ_MASK(buf[0]); + + while (retries < 5) { + ret = spi_read_write(client, buf, len, NVTREAD); + if (ret == 0) break; + retries++; + } + + if (unlikely(retries == 5)) { + NVT_ERR("read error, ret=%d\n", ret); + ret = -EIO; + } else { + memcpy((buf+1), (ts->rbuf+2), (len-1)); + } + + mutex_unlock(&ts->xbuf_lock); + + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen spi write function. + +return: + Executive outcomes. 1---succeed. -5---I/O error +*******************************************************/ +int32_t CTP_SPI_WRITE(struct spi_device *client, uint8_t *buf, uint16_t len) +{ + int32_t ret = -1; + int32_t retries = 0; + + mutex_lock(&ts->xbuf_lock); + + buf[0] = SPI_WRITE_MASK(buf[0]); + + while (retries < 5) { + ret = spi_read_write(client, buf, len, NVTWRITE); + if (ret == 0) break; + retries++; + } + + if (unlikely(retries == 5)) { + NVT_ERR("error, ret=%d\n", ret); + ret = -EIO; + } + + mutex_unlock(&ts->xbuf_lock); + + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen set index/page/addr address. + +return: + Executive outcomes. 0---succeed. -5---access fail. +*******************************************************/ +int32_t nvt_set_page(uint32_t addr) +{ + uint8_t buf[4] = {0}; + + buf[0] = 0xFF; //set index/page/addr command + buf[1] = (addr >> 15) & 0xFF; + buf[2] = (addr >> 7) & 0xFF; + + return CTP_SPI_WRITE(ts->client, buf, 3); +} + +/******************************************************* +Description: + Novatek touchscreen write data to specify address. + +return: + Executive outcomes. 0---succeed. -5---access fail. +*******************************************************/ +int32_t nvt_write_addr(uint32_t addr, uint8_t data) +{ + int32_t ret = 0; + uint8_t buf[4] = {0}; + + //---set xdata index--- + buf[0] = 0xFF; //set index/page/addr command + buf[1] = (addr >> 15) & 0xFF; + buf[2] = (addr >> 7) & 0xFF; + ret = CTP_SPI_WRITE(ts->client, buf, 3); + if (ret) { + NVT_ERR("set page 0x%06X failed, ret = %d\n", addr, ret); + return ret; + } + + //---write data to index--- + buf[0] = addr & (0x7F); + buf[1] = data; + ret = CTP_SPI_WRITE(ts->client, buf, 2); + if (ret) { + NVT_ERR("write data to 0x%06X failed, ret = %d\n", addr, ret); + return ret; + } + + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen enable hw bld crc function. + +return: + N/A. +*******************************************************/ +void nvt_bld_crc_enable(void) +{ + uint8_t buf[4] = {0}; + + //---set xdata index to BLD_CRC_EN_ADDR--- + nvt_set_page(ts->mmap->BLD_CRC_EN_ADDR); + + //---read data from index--- + buf[0] = ts->mmap->BLD_CRC_EN_ADDR & (0x7F); + buf[1] = 0xFF; + CTP_SPI_READ(ts->client, buf, 2); + + //---write data to index--- + buf[0] = ts->mmap->BLD_CRC_EN_ADDR & (0x7F); + buf[1] = buf[1] | (0x01 << 7); + CTP_SPI_WRITE(ts->client, buf, 2); +} + +/******************************************************* +Description: + Novatek touchscreen clear status & enable fw crc function. + +return: + N/A. +*******************************************************/ +void nvt_fw_crc_enable(void) +{ + uint8_t buf[4] = {0}; + + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(ts->mmap->EVENT_BUF_ADDR); + + //---clear fw reset status--- + buf[0] = EVENT_MAP_RESET_COMPLETE & (0x7F); + buf[1] = 0x00; + CTP_SPI_WRITE(ts->client, buf, 2); + + //---enable fw crc--- + buf[0] = EVENT_MAP_HOST_CMD & (0x7F); + buf[1] = 0xAE; //enable fw crc command + CTP_SPI_WRITE(ts->client, buf, 2); +} + +/******************************************************* +Description: + Novatek touchscreen set boot ready function. + +return: + N/A. +*******************************************************/ +void nvt_boot_ready(void) +{ + //---write BOOT_RDY status cmds--- + nvt_write_addr(ts->mmap->BOOT_RDY_ADDR, 1); + + mdelay(5); + + if (!ts->hw_crc) { + //---write BOOT_RDY status cmds--- + nvt_write_addr(ts->mmap->BOOT_RDY_ADDR, 0); + + //---write POR_CD cmds--- + nvt_write_addr(ts->mmap->POR_CD_ADDR, 0xA0); + } +} + +/******************************************************* +Description: + Novatek touchscreen enable auto copy mode function. + +return: + N/A. +*******************************************************/ +void nvt_tx_auto_copy_mode(void) +{ + //---write TX_AUTO_COPY_EN cmds--- + nvt_write_addr(ts->mmap->TX_AUTO_COPY_EN, 0x69); + + NVT_ERR("tx auto copy mode enable\n"); +} + +/******************************************************* +Description: + Novatek touchscreen check spi dma tx info function. + +return: + N/A. +*******************************************************/ +int32_t nvt_check_spi_dma_tx_info(void) +{ + uint8_t buf[8] = {0}; + int32_t i = 0; + const int32_t retry = 200; + + for (i = 0; i < retry; i++) { + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(ts->mmap->SPI_DMA_TX_INFO); + + //---read fw status--- + buf[0] = ts->mmap->SPI_DMA_TX_INFO & 0x7F; + buf[1] = 0xFF; + CTP_SPI_READ(ts->client, buf, 2); + + if (buf[1] == 0x00) + break; + + usleep_range(1000, 1000); + } + + if (i >= retry) { + NVT_ERR("failed, i=%d, buf[1]=0x%02X\n", i, buf[1]); + return -1; + } else { + return 0; + } +} + +/******************************************************* +Description: + Novatek touchscreen eng reset cmd + function. + +return: + n.a. +*******************************************************/ +void nvt_eng_reset(void) +{ + //---eng reset cmds to ENG_RST_ADDR--- + nvt_write_addr(ENG_RST_ADDR, 0x5A); + + mdelay(1); //wait tMCU_Idle2TP_REX_Hi after TP_RST +} + +/******************************************************* +Description: + Novatek touchscreen reset MCU + function. + +return: + n.a. +*******************************************************/ +void nvt_sw_reset(void) +{ + //---software reset cmds to SWRST_N8_ADDR--- + nvt_write_addr(SWRST_N8_ADDR, 0x55); + + msleep(10); +} + +/******************************************************* +Description: + Novatek touchscreen reset MCU then into idle mode + function. + +return: + n.a. +*******************************************************/ +void nvt_sw_reset_idle(void) +{ + //---MCU idle cmds to SWRST_N8_ADDR--- + nvt_write_addr(SWRST_N8_ADDR, 0xAA); + + msleep(15); +} + +/******************************************************* +Description: + Novatek touchscreen reset MCU (boot) function. + +return: + n.a. +*******************************************************/ +void nvt_bootloader_reset(void) +{ + //---reset cmds to SWRST_N8_ADDR--- + nvt_write_addr(SWRST_N8_ADDR, 0x69); + + mdelay(5); //wait tBRST2FR after Bootload RST + + if (SPI_RD_FAST_ADDR) { + /* disable SPI_RD_FAST */ + nvt_write_addr(SPI_RD_FAST_ADDR, 0x00); + } +} + +/******************************************************* +Description: + Novatek touchscreen clear FW status function. + +return: + Executive outcomes. 0---succeed. -1---fail. +*******************************************************/ +int32_t nvt_clear_fw_status(void) +{ + uint8_t buf[8] = {0}; + int32_t i = 0; + const int32_t retry = 20; + + for (i = 0; i < retry; i++) { + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE); + + //---clear fw status--- + buf[0] = EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = 0x00; + CTP_SPI_WRITE(ts->client, buf, 2); + + //---read fw status--- + buf[0] = EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = 0xFF; + CTP_SPI_READ(ts->client, buf, 2); + + if (buf[1] == 0x00) + break; + + usleep_range(10000, 10000); + } + + if (i >= retry) { + NVT_ERR("failed, i=%d, buf[1]=0x%02X\n", i, buf[1]); + return -1; + } else { + return 0; + } +} + +/******************************************************* +Description: + Novatek touchscreen check FW status function. + +return: + Executive outcomes. 0---succeed. -1---failed. +*******************************************************/ +int32_t nvt_check_fw_status(void) +{ + uint8_t buf[8] = {0}; + int32_t i = 0; + const int32_t retry = 50; + + for (i = 0; i < retry; i++) { + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE); + + //---read fw status--- + buf[0] = EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = 0x00; + CTP_SPI_READ(ts->client, buf, 2); + + if ((buf[1] & 0xF0) == 0xA0) + break; + + usleep_range(10000, 10000); + } + + if (i >= retry) { + NVT_ERR("failed, i=%d, buf[1]=0x%02X\n", i, buf[1]); + return -1; + } else { + return 0; + } +} + +/******************************************************* +Description: + Novatek touchscreen check FW reset state function. + +return: + Executive outcomes. 0---succeed. -1---failed. +*******************************************************/ +int32_t nvt_check_fw_reset_state(RST_COMPLETE_STATE check_reset_state) +{ + uint8_t buf[8] = {0}; + int32_t ret = 0; + int32_t retry = 0; + int32_t retry_max = (check_reset_state == RESET_STATE_INIT) ? 10 : 50; + + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_RESET_COMPLETE); + + while (1) { + //---read reset state--- + buf[0] = EVENT_MAP_RESET_COMPLETE; + buf[1] = 0x00; + CTP_SPI_READ(ts->client, buf, 6); + + if ((buf[1] >= check_reset_state) && (buf[1] <= RESET_STATE_MAX)) { + ret = 0; + break; + } + + retry++; + if(unlikely(retry > retry_max)) { + NVT_ERR("error, retry=%d, buf[1]=0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X\n", + retry, buf[1], buf[2], buf[3], buf[4], buf[5]); + ret = -1; + break; + } + + usleep_range(10000, 10000); + } + + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen get novatek project id information + function. + +return: + Executive outcomes. 0---success. -1---fail. +*******************************************************/ +int32_t nvt_read_pid(void) +{ + uint8_t buf[4] = {0}; + int32_t ret = 0; + + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_PROJECTID); + + //---read project id--- + buf[0] = EVENT_MAP_PROJECTID; + buf[1] = 0x00; + buf[2] = 0x00; + CTP_SPI_READ(ts->client, buf, 3); + + ts->nvt_pid = (buf[2] << 8) + buf[1]; + + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(ts->mmap->EVENT_BUF_ADDR); + + NVT_LOG("PID=%04X\n", ts->nvt_pid); + + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen get firmware related information + function. + +return: + Executive outcomes. 0---success. -1---fail. +*******************************************************/ +int32_t nvt_get_fw_info(void) +{ + uint8_t buf[64] = {0}; + uint32_t retry_count = 0; + int32_t ret = 0; + +info_retry: + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_FWINFO); + + //---read fw info--- + buf[0] = EVENT_MAP_FWINFO; + CTP_SPI_READ(ts->client, buf, 39); + ts->fw_ver = buf[1]; + ts->x_num = buf[3]; + ts->y_num = buf[4]; + ts->abs_x_max = (uint16_t)((buf[5] << 8) | buf[6]); + ts->abs_y_max = (uint16_t)((buf[7] << 8) | buf[8]); + ts->max_button_num = buf[11]; + ts->cascade = buf[34] & 0x01; + if (ts->pen_support) { + ts->x_gang_num = buf[37]; + ts->y_gang_num = buf[38]; + } + + //---clear x_num, y_num if fw info is broken--- + if ((buf[1] + buf[2]) != 0xFF) { + NVT_ERR("FW info is broken! fw_ver=0x%02X, ~fw_ver=0x%02X\n", buf[1], buf[2]); + ts->fw_ver = 0; + ts->x_num = 18; + ts->y_num = 32; + ts->abs_x_max = TOUCH_DEFAULT_MAX_WIDTH; + ts->abs_y_max = TOUCH_DEFAULT_MAX_HEIGHT; + ts->max_button_num = TOUCH_KEY_NUM; + + if(retry_count < 3) { + retry_count++; + NVT_ERR("retry_count=%d\n", retry_count); + goto info_retry; + } else { + NVT_ERR("Set default fw_ver=%d, x_num=%d, y_num=%d, " + "abs_x_max=%d, abs_y_max=%d, max_button_num=%d!\n", + ts->fw_ver, ts->x_num, ts->y_num, + ts->abs_x_max, ts->abs_y_max, ts->max_button_num); + ret = -1; + } + } else { + ret = 0; + } + + NVT_LOG("fw_ver = 0x%02X, fw_type = 0x%02X, x_num=%d, y_num=%d\n", ts->fw_ver, buf[14], ts->x_num, ts->y_num); + + //---Get Novatek PID--- + nvt_read_pid(); + + return ret; +} + +static void release_pen_event(void) { + if (ts && ts->pen_input_dev) { + input_report_abs(ts->pen_input_dev, ABS_X, 0); + input_report_abs(ts->pen_input_dev, ABS_Y, 0); + input_report_abs(ts->pen_input_dev, ABS_PRESSURE, 0); + input_report_abs(ts->pen_input_dev, ABS_TILT_X, 0); + input_report_abs(ts->pen_input_dev, ABS_TILT_Y, 0); + input_report_abs(ts->pen_input_dev, ABS_DISTANCE, 0); + input_report_key(ts->pen_input_dev, BTN_TOUCH, 0); + input_report_key(ts->pen_input_dev, BTN_TOOL_PEN, 0); + input_report_key(ts->pen_input_dev, BTN_STYLUS, 0); + input_report_key(ts->pen_input_dev, BTN_STYLUS2, 0); + input_sync(ts->pen_input_dev); + } +} + +/******************************************************* +Description: + Novatek touchscreen parse device tree function. + +return: + n.a. +*******************************************************/ +#ifdef CONFIG_OF +static int32_t nvt_parse_dt(struct device *dev) +{ + struct device_node *np = dev->of_node; + int32_t ret = 0; + +#if NVT_TOUCH_SUPPORT_HW_RST + ts->reset_gpio = of_get_named_gpio_flags(np, "novatek,reset-gpio", 0, &ts->reset_flags); + NVT_LOG("novatek,reset-gpio=%d\n", ts->reset_gpio); +#endif + ts->irq_gpio = of_get_named_gpio(np, "novatek,irq-gpio", 0); + NVT_LOG("novatek,irq-gpio=%d\n", ts->irq_gpio); + + ts->pen_support = of_property_read_bool(np, "novatek,pen-support"); + NVT_LOG("novatek,pen-support=%d\n", ts->pen_support); + + ts->wgp_stylus = of_property_read_bool(np, "novatek,wgp-stylus"); + NVT_LOG("novatek,wgp-stylus=%d\n", ts->wgp_stylus); + + ret = of_property_read_u32(np, "novatek,swrst-n8-addr", &SWRST_N8_ADDR); + if (ret) { + NVT_ERR("error reading novatek,swrst-n8-addr. ret=%d\n", ret); + return ret; + } else { + NVT_LOG("SWRST_N8_ADDR=0x%06X\n", SWRST_N8_ADDR); + } + + ret = of_property_read_u32(np, "novatek,spi-rd-fast-addr", &SPI_RD_FAST_ADDR); + if (ret) { + NVT_LOG("not support novatek,spi-rd-fast-addr\n"); + SPI_RD_FAST_ADDR = 0; + ret = 0; + } else { + NVT_LOG("SPI_RD_FAST_ADDR=0x%06X\n", SPI_RD_FAST_ADDR); + } + + ret = of_property_read_string(np, "firmware-name", &ts->fw_name); + if (ret) { + NVT_LOG("Unable to get touchscreen firmware name\n"); + ts->fw_name = DEFAULT_BOOT_UPDATE_FIRMWARE_NAME; + } + + ret = of_property_read_u32(np, "spi-max-frequency", &ts->spi_max_freq); + if (ret) { + NVT_LOG("Unable to get spi freq\n"); + return ret; + } else { + NVT_LOG("spi-max-frequency: %u\n", ts->spi_max_freq); + } + + return ret; +} +#else +static int32_t nvt_parse_dt(struct device *dev) +{ +#if NVT_TOUCH_SUPPORT_HW_RST + ts->reset_gpio = NVTTOUCH_RST_PIN; +#endif + ts->irq_gpio = NVTTOUCH_INT_PIN; + return 0; +} +#endif + +/******************************************************* +Description: + Novatek touchscreen config and request gpio + +return: + Executive outcomes. 0---succeed. not 0---failed. +*******************************************************/ +static int nvt_gpio_config(struct nvt_ts_data *ts) +{ + int32_t ret = 0; + +#if NVT_TOUCH_SUPPORT_HW_RST + /* request RST-pin (Output/High) */ + if (gpio_is_valid(ts->reset_gpio)) { + ret = gpio_request_one(ts->reset_gpio, GPIOF_OUT_INIT_LOW, "NVT-tp-rst"); + if (ret) { + NVT_ERR("Failed to request NVT-tp-rst GPIO\n"); + goto err_request_reset_gpio; + } + } +#endif + + /* request INT-pin (Input) */ + if (gpio_is_valid(ts->irq_gpio)) { + ret = gpio_request_one(ts->irq_gpio, GPIOF_IN, "NVT-int"); + if (ret) { + NVT_ERR("Failed to request NVT-int GPIO\n"); + goto err_request_irq_gpio; + } + } + + return ret; + +err_request_irq_gpio: +#if NVT_TOUCH_SUPPORT_HW_RST + gpio_free(ts->reset_gpio); +err_request_reset_gpio: +#endif + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen deconfig gpio + +return: + n.a. +*******************************************************/ +static void nvt_gpio_deconfig(struct nvt_ts_data *ts) +{ + if (gpio_is_valid(ts->irq_gpio)) + gpio_free(ts->irq_gpio); +#if NVT_TOUCH_SUPPORT_HW_RST + if (gpio_is_valid(ts->reset_gpio)) + gpio_free(ts->reset_gpio); +#endif +} + +static uint8_t nvt_fw_recovery(uint8_t *point_data) +{ + uint8_t i = 0; + uint8_t detected = true; + + /* check pattern */ + for (i=1 ; i<7 ; i++) { + if (point_data[i] != 0x77) { + detected = false; + break; + } + } + + return detected; +} + +void nvt_set_dbgfw_status(bool enable) +{ + ts->fw_debug = enable; +} + +bool nvt_get_dbgfw_status(void) +{ + return ts->fw_debug; +} + +#if NVT_TOUCH_ESD_PROTECT +void nvt_esd_check_enable(uint8_t enable) +{ + /* update interrupt timer */ + irq_timer = jiffies; + /* clear esd_retry counter, if protect function is enabled */ + esd_retry = enable ? 0 : esd_retry; + /* enable/disable esd check flag */ + esd_check = enable; +} + +static void nvt_esd_check_func(struct work_struct *work) +{ + unsigned int timer = jiffies_to_msecs(jiffies - irq_timer); + + //NVT_LOG("esd_check = %d (retry %d)\n", esd_check, esd_retry); //DEBUG + + if ((timer > NVT_TOUCH_ESD_CHECK_PERIOD) && esd_check) { + mutex_lock(&ts->lock); + NVT_ERR("do ESD recovery, timer = %d, retry = %d\n", timer, esd_retry); + /* do esd recovery, reload fw */ + nvt_update_firmware(ts->fw_name); + mutex_unlock(&ts->lock); + /* update interrupt timer */ + irq_timer = jiffies; + /* update esd_retry counter */ + esd_retry++; + } + + queue_delayed_work(nvt_esd_check_wq, &nvt_esd_check_work, + msecs_to_jiffies(NVT_TOUCH_ESD_CHECK_PERIOD)); +} +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + +#if NVT_TOUCH_WDT_RECOVERY +static uint8_t recovery_cnt = 0; +static uint8_t nvt_wdt_fw_recovery(uint8_t *point_data) +{ + uint32_t recovery_cnt_max = 10; + uint8_t recovery_enable = false; + uint8_t i = 0; + + recovery_cnt++; + + /* check pattern */ + for (i=1 ; i<7 ; i++) { + if ((point_data[i] != 0xFD) && (point_data[i] != 0xFE)) { + recovery_cnt = 0; + break; + } + } + + if (recovery_cnt > recovery_cnt_max){ + recovery_enable = true; + recovery_cnt = 0; + } + + return recovery_enable; +} +#endif /* #if NVT_TOUCH_WDT_RECOVERY */ + +#define PEN_DATA_LEN 14 +#define FW_HISTORY_SIZE 128 +static uint32_t nvt_dump_fw_history(void) +{ + int32_t ret = 0; + uint8_t buf[FW_HISTORY_SIZE + 1 + DUMMY_BYTES] = {0}; + int32_t i = 0; + char *tmp_dump = NULL; + int32_t line_cnt = 0; + + if (ts->mmap->FW_HISTORY_ADDR == 0) { + NVT_ERR("FW_HISTORY_ADDR not available!\n"); + ret = -1; + goto exit_nvt_dump_fw_history; + } + nvt_set_page(ts->mmap->FW_HISTORY_ADDR); + buf[0] = ts->mmap->FW_HISTORY_ADDR & 0xFF; + CTP_SPI_READ(ts->client, buf, FW_HISTORY_SIZE + 1); + if (ret) { + NVT_ERR("CTP_SPI_READ failed.(%d)\n", ret); + ret = -1; + goto exit_nvt_dump_fw_history; + } + + tmp_dump = (char *)kzalloc(FW_HISTORY_SIZE * 4, GFP_KERNEL); + for (i = 0; i < FW_HISTORY_SIZE; i++) { + sprintf(tmp_dump + i * 3 + line_cnt, "%02X ", buf[1 + i]); + if ((i + 1) % 16 == 0) { + sprintf(tmp_dump + i * 3 + line_cnt + 3, "%c", '\n'); + line_cnt++; + } + } + NVT_LOG("%s", tmp_dump); + +exit_nvt_dump_fw_history: + if (tmp_dump) { + kfree(tmp_dump); + tmp_dump = NULL; + } + nvt_set_page(ts->mmap->EVENT_BUF_ADDR); + + return ret; +} + +#define POINT_DATA_LEN 65 +/******************************************************* +Description: + Novatek touchscreen work function. + +return: + n.a. +*******************************************************/ +static irqreturn_t nvt_ts_work_func(int irq, void *data) +{ + int32_t ret = -1; + uint8_t point_data[POINT_DATA_LEN + PEN_DATA_LEN + 1 + DUMMY_BYTES] = {0}; + uint32_t position = 0; + uint32_t input_x = 0; + uint32_t input_y = 0; + uint32_t input_w = 0; + uint32_t input_p = 0; + uint8_t input_id = 0; +#if MT_PROTOCOL_B + uint8_t press_id[TOUCH_MAX_FINGER_NUM] = {0}; +#endif /* MT_PROTOCOL_B */ + int32_t i = 0; + int32_t finger_cnt = 0; + uint8_t pen_format_id = 0; + uint32_t pen_x = 0; + uint32_t pen_y = 0; + uint32_t pen_pressure = 0; + uint32_t pen_distance = 0; + int8_t pen_tilt_x = 0; + int8_t pen_tilt_y = 0; + uint32_t pen_btn1 = 0; + uint32_t pen_btn2 = 0; + uint32_t pen_battery = 0; + + mutex_lock(&ts->lock); + + if (ts->dev_pm_suspend) { + ret = wait_for_completion_timeout(&ts->dev_pm_suspend_completion, msecs_to_jiffies(500)); + if (!ret) { + NVT_ERR("system(spi) can't finished resuming procedure, skip it\n"); + goto XFER_ERROR; + } + } + + if (ts->pen_support) + ret = CTP_SPI_READ(ts->client, point_data, POINT_DATA_LEN + PEN_DATA_LEN + 1); + else + ret = CTP_SPI_READ(ts->client, point_data, POINT_DATA_LEN + 1); + if (ret < 0) { + NVT_ERR("CTP_SPI_READ failed.(%d)\n", ret); + goto XFER_ERROR; + } + + /*--- dump SPI buf --- + for (i = 0; i < 10; i++) { + NVT_LOG("%02X %02X %02X %02X %02X %02X \n", + point_data[1+i*6], point_data[2+i*6], point_data[3+i*6], point_data[4+i*6], point_data[5+i*6], point_data[6+i*6]); + } + */ + +#if NVT_TOUCH_WDT_RECOVERY + /* ESD protect by WDT */ + if (nvt_wdt_fw_recovery(point_data)) { + NVT_ERR("Recover for fw reset, %02X\n", point_data[1]); + if (point_data[1] == 0xFD) { + NVT_ERR("Dump FW history:\n"); + nvt_dump_fw_history(); + } + nvt_update_firmware(ts->fw_name); + goto XFER_ERROR; + } +#endif /* #if NVT_TOUCH_WDT_RECOVERY */ + + /* ESD protect by FW handshake */ + if (nvt_fw_recovery(point_data)) { +#if NVT_TOUCH_ESD_PROTECT + nvt_esd_check_enable(true); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + goto XFER_ERROR; + } + + finger_cnt = 0; + + for (i = 0; i < ts->max_touch_num; i++) { + position = 1 + 6 * i; + input_id = (uint8_t)(point_data[position + 0] >> 3); + if ((input_id == 0) || (input_id > ts->max_touch_num)) + continue; + + if (((point_data[position] & 0x07) == 0x01) || ((point_data[position] & 0x07) == 0x02)) { //finger down (enter & moving) +#if NVT_TOUCH_ESD_PROTECT + /* update interrupt timer */ + irq_timer = jiffies; +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + input_x = (uint32_t)(point_data[position + 1] << 4) + (uint32_t) (point_data[position + 3] >> 4); + input_y = (uint32_t)(point_data[position + 2] << 4) + (uint32_t) (point_data[position + 3] & 0x0F); + if ((input_x < 0) || (input_y < 0)) + continue; + if ((input_x > ts->abs_x_max) || (input_y > ts->abs_y_max)) + continue; + input_w = (uint32_t)(point_data[position + 4]); + if (input_w == 0) + input_w = 1; + if (i < 2) { + input_p = (uint32_t)(point_data[position + 5]) + (uint32_t)(point_data[i + 63] << 8); + if (input_p > TOUCH_FORCE_NUM) + input_p = TOUCH_FORCE_NUM; + } else { + input_p = (uint32_t)(point_data[position + 5]); + } + if (input_p == 0) + input_p = 1; + +#if MT_PROTOCOL_B + press_id[input_id - 1] = 1; + input_mt_slot(ts->input_dev, input_id - 1); + input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true); +#else /* MT_PROTOCOL_B */ + input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, input_id - 1); + input_report_key(ts->input_dev, BTN_TOUCH, 1); +#endif /* MT_PROTOCOL_B */ + + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, input_x); + input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, input_y); + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, input_w); + input_report_abs(ts->input_dev, ABS_MT_PRESSURE, input_p); + +#if MT_PROTOCOL_B +#else /* MT_PROTOCOL_B */ + input_mt_sync(ts->input_dev); +#endif /* MT_PROTOCOL_B */ + + finger_cnt++; + } + } + +#if MT_PROTOCOL_B + for (i = 0; i < ts->max_touch_num; i++) { + if (press_id[i] != 1) { + input_mt_slot(ts->input_dev, i); + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0); + input_report_abs(ts->input_dev, ABS_MT_PRESSURE, 0); + input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false); + } + } + input_report_key(ts->input_dev, BTN_TOUCH, (finger_cnt > 0)); +#else /* MT_PROTOCOL_B */ + if (finger_cnt == 0) { + input_report_key(ts->input_dev, BTN_TOUCH, 0); + input_mt_sync(ts->input_dev); + } +#endif /* MT_PROTOCOL_B */ + +#if TOUCH_KEY_NUM > 0 + if (point_data[61] == 0xF8) { +#if NVT_TOUCH_ESD_PROTECT + /* update interrupt timer */ + irq_timer = jiffies; +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + for (i = 0; i < ts->max_button_num; i++) { + input_report_key(ts->input_dev, touch_key_array[i], ((point_data[62] >> i) & 0x01)); + } + } else { + for (i = 0; i < ts->max_button_num; i++) { + input_report_key(ts->input_dev, touch_key_array[i], 0); + } + } +#endif + + input_sync(ts->input_dev); + + if (ts->pen_support && ts->pen_input_dev_enable && !(ts->pen_is_charge)) { +/* + //--- dump pen buf --- + printk("%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", + point_data[66], point_data[67], point_data[68], point_data[69], point_data[70], + point_data[71], point_data[72], point_data[73], point_data[74], point_data[75], + point_data[76], point_data[77], point_data[78], point_data[79]); +*/ + // parse and handle pen report + pen_format_id = point_data[66]; + if (pen_format_id != 0xFF) { + if (pen_format_id == 0x01) { + // report pen data + pen_x = (uint32_t)(point_data[67] << 8) + (uint32_t)(point_data[68]); + pen_y = (uint32_t)(point_data[69] << 8) + (uint32_t)(point_data[70]); + if (pen_x >= ts->abs_x_max * 8 - 1) { + pen_x -= 1; + } + if (pen_y >= ts->abs_y_max * 8 - 1) { + pen_y -= 1; + } + pen_pressure = (uint32_t)(point_data[71] << 8) + (uint32_t)(point_data[72]); + pen_tilt_x = (int32_t)point_data[73]; + pen_tilt_y = (int32_t)point_data[74]; + pen_distance = (uint32_t)(point_data[75] << 8) + (uint32_t)(point_data[76]); + pen_btn1 = (uint32_t)(point_data[77] & 0x01); + pen_btn2 = (uint32_t)((point_data[77] >> 1) & 0x01); + pen_battery = (uint32_t)point_data[78]; +// printk("x=%d,y=%d,p=%d,tx=%d,ty=%d,d=%d,b1=%d,b2=%d,bat=%d\n", pen_x, pen_y, pen_pressure, +// pen_tilt_x, pen_tilt_y, pen_distance, pen_btn1, pen_btn2, pen_battery); + + input_report_abs(ts->pen_input_dev, ABS_X, pen_x); + input_report_abs(ts->pen_input_dev, ABS_Y, pen_y); + input_report_abs(ts->pen_input_dev, ABS_PRESSURE, pen_pressure); + input_report_key(ts->pen_input_dev, BTN_TOUCH, !!pen_pressure); + input_report_abs(ts->pen_input_dev, ABS_TILT_X, pen_tilt_x); + input_report_abs(ts->pen_input_dev, ABS_TILT_Y, pen_tilt_y); + input_report_abs(ts->pen_input_dev, ABS_DISTANCE, pen_distance); + input_report_key(ts->pen_input_dev, BTN_TOOL_PEN, !!pen_distance || !!pen_pressure); + input_report_key(ts->pen_input_dev, BTN_STYLUS, pen_btn1); + input_report_key(ts->pen_input_dev, BTN_STYLUS2, pen_btn2); + input_sync(ts->pen_input_dev); + // TBD: pen battery event report + // NVT_LOG("pen_battery=%d\n", pen_battery); + } else if (pen_format_id == 0xF0) { + // report Pen ID + } else { + NVT_ERR("Unknown pen format id!\n"); + goto XFER_ERROR; + } + } else { // pen_format_id = 0xFF, i.e. no pen present + release_pen_event(); + } + } /* if (ts->pen_support) */ + +XFER_ERROR: + + mutex_unlock(&ts->lock); + return IRQ_HANDLED; +} + + +/******************************************************* +Description: + Novatek touchscreen check chip version trim function. + +return: + Executive outcomes. 0---NVT IC. -1---not NVT IC. +*******************************************************/ +static int8_t nvt_ts_check_chip_ver_trim(uint32_t chip_ver_trim_addr) +{ + + ts->mmap = &NT36523_memory_map; + ts->carrier_system = NT36523_hw_info.carrier_system; + ts->hw_crc = NT36523_hw_info.hw_crc; + return 0; +} + +static int32_t disable_pen_input_device(bool disable) { + uint8_t buf[8] = {0}; + int32_t ret = 0; + + NVT_LOG("++\n"); + if (!bTouchIsAwake || !ts) { + NVT_LOG("touch suspend, stop set pen state %s", disable ? "DISABLE" : "ENABLE"); + goto nvt_set_pen_enable_out; + } + + msleep(35); + disable = (!(ts->pen_input_dev_enable) || ts->pen_is_charge) ? true : disable; + + //---set xdata index to EVENT BUF ADDR--- + ret = nvt_set_page(ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_HOST_CMD); + if (ret < 0) { + NVT_ERR("Set event buffer index fail!\n"); + goto nvt_set_pen_enable_out; + } + + buf[0] = EVENT_MAP_HOST_CMD; + buf[1] = 0x7B; + buf[2] = !!disable; + ret = CTP_SPI_WRITE(ts->client, buf, 3); + if (ret < 0) { + NVT_ERR("set pen %s failed!\n", disable ? "DISABLE" : "ENABLE"); + goto nvt_set_pen_enable_out; + } + NVT_LOG("pen charge state is %s, %s pen input device\n", + ts->pen_is_charge ? "ENABLE" : "DISABLE", + disable ? "DISABLE" : "ENABLE"); + +nvt_set_pen_enable_out: + NVT_LOG("--\n"); + return ret; +} + +static void nvt_suspend_work(struct work_struct *work) +{ + struct nvt_ts_data *ts_core = container_of(work, struct nvt_ts_data, suspend_work); + nvt_ts_suspend(&ts_core->client->dev); +} + +static void nvt_resume_work(struct work_struct *work) +{ + struct nvt_ts_data *ts_core = container_of(work, struct nvt_ts_data, resume_work); + nvt_ts_resume(&ts_core->client->dev); +} + +/******************************************************* +Description: + Novatek touchscreen driver probe function. + +return: + Executive outcomes. 0---succeed. negative---failed +*******************************************************/ +static int32_t nvt_ts_probe(struct spi_device *client) +{ + int32_t ret = 0; +#if (TOUCH_KEY_NUM > 0) + int32_t retry = 0; +#endif + + NVT_LOG("probe start\n"); + + ts = kzalloc(sizeof(struct nvt_ts_data), GFP_KERNEL); + if (ts == NULL) { + NVT_ERR("failed to allocated memory for nvt ts data\n"); + return -ENOMEM; + } + + ts->xbuf = (uint8_t *)kzalloc((NVT_TRANSFER_LEN+1+DUMMY_BYTES), GFP_KERNEL); + if(ts->xbuf == NULL) { + NVT_ERR("kzalloc for xbuf failed!\n"); + ret = -ENOMEM; + goto err_malloc_xbuf; + } + + ts->rbuf = (uint8_t *)kzalloc(NVT_READ_LEN, GFP_KERNEL); + if(ts->rbuf == NULL) { + NVT_ERR("kzalloc for rbuf failed!\n"); + ret = -ENOMEM; + goto err_malloc_rbuf; + } + + ts->client = client; + spi_set_drvdata(client, ts); + + //---prepare for spi parameter--- + if (ts->client->controller->flags & SPI_CONTROLLER_HALF_DUPLEX) { + NVT_ERR("Full duplex not supported by master\n"); + ret = -EIO; + goto err_ckeck_full_duplex; + } + ts->client->bits_per_word = 8; + ts->client->mode = SPI_MODE_0; + + ret = spi_setup(ts->client); + if (ret < 0) { + NVT_ERR("Failed to perform SPI setup\n"); + goto err_spi_setup; + } + +#ifdef CONFIG_MTK_SPI + /* old usage of MTK spi API */ + memcpy(&ts->spi_ctrl, &spi_ctrdata, sizeof(struct mt_chip_conf)); + ts->client->controller_data = (void *)&ts->spi_ctrl; +#endif + +#ifdef CONFIG_SPI_MT65XX + /* new usage of MTK spi API */ + memcpy(&ts->spi_ctrl, &spi_ctrdata, sizeof(struct mtk_chip_config)); + ts->client->controller_data = (void *)&ts->spi_ctrl; +#endif + + NVT_LOG("mode=%d, max_speed_hz=%d\n", ts->client->mode, ts->client->max_speed_hz); + + //---parse dts--- + ret = nvt_parse_dt(&client->dev); + if (ret) { + NVT_ERR("parse dt error\n"); + goto err_spi_setup; + } + + //---request and config GPIOs--- + ret = nvt_gpio_config(ts); + if (ret) { + NVT_ERR("gpio config error!\n"); + goto err_gpio_config_failed; + } + + mutex_init(&ts->lock); + mutex_init(&ts->xbuf_lock); + + //---eng reset before TP_RESX high + nvt_eng_reset(); + +#if NVT_TOUCH_SUPPORT_HW_RST + gpio_set_value(ts->reset_gpio, 1); +#endif + + // need 10ms delay after POR(power on reset) + msleep(10); + + //---check chip version trim--- + ret = nvt_ts_check_chip_ver_trim(CHIP_VER_TRIM_ADDR); + if (ret) { + NVT_LOG("try to check from old chip ver trim address\n"); + ret = nvt_ts_check_chip_ver_trim(CHIP_VER_TRIM_OLD_ADDR); + if (ret) { + NVT_ERR("chip is not identified\n"); + ret = -EINVAL; + goto err_chipvertrim_failed; + } + } + + ts->abs_x_max = TOUCH_DEFAULT_MAX_WIDTH; + ts->abs_y_max = TOUCH_DEFAULT_MAX_HEIGHT; + + //---allocate input device--- + ts->input_dev = input_allocate_device(); + if (ts->input_dev == NULL) { + NVT_ERR("allocate input device failed\n"); + ret = -ENOMEM; + goto err_input_dev_alloc_failed; + } + + ts->max_touch_num = TOUCH_MAX_FINGER_NUM; + +#if TOUCH_KEY_NUM > 0 + ts->max_button_num = TOUCH_KEY_NUM; +#endif + + ts->int_trigger_type = INT_TRIGGER_TYPE; + + //---set input device info.--- + ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + ts->input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + ts->input_dev->propbit[0] = BIT(INPUT_PROP_DIRECT); + + ts->db_wakeup = 0; + ts->fw_ver = 0; + ts->x_num = 32; + ts->y_num = 50; + ts->x_gang_num = 4; + ts->y_gang_num = 6; + ts->abs_x_max = TOUCH_DEFAULT_MAX_WIDTH; + ts->abs_y_max = TOUCH_DEFAULT_MAX_HEIGHT; + ts->max_button_num = TOUCH_KEY_NUM; + NVT_LOG("Set default fw_ver=%d, x_num=%d, y_num=%d, " + "abs_x_max=%d, abs_y_max=%d, max_button_num=%d!\n", + ts->fw_ver, ts->x_num, ts->y_num, + ts->abs_x_max, ts->abs_y_max, ts->max_button_num); + +#if MT_PROTOCOL_B + input_mt_init_slots(ts->input_dev, ts->max_touch_num, 0); +#endif + + input_set_abs_params(ts->input_dev, ABS_MT_PRESSURE, 0, TOUCH_FORCE_NUM, 0, 0); //pressure = TOUCH_FORCE_NUM + +#if TOUCH_MAX_FINGER_NUM > 1 + input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); //area = 255 + + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, ts->abs_x_max - 1, 0, 0); + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, ts->abs_y_max - 1, 0, 0); +#if MT_PROTOCOL_B + // no need to set ABS_MT_TRACKING_ID, input_mt_init_slots() already set it +#else + input_set_abs_params(ts->input_dev, ABS_MT_TRACKING_ID, 0, ts->max_touch_num, 0, 0); +#endif //MT_PROTOCOL_B +#endif //TOUCH_MAX_FINGER_NUM > 1 + +#if TOUCH_KEY_NUM > 0 + for (retry = 0; retry < ts->max_button_num; retry++) { + input_set_capability(ts->input_dev, EV_KEY, touch_key_array[retry]); + } +#endif + + sprintf(ts->phys, "input/ts"); + ts->input_dev->name = NVT_TS_NAME; + ts->input_dev->phys = ts->phys; + ts->input_dev->id.bustype = BUS_SPI; + + //---register input device--- + ret = input_register_device(ts->input_dev); + if (ret) { + NVT_ERR("register input device (%s) failed. ret=%d\n", ts->input_dev->name, ret); + goto err_input_register_device_failed; + } + + if (ts->pen_support) { + //---allocate pen input device--- + ts->pen_input_dev = input_allocate_device(); + if (ts->pen_input_dev == NULL) { + NVT_ERR("allocate pen input device failed\n"); + ret = -ENOMEM; + goto err_pen_input_dev_alloc_failed; + } + + //---set pen input device info.--- + ts->pen_input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + ts->pen_input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + ts->pen_input_dev->keybit[BIT_WORD(BTN_TOOL_PEN)] |= BIT_MASK(BTN_TOOL_PEN); + //ts->pen_input_dev->keybit[BIT_WORD(BTN_TOOL_RUBBER)] |= BIT_MASK(BTN_TOOL_RUBBER); + ts->pen_input_dev->keybit[BIT_WORD(BTN_STYLUS)] |= BIT_MASK(BTN_STYLUS); + ts->pen_input_dev->keybit[BIT_WORD(BTN_STYLUS2)] |= BIT_MASK(BTN_STYLUS2); + ts->pen_input_dev->propbit[0] = BIT(INPUT_PROP_DIRECT); + + int x_max, y_max; + + if (ts->wgp_stylus) { + x_max = ts->abs_x_max * 8 - 1; + y_max = ts->abs_y_max * 8 - 1; + } else { + x_max = ts->abs_x_max - 1; + y_max = ts->abs_y_max - 1; + } + + input_set_abs_params(ts->pen_input_dev, ABS_X, 0, x_max, 0, 0); + input_set_abs_params(ts->pen_input_dev, ABS_Y, 0, y_max , 0, 0); + input_abs_set_res(ts->pen_input_dev, ABS_X, x_max / PANEL_DEFAULT_WIDTH_MM); + input_abs_set_res(ts->pen_input_dev, ABS_Y, y_max / PANEL_DEFAULT_HEIGHT_MM); + + input_set_abs_params(ts->pen_input_dev, ABS_PRESSURE, 0, PEN_PRESSURE_MAX, 0, 0); + input_set_abs_params(ts->pen_input_dev, ABS_DISTANCE, 0, PEN_DISTANCE_MAX, 0, 0); + input_set_abs_params(ts->pen_input_dev, ABS_TILT_X, PEN_TILT_MIN, PEN_TILT_MAX, 0, 0); + input_set_abs_params(ts->pen_input_dev, ABS_TILT_Y, PEN_TILT_MIN, PEN_TILT_MAX, 0, 0); + + sprintf(ts->pen_phys, "input/pen"); + ts->pen_input_dev->name = NVT_PEN_NAME; + ts->pen_input_dev->phys = ts->pen_phys; + ts->pen_input_dev->id.bustype = BUS_SPI; + + //---register pen input device--- + ret = input_register_device(ts->pen_input_dev); + if (ret) { + NVT_ERR("register pen input device (%s) failed. ret=%d\n", ts->pen_input_dev->name, ret); + goto err_pen_input_register_device_failed; + } + } /* if (ts->pen_support) */ + + //---set int-pin & request irq--- + client->irq = gpio_to_irq(ts->irq_gpio); + if (client->irq) { + NVT_LOG("int_trigger_type=%d\n", ts->int_trigger_type); + ts->irq_enabled = true; + ret = request_threaded_irq(client->irq, NULL, nvt_ts_work_func, + ts->int_trigger_type | IRQF_ONESHOT, NVT_SPI_NAME, ts); + if (ret != 0) { + NVT_ERR("request irq failed. ret=%d\n", ret); + goto err_int_request_failed; + } else { + nvt_irq_enable(false); + NVT_LOG("request irq %d succeed\n", client->irq); + } + } + + ts->pen_is_charge = false; + + ts->lkdown_readed =false; + pm_stay_awake(&client->dev); + + ts->ic_state = NVT_IC_INIT; + ts->dev_pm_suspend = false; + ts->gesture_command_delayed = -1; + init_completion(&ts->dev_pm_suspend_completion); + ts->fw_debug = false; + +//#ifdef CONFIG_FACTORY_BUILD + ts->pen_input_dev_enable = 1; +//#else +// ts->pen_input_dev_enable = 0; +//#endif + +#if BOOT_UPDATE_FIRMWARE + ret = nvt_update_firmware(ts->fw_name); + if (ret) + NVT_ERR("download firmware failed\n"); +#endif + + NVT_LOG("NVT_TOUCH_ESD_PROTECT is %d\n", NVT_TOUCH_ESD_PROTECT); +#if NVT_TOUCH_ESD_PROTECT + INIT_DELAYED_WORK(&nvt_esd_check_work, nvt_esd_check_func); + nvt_esd_check_wq = alloc_workqueue("nvt_esd_check_wq", WQ_MEM_RECLAIM, 1); + if (!nvt_esd_check_wq) { + NVT_ERR("nvt_esd_check_wq create workqueue failed\n"); + ret = -ENOMEM; + goto err_create_nvt_esd_check_wq_failed; + } + queue_delayed_work(nvt_esd_check_wq, &nvt_esd_check_work, + msecs_to_jiffies(NVT_TOUCH_ESD_CHECK_PERIOD)); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + + ts->event_wq = alloc_workqueue("nvt-event-queue", + WQ_UNBOUND | WQ_HIGHPRI | WQ_CPU_INTENSIVE, 1); + if (!ts->event_wq) { + NVT_ERR("Can not create work thread for suspend/resume!!"); + ret = -ENOMEM; + goto err_alloc_work_thread_failed; + } + INIT_WORK(&ts->resume_work, nvt_resume_work); + INIT_WORK(&ts->suspend_work, nvt_suspend_work); + +#ifdef CONFIG_DRM + ts->drm_notif.notifier_call = nvt_drm_notifier_callback; + ret = mi_drm_register_client(&ts->drm_notif); + if(ret) { + NVT_ERR("register drm_notifier failed. ret=%d\n", ret); + goto err_register_drm_notif_failed; + } +#endif + + bTouchIsAwake = 1; + disable_pen_input_device(false); + NVT_LOG("end\n"); + + nvt_irq_enable(true); + + return 0; + +#ifdef CONFIG_DRM + if (mi_drm_unregister_client(&ts->drm_notif)) + NVT_ERR("Error occurred while unregistering drm_notifier.\n"); +err_register_drm_notif_failed: +#endif + +err_alloc_work_thread_failed: + +#if NVT_TOUCH_ESD_PROTECT + if (nvt_esd_check_wq) { + cancel_delayed_work_sync(&nvt_esd_check_work); + destroy_workqueue(nvt_esd_check_wq); + nvt_esd_check_wq = NULL; + } +err_create_nvt_esd_check_wq_failed: +#endif + free_irq(client->irq, ts); +err_int_request_failed: + if (ts->pen_support) { + input_unregister_device(ts->pen_input_dev); + ts->pen_input_dev = NULL; + } +err_pen_input_register_device_failed: + if (ts->pen_support) { + if (ts->pen_input_dev) { + input_free_device(ts->pen_input_dev); + ts->pen_input_dev = NULL; + } + } +err_pen_input_dev_alloc_failed: + input_unregister_device(ts->input_dev); + ts->input_dev = NULL; +err_input_register_device_failed: + if (ts->input_dev) { + input_free_device(ts->input_dev); + ts->input_dev = NULL; + } +err_input_dev_alloc_failed: +err_chipvertrim_failed: + mutex_destroy(&ts->xbuf_lock); + mutex_destroy(&ts->lock); + nvt_gpio_deconfig(ts); +err_gpio_config_failed: +err_spi_setup: +err_ckeck_full_duplex: + spi_set_drvdata(client, NULL); + if (ts->rbuf) { + kfree(ts->rbuf); + ts->rbuf = NULL; + } +err_malloc_rbuf: + if (ts->xbuf) { + kfree(ts->xbuf); + ts->xbuf = NULL; + } +err_malloc_xbuf: + if (ts) { + kfree(ts); + ts = NULL; + } + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen driver release function. + +return: + Executive outcomes. 0---succeed. +*******************************************************/ +static void nvt_ts_remove(struct spi_device *client) +{ + NVT_LOG("Removing driver...\n"); + +#ifdef CONFIG_DRM + if (mi_drm_unregister_client(&ts->drm_notif)) + NVT_ERR("Error occurred while unregistering drm_notifier.\n"); +#endif + +#if NVT_TOUCH_ESD_PROTECT + if (nvt_esd_check_wq) { + cancel_delayed_work_sync(&nvt_esd_check_work); + nvt_esd_check_enable(false); + destroy_workqueue(nvt_esd_check_wq); + nvt_esd_check_wq = NULL; + } +#endif + + nvt_irq_enable(false); + free_irq(client->irq, ts); + + mutex_destroy(&ts->xbuf_lock); + mutex_destroy(&ts->lock); + + nvt_gpio_deconfig(ts); + + if (ts->pen_support) { + if (ts->pen_input_dev) { + input_unregister_device(ts->pen_input_dev); + ts->pen_input_dev = NULL; + } + } + + if (ts->input_dev) { + input_unregister_device(ts->input_dev); + ts->input_dev = NULL; + } + + spi_set_drvdata(client, NULL); + + if (ts) { + kfree(ts); + ts = NULL; + } +} + +static void nvt_ts_shutdown(struct spi_device *client) +{ + NVT_LOG("Shutdown driver...\n"); + + nvt_irq_enable(false); + +#ifdef CONFIG_DRM + if (mi_drm_unregister_client(&ts->drm_notif)) + NVT_ERR("Error occurred while unregistering drm_notifier.\n"); +#endif + + destroy_workqueue(ts->event_wq); + +#if NVT_TOUCH_ESD_PROTECT + if (nvt_esd_check_wq) { + cancel_delayed_work_sync(&nvt_esd_check_work); + nvt_esd_check_enable(false); + destroy_workqueue(nvt_esd_check_wq); + nvt_esd_check_wq = NULL; + } +#endif /* #if NVT_TOUCH_ESD_PROTECT */ +} + +/******************************************************* +Description: + Novatek touchscreen driver suspend function. + +return: + Executive outcomes. 0---succeed. +*******************************************************/ +static int32_t nvt_ts_suspend(struct device *dev) +{ + uint8_t buf[4] = {0}; +#if MT_PROTOCOL_B + uint32_t i = 0; +#endif + + if (!bTouchIsAwake) { + NVT_LOG("Touch is already suspend\n"); + return 0; + } + + pm_stay_awake(dev); + ts->ic_state = NVT_IC_SUSPEND_IN; + + if (!ts->db_wakeup) { + if (!ts->irq_enabled) + NVT_LOG("IRQ already disabled\n"); + else + nvt_irq_enable(false); + } + +#if NVT_TOUCH_ESD_PROTECT + NVT_LOG("cancel delayed work sync\n"); + cancel_delayed_work_sync(&nvt_esd_check_work); + nvt_esd_check_enable(false); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + + mutex_lock(&ts->lock); + + NVT_LOG("suspend start\n"); + + if (ts->pen_input_dev_enable) { + NVT_LOG("if enable pen,will close it"); + disable_pen_input_device(true); + } + + bTouchIsAwake = 0; + + if (ts->db_wakeup) { + /*---write command to enter "wakeup gesture mode"---*/ + /*DoubleClick wakeup CMD was sent by display to meet timing*/ + /* + buf[0] = EVENT_MAP_HOST_CMD; + buf[1] = 0x13; + CTP_SPI_WRITE(ts->client, buf, 2); + */ + enable_irq_wake(ts->client->irq); + + NVT_LOG("Enabled touch wakeup gesture\n"); + } else { + /*---write command to enter "deep sleep mode"---*/ + buf[0] = EVENT_MAP_HOST_CMD; + buf[1] = 0x11; + CTP_SPI_WRITE(ts->client, buf, 2); + } + + mutex_unlock(&ts->lock); + + /* release all touches */ +#if MT_PROTOCOL_B + for (i = 0; i < ts->max_touch_num; i++) { + input_mt_slot(ts->input_dev, i); + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0); + input_report_abs(ts->input_dev, ABS_MT_PRESSURE, 0); + input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, 0); + } +#endif + input_report_key(ts->input_dev, BTN_TOUCH, 0); +#if !MT_PROTOCOL_B + input_mt_sync(ts->input_dev); +#endif + input_sync(ts->input_dev); + + msleep(50); + /* release pen event */ + release_pen_event(); + if (likely(ts->ic_state == NVT_IC_SUSPEND_IN)) + ts->ic_state = NVT_IC_SUSPEND_OUT; + else + NVT_ERR("IC state may error,caused by suspend/resume flow, please CHECK!!"); + pm_relax(dev); + NVT_LOG("end\n"); + + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen driver resume function. + +return: + Executive outcomes. 0---succeed. +*******************************************************/ +static int32_t nvt_ts_resume(struct device *dev) +{ + int ret = 0; + if (bTouchIsAwake) { + NVT_LOG("Touch is already resume\n"); + return 0; + } + + if (ts->dev_pm_suspend) + pm_stay_awake(dev); + + mutex_lock(&ts->lock); + + NVT_LOG("resume start\n"); + ts->ic_state = NVT_IC_RESUME_IN; + + // please make sure display reset(RESX) sequence and mipi dsi cmds sent before this +#if NVT_TOUCH_SUPPORT_HW_RST + gpio_set_value(ts->reset_gpio, 1); +#endif + ret = nvt_update_firmware(ts->fw_name); + if (ret) + NVT_ERR("download firmware failed\n"); + + nvt_check_fw_reset_state(RESET_STATE_REK); + + if (!ts->db_wakeup && !ts->irq_enabled) { + nvt_irq_enable(true); + } + +#if NVT_TOUCH_ESD_PROTECT + nvt_esd_check_enable(false); + queue_delayed_work(nvt_esd_check_wq, &nvt_esd_check_work, + msecs_to_jiffies(NVT_TOUCH_ESD_CHECK_PERIOD)); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + + bTouchIsAwake = 1; + + mutex_unlock(&ts->lock); + + disable_pen_input_device(false); + if (likely(ts->ic_state == NVT_IC_RESUME_IN)) { + ts->ic_state = NVT_IC_RESUME_OUT; + } else { + NVT_ERR("IC state may error,caused by suspend/resume flow, please CHECK!!"); + } + if (ts->gesture_command_delayed >= 0){ + ts->db_wakeup = ts->gesture_command_delayed; + ts->gesture_command_delayed = -1; + NVT_LOG("execute delayed command, set double click wakeup %d\n", ts->db_wakeup); + } + + if (ts->dev_pm_suspend) + pm_relax(dev); + NVT_LOG("end\n"); + + return 0; +} + + +#ifdef CONFIG_DRM +static int nvt_drm_notifier_callback(struct notifier_block *self, unsigned long event, void *data) +{ + int blank = *(enum drm_notifier_data *)data; + struct nvt_ts_data *ts_data = + container_of(self, struct nvt_ts_data, drm_notif); + + if (data && ts_data) { + if (event == MI_DRM_EARLY_EVENT_BLANK) { + if (blank == MI_DRM_BLANK_POWERDOWN) { + NVT_LOG("event=%lu, *blank=%d\n", event, blank); + flush_workqueue(ts_data->event_wq); + queue_work(ts_data->event_wq, &ts_data->suspend_work); + } + } else if (event == MI_DRM_EVENT_BLANK) { + if (blank == MI_DRM_BLANK_UNBLANK) { + NVT_LOG("event=%lu, *blank=%d\n", event, blank); + flush_workqueue(ts_data->event_wq); + queue_work(ts_data->event_wq, &ts_data->resume_work); + } + } + } + + return 0; +} +#endif + +static int nvt_pm_suspend(struct device *dev) +{ + if (device_may_wakeup(dev) && ts->db_wakeup) { + NVT_LOG("enable touch irq wake\n"); + enable_irq_wake(ts->client->irq); + } + ts->dev_pm_suspend = true; + reinit_completion(&ts->dev_pm_suspend_completion); + + return 0; +} + +static int nvt_pm_resume(struct device *dev) +{ + if (device_may_wakeup(dev) && ts->db_wakeup) { + NVT_LOG("disable touch irq wake\n"); + disable_irq_wake(ts->client->irq); + } + ts->dev_pm_suspend = false; + complete(&ts->dev_pm_suspend_completion); + + return 0; +} + +static const struct dev_pm_ops nvt_dev_pm_ops = { + .suspend = nvt_pm_suspend, + .resume = nvt_pm_resume, +}; + +static const struct spi_device_id nvt_ts_id[] = { + { NVT_SPI_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, nvt_ts_id); + +#ifdef CONFIG_OF +static struct of_device_id nvt_match_table[] = { + { .compatible = "novatek,NVT-ts-spi",}, + { }, +}; +MODULE_DEVICE_TABLE(of, nvt_match_table); +#endif + +static struct spi_driver nvt_spi_driver = { + .probe = nvt_ts_probe, + .remove = nvt_ts_remove, + .shutdown = nvt_ts_shutdown, + .id_table = nvt_ts_id, + .driver = { + .name = NVT_SPI_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &nvt_dev_pm_ops, +#endif +#ifdef CONFIG_OF + .of_match_table = nvt_match_table, +#endif + }, +}; + +module_spi_driver(nvt_spi_driver); + +MODULE_DESCRIPTION("Novatek Touchscreen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/nt36523/nt36xxx.h b/drivers/input/touchscreen/nt36523/nt36xxx.h new file mode 100644 index 000000000000..530d3661365d --- /dev/null +++ b/drivers/input/touchscreen/nt36523/nt36xxx.h @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2010 - 2018 Novatek, Inc. + * Copyright (C) 2021 XiaoMi, Inc. + * + * $Revision: 69262 $ + * $Date: 2020-09-23 15:07:14 +0800 (週三, 23 九月 2020) $ + * + * 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 useful, 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 _LINUX_NVT_TOUCH_H +#define _LINUX_NVT_TOUCH_H + +#include +#include +#include +#include +#include + +#include "nt36xxx_mem_map.h" + +#define NVT_DEBUG 1 + +//---GPIO number--- +#define NVTTOUCH_RST_PIN 980 +#define NVTTOUCH_INT_PIN 943 + +#define PINCTRL_STATE_ACTIVE "pmx_ts_active" +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" + +//---INT trigger mode--- +//#define IRQ_TYPE_EDGE_RISING 1 +//#define IRQ_TYPE_EDGE_FALLING 2 +#define INT_TRIGGER_TYPE IRQ_TYPE_EDGE_RISING + + +//---SPI driver info.--- +#define NVT_SPI_NAME "NVT-ts-spi" + +#if NVT_DEBUG +#define NVT_LOG(fmt, args...) pr_err("[%s] %s %d: " fmt, NVT_SPI_NAME, __func__, __LINE__, ##args) +#else +#define NVT_LOG(fmt, args...) pr_info("[%s] %s %d: " fmt, NVT_SPI_NAME, __func__, __LINE__, ##args) +#endif +#define NVT_ERR(fmt, args...) pr_err("[%s] %s %d: " fmt, NVT_SPI_NAME, __func__, __LINE__, ##args) + +//---Input device info.--- +#define NVT_TS_NAME "NVTCapacitiveTouchScreen" +#define NVT_PEN_NAME "NVTCapacitivePen" + +//---Touch info.--- +#define TOUCH_DEFAULT_MAX_WIDTH 1600 +#define TOUCH_DEFAULT_MAX_HEIGHT 2560 +#define TOUCH_MAX_FINGER_NUM 10 +#define TOUCH_KEY_NUM 0 +#if TOUCH_KEY_NUM > 0 +extern const uint16_t touch_key_array[TOUCH_KEY_NUM]; +#endif +#define TOUCH_FORCE_NUM 1000 +//---for Pen--- +#define PEN_PRESSURE_MAX (4095) +#define PEN_DISTANCE_MAX (1) +#define PEN_TILT_MIN (-60) +#define PEN_TILT_MAX (60) +//---for pen resolution--- +#define PANEL_DEFAULT_WIDTH_MM 148 // 148mm +#define PANEL_DEFAULT_HEIGHT_MM 237 // 237mm +/* Enable only when module have tp reset pin and connected to host */ +#define NVT_TOUCH_SUPPORT_HW_RST 0 + +//---Customerized func.--- +#define NVT_TOUCH_MP 0 +#define NVT_TOUCH_MP_SETTING_CRITERIA_FROM_CSV 0 +#define MT_PROTOCOL_B 1 +#define FUNCPAGE_PALM 4 +#define PACKET_PALM_ON 3 +#define PACKET_PALM_OFF 4 + +#define BOOT_UPDATE_FIRMWARE 1 +#define DEFAULT_BOOT_UPDATE_FIRMWARE_NAME "novatek/nt36523.bin" +#define DEFAULT_MP_UPDATE_FIRMWARE_NAME "novatek_ts_mp.bin" + +//---ESD Protect.--- +#define NVT_TOUCH_ESD_PROTECT 1 +#define NVT_TOUCH_ESD_CHECK_PERIOD 1500 /* ms */ +#define NVT_TOUCH_WDT_RECOVERY 1 + +enum nvt_ic_state { + NVT_IC_SUSPEND_IN, + NVT_IC_SUSPEND_OUT, + NVT_IC_RESUME_IN, + NVT_IC_RESUME_OUT, + NVT_IC_INIT, +}; + +struct nvt_config_info { + u8 tp_vendor; + u8 tp_color; + u8 display_maker; + u8 glass_vendor; + const char *nvt_fw_name; + const char *nvt_mp_name; + const char *nvt_limit_name; +}; + +struct nvt_ts_data { + struct spi_device *client; + struct input_dev *input_dev; + struct delayed_work nvt_fwu_work; + struct work_struct switch_mode_work; + struct work_struct pen_charge_state_change_work; + bool pen_is_charge; + struct notifier_block pen_charge_state_notifier; + uint16_t addr; + int8_t phys[32]; +#if defined(CONFIG_FB) +#ifdef CONFIG_DRM + struct notifier_block drm_notif; +#else + struct notifier_block fb_notif; +#endif +#endif + uint32_t config_array_size; + struct nvt_config_info *config_array; + const char *fw_name; + bool lkdown_readed; + uint8_t fw_ver; + uint8_t x_num; + uint8_t y_num; + int ic_state; + uint16_t abs_x_max; + uint16_t abs_y_max; + uint8_t max_touch_num; + uint8_t max_button_num; + uint32_t int_trigger_type; + int32_t irq_gpio; + uint32_t irq_flags; + int32_t reset_gpio; + uint32_t reset_flags; + struct mutex lock; + const struct nvt_ts_mem_map *mmap; + uint8_t carrier_system; + uint8_t hw_crc; + uint16_t nvt_pid; + uint8_t *rbuf; + uint8_t *xbuf; + struct mutex xbuf_lock; + bool irq_enabled; + uint8_t cascade; + bool pen_support; + bool wgp_stylus; + uint8_t x_gang_num; + uint8_t y_gang_num; + struct input_dev *pen_input_dev; + bool pen_input_dev_enable; + int8_t pen_phys[32]; + struct workqueue_struct *event_wq; + struct work_struct suspend_work; + struct work_struct resume_work; + int result_type; + int panel_index; + uint32_t spi_max_freq; + int db_wakeup; + uint8_t debug_flag; + bool fw_debug; + bool dev_pm_suspend; + struct completion dev_pm_suspend_completion; + bool palm_sensor_switch; + int gesture_command_delayed; + struct pinctrl *ts_pinctrl; + struct pinctrl_state *pinctrl_state_active; + struct pinctrl_state *pinctrl_state_suspend; +}; + +typedef enum { + RESET_STATE_INIT = 0xA0,// IC reset + RESET_STATE_REK, // ReK baseline + RESET_STATE_REK_FINISH, // baseline is ready + RESET_STATE_NORMAL_RUN, // normal run + RESET_STATE_MAX = 0xAF +} RST_COMPLETE_STATE; + +typedef enum { + EVENT_MAP_HOST_CMD = 0x50, + EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE = 0x51, + EVENT_MAP_RESET_COMPLETE = 0x60, + EVENT_MAP_FWINFO = 0x78, + EVENT_MAP_PROJECTID = 0x9A, +} SPI_EVENT_MAP; + +//---SPI READ/WRITE--- +#define SPI_WRITE_MASK(a) (a | 0x80) +#define SPI_READ_MASK(a) (a & 0x7F) + +#define DUMMY_BYTES (1) +#define NVT_TRANSFER_LEN (63*1024) +#define NVT_READ_LEN (2*1024) + +typedef enum { + NVTWRITE = 0, + NVTREAD = 1 +} NVT_SPI_RW; + +//---extern structures--- +extern struct nvt_ts_data *ts; + +//---extern functions--- +int32_t CTP_SPI_READ(struct spi_device *client, uint8_t *buf, uint16_t len); +int32_t CTP_SPI_WRITE(struct spi_device *client, uint8_t *buf, uint16_t len); +void nvt_bootloader_reset(void); +void nvt_eng_reset(void); +void nvt_sw_reset(void); +void nvt_sw_reset_idle(void); +void nvt_boot_ready(void); +void nvt_bld_crc_enable(void); +void nvt_fw_crc_enable(void); +void nvt_tx_auto_copy_mode(void); +void nvt_set_dbgfw_status(bool enable); +void nvt_match_fw(void); +int32_t nvt_update_firmware(const char *firmware_name); +int32_t nvt_check_fw_reset_state(RST_COMPLETE_STATE check_reset_state); +int32_t nvt_get_fw_info(void); +int32_t nvt_clear_fw_status(void); +int32_t nvt_check_fw_status(void); +int32_t nvt_check_spi_dma_tx_info(void); +int32_t nvt_set_page(uint32_t addr); +int32_t nvt_write_addr(uint32_t addr, uint8_t data); +int32_t nvt_read_pid(void); +bool nvt_get_dbgfw_status(void); +int32_t nvt_set_pocket_palm_switch(uint8_t pocket_palm_switch); +void Boot_Update_Firmware(struct work_struct *work); +#if NVT_TOUCH_ESD_PROTECT +extern void nvt_esd_check_enable(uint8_t enable); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + +#endif /* _LINUX_NVT_TOUCH_H */ \ No newline at end of file diff --git a/drivers/input/touchscreen/nt36523/nt36xxx_fw_update.c b/drivers/input/touchscreen/nt36523/nt36xxx_fw_update.c new file mode 100644 index 000000000000..f5da9275542d --- /dev/null +++ b/drivers/input/touchscreen/nt36523/nt36xxx_fw_update.c @@ -0,0 +1,841 @@ +/* + * Copyright (C) 2010 - 2018 Novatek, Inc. + * Copyright (C) 2021 XiaoMi, Inc. + * + * $Revision: 68983 $ + * $Date: 2020-09-17 09:43:23 +0800 (週四, 17 九月 2020) $ + * + * 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 useful, 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 +#include + +#include "nt36xxx.h" + +#if BOOT_UPDATE_FIRMWARE + +#define SIZE_4KB 4096 +#define FLASH_SECTOR_SIZE SIZE_4KB +#define FW_BIN_VER_OFFSET (fw_need_write_size - SIZE_4KB) +#define FW_BIN_VER_BAR_OFFSET (FW_BIN_VER_OFFSET + 1) +#define NVT_FLASH_END_FLAG_LEN 3 +#define NVT_FLASH_END_FLAG_ADDR (fw_need_write_size - NVT_FLASH_END_FLAG_LEN) + +static ktime_t start, end; +const struct firmware *fw_entry = NULL; +static size_t fw_need_write_size = 0; +static uint8_t *fwbuf = NULL; + +struct nvt_ts_bin_map { + char name[12]; + uint32_t BIN_addr; + uint32_t SRAM_addr; + uint32_t size; + uint32_t crc; +}; + +static struct nvt_ts_bin_map *bin_map; + +/******************************************************* +Description: + Novatek touchscreen init variable and allocate buffer +for download firmware function. + +return: + n.a. +*******************************************************/ +static int32_t nvt_download_init(void) +{ + /* allocate buffer for transfer firmware */ + //NVT_LOG("NVT_TRANSFER_LEN = 0x%06X\n", NVT_TRANSFER_LEN); + + if (fwbuf == NULL) { + fwbuf = (uint8_t *)kzalloc((NVT_TRANSFER_LEN + 1 + DUMMY_BYTES), GFP_KERNEL); + if(fwbuf == NULL) { + NVT_ERR("kzalloc for fwbuf failed!\n"); + return -ENOMEM; + } + } + + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen checksum function. Calculate bin +file checksum for comparison. + +return: + n.a. +*******************************************************/ +static uint32_t CheckSum(const u8 *data, size_t len) +{ + uint32_t i = 0; + uint32_t checksum = 0; + + for (i = 0 ; i < len+1 ; i++) + checksum += data[i]; + + checksum += len; + checksum = ~checksum +1; + + return checksum; +} + +static uint32_t byte_to_word(const uint8_t *data) +{ + return data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24); +} + +/******************************************************* +Description: + Novatek touchscreen parsing bin header function. + +return: + n.a. +*******************************************************/ +static uint32_t partition = 0; +static uint8_t ilm_dlm_num = 2; +static uint8_t cascade_2nd_header_info = 0; +static int32_t nvt_bin_header_parser(const u8 *fwdata, size_t fwsize) +{ + uint32_t list = 0; + uint32_t pos = 0x00; + uint32_t end = 0x00; + uint8_t info_sec_num = 0; + uint8_t ovly_sec_num = 0; + uint8_t ovly_info = 0; + uint8_t find_bin_header = 0; + + /* Find the header size */ + end = fwdata[0] + (fwdata[1] << 8) + (fwdata[2] << 16) + (fwdata[3] << 24); + + /* check cascade next header */ + cascade_2nd_header_info = (fwdata[0x20] & 0x02) >> 1; + NVT_LOG("cascade_2nd_header_info = %d\n", cascade_2nd_header_info); + + if (cascade_2nd_header_info) { + pos = 0x30; // info section start at 0x30 offset + while (pos < (end / 2)) { + info_sec_num ++; + pos += 0x10; /* each header info is 16 bytes */ + } + + info_sec_num = info_sec_num + 1; //next header section + } else { + pos = 0x30; // info section start at 0x30 offset + while (pos < end) { + info_sec_num ++; + pos += 0x10; /* each header info is 16 bytes */ + } + } + + /* + * Find the DLM OVLY section + * [0:3] Overlay Section Number + * [4] Overlay Info + */ + ovly_info = (fwdata[0x28] & 0x10) >> 4; + ovly_sec_num = (ovly_info) ? (fwdata[0x28] & 0x0F) : 0; + + /* + * calculate all partition number + * ilm_dlm_num (ILM & DLM) + ovly_sec_num + info_sec_num + */ + partition = ilm_dlm_num + ovly_sec_num + info_sec_num; + NVT_LOG("ovly_info = %d, ilm_dlm_num = %d, ovly_sec_num = %d, info_sec_num = %d, partition = %d\n", + ovly_info, ilm_dlm_num, ovly_sec_num, info_sec_num, partition); + + /* allocated memory for header info */ + bin_map = (struct nvt_ts_bin_map *)kzalloc((partition+1) * sizeof(struct nvt_ts_bin_map), GFP_KERNEL); + if(bin_map == NULL) { + NVT_ERR("kzalloc for bin_map failed!\n"); + return -ENOMEM; + } + + for (list = 0; list < partition; list++) { + /* + * [1] parsing ILM & DLM header info + * BIN_addr : SRAM_addr : size (12-bytes) + * crc located at 0x18 & 0x1C + */ + if (list < ilm_dlm_num) { + bin_map[list].BIN_addr = byte_to_word(&fwdata[0 + list*12]); + bin_map[list].SRAM_addr = byte_to_word(&fwdata[4 + list*12]); + bin_map[list].size = byte_to_word(&fwdata[8 + list*12]); + if (ts->hw_crc) + bin_map[list].crc = byte_to_word(&fwdata[0x18 + list*4]); + else { //ts->hw_crc + if ((bin_map[list].BIN_addr + bin_map[list].size) < fwsize) + bin_map[list].crc = CheckSum(&fwdata[bin_map[list].BIN_addr], bin_map[list].size); + else { + NVT_ERR("access range (0x%08X to 0x%08X) is larger than bin size!\n", + bin_map[list].BIN_addr, bin_map[list].BIN_addr + bin_map[list].size); + return -EINVAL; + } + } //ts->hw_crc + if (list == 0) + sprintf(bin_map[list].name, "ILM"); + else if (list == 1) + sprintf(bin_map[list].name, "DLM"); + } + + /* + * [2] parsing others header info + * SRAM_addr : size : BIN_addr : crc (16-bytes) + */ + if ((list >= ilm_dlm_num) && (list < (ilm_dlm_num + info_sec_num))) { + if (find_bin_header == 0) { + /* others partition located at 0x30 offset */ + pos = 0x30 + (0x10 * (list - ilm_dlm_num)); + } else if (find_bin_header && cascade_2nd_header_info) { + /* cascade 2nd header info */ + pos = end - 0x10; + } + + bin_map[list].SRAM_addr = byte_to_word(&fwdata[pos]); + bin_map[list].size = byte_to_word(&fwdata[pos+4]); + bin_map[list].BIN_addr = byte_to_word(&fwdata[pos+8]); + if (ts->hw_crc) + bin_map[list].crc = byte_to_word(&fwdata[pos+12]); + else { //ts->hw_crc + if ((bin_map[list].BIN_addr + bin_map[list].size) < fwsize) + bin_map[list].crc = CheckSum(&fwdata[bin_map[list].BIN_addr], bin_map[list].size); + else { + NVT_ERR("access range (0x%08X to 0x%08X) is larger than bin size!\n", + bin_map[list].BIN_addr, bin_map[list].BIN_addr + bin_map[list].size); + return -EINVAL; + } + } //ts->hw_crc + /* detect header end to protect parser function */ + if ((bin_map[list].BIN_addr < end) && (bin_map[list].size != 0)) { + sprintf(bin_map[list].name, "Header"); + find_bin_header = 1; + } else { + sprintf(bin_map[list].name, "Info-%d", (list - ilm_dlm_num)); + } + } + + /* + * [3] parsing overlay section header info + * SRAM_addr : size : BIN_addr : crc (16-bytes) + */ + if (list >= (ilm_dlm_num + info_sec_num)) { + /* overlay info located at DLM (list = 1) start addr */ + pos = bin_map[1].BIN_addr + (0x10 * (list- ilm_dlm_num - info_sec_num)); + + bin_map[list].SRAM_addr = byte_to_word(&fwdata[pos]); + bin_map[list].size = byte_to_word(&fwdata[pos+4]); + bin_map[list].BIN_addr = byte_to_word(&fwdata[pos+8]); + if (ts->hw_crc) + bin_map[list].crc = byte_to_word(&fwdata[pos+12]); + else { //ts->hw_crc + if ((bin_map[list].BIN_addr + bin_map[list].size) < fwsize) + bin_map[list].crc = CheckSum(&fwdata[bin_map[list].BIN_addr], bin_map[list].size); + else { + NVT_ERR("access range (0x%08X to 0x%08X) is larger than bin size!\n", + bin_map[list].BIN_addr, bin_map[list].BIN_addr + bin_map[list].size); + return -EINVAL; + } + } //ts->hw_crc + sprintf(bin_map[list].name, "Overlay-%d", (list- ilm_dlm_num - info_sec_num)); + } + + /* BIN size error detect */ + if ((bin_map[list].BIN_addr + bin_map[list].size) > fwsize) { + NVT_ERR("access range (0x%08X to 0x%08X) is larger than bin size!\n", + bin_map[list].BIN_addr, bin_map[list].BIN_addr + bin_map[list].size); + return -EINVAL; + } + +// NVT_LOG("[%d][%s] SRAM (0x%08X), SIZE (0x%08X), BIN (0x%08X), CRC (0x%08X)\n", +// list, bin_map[list].name, +// bin_map[list].SRAM_addr, bin_map[list].size, bin_map[list].BIN_addr, bin_map[list].crc); + } + + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen release update firmware function. + +return: + n.a. +*******************************************************/ +static void update_firmware_release(void) +{ + if (fw_entry) { + release_firmware(fw_entry); + } + + fw_entry = NULL; +} + +/******************************************************* +Description: + Novatek touchscreen request update firmware function. + +return: + Executive outcomes. 0---succeed. -1,-22---failed. +*******************************************************/ +static int32_t update_firmware_request(const char *filename) +{ + uint8_t retry = 0; + int32_t ret = 0; + + if (NULL == filename) { + return -ENOENT; + } + + while (1) { + NVT_LOG("filename is %s\n", filename); + + ret = request_firmware(&fw_entry, filename, &ts->client->dev); + if (ret) { + NVT_ERR("firmware load failed, ret=%d\n", ret); + goto request_fail; + } + + fw_need_write_size = fw_entry->size; + + // check if FW version add FW version bar equals 0xFF + if (*(fw_entry->data + FW_BIN_VER_OFFSET) + *(fw_entry->data + FW_BIN_VER_BAR_OFFSET) != 0xFF) { + NVT_ERR("bin file FW_VER + FW_VER_BAR should be 0xFF!\n"); + NVT_ERR("FW_VER=0x%02X, FW_VER_BAR=0x%02X\n", *(fw_entry->data+FW_BIN_VER_OFFSET), *(fw_entry->data+FW_BIN_VER_BAR_OFFSET)); + ret = -ENOEXEC; + goto invalid; + } + + /* BIN Header Parser */ + ret = nvt_bin_header_parser(fw_entry->data, fw_entry->size); + if (ret) { + NVT_ERR("bin header parser failed\n"); + goto invalid; + } else { + break; + } + +invalid: + update_firmware_release(); + if (!IS_ERR_OR_NULL(bin_map)) { + kfree(bin_map); + bin_map = NULL; + } + +request_fail: + retry++; + if(unlikely(retry > 2)) { + NVT_ERR("error, retry=%d\n", retry); + break; + } + } + + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen write data to sram function. + +- fwdata : The buffer is written +- SRAM_addr: The sram destination address +- size : Number of data bytes in @fwdata being written +- BIN_addr : The transferred data offset of @fwdata + +return: + Executive outcomes. 0---succeed. else---fail. +*******************************************************/ +static int32_t nvt_write_sram(const u8 *fwdata, + uint32_t SRAM_addr, uint32_t size, uint32_t BIN_addr) +{ + int32_t ret = 0; + uint32_t i = 0; + uint16_t len = 0; + int32_t count = 0; + + if (size % NVT_TRANSFER_LEN) + count = (size / NVT_TRANSFER_LEN) + 1; + else + count = (size / NVT_TRANSFER_LEN); + + for (i = 0 ; i < count ; i++) { + len = (size < NVT_TRANSFER_LEN) ? size : NVT_TRANSFER_LEN; + + //---set xdata index to start address of SRAM--- + ret = nvt_set_page(SRAM_addr); + if (ret) { + NVT_ERR("set page failed, ret = %d\n", ret); + return ret; + } + + //---write data into SRAM--- + fwbuf[0] = SRAM_addr & 0x7F; //offset + memcpy(fwbuf+1, &fwdata[BIN_addr], len); //payload + ret = CTP_SPI_WRITE(ts->client, fwbuf, len+1); + if (ret) { + NVT_ERR("write to sram failed, ret = %d\n", ret); + return ret; + } + + SRAM_addr += NVT_TRANSFER_LEN; + BIN_addr += NVT_TRANSFER_LEN; + size -= NVT_TRANSFER_LEN; + } + + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen nvt_write_firmware function to write +firmware into each partition. + +return: + n.a. +*******************************************************/ +static int32_t nvt_write_firmware(const u8 *fwdata, size_t fwsize) +{ + uint32_t list = 0; + char *name; + uint32_t BIN_addr, SRAM_addr, size; + int32_t ret = 0; + + memset(fwbuf, 0, (NVT_TRANSFER_LEN+1)); + + for (list = 0; list < partition; list++) { + /* initialize variable */ + SRAM_addr = bin_map[list].SRAM_addr; + size = bin_map[list].size; + BIN_addr = bin_map[list].BIN_addr; + name = bin_map[list].name; + +// NVT_LOG("[%d][%s] SRAM (0x%08X), SIZE (0x%08X), BIN (0x%08X)\n", +// list, name, SRAM_addr, size, BIN_addr); + + /* Check data size */ + if ((BIN_addr + size) > fwsize) { + NVT_ERR("access range (0x%08X to 0x%08X) is larger than bin size!\n", + BIN_addr, BIN_addr + size); + ret = -EINVAL; + goto out; + } + + /* ignore reserved partition (Reserved Partition size is zero) */ + if (!size) + continue; + else + size = size +1; + + /* write data to SRAM */ + ret = nvt_write_sram(fwdata, SRAM_addr, size, BIN_addr); + if (ret) { + NVT_ERR("sram program failed, ret = %d\n", ret); + goto out; + } + } + +out: + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen check checksum function. +This function will compare file checksum and fw checksum. + +return: + n.a. +*******************************************************/ +static int32_t nvt_check_fw_checksum(void) +{ + uint32_t fw_checksum = 0; + uint32_t len = partition*4; + uint32_t list = 0; + int32_t ret = 0; + + memset(fwbuf, 0, (len+1)); + + //---set xdata index to checksum--- + nvt_set_page(ts->mmap->R_ILM_CHECKSUM_ADDR); + + /* read checksum */ + fwbuf[0] = (ts->mmap->R_ILM_CHECKSUM_ADDR) & 0x7F; + ret = CTP_SPI_READ(ts->client, fwbuf, len+1); + if (ret) { + NVT_ERR("Read fw checksum failed\n"); + return ret; + } + + /* + * Compare each checksum from fw + * ILM + DLM + Overlay + Info + * ilm_dlm_num (ILM & DLM) + ovly_sec_num + info_sec_num + */ + for (list = 0; list < partition; list++) { + fw_checksum = byte_to_word(&fwbuf[1+list*4]); + + /* ignore reserved partition (Reserved Partition size is zero) */ + if(!bin_map[list].size) + continue; + + if (bin_map[list].crc != fw_checksum) { + NVT_ERR("[%d] BIN_checksum=0x%08X, FW_checksum=0x%08X\n", + list, bin_map[list].crc, fw_checksum); + ret = -EIO; + } + } + + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen set bootload crc reg bank function. +This function will set hw crc reg before enable crc function. + +return: + n.a. +*******************************************************/ +static void nvt_set_bld_crc_bank(uint32_t DES_ADDR, uint32_t SRAM_ADDR, + uint32_t LENGTH_ADDR, uint32_t size, + uint32_t G_CHECKSUM_ADDR, uint32_t crc) +{ + /* write destination address */ + nvt_set_page(DES_ADDR); + fwbuf[0] = DES_ADDR & 0x7F; + fwbuf[1] = (SRAM_ADDR) & 0xFF; + fwbuf[2] = (SRAM_ADDR >> 8) & 0xFF; + fwbuf[3] = (SRAM_ADDR >> 16) & 0xFF; + CTP_SPI_WRITE(ts->client, fwbuf, 4); + + /* write length */ + //nvt_set_page(LENGTH_ADDR); + fwbuf[0] = LENGTH_ADDR & 0x7F; + fwbuf[1] = (size) & 0xFF; + fwbuf[2] = (size >> 8) & 0xFF; + fwbuf[3] = (size >> 16) & 0x01; + if (ts->hw_crc == 1) { + CTP_SPI_WRITE(ts->client, fwbuf, 3); + } else if (ts->hw_crc > 1) { + CTP_SPI_WRITE(ts->client, fwbuf, 4); + } + + /* write golden dlm checksum */ + //nvt_set_page(G_CHECKSUM_ADDR); + fwbuf[0] = G_CHECKSUM_ADDR & 0x7F; + fwbuf[1] = (crc) & 0xFF; + fwbuf[2] = (crc >> 8) & 0xFF; + fwbuf[3] = (crc >> 16) & 0xFF; + fwbuf[4] = (crc >> 24) & 0xFF; + CTP_SPI_WRITE(ts->client, fwbuf, 5); + + return; +} + +/******************************************************* +Description: + Novatek touchscreen set BLD hw crc function. +This function will set ILM and DLM crc information to register. + +return: + n.a. +*******************************************************/ +static void nvt_set_bld_hw_crc(void) +{ + /* [0] ILM */ + /* write register bank */ + nvt_set_bld_crc_bank(ts->mmap->ILM_DES_ADDR, bin_map[0].SRAM_addr, + ts->mmap->ILM_LENGTH_ADDR, bin_map[0].size, + ts->mmap->G_ILM_CHECKSUM_ADDR, bin_map[0].crc); + + /* [1] DLM */ + /* write register bank */ + nvt_set_bld_crc_bank(ts->mmap->DLM_DES_ADDR, bin_map[1].SRAM_addr, + ts->mmap->DLM_LENGTH_ADDR, bin_map[1].size, + ts->mmap->G_DLM_CHECKSUM_ADDR, bin_map[1].crc); +} + +/******************************************************* +Description: + Novatek touchscreen read BLD hw crc info function. +This function will check crc results from register. + +return: + n.a. +*******************************************************/ +static void nvt_read_bld_hw_crc(void) +{ + uint8_t buf[8] = {0}; + uint32_t g_crc = 0, r_crc = 0; + + /* CRC Flag */ + nvt_set_page(ts->mmap->BLD_ILM_DLM_CRC_ADDR); + buf[0] = ts->mmap->BLD_ILM_DLM_CRC_ADDR & 0x7F; + buf[1] = 0x00; + CTP_SPI_READ(ts->client, buf, 2); + NVT_ERR("crc_done = %d, ilm_crc_flag = %d, dlm_crc_flag = %d\n", + (buf[1] >> 2) & 0x01, (buf[1] >> 0) & 0x01, (buf[1] >> 1) & 0x01); + + /* ILM CRC */ + nvt_set_page(ts->mmap->G_ILM_CHECKSUM_ADDR); + buf[0] = ts->mmap->G_ILM_CHECKSUM_ADDR & 0x7F; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + CTP_SPI_READ(ts->client, buf, 5); + g_crc = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24); + + nvt_set_page(ts->mmap->R_ILM_CHECKSUM_ADDR); + buf[0] = ts->mmap->R_ILM_CHECKSUM_ADDR & 0x7F; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + CTP_SPI_READ(ts->client, buf, 5); + r_crc = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24); + + NVT_ERR("ilm: bin crc = 0x%08X, golden = 0x%08X, result = 0x%08X\n", + bin_map[0].crc, g_crc, r_crc); + + /* DLM CRC */ + nvt_set_page(ts->mmap->G_DLM_CHECKSUM_ADDR); + buf[0] = ts->mmap->G_DLM_CHECKSUM_ADDR & 0x7F; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + CTP_SPI_READ(ts->client, buf, 5); + g_crc = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24); + + nvt_set_page(ts->mmap->R_DLM_CHECKSUM_ADDR); + buf[0] = ts->mmap->R_DLM_CHECKSUM_ADDR & 0x7F; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + CTP_SPI_READ(ts->client, buf, 5); + r_crc = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24); + + NVT_ERR("dlm: bin crc = 0x%08X, golden = 0x%08X, result = 0x%08X\n", + bin_map[1].crc, g_crc, r_crc); + + return; +} + +/******************************************************* +Description: + Novatek touchscreen Download_Firmware with HW CRC +function. It's complete download firmware flow. + +return: + Executive outcomes. 0---succeed. else---fail. +*******************************************************/ +static int32_t nvt_download_firmware_hw_crc(void) +{ + uint8_t retry = 0; + int32_t ret = 0; + + start = ktime_get(); + + while (1) { + /* bootloader reset to reset MCU */ + nvt_bootloader_reset(); + + /* set ilm & dlm reg bank */ + nvt_set_bld_hw_crc(); + + /* Start to write firmware process */ + if (cascade_2nd_header_info) { + /* for cascade */ + nvt_tx_auto_copy_mode(); + + ret = nvt_write_firmware(fw_entry->data, fw_entry->size); + if (ret) { + NVT_ERR("Write_Firmware failed. (%d)\n", ret); + goto fail; + } + + ret = nvt_check_spi_dma_tx_info(); + if (ret) { + NVT_ERR("spi dma tx info failed. (%d)\n", ret); + goto fail; + } + } else { + ret = nvt_write_firmware(fw_entry->data, fw_entry->size); + if (ret) { + NVT_ERR("Write_Firmware failed. (%d)\n", ret); + goto fail; + } + } + + /* enable hw bld crc function */ + nvt_bld_crc_enable(); + + /* clear fw reset status & enable fw crc check */ + nvt_fw_crc_enable(); + + /* Set Boot Ready Bit */ + nvt_boot_ready(); + + ret = nvt_check_fw_reset_state(RESET_STATE_INIT); + if (ret) { + NVT_ERR("nvt_check_fw_reset_state failed. (%d)\n", ret); + goto fail; + } else { + break; + } + +fail: + retry++; + if(unlikely(retry > 2)) { + NVT_ERR("error, retry=%d\n", retry); + nvt_read_bld_hw_crc(); + break; + } + } + + end = ktime_get(); + + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen Download_Firmware function. It's +complete download firmware flow. + +return: + n.a. +*******************************************************/ +static int32_t nvt_download_firmware(void) +{ + uint8_t retry = 0; + int32_t ret = 0; + + start = ktime_get(); + + while (1) { + /* + * Send eng reset cmd before download FW + * Keep TP_RESX low when send eng reset cmd + */ +#if NVT_TOUCH_SUPPORT_HW_RST + gpio_set_value(ts->reset_gpio, 0); + mdelay(1); //wait 1ms +#endif + nvt_eng_reset(); +#if NVT_TOUCH_SUPPORT_HW_RST + gpio_set_value(ts->reset_gpio, 1); + mdelay(10); //wait tRT2BRST after TP_RST +#endif + nvt_bootloader_reset(); + + /* clear fw reset status */ + nvt_write_addr(ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_RESET_COMPLETE, 0x00); + + /* Start to write firmware process */ + ret = nvt_write_firmware(fw_entry->data, fw_entry->size); + if (ret) { + NVT_ERR("Write_Firmware failed. (%d)\n", ret); + goto fail; + } + + /* Set Boot Ready Bit */ + nvt_boot_ready(); + + ret = nvt_check_fw_reset_state(RESET_STATE_INIT); + if (ret) { + NVT_ERR("nvt_check_fw_reset_state failed. (%d)\n", ret); + goto fail; + } + + /* check fw checksum result */ + ret = nvt_check_fw_checksum(); + if (ret) { + NVT_ERR("firmware checksum not match, retry=%d\n", retry); + goto fail; + } else { + break; + } + +fail: + retry++; + if(unlikely(retry > 2)) { + NVT_ERR("error, retry=%d\n", retry); + break; + } + } + + end = ktime_get(); + + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen update firmware main function. + +return: + n.a. +*******************************************************/ +int32_t nvt_update_firmware(const char *firmware_name) +{ + int32_t ret = 0; + + // request bin file in "/etc/firmware" + ret = update_firmware_request(firmware_name); + if (ret) { + NVT_ERR("update_firmware_request failed. (%d)\n", ret); + goto request_firmware_fail; + } + + /* initial buffer and variable */ + ret = nvt_download_init(); + if (ret) { + NVT_ERR("Download Init failed. (%d)\n", ret); + goto download_fail; + } + + /* download firmware process */ + if (ts->hw_crc) + ret = nvt_download_firmware_hw_crc(); + else + ret = nvt_download_firmware(); + if (ret) { + NVT_ERR("Download Firmware failed. (%d)\n", ret); + goto download_fail; + } + + NVT_LOG("Update firmware success! <%ld us>\n", + (long) ktime_us_delta(end, start)); + + /* Get FW Info */ + ret = nvt_get_fw_info(); + if (ret) { + NVT_ERR("nvt_get_fw_info failed. (%d)\n", ret); + } + +download_fail: + if (!IS_ERR_OR_NULL(bin_map)) { + kfree(bin_map); + bin_map = NULL; + } + + update_firmware_release(); +request_firmware_fail: + + return ret; +} +#endif /* BOOT_UPDATE_FIRMWARE */ diff --git a/drivers/input/touchscreen/nt36523/nt36xxx_mem_map.h b/drivers/input/touchscreen/nt36523/nt36xxx_mem_map.h new file mode 100644 index 000000000000..3f4195d636a0 --- /dev/null +++ b/drivers/input/touchscreen/nt36523/nt36xxx_mem_map.h @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2010 - 2018 Novatek, Inc. + * Copyright (C) 2021 XiaoMi, Inc. + * + * $Revision: 57674 $ + * $Date: 2020-03-02 11:16:20 +0800 (週一, 02 三月 2020) $ + * + * 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 useful, 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. + * + */ + +#define CHIP_VER_TRIM_ADDR 0x3F004 +#define CHIP_VER_TRIM_OLD_ADDR 0x1F64E + +struct nvt_ts_mem_map { + uint32_t EVENT_BUF_ADDR; + uint32_t RAW_PIPE0_ADDR; + uint32_t RAW_PIPE1_ADDR; + uint32_t BASELINE_ADDR; + uint32_t BASELINE_BTN_ADDR; + uint32_t DIFF_PIPE0_ADDR; + uint32_t DIFF_PIPE1_ADDR; + uint32_t RAW_BTN_PIPE0_ADDR; + uint32_t RAW_BTN_PIPE1_ADDR; + uint32_t DIFF_BTN_PIPE0_ADDR; + uint32_t DIFF_BTN_PIPE1_ADDR; + uint32_t PEN_2D_BL_TIP_X_ADDR; + uint32_t PEN_2D_BL_TIP_Y_ADDR; + uint32_t PEN_2D_BL_RING_X_ADDR; + uint32_t PEN_2D_BL_RING_Y_ADDR; + uint32_t PEN_2D_DIFF_TIP_X_ADDR; + uint32_t PEN_2D_DIFF_TIP_Y_ADDR; + uint32_t PEN_2D_DIFF_RING_X_ADDR; + uint32_t PEN_2D_DIFF_RING_Y_ADDR; + uint32_t PEN_2D_RAW_TIP_X_ADDR; + uint32_t PEN_2D_RAW_TIP_Y_ADDR; + uint32_t PEN_2D_RAW_RING_X_ADDR; + uint32_t PEN_2D_RAW_RING_Y_ADDR; + uint32_t PEN_1D_DIFF_TIP_X_ADDR; + uint32_t PEN_1D_DIFF_TIP_Y_ADDR; + uint32_t PEN_1D_DIFF_RING_X_ADDR; + uint32_t PEN_1D_DIFF_RING_Y_ADDR; + uint32_t READ_FLASH_CHECKSUM_ADDR; + uint32_t RW_FLASH_DATA_ADDR; + /* Phase 2 Host Download */ + uint32_t BOOT_RDY_ADDR; + uint32_t POR_CD_ADDR; + uint32_t TX_AUTO_COPY_EN; + uint32_t SPI_DMA_TX_INFO; + /* BLD CRC */ + uint32_t BLD_LENGTH_ADDR; + uint32_t ILM_LENGTH_ADDR; + uint32_t DLM_LENGTH_ADDR; + uint32_t BLD_DES_ADDR; + uint32_t ILM_DES_ADDR; + uint32_t DLM_DES_ADDR; + uint32_t G_ILM_CHECKSUM_ADDR; + uint32_t G_DLM_CHECKSUM_ADDR; + uint32_t R_ILM_CHECKSUM_ADDR; + uint32_t R_DLM_CHECKSUM_ADDR; + uint32_t BLD_CRC_EN_ADDR; + uint32_t DMA_CRC_EN_ADDR; + uint32_t BLD_ILM_DLM_CRC_ADDR; + uint32_t DMA_CRC_FLAG_ADDR; + uint32_t FW_HISTORY_ADDR; +}; + +struct nvt_ts_hw_info { + uint8_t carrier_system; + uint8_t hw_crc; +}; + +static const struct nvt_ts_mem_map NT36523_memory_map = { + .EVENT_BUF_ADDR = 0x2FE00, + .RAW_PIPE0_ADDR = 0x30FA0, + .RAW_PIPE1_ADDR = 0x30FA0, + .BASELINE_ADDR = 0x36510, + .BASELINE_BTN_ADDR = 0, + .DIFF_PIPE0_ADDR = 0x373E8, + .DIFF_PIPE1_ADDR = 0x38068, + .RAW_BTN_PIPE0_ADDR = 0, + .RAW_BTN_PIPE1_ADDR = 0, + .DIFF_BTN_PIPE0_ADDR = 0, + .DIFF_BTN_PIPE1_ADDR = 0, + .PEN_2D_BL_TIP_X_ADDR = 0x2988A, + .PEN_2D_BL_TIP_Y_ADDR = 0x29A1A, + .PEN_2D_BL_RING_X_ADDR = 0x29BAA, + .PEN_2D_BL_RING_Y_ADDR = 0x29D3A, + .PEN_2D_DIFF_TIP_X_ADDR = 0x29ECA, + .PEN_2D_DIFF_TIP_Y_ADDR = 0x2A05A, + .PEN_2D_DIFF_RING_X_ADDR = 0x2A1EA, + .PEN_2D_DIFF_RING_Y_ADDR = 0x2A37A, + .PEN_2D_RAW_TIP_X_ADDR = 0x2A50A, + .PEN_2D_RAW_TIP_Y_ADDR = 0x2A69A, + .PEN_2D_RAW_RING_X_ADDR = 0x2A82A, + .PEN_2D_RAW_RING_Y_ADDR = 0x2A9BA, + .PEN_1D_DIFF_TIP_X_ADDR = 0x2AB4A, + .PEN_1D_DIFF_TIP_Y_ADDR = 0x2ABAE, + .PEN_1D_DIFF_RING_X_ADDR = 0x2AC12, + .PEN_1D_DIFF_RING_Y_ADDR = 0x2AC76, + .READ_FLASH_CHECKSUM_ADDR = 0x24000, + .RW_FLASH_DATA_ADDR = 0x24002, + /* Phase 2 Host Download */ + .BOOT_RDY_ADDR = 0x3F10D, + .TX_AUTO_COPY_EN = 0x3F7E8, + .SPI_DMA_TX_INFO = 0x3F7F1, + /* BLD CRC */ + .BLD_LENGTH_ADDR = 0x3F138, //0x3F138 ~ 0x3F13A (3 bytes) + .ILM_LENGTH_ADDR = 0x3F118, //0x3F118 ~ 0x3F11A (3 bytes) + .DLM_LENGTH_ADDR = 0x3F130, //0x3F130 ~ 0x3F132 (3 bytes) + .BLD_DES_ADDR = 0x3F114, //0x3F114 ~ 0x3F116 (3 bytes) + .ILM_DES_ADDR = 0x3F128, //0x3F128 ~ 0x3F12A (3 bytes) + .DLM_DES_ADDR = 0x3F12C, //0x3F12C ~ 0x3F12E (3 bytes) + .G_ILM_CHECKSUM_ADDR = 0x3F100, //0x3F100 ~ 0x3F103 (4 bytes) + .G_DLM_CHECKSUM_ADDR = 0x3F104, //0x3F104 ~ 0x3F107 (4 bytes) + .R_ILM_CHECKSUM_ADDR = 0x3F120, //0x3F120 ~ 0x3F123 (4 bytes) + .R_DLM_CHECKSUM_ADDR = 0x3F124, //0x3F124 ~ 0x3F127 (4 bytes) + .BLD_CRC_EN_ADDR = 0x3F30E, + .DMA_CRC_EN_ADDR = 0x3F136, + .BLD_ILM_DLM_CRC_ADDR = 0x3F133, + .DMA_CRC_FLAG_ADDR = 0x3F134, + .FW_HISTORY_ADDR = 0x38D54, +}; + +static const struct nvt_ts_mem_map NT36526_memory_map = { + .EVENT_BUF_ADDR = 0x22D00, + .RAW_PIPE0_ADDR = 0x24000, + .RAW_PIPE1_ADDR = 0x24000, + .BASELINE_ADDR = 0x21758, + .BASELINE_BTN_ADDR = 0, + .DIFF_PIPE0_ADDR = 0x20AB0, + .DIFF_PIPE1_ADDR = 0x24AB0, + .RAW_BTN_PIPE0_ADDR = 0, + .RAW_BTN_PIPE1_ADDR = 0, + .DIFF_BTN_PIPE0_ADDR = 0, + .DIFF_BTN_PIPE1_ADDR = 0, + .READ_FLASH_CHECKSUM_ADDR = 0x24000, + .RW_FLASH_DATA_ADDR = 0x24002, + /* Phase 2 Host Download */ + .BOOT_RDY_ADDR = 0x3F10D, + /* BLD CRC */ + .BLD_LENGTH_ADDR = 0x3F138, //0x3F138 ~ 0x3F13A (3 bytes) + .ILM_LENGTH_ADDR = 0x3F118, //0x3F118 ~ 0x3F11A (3 bytes) + .DLM_LENGTH_ADDR = 0x3F130, //0x3F130 ~ 0x3F132 (3 bytes) + .BLD_DES_ADDR = 0x3F114, //0x3F114 ~ 0x3F116 (3 bytes) + .ILM_DES_ADDR = 0x3F128, //0x3F128 ~ 0x3F12A (3 bytes) + .DLM_DES_ADDR = 0x3F12C, //0x3F12C ~ 0x3F12E (3 bytes) + .G_ILM_CHECKSUM_ADDR = 0x3F100, //0x3F100 ~ 0x3F103 (4 bytes) + .G_DLM_CHECKSUM_ADDR = 0x3F104, //0x3F104 ~ 0x3F107 (4 bytes) + .R_ILM_CHECKSUM_ADDR = 0x3F120, //0x3F120 ~ 0x3F123 (4 bytes) + .R_DLM_CHECKSUM_ADDR = 0x3F124, //0x3F124 ~ 0x3F127 (4 bytes) + .BLD_CRC_EN_ADDR = 0x3F30E, + .DMA_CRC_EN_ADDR = 0x3F136, + .BLD_ILM_DLM_CRC_ADDR = 0x3F133, + .DMA_CRC_FLAG_ADDR = 0x3F134, +}; + + +static const struct nvt_ts_mem_map NT36675_memory_map = { + .EVENT_BUF_ADDR = 0x22D00, + .RAW_PIPE0_ADDR = 0x24000, + .RAW_PIPE1_ADDR = 0x24000, + .BASELINE_ADDR = 0x21B90, + .BASELINE_BTN_ADDR = 0, + .DIFF_PIPE0_ADDR = 0x20C60, + .DIFF_PIPE1_ADDR = 0x24C60, + .RAW_BTN_PIPE0_ADDR = 0, + .RAW_BTN_PIPE1_ADDR = 0, + .DIFF_BTN_PIPE0_ADDR = 0, + .DIFF_BTN_PIPE1_ADDR = 0, + .READ_FLASH_CHECKSUM_ADDR = 0x24000, + .RW_FLASH_DATA_ADDR = 0x24002, + /* Phase 2 Host Download */ + .BOOT_RDY_ADDR = 0x3F10D, + /* BLD CRC */ + .BLD_LENGTH_ADDR = 0x3F138, //0x3F138 ~ 0x3F13A (3 bytes) + .ILM_LENGTH_ADDR = 0x3F118, //0x3F118 ~ 0x3F11A (3 bytes) + .DLM_LENGTH_ADDR = 0x3F130, //0x3F130 ~ 0x3F132 (3 bytes) + .BLD_DES_ADDR = 0x3F114, //0x3F114 ~ 0x3F116 (3 bytes) + .ILM_DES_ADDR = 0x3F128, //0x3F128 ~ 0x3F12A (3 bytes) + .DLM_DES_ADDR = 0x3F12C, //0x3F12C ~ 0x3F12E (3 bytes) + .G_ILM_CHECKSUM_ADDR = 0x3F100, //0x3F100 ~ 0x3F103 (4 bytes) + .G_DLM_CHECKSUM_ADDR = 0x3F104, //0x3F104 ~ 0x3F107 (4 bytes) + .R_ILM_CHECKSUM_ADDR = 0x3F120, //0x3F120 ~ 0x3F123 (4 bytes) + .R_DLM_CHECKSUM_ADDR = 0x3F124, //0x3F124 ~ 0x3F127 (4 bytes) + .BLD_CRC_EN_ADDR = 0x3F30E, + .DMA_CRC_EN_ADDR = 0x3F136, + .BLD_ILM_DLM_CRC_ADDR = 0x3F133, + .DMA_CRC_FLAG_ADDR = 0x3F134, +}; + + +static const struct nvt_ts_mem_map NT36672A_memory_map = { + .EVENT_BUF_ADDR = 0x21C00, + .RAW_PIPE0_ADDR = 0x20000, + .RAW_PIPE1_ADDR = 0x23000, + .BASELINE_ADDR = 0x20BFC, + .BASELINE_BTN_ADDR = 0x23BFC, + .DIFF_PIPE0_ADDR = 0x206DC, + .DIFF_PIPE1_ADDR = 0x236DC, + .RAW_BTN_PIPE0_ADDR = 0x20510, + .RAW_BTN_PIPE1_ADDR = 0x23510, + .DIFF_BTN_PIPE0_ADDR = 0x20BF0, + .DIFF_BTN_PIPE1_ADDR = 0x23BF0, + .READ_FLASH_CHECKSUM_ADDR = 0x24000, + .RW_FLASH_DATA_ADDR = 0x24002, + /* Phase 2 Host Download */ + .BOOT_RDY_ADDR = 0x3F10D, + /* BLD CRC */ + .BLD_LENGTH_ADDR = 0x3F10E, //0x3F10E ~ 0x3F10F (2 bytes) + .ILM_LENGTH_ADDR = 0x3F118, //0x3F118 ~ 0x3F119 (2 bytes) + .DLM_LENGTH_ADDR = 0x3F130, //0x3F130 ~ 0x3F131 (2 bytes) + .BLD_DES_ADDR = 0x3F114, //0x3F114 ~ 0x3F116 (3 bytes) + .ILM_DES_ADDR = 0x3F128, //0x3F128 ~ 0x3F12A (3 bytes) + .DLM_DES_ADDR = 0x3F12C, //0x3F12C ~ 0x3F12E (3 bytes) + .G_ILM_CHECKSUM_ADDR = 0x3F100, //0x3F100 ~ 0x3F103 (4 bytes) + .G_DLM_CHECKSUM_ADDR = 0x3F104, //0x3F104 ~ 0x3F107 (4 bytes) + .R_ILM_CHECKSUM_ADDR = 0x3F120, //0x3F120 ~ 0x3F123 (4 bytes) + .R_DLM_CHECKSUM_ADDR = 0x3F124, //0x3F124 ~ 0x3F127 (4 bytes) + .BLD_CRC_EN_ADDR = 0x3F30E, + .DMA_CRC_EN_ADDR = 0x3F132, + .BLD_ILM_DLM_CRC_ADDR = 0x3F133, + .DMA_CRC_FLAG_ADDR = 0x3F134, +}; + +static const struct nvt_ts_mem_map NT36772_memory_map = { + .EVENT_BUF_ADDR = 0x11E00, + .RAW_PIPE0_ADDR = 0x10000, + .RAW_PIPE1_ADDR = 0x12000, + .BASELINE_ADDR = 0x10E70, + .BASELINE_BTN_ADDR = 0x12E70, + .DIFF_PIPE0_ADDR = 0x10830, + .DIFF_PIPE1_ADDR = 0x12830, + .RAW_BTN_PIPE0_ADDR = 0x10E60, + .RAW_BTN_PIPE1_ADDR = 0x12E60, + .DIFF_BTN_PIPE0_ADDR = 0x10E68, + .DIFF_BTN_PIPE1_ADDR = 0x12E68, + .READ_FLASH_CHECKSUM_ADDR = 0x14000, + .RW_FLASH_DATA_ADDR = 0x14002, + /* Phase 2 Host Download */ + .BOOT_RDY_ADDR = 0x1F141, + .POR_CD_ADDR = 0x1F61C, + /* BLD CRC */ + .R_ILM_CHECKSUM_ADDR = 0x1BF00, +}; + +static const struct nvt_ts_mem_map NT36525_memory_map = { + .EVENT_BUF_ADDR = 0x11A00, + .RAW_PIPE0_ADDR = 0x10000, + .RAW_PIPE1_ADDR = 0x12000, + .BASELINE_ADDR = 0x10B08, + .BASELINE_BTN_ADDR = 0x12B08, + .DIFF_PIPE0_ADDR = 0x1064C, + .DIFF_PIPE1_ADDR = 0x1264C, + .RAW_BTN_PIPE0_ADDR = 0x10634, + .RAW_BTN_PIPE1_ADDR = 0x12634, + .DIFF_BTN_PIPE0_ADDR = 0x10AFC, + .DIFF_BTN_PIPE1_ADDR = 0x12AFC, + .READ_FLASH_CHECKSUM_ADDR = 0x14000, + .RW_FLASH_DATA_ADDR = 0x14002, + /* Phase 2 Host Download */ + .BOOT_RDY_ADDR = 0x1F141, + .POR_CD_ADDR = 0x1F61C, + /* BLD CRC */ + .R_ILM_CHECKSUM_ADDR = 0x1BF00, +}; + +static const struct nvt_ts_mem_map NT36676F_memory_map = { + .EVENT_BUF_ADDR = 0x11A00, + .RAW_PIPE0_ADDR = 0x10000, + .RAW_PIPE1_ADDR = 0x12000, + .BASELINE_ADDR = 0x10B08, + .BASELINE_BTN_ADDR = 0x12B08, + .DIFF_PIPE0_ADDR = 0x1064C, + .DIFF_PIPE1_ADDR = 0x1264C, + .RAW_BTN_PIPE0_ADDR = 0x10634, + .RAW_BTN_PIPE1_ADDR = 0x12634, + .DIFF_BTN_PIPE0_ADDR = 0x10AFC, + .DIFF_BTN_PIPE1_ADDR = 0x12AFC, + .READ_FLASH_CHECKSUM_ADDR = 0x14000, + .RW_FLASH_DATA_ADDR = 0x14002, +}; + +static struct nvt_ts_hw_info NT36523_hw_info = { + .carrier_system = 2, + .hw_crc = 2, +}; + +static struct nvt_ts_hw_info NT36526_hw_info = { + .carrier_system = 2, + .hw_crc = 2, +}; + +static struct nvt_ts_hw_info NT36675_hw_info = { + .carrier_system = 2, + .hw_crc = 2, +}; + +static struct nvt_ts_hw_info NT36672A_hw_info = { + .carrier_system = 0, + .hw_crc = 1, +}; + +static struct nvt_ts_hw_info NT36772_hw_info = { + .carrier_system = 0, + .hw_crc = 0, +}; + +static struct nvt_ts_hw_info NT36525_hw_info = { + .carrier_system = 0, + .hw_crc = 0, +}; + +static struct nvt_ts_hw_info NT36676F_hw_info = { + .carrier_system = 0, + .hw_crc = 0, +}; + +#define NVT_ID_BYTE_MAX 6 +struct nvt_ts_trim_id_table { + uint8_t id[NVT_ID_BYTE_MAX]; + uint8_t mask[NVT_ID_BYTE_MAX]; + const struct nvt_ts_mem_map *mmap; + const struct nvt_ts_hw_info *hwinfo; +}; + +static const struct nvt_ts_trim_id_table trim_id_table[] = { + {.id = {0x20, 0xFF, 0xFF, 0x23, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36523_memory_map, .hwinfo = &NT36523_hw_info}, + {.id = {0x0C, 0xFF, 0xFF, 0x23, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36523_memory_map, .hwinfo = &NT36523_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x23, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36523_memory_map, .hwinfo = &NT36523_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x23, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36523_memory_map, .hwinfo = &NT36523_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x23, 0x65, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36523_memory_map, .hwinfo = &NT36523_hw_info}, + {.id = {0x0C, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36675_memory_map, .hwinfo = &NT36675_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x26, 0x65, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36526_memory_map, .hwinfo = &NT36526_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x75, 0x66, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36675_memory_map, .hwinfo = &NT36675_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x82, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x25, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x72, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x82, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x70, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x70, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x72, 0x67, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x55, 0x00, 0xFF, 0x00, 0x00, 0x00}, .mask = {1, 1, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0x55, 0x72, 0xFF, 0x00, 0x00, 0x00}, .mask = {1, 1, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xAA, 0x00, 0xFF, 0x00, 0x00, 0x00}, .mask = {1, 1, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xAA, 0x72, 0xFF, 0x00, 0x00, 0x00}, .mask = {1, 1, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x72, 0x67, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x70, 0x66, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x70, 0x67, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x25, 0x65, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36525_memory_map, .hwinfo = &NT36525_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x76, 0x66, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36676F_memory_map, .hwinfo = &NT36676F_hw_info} +}; diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index f0ede1cecb6a..dd557defcb66 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -1156,6 +1156,14 @@ config BATTERY_CHARGER_SURFACE_RT This driver can also be built as a module. If so, the module will be called surface-rt-ec. +config BATTERY_QCOM_FG + tristate "Qualcomm PMIC fuel gauge driver" + depends on MFD_SPMI_PMIC + help + Say Y here to enable the Qualcomm PMIC Fuel Gauge driver. This + adds support for battery fuel gauging and state of charge of + a battery connected to the fuel gauge. + config CHARGER_QCOM_SMB2 tristate "Qualcomm PMI8998 PMIC charger driver" depends on MFD_SPMI_PMIC diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 31fe4a145929..d052acdf1569 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -129,6 +129,7 @@ obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o obj-$(CONFIG_BATTERY_CHARGER_SURFACE_RT) += surface-rt-ec.o obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o +obj-$(CONFIG_BATTERY_QCOM_FG) += qcom_fg.o obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_smbx.o obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o obj-$(CONFIG_MACSMC_POWER) += macsmc-power.o diff --git a/drivers/power/supply/qcom_fg.c b/drivers/power/supply/qcom_fg.c new file mode 100644 index 000000000000..1282d9519ea8 --- /dev/null +++ b/drivers/power/supply/qcom_fg.c @@ -0,0 +1,1386 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* SOC */ +#define BATT_MONOTONIC_SOC 0x009 + +/* BATT */ +#define PARAM_ADDR_BATT_TEMP 0x150 +#define BATT_INFO_JEITA_COLD 0x162 +#define BATT_INFO_JEITA_COOL 0x163 +#define BATT_INFO_JEITA_WARM 0x164 +#define BATT_INFO_JEITA_HOT 0x165 +#define PARAM_ADDR_BATT_VOLTAGE 0x1a0 +#define PARAM_ADDR_BATT_CURRENT 0x1a2 + +/* RRADC */ +#define ADC_RR_BATT_TEMP_LSB 0x288 + +/* MEMIF */ +#define MEM_INTF_STS(chip) (chip->ops->memif_base + 0x10) +#define MEM_INTF_CFG(chip) (chip->ops->memif_base + 0x50) +#define MEM_INTF_CTL(chip) (chip->ops->memif_base + 0x51) +#define MEM_INTF_IMA_CFG(chip) (chip->ops->memif_base + 0x52) +#define MEM_INTF_IMA_EXP_STS(chip) (chip->ops->memif_base + 0x55) +#define MEM_INTF_IMA_HW_STS(chip) (chip->ops->memif_base + 0x56) +#define MEM_INTF_IMA_ERR_STS(chip) (chip->ops->memif_base + 0x5f) +#define MEM_INTF_IMA_BYTE_EN(chip) (chip->ops->memif_base + 0x60) +#define MEM_INTF_ADDR_LSB(chip) (chip->ops->memif_base + 0x61) +#define MEM_INTF_RD_DATA0(chip) (chip->ops->memif_base + 0x67) +#define MEM_INTF_WR_DATA0(chip) (chip->ops->memif_base + 0x63) +#define MEM_IF_DMA_STS(chip) (chip->ops->memif_base + 0x70) +#define MEM_IF_DMA_CTL(chip) (chip->ops->memif_base + 0x71) + +/* SRAM addresses */ +#define TEMP_THRESHOLD 0x454 +#define BATT_TEMP 0x550 +#define BATT_VOLTAGE_CURRENT 0x5cc + +#define BATT_TEMP_LSB_MASK GENMASK(7, 0) +#define BATT_TEMP_MSB_MASK GENMASK(2, 0) + +#define BATT_TEMP_JEITA_COLD 100 +#define BATT_TEMP_JEITA_COOL 50 +#define BATT_TEMP_JEITA_WARM 400 +#define BATT_TEMP_JEITA_HOT 450 + +#define MEM_INTF_AVAIL BIT(0) +#define MEM_INTF_CTL_BURST BIT(7) +#define MEM_INTF_CTL_WR_EN BIT(6) +#define RIF_MEM_ACCESS_REQ BIT(7) + +#define MEM_IF_TIMEOUT_MS 5000 +#define SRAM_ACCESS_RELEASE_DELAY_MS 500 + +struct qcom_fg_chip; + +struct qcom_fg_ops { + int (*get_capacity)(struct qcom_fg_chip *chip, int *); + int (*get_temperature)(struct qcom_fg_chip *chip, int *); + int (*get_current)(struct qcom_fg_chip *chip, int *); + int (*get_voltage)(struct qcom_fg_chip *chip, int *); + int (*get_temp_threshold)(struct qcom_fg_chip *chip, + enum power_supply_property psp, int *); + int (*set_temp_threshold)(struct qcom_fg_chip *chip, + enum power_supply_property psp, int); + + short memif_base; +}; + +struct qcom_fg_chip { + struct device *dev; + unsigned int base; + struct regmap *regmap; + const struct qcom_fg_ops *ops; + struct notifier_block nb; + + struct power_supply *batt_psy; + struct power_supply_battery_info *batt_info; + struct power_supply *chg_psy; + int status; + struct delayed_work status_changed_work; + + struct completion sram_access_granted; + struct completion sram_access_revoked; + struct workqueue_struct *sram_wq; + struct delayed_work sram_release_access_work; + spinlock_t sram_request_lock; + spinlock_t sram_rw_lock; + int sram_requests; +}; + +/************************ + * IO FUNCTIONS + * **********************/ + +/** + * @brief qcom_fg_read() - Read multiple registers with regmap_bulk_read + * + * @param chip Pointer to chip + * @param val Pointer to read values into + * @param addr Address to read from + * @param len Number of registers (bytes) to read + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_read(struct qcom_fg_chip *chip, u8 *val, u16 addr, int len) +{ + if (((chip->base + addr) & 0xff00) == 0) + return -EINVAL; + + dev_vdbg(chip->dev, "%s: Reading 0x%x bytes from 0x%x", __func__, len, + addr); + + return regmap_bulk_read(chip->regmap, chip->base + addr, val, len); +} + +/** + * @brief qcom_fg_write() - Write multiple registers with regmap_bulk_write + * + * @param chip Pointer to chip + * @param val Pointer to write values from + * @param addr Address to write to + * @param len Number of registers (bytes) to write + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_write(struct qcom_fg_chip *chip, u8 *val, u16 addr, int len) +{ + bool sec_access = (addr & 0xff) > 0xd0; + u8 sec_addr_val = 0xa5; + int ret; + + if (((chip->base + addr) & 0xff00) == 0) + return -EINVAL; + + dev_vdbg(chip->dev, "%s: Writing 0x%x to 0x%x", __func__, *val, addr); + + if (sec_access) { + ret = regmap_bulk_write(chip->regmap, + ((chip->base + addr) & 0xff00) | 0xd0, + &sec_addr_val, 1); + if (ret) + return ret; + } + + return regmap_bulk_write(chip->regmap, chip->base + addr, val, len); +} + +/** + * @brief qcom_fg_masked_write() - like qcom_fg_write but applies + * a mask first. + * + * @param chip Pointer to chip + * @param val Pointer to write values from + * @param addr Address to write to + * @param len Number of registers (bytes) to write + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_masked_write(struct qcom_fg_chip *chip, u16 addr, u8 mask, + u8 val) +{ + u8 reg; + int ret; + + ret = qcom_fg_read(chip, ®, addr, 1); + if (ret) + return ret; + + reg &= ~mask; + reg |= val & mask; + + return qcom_fg_write(chip, ®, addr, 1); +} + +/************************ + * SRAM FUNCTIONS + * **********************/ + +/** + * @brief qcom_fg_sram_check_access() - Check if SRAM is accessible + * + * @param chip Pointer to chip + * @return bool true if accessible, false otherwise + */ +static bool qcom_fg_sram_check_access(struct qcom_fg_chip *chip) +{ + u8 mem_if_status; + int ret; + + ret = qcom_fg_read(chip, &mem_if_status, MEM_INTF_STS(chip), 1); + + if (ret || !(mem_if_status & MEM_INTF_AVAIL)) + return false; + + ret = qcom_fg_read(chip, &mem_if_status, MEM_INTF_CFG(chip), 1); + + if (ret) + return false; + + return !!(mem_if_status & RIF_MEM_ACCESS_REQ); +} + +/** + * @brief qcom_fg_sram_request_access() - Request access to SRAM and wait for it + * + * @param chip Pointer to chip + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_sram_request_access(struct qcom_fg_chip *chip) +{ + bool sram_accessible; + int ret; + + spin_lock(&chip->sram_request_lock); + + sram_accessible = qcom_fg_sram_check_access(chip); + + dev_vdbg(chip->dev, + "Requesting SRAM access, current state: %d, requests: %d\n", + sram_accessible, chip->sram_requests); + + if (!sram_accessible && chip->sram_requests == 0) { + ret = qcom_fg_masked_write(chip, MEM_INTF_CFG(chip), + RIF_MEM_ACCESS_REQ, + RIF_MEM_ACCESS_REQ); + if (ret) { + dev_err(chip->dev, + "Failed to set SRAM access request bit: %d\n", + ret); + + spin_unlock(&chip->sram_request_lock); + return ret; + } + } + + chip->sram_requests++; + + spin_unlock(&chip->sram_request_lock); + + /* Wait to get access to SRAM, and try again if interrupted */ + do { + ret = wait_for_completion_interruptible_timeout( + &chip->sram_access_granted, + msecs_to_jiffies(MEM_IF_TIMEOUT_MS)); + } while (ret == -ERESTARTSYS); + + if (ret <= 0) { + ret = -ETIMEDOUT; + + spin_lock(&chip->sram_request_lock); + chip->sram_requests--; + spin_unlock(&chip->sram_request_lock); + } else { + ret = 0; + + reinit_completion(&chip->sram_access_revoked); + } + + return ret; +} + +/** + * @brief qcom_fg_sram_release_access() - Release access to SRAM + * + * @param chip Pointer to chip + * @return int 0 on success, negative errno on error + */ +static void qcom_fg_sram_release_access(struct qcom_fg_chip *chip) +{ + spin_lock(&chip->sram_request_lock); + + chip->sram_requests--; + + if (WARN(chip->sram_requests < 0, + "sram_requests=%d, cannot be negative! resetting to 0.\n", + chip->sram_requests)) + chip->sram_requests = 0; + + if (chip->sram_requests == 0) + /* Schedule access release */ + queue_delayed_work( + chip->sram_wq, &chip->sram_release_access_work, + msecs_to_jiffies(SRAM_ACCESS_RELEASE_DELAY_MS)); + + spin_unlock(&chip->sram_request_lock); +} + +static void qcom_fg_sram_release_access_worker(struct work_struct *work) +{ + struct qcom_fg_chip *chip; + bool wait = false; + int ret; + + chip = container_of(work, struct qcom_fg_chip, + sram_release_access_work.work); + + spin_lock(&chip->sram_request_lock); + + /* Request access release if there are still no access requests */ + if (chip->sram_requests == 0) { + qcom_fg_masked_write(chip, MEM_INTF_CFG(chip), + RIF_MEM_ACCESS_REQ, 0); + wait = true; + } + + spin_unlock(&chip->sram_request_lock); + + if (!wait) + return; + + /* Wait for SRAM access to be released, and try again if interrupted */ + do { + ret = wait_for_completion_interruptible_timeout( + &chip->sram_access_revoked, + msecs_to_jiffies(MEM_IF_TIMEOUT_MS)); + } while (ret == -ERESTARTSYS); + + reinit_completion(&chip->sram_access_granted); +} + +/** + * @brief qcom_fg_sram_config_access() - Configure access to SRAM + * + * @param chip Pointer to chip + * @param write 0 for read access, 1 for write access + * @param burst 1 to access mutliple addresses successively + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_sram_config_access(struct qcom_fg_chip *chip, bool write, + bool burst) +{ + u8 intf_ctl; + int ret; + + intf_ctl = (write ? MEM_INTF_CTL_WR_EN : 0) | + (burst ? MEM_INTF_CTL_BURST : 0); + + ret = qcom_fg_write(chip, &intf_ctl, MEM_INTF_CTL(chip), 1); + if (ret) { + dev_err(chip->dev, "Failed to configure SRAM access: %d\n", + ret); + return ret; + } + + return 0; +} + +/** + * @brief qcom_fg_sram_read() - Read data from SRAM + * + * @param chip Pointer to chip + * @param val Pointer to read values into + * @param addr Address to read from + * @param len Number of bytes to read + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_sram_read(struct qcom_fg_chip *chip, u8 *val, u16 addr, + int len, int offset) +{ + u8 *rd_data = val; + int ret = 0; + + ret = qcom_fg_sram_request_access(chip); + if (ret) { + dev_err(chip->dev, "Failed to request SRAM access: %d", ret); + return ret; + } + + spin_lock(&chip->sram_rw_lock); + + dev_vdbg(chip->dev, + "Reading address 0x%x with offset %d of length %d from SRAM", + addr, len, offset); + + ret = qcom_fg_sram_config_access(chip, 0, (len > 4)); + if (ret) { + dev_err(chip->dev, "Failed to configure SRAM access: %d", ret); + goto out; + } + + while (len > 0) { + /* Set SRAM address register */ + ret = qcom_fg_write(chip, (u8 *)&addr, MEM_INTF_ADDR_LSB(chip), + 2); + if (ret) { + dev_err(chip->dev, "Failed to set SRAM address: %d", + ret); + goto out; + } + + ret = qcom_fg_read(chip, rd_data, + MEM_INTF_RD_DATA0(chip) + offset, len); + + addr += 4; + + if (ret) + goto out; + + rd_data += 4 - offset; + len -= 4 - offset; + offset = 0; + } +out: + spin_unlock(&chip->sram_rw_lock); + qcom_fg_sram_release_access(chip); + + return ret; +} + +/** + * @brief qcom_fg_sram_write() - Write data to SRAM + * + * @param chip Pointer to chip + * @param val Pointer to write values from + * @param addr Address to write to + * @param len Number of bytes to write + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_sram_write(struct qcom_fg_chip *chip, u8 *val, u16 addr, + int len, int offset) +{ + u8 *wr_data = val; + int ret; + + ret = qcom_fg_sram_request_access(chip); + if (ret) { + dev_err(chip->dev, "Failed to request SRAM access: %d", ret); + return ret; + } + + spin_lock(&chip->sram_rw_lock); + + dev_vdbg(chip->dev, + "Wrtiting address 0x%x with offset %d of length %d to SRAM", + addr, len, offset); + + ret = qcom_fg_sram_config_access(chip, 1, (len > 4)); + if (ret) { + dev_err(chip->dev, "Failed to configure SRAM access: %d", ret); + goto out; + } + + while (len > 0) { + /* Set SRAM address register */ + ret = qcom_fg_write(chip, (u8 *)&addr, MEM_INTF_ADDR_LSB(chip), + 2); + if (ret) { + dev_err(chip->dev, "Failed to set SRAM address: %d", + ret); + goto out; + } + + ret = qcom_fg_write(chip, wr_data, + MEM_INTF_WR_DATA0(chip) + offset, len); + + addr += 4; + + if (ret) + goto out; + + wr_data += 4 - offset; + len -= 4 - offset; + offset = 0; + } +out: + spin_unlock(&chip->sram_rw_lock); + qcom_fg_sram_release_access(chip); + + return ret; +} + +/************************* + * BATTERY STATUS + * ***********************/ + +/** + * @brief qcom_fg_get_capacity() - Get remaining capacity of battery + * + * @param chip Pointer to chip + * @param val Pointer to store value at + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_get_capacity(struct qcom_fg_chip *chip, int *val) +{ + u8 cap[2]; + int ret; + + ret = qcom_fg_read(chip, cap, BATT_MONOTONIC_SOC, 2); + if (ret) { + dev_err(chip->dev, "Failed to read capacity: %d", ret); + return ret; + } + + if (cap[0] != cap[1]) { + cap[0] = cap[0] < cap[1] ? cap[0] : cap[1]; + } + + *val = DIV_ROUND_CLOSEST((cap[0] - 1) * 98, 0xff - 2) + 1; + + return 0; +} + +/** + * @brief qcom_fg_get_temperature() - Get temperature of battery + * + * @param chip Pointer to chip + * @param val Pointer to store value at + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_get_temperature(struct qcom_fg_chip *chip, int *val) +{ + int temp; + u8 readval[2]; + int ret; + + ret = qcom_fg_sram_read(chip, readval, BATT_TEMP, 2, 2); + if (ret) { + dev_err(chip->dev, "Failed to read temperature: %d", ret); + return ret; + } + + temp = readval[1] << 8 | readval[0]; + *val = temp * 625 / 1000 - 2730; + return 0; +} + +/** + * @brief qcom_fg_get_current() - Get current being drawn from battery + * + * @param chip Pointer to chip + * @param val Pointer to store value at + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_get_current(struct qcom_fg_chip *chip, int *val) +{ + s16 temp; + u8 readval[2]; + int ret; + + ret = qcom_fg_sram_read(chip, readval, BATT_VOLTAGE_CURRENT, 2, 3); + if (ret) { + dev_err(chip->dev, "Failed to read current: %d", ret); + return ret; + } + + temp = (s16)(readval[1] << 8 | readval[0]); + *val = div_s64((s64)temp * 152587, 1000); + + return 0; +} + +/** + * @brief qcom_fg_get_voltage() - Get voltage of battery + * + * @param chip Pointer to chip + * @param val Pointer to store value at + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_get_voltage(struct qcom_fg_chip *chip, int *val) +{ + int temp; + u8 readval[2]; + int ret; + + ret = qcom_fg_sram_read(chip, readval, BATT_VOLTAGE_CURRENT, 2, 1); + if (ret) { + dev_err(chip->dev, "Failed to read voltage: %d", ret); + return ret; + } + + temp = readval[1] << 8 | readval[0]; + *val = div_u64((u64)temp * 152587, 1000); + + return 0; +} + +/** + * @brief qcom_fg_get_temp_threshold() - Get configured temperature thresholds + * + * @param chip Pointer to chip + * @param psp Power supply property of temperature limit + * @param val Pointer to store value at + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_get_temp_threshold(struct qcom_fg_chip *chip, + enum power_supply_property psp, int *val) +{ + u8 temp; + int offset; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP_MIN: + offset = 0; + break; + case POWER_SUPPLY_PROP_TEMP_MAX: + offset = 1; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + offset = 2; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + offset = 3; + break; + default: + return -EINVAL; + } + + ret = qcom_fg_sram_read(chip, &temp, TEMP_THRESHOLD, 1, offset); + if (ret < 0) { + dev_err(chip->dev, + "Failed to read JEITA property %d level: %d\n", psp, + ret); + return ret; + } + + *val = (temp - 30) * 10; + + return 0; +} + +/** + * @brief qcom_fg_set_temp_threshold() - Configure temperature thresholds + * + * @param chip Pointer to chip + * @param psp Power supply property of temperature limit + * @param val Pointer to get value from + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_set_temp_threshold(struct qcom_fg_chip *chip, + enum power_supply_property psp, int val) +{ + u8 temp; + int offset; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP_MIN: + offset = 0; + break; + case POWER_SUPPLY_PROP_TEMP_MAX: + offset = 1; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + offset = 2; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + offset = 3; + break; + default: + return -EINVAL; + } + + temp = val / 10 + 30; + + ret = qcom_fg_sram_write(chip, &temp, TEMP_THRESHOLD, 1, offset); + if (ret < 0) { + dev_err(chip->dev, + "Failed to write JEITA property %d level: %d\n", psp, + ret); + return ret; + } + + return 0; +} + +/************************* + * BATTERY STATUS, GEN3 + * ***********************/ + +/** + * @brief qcom_fg_gen3_get_temperature() - Get temperature of battery + * + * @param chip Pointer to chip + * @param val Pointer to store value at + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_gen3_get_temperature(struct qcom_fg_chip *chip, int *val) +{ + int temp; + u8 readval[2]; + int ret; + + ret = qcom_fg_read(chip, readval, PARAM_ADDR_BATT_TEMP, 2); + if (ret) { + dev_err(chip->dev, "Failed to read temperature: %d\n", ret); + return ret; + } + + temp = ((readval[1] & BATT_TEMP_MSB_MASK) << 8) | + (readval[0] & BATT_TEMP_LSB_MASK); + temp = DIV_ROUND_CLOSEST(temp * 10, 4); + + *val = temp - 2730; + return 0; +} + +/** + * @brief qcom_fg_gen3_get_current() - Get current being drawn from battery + * + * @param chip Pointer to chip + * @param val Pointer to store value at + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_gen3_get_current(struct qcom_fg_chip *chip, int *val) +{ + s16 temp; + u8 readval[2]; + int ret; + + ret = qcom_fg_read(chip, readval, PARAM_ADDR_BATT_CURRENT, 2); + if (ret) { + dev_err(chip->dev, "Failed to read current: %d\n", ret); + return ret; + } + + //handle rev 1 too + temp = (s16)(readval[1] << 8 | readval[0]); + *val = div_s64((s64)temp * 488281, 1000); + + /* + * PSY API expects charging batteries to report a positive current, which is inverted + * to what the PMIC reports. + */ + *val = -*val; + + return 0; +} + +/** + * @brief qcom_fg_gen3_get_voltage() - Get voltage of battery + * + * @param chip Pointer to chip + * @param val Pointer to store value at + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_gen3_get_voltage(struct qcom_fg_chip *chip, int *val) +{ + int temp; + u8 readval[2]; + int ret; + + ret = qcom_fg_read(chip, readval, PARAM_ADDR_BATT_VOLTAGE, 2); + if (ret) { + dev_err(chip->dev, "Failed to read voltage: %d\n", ret); + return ret; + } + + //handle rev 1 too + temp = readval[1] << 8 | readval[0]; + *val = div_u64((u64)temp * 122070, 1000); + return 0; +} + +/** + * @brief qcom_fg_gen3_get_temp_threshold() - Get configured temperature thresholds + * + * @param chip Pointer to chip + * @param psp Power supply property of temperature limit + * @param val Pointer to store value at + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_gen3_get_temp_threshold(struct qcom_fg_chip *chip, + enum power_supply_property psp, + int *val) +{ + u8 temp; + u16 reg; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP_MIN: + reg = BATT_INFO_JEITA_COLD; + break; + case POWER_SUPPLY_PROP_TEMP_MAX: + reg = BATT_INFO_JEITA_HOT; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + reg = BATT_INFO_JEITA_COOL; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + reg = BATT_INFO_JEITA_WARM; + break; + default: + return -EINVAL; + } + + ret = qcom_fg_read(chip, &temp, reg, 1); + if (ret < 0) { + dev_err(chip->dev, + "Failed to read JEITA property %d level: %d\n", psp, + ret); + return ret; + } + + /* Resolution is 0.5C. Base is -30C. */ + *val = (((5 * temp) / 10) - 30) * 10; + return 0; +} + +/************************* + * BATTERY STATUS, GEN4 + * ***********************/ + +/** + * @brief qcom_fg_gen4_get_temperature() - Get temperature of battery + * + * @param chip Pointer to chip + * @param val Pointer to store value at + * @return int 0 on success, negative errno on error + */ +static int qcom_fg_gen4_get_temperature(struct qcom_fg_chip *chip, int *val) +{ + int temp; + u8 readval[2]; + int ret; + + ret = qcom_fg_read(chip, readval, ADC_RR_BATT_TEMP_LSB, 2); + if (ret) { + dev_err(chip->dev, "Failed to read temperature: %d", ret); + return ret; + } + + temp = readval[1] << 8 | readval[0]; + *val = temp * 10; + return 0; +} + +/************************ + * BATTERY POWER SUPPLY + * **********************/ + +/* Pre-Gen3 fuel gauge. PMI8996 and older */ +static const struct qcom_fg_ops ops_fg = { + .get_capacity = qcom_fg_get_capacity, + .get_temperature = qcom_fg_get_temperature, + .get_current = qcom_fg_get_current, + .get_voltage = qcom_fg_get_voltage, + .get_temp_threshold = qcom_fg_get_temp_threshold, + .set_temp_threshold = qcom_fg_set_temp_threshold, + .memif_base = 0x400, +}; + +/* Gen3 fuel gauge. PMI8998 and newer */ +static const struct qcom_fg_ops ops_fg_gen3 = { + .get_capacity = qcom_fg_get_capacity, + .get_temperature = qcom_fg_gen3_get_temperature, + .get_current = qcom_fg_gen3_get_current, + .get_voltage = qcom_fg_gen3_get_voltage, + .get_temp_threshold = qcom_fg_gen3_get_temp_threshold, + .memif_base = 0x400, +}; + +/* Gen4 fuel gauge. PM8150B and newer */ +static const struct qcom_fg_ops ops_fg_gen4 = { + .get_capacity = qcom_fg_get_capacity, + .get_temperature = qcom_fg_gen4_get_temperature, + .get_current = qcom_fg_gen3_get_current, + .get_voltage = qcom_fg_gen3_get_voltage, + .get_temp_threshold = qcom_fg_gen3_get_temp_threshold, + .memif_base = 0x300, +}; + +static enum power_supply_property qcom_fg_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_MIN, + POWER_SUPPLY_PROP_TEMP_MAX, + POWER_SUPPLY_PROP_TEMP_ALERT_MIN, + POWER_SUPPLY_PROP_TEMP_ALERT_MAX, +}; + +static int qcom_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct qcom_fg_chip *chip = power_supply_get_drvdata(psy); + int temp, ret = 0; + + dev_dbg(chip->dev, "Getting property: %d", psp); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + /* Get status from charger if available */ + if (chip->chg_psy && + chip->status != POWER_SUPPLY_STATUS_UNKNOWN) { + val->intval = chip->status; + break; + } else { + /* + * Fall back to capacity and current-based + * status checking + */ + ret = chip->ops->get_capacity(chip, &temp); + if (ret) { + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + if (temp == 100) { + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + } + + ret = chip->ops->get_current(chip, &temp); + if (ret) { + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + if (temp < 0) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (temp > 0) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = chip->ops->get_capacity(chip, &val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = chip->ops->get_current(chip, &val->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = chip->ops->get_voltage(chip, &val->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = chip->batt_info->voltage_min_design_uv; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = chip->batt_info->voltage_max_design_uv; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = chip->batt_info->charge_full_design_uah; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TEMP: + ret = chip->ops->get_temperature(chip, &val->intval); + break; + case POWER_SUPPLY_PROP_TEMP_MIN: + case POWER_SUPPLY_PROP_TEMP_MAX: + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = chip->ops->get_temp_threshold(chip, psp, &val->intval); + break; + default: + dev_err(chip->dev, "invalid property: %d\n", psp); + return -EINVAL; + } + + return ret; +} + +static const struct power_supply_desc batt_psy_desc = { + .name = "qcom-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = qcom_fg_props, + .num_properties = ARRAY_SIZE(qcom_fg_props), + .get_property = qcom_fg_get_property, +}; + +/******************** + * INIT FUNCTIONS + * ******************/ + +static int qcom_fg_iacs_clear_sequence(struct qcom_fg_chip *chip) +{ + u8 temp; + int ret; + + /* clear the error */ + ret = qcom_fg_masked_write(chip, MEM_INTF_IMA_CFG(chip), BIT(2), + BIT(2)); + if (ret) { + dev_err(chip->dev, "Failed to write IMA_CFG: %d\n", ret); + return ret; + } + + temp = 0x4; + ret = qcom_fg_write(chip, &temp, MEM_INTF_ADDR_LSB(chip) + 1, 1); + if (ret) { + dev_err(chip->dev, "Failed to write MEM_INTF_ADDR_MSB: %d\n", + ret); + return ret; + } + + temp = 0x0; + ret = qcom_fg_write(chip, &temp, MEM_INTF_WR_DATA0(chip) + 3, 1); + if (ret) { + dev_err(chip->dev, "Failed to write WR_DATA3: %d\n", ret); + return ret; + } + + ret = qcom_fg_read(chip, &temp, MEM_INTF_RD_DATA0(chip) + 3, 1); + if (ret) { + dev_err(chip->dev, "Failed to write RD_DATA3: %d\n", ret); + return ret; + } + + ret = qcom_fg_masked_write(chip, MEM_INTF_IMA_CFG(chip), BIT(2), 0); + if (ret) { + dev_err(chip->dev, "Failed to write IMA_CFG: %d\n", ret); + return ret; + } + + return 0; +} + +static int qcom_fg_clear_ima(struct qcom_fg_chip *chip, bool check_hw_sts) +{ + u8 err_sts, exp_sts, hw_sts; + bool run_err_clr_seq = false; + int ret; + + ret = qcom_fg_read(chip, &err_sts, MEM_INTF_IMA_ERR_STS(chip), 1); + if (ret) { + dev_err(chip->dev, "Failed to read IMA_ERR_STS: %d\n", ret); + return ret; + } + + ret = qcom_fg_read(chip, &exp_sts, MEM_INTF_IMA_EXP_STS(chip), 1); + if (ret) { + dev_err(chip->dev, "Failed to read IMA_EXP_STS: %d\n", ret); + return ret; + } + + if (check_hw_sts) { + ret = qcom_fg_read(chip, &hw_sts, MEM_INTF_IMA_HW_STS(chip), 1); + if (ret) { + dev_err(chip->dev, "Failed to read IMA_HW_STS: %d\n", + ret); + return ret; + } + /* + * Lower nibble should be equal to upper nibble before SRAM + * transactions begins from SW side. + */ + if ((hw_sts & 0x0f) != hw_sts >> 4) { + dev_dbg(chip->dev, + "IMA HW not in correct state, hw_sts=%x\n", + hw_sts); + run_err_clr_seq = true; + } + } + + if (exp_sts & + (BIT(0) | BIT(1) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7))) { + dev_dbg(chip->dev, "IMA exception bit set, exp_sts=%x\n", + exp_sts); + run_err_clr_seq = true; + } + + if (run_err_clr_seq) { + ret = qcom_fg_iacs_clear_sequence(chip); + if (!ret) + return -EAGAIN; + } + + return 0; +} + +static irqreturn_t qcom_fg_handle_soc_delta(int irq, void *data) +{ + struct qcom_fg_chip *chip = data; + + /* Signal change in state of charge */ + power_supply_changed(chip->batt_psy); + dev_dbg(chip->dev, "SOC changed"); + + return IRQ_HANDLED; +} + +static irqreturn_t qcom_fg_handle_mem_avail(int irq, void *data) +{ + struct qcom_fg_chip *chip = data; + + if (qcom_fg_sram_check_access(chip)) { + complete_all(&chip->sram_access_granted); + dev_dbg(chip->dev, "SRAM access granted"); + } else { + complete_all(&chip->sram_access_revoked); + dev_dbg(chip->dev, "SRAM access revoked"); + } + + return IRQ_HANDLED; +} + +static void qcom_fg_status_changed_worker(struct work_struct *work) +{ + struct qcom_fg_chip *chip = container_of(work, struct qcom_fg_chip, + status_changed_work.work); + + power_supply_changed(chip->batt_psy); +} + +static int qcom_fg_notifier_call(struct notifier_block *nb, unsigned long val, + void *v) +{ + struct qcom_fg_chip *chip = container_of(nb, struct qcom_fg_chip, nb); + struct power_supply *psy = v; + union power_supply_propval propval; + int ret; + + if (psy == chip->chg_psy) { + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, + &propval); + if (ret) + chip->status = POWER_SUPPLY_STATUS_UNKNOWN; + + chip->status = propval.intval; + + power_supply_changed(chip->batt_psy); + + if (chip->status == POWER_SUPPLY_STATUS_UNKNOWN) { + /* + * REVISIT: Find better solution or remove current-based + * status checking once checking is properly implemented + * in charger drivers + + * Sometimes it take a while for current to stabilize, + * so signal property change again later to make sure + * current-based status is properly detected. + */ + cancel_delayed_work_sync(&chip->status_changed_work); + schedule_delayed_work(&chip->status_changed_work, + msecs_to_jiffies(1000)); + } + } + + return NOTIFY_OK; +} + +static int qcom_fg_probe(struct platform_device *pdev) +{ + struct power_supply_config supply_config = {}; + struct qcom_fg_chip *chip; + const __be32 *prop_addr; + int irq; + u8 dma_status; + bool error_present; + int ret; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &pdev->dev; + chip->ops = of_device_get_match_data(&pdev->dev); + + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->regmap) { + dev_err(chip->dev, "Failed to locate the regmap\n"); + return -ENODEV; + } + + /* Get base address */ + prop_addr = of_get_address(pdev->dev.of_node, 0, NULL, NULL); + if (!prop_addr) { + dev_err(chip->dev, "Failed to read SOC base address from dt\n"); + return -EINVAL; + } + chip->base = be32_to_cpu(*prop_addr); + + /* + * Change the FG_MEM_INT interrupt to track IACS_READY + * condition instead of end-of-transaction. This makes sure + * that the next transaction starts only after the hw is ready. + * IACS_INTR_SRC_SLCT is BIT(3) + */ + ret = qcom_fg_masked_write(chip, MEM_INTF_IMA_CFG(chip), BIT(3), + BIT(3)); + if (ret) { + dev_err(chip->dev, + "Failed to configure interrupt sourete: %d\n", ret); + return ret; + } + + ret = qcom_fg_clear_ima(chip, true); + if (ret && ret != -EAGAIN) { + dev_err(chip->dev, "Failed to clear IMA exception: %d\n", ret); + return ret; + } + + /* Check and clear DMA errors */ + ret = qcom_fg_read(chip, &dma_status, MEM_IF_DMA_STS(chip), 1); + if (ret < 0) { + dev_err(chip->dev, "Failed to read dma_status: %d\n", ret); + return ret; + } + + error_present = dma_status & (BIT(1) | BIT(2)); + ret = qcom_fg_masked_write(chip, MEM_IF_DMA_CTL(chip), BIT(0), + error_present ? BIT(0) : 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to write dma_ctl: %d\n", ret); + return ret; + } + + supply_config.drv_data = chip; + + chip->batt_psy = devm_power_supply_register(chip->dev, &batt_psy_desc, + &supply_config); + if (IS_ERR(chip->batt_psy)) { + if (PTR_ERR(chip->batt_psy) != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to register battery\n"); + return PTR_ERR(chip->batt_psy); + } + + platform_set_drvdata(pdev, chip); + + ret = power_supply_get_battery_info(chip->batt_psy, &chip->batt_info); + if (ret) { + dev_err(&pdev->dev, "Failed to get battery info: %d\n", ret); + return ret; + } + + /* Initialize SRAM */ + if (of_device_is_compatible(pdev->dev.of_node, "qcom,pmi8994-fg")) { + irq = of_irq_get_byname(pdev->dev.of_node, "mem-avail"); + if (irq < 0) { + dev_err(&pdev->dev, + "Failed to get irq mem-avail byname: %d\n", + irq); + return irq; + } + + init_completion(&chip->sram_access_granted); + init_completion(&chip->sram_access_revoked); + + chip->sram_wq = create_singlethread_workqueue("qcom_fg"); + INIT_DELAYED_WORK(&chip->sram_release_access_work, + qcom_fg_sram_release_access_worker); + + ret = devm_request_threaded_irq(chip->dev, irq, NULL, + qcom_fg_handle_mem_avail, + IRQF_ONESHOT, "mem-avail", + chip); + if (ret < 0) { + dev_err(&pdev->dev, + "Failed to request mem-avail IRQ: %d\n", ret); + return ret; + } + + spin_lock_init(&chip->sram_request_lock); + spin_lock_init(&chip->sram_rw_lock); + chip->sram_requests = 0; + } + + /* Set default temperature thresholds */ + if (chip->ops->set_temp_threshold) { + ret = chip->ops->set_temp_threshold( + chip, POWER_SUPPLY_PROP_TEMP_MIN, BATT_TEMP_JEITA_COLD); + if (ret) { + dev_err(chip->dev, "Failed to set cold threshold: %d\n", + ret); + return ret; + } + + ret = chip->ops->set_temp_threshold( + chip, POWER_SUPPLY_PROP_TEMP_MAX, BATT_TEMP_JEITA_WARM); + if (ret) { + dev_err(chip->dev, "Failed to set warm threshold: %d\n", + ret); + return ret; + } + + ret = chip->ops->set_temp_threshold( + chip, POWER_SUPPLY_PROP_TEMP_ALERT_MIN, + BATT_TEMP_JEITA_COOL); + if (ret) { + dev_err(chip->dev, "Failed to set cool threshold: %d\n", + ret); + return ret; + } + + ret = chip->ops->set_temp_threshold( + chip, POWER_SUPPLY_PROP_TEMP_ALERT_MAX, + BATT_TEMP_JEITA_HOT); + if (ret) { + dev_err(chip->dev, "Failed to set hot threshold: %d\n", + ret); + return ret; + } + } + + /* Get soc-delta IRQ */ + irq = of_irq_get_byname(pdev->dev.of_node, "soc-delta"); + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get irq soc-delta byname: %d\n", + irq); + return irq; + } + + ret = devm_request_threaded_irq(chip->dev, irq, NULL, + qcom_fg_handle_soc_delta, IRQF_ONESHOT, + "soc-delta", chip); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request soc-delta IRQ: %d\n", + ret); + return ret; + } + + /* Optional: Get charger power supply for status checking */ + chip->chg_psy = power_supply_get_by_reference(dev_fwnode(chip->dev), + "power-supplies"); + if (IS_ERR(chip->chg_psy)) { + ret = PTR_ERR(chip->chg_psy); + dev_warn(chip->dev, "Failed to get charger supply: %d\n", ret); + chip->chg_psy = NULL; + } + + if (chip->chg_psy) { + INIT_DELAYED_WORK(&chip->status_changed_work, + qcom_fg_status_changed_worker); + + chip->nb.notifier_call = qcom_fg_notifier_call; + ret = power_supply_reg_notifier(&chip->nb); + if (ret) { + dev_err(chip->dev, "Failed to register notifier: %d\n", + ret); + return ret; + } + } + + return 0; +} + +static void qcom_fg_remove(struct platform_device *pdev) +{ + struct qcom_fg_chip *chip = platform_get_drvdata(pdev); + + power_supply_put_battery_info(chip->batt_psy, chip->batt_info); + + if (chip->sram_wq) + destroy_workqueue(chip->sram_wq); +} + +static const struct of_device_id fg_match_id_table[] = { + { .compatible = "qcom,pmi8994-fg", .data = &ops_fg }, + { .compatible = "qcom,pmi8998-fg", .data = &ops_fg_gen3 }, + { .compatible = "qcom,pm8150b-fg", .data = &ops_fg_gen4 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fg_match_id_table); + +static struct platform_driver qcom_fg_driver = { + .probe = qcom_fg_probe, + .remove = qcom_fg_remove, + .driver = { + .name = "qcom-fg", + .of_match_table = fg_match_id_table, + }, +}; + +module_platform_driver(qcom_fg_driver); + +MODULE_AUTHOR("Caleb Connolly "); +MODULE_AUTHOR("Joel Selvaraj "); +MODULE_AUTHOR("Yassine Oudjana "); +MODULE_DESCRIPTION("Qualcomm PMIC Fuel Gauge Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/senemos/configs/nabu-minimal.config b/senemos/configs/nabu-minimal.config index 54c7a6d09e48..194037af4e75 100644 --- a/senemos/configs/nabu-minimal.config +++ b/senemos/configs/nabu-minimal.config @@ -31,6 +31,10 @@ CONFIG_DRM_PANEL_NOVATEK_NT36523=m CONFIG_BACKLIGHT_CLASS_DEVICE=y CONFIG_BACKLIGHT_KTZ8866=m +# Nabu SPI touch, wake gestures and PM8150 fuel gauge. +CONFIG_TOUCHSCREEN_NT36523_SPI=m +CONFIG_BATTERY_QCOM_FG=y + # Remote processors, FastRPC, connectivity and persistent diagnostics. CONFIG_REMOTEPROC=y CONFIG_QCOM_Q6V5_PAS=m