From 563fd7672f0aa14120e76ed3f82906dbdcc9e375 Mon Sep 17 00:00:00 2001 From: Jon Lin Date: Sun, 25 Feb 2024 11:15:08 +0800 Subject: [PATCH 01/28] spi: rockchip-sfc: Using normal memory for dma Nornal memory CPU copy with cache invalidate is more efficient than uncache memory copy. Change-Id: I04e9e9a532bba14858b6f4baa969e3e848f6f032 Signed-off-by: Jon Lin --- drivers/spi/spi-rockchip-sfc.c | 60 ++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index d9e0fe501205..79bbd6b59a96 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -160,7 +160,7 @@ #define SFC_MAX_CHIPSELECT_NUM 2 #define SFC_MAX_IOSIZE_VER3 (512 * 31) -#define SFC_MAX_IOSIZE_VER4 (0xFFFFFFFFU) +#define SFC_MAX_IOSIZE_VER4 (0x10000U) /* Although up to 4GB, 64KB is enough with less mem reserved */ /* DMA is only enabled for large data transmission */ #define SFC_DMA_TRANS_THRETHOLD (0x40) @@ -222,6 +222,9 @@ static u16 rockchip_sfc_get_version(struct rockchip_sfc *sfc) static u32 rockchip_sfc_get_max_iosize(struct rockchip_sfc *sfc) { + if (sfc->version >= SFC_VER_4) + return SFC_MAX_IOSIZE_VER4; + return SFC_MAX_IOSIZE_VER3; } @@ -488,20 +491,46 @@ static int rockchip_sfc_xfer_data_dma(struct rockchip_sfc *sfc, const struct spi_mem_op *op, u32 len) { int ret; +#ifdef ROCKCHIP_SFC_VERBOSE + ktime_t start_time; + ktime_t end_time; + unsigned long us = 0; +#endif dev_dbg(sfc->dev, "sfc xfer_dma len=%x\n", len); - if (op->data.dir == SPI_MEM_DATA_OUT) + if (op->data.dir == SPI_MEM_DATA_OUT) { memcpy(sfc->buffer, op->data.buf.out, len); + dma_sync_single_for_device(sfc->dev, sfc->dma_buffer, len, DMA_TO_DEVICE); + } +#ifdef ROCKCHIP_SFC_VERBOSE + start_time = ktime_get(); +#endif ret = rockchip_sfc_fifo_transfer_dma(sfc, sfc->dma_buffer, len); if (!wait_for_completion_timeout(&sfc->cp, msecs_to_jiffies(2000))) { dev_err(sfc->dev, "DMA wait for transfer finish timeout\n"); ret = -ETIMEDOUT; } +#ifdef ROCKCHIP_SFC_VERBOSE + end_time = ktime_get(); + us = ktime_to_us(ktime_sub(end_time, start_time)); + dev_err(sfc->dev, "sfc io %d cost %ldus speed:%ldKB/S %llx\n", len, us, len * 1000 / us, sfc->dma_buffer); +#endif rockchip_sfc_irq_mask(sfc, SFC_IMR_DMA); - if (op->data.dir == SPI_MEM_DATA_IN) + +#ifdef ROCKCHIP_SFC_VERBOSE + start_time = ktime_get(); +#endif + if (op->data.dir == SPI_MEM_DATA_IN) { + dma_sync_single_for_cpu(sfc->dev, sfc->dma_buffer, len, DMA_FROM_DEVICE); memcpy(op->data.buf.in, sfc->buffer, len); + } +#ifdef ROCKCHIP_SFC_VERBOSE + end_time = ktime_get(); + us = ktime_to_us(ktime_sub(end_time, start_time)); + dev_err(sfc->dev, "sfc cp %d cost %ldus speed:%ldKB/S\n", len, us, len * 1000 / us); +#endif return ret; } @@ -780,20 +809,6 @@ static int rockchip_sfc_probe(struct platform_device *pdev) sfc->use_dma = !of_property_read_bool(sfc->dev->of_node, "rockchip,sfc-no-dma"); - if (sfc->use_dma) { - ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); - if (ret) { - dev_warn(dev, "Unable to set dma mask\n"); - return ret; - } - - sfc->buffer = dmam_alloc_coherent(dev, SFC_MAX_IOSIZE_VER3, - &sfc->dma_buffer, - GFP_KERNEL); - if (!sfc->buffer) - return -ENOMEM; - } - ret = clk_prepare_enable(sfc->hclk); if (ret) { dev_err(&pdev->dev, "Failed to enable ahb clk\n"); @@ -836,8 +851,8 @@ static int rockchip_sfc_probe(struct platform_device *pdev) if (ret) goto err_irq; - sfc->max_iosize = rockchip_sfc_get_max_iosize(sfc); sfc->version = rockchip_sfc_get_version(sfc); + sfc->max_iosize = rockchip_sfc_get_max_iosize(sfc); pm_runtime_set_autosuspend_delay(dev, ROCKCHIP_AUTOSUSPEND_DELAY); pm_runtime_use_autosuspend(dev); @@ -845,6 +860,13 @@ static int rockchip_sfc_probe(struct platform_device *pdev) pm_runtime_enable(dev); pm_runtime_get_noresume(dev); + if (sfc->use_dma) { + sfc->buffer = (u8 *)__get_free_pages(GFP_KERNEL | GFP_DMA32, get_order(sfc->max_iosize)); + if (!sfc->buffer) + return -ENOMEM; + sfc->dma_buffer = virt_to_phys(sfc->buffer); + } + ret = spi_register_master(master); if (ret) goto err_register; @@ -855,6 +877,7 @@ static int rockchip_sfc_probe(struct platform_device *pdev) return 0; err_register: + free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); pm_runtime_disable(sfc->dev); pm_runtime_set_suspended(sfc->dev); pm_runtime_dont_use_autosuspend(sfc->dev); @@ -871,6 +894,7 @@ static int rockchip_sfc_remove(struct platform_device *pdev) struct spi_master *master = platform_get_drvdata(pdev); struct rockchip_sfc *sfc = platform_get_drvdata(pdev); + free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); spi_unregister_master(master); clk_disable_unprepare(sfc->clk); From e26de7b6cee480dee4828a0f3921cca50a74bfca Mon Sep 17 00:00:00 2001 From: Jon Lin Date: Wed, 21 Feb 2024 21:25:34 +0800 Subject: [PATCH 02/28] spi: rockchip-sfc: Set the max speed depend on the IP version After SFC_VER_8, the FSPI support ddr mode, and the working clock of the controller is twice that of the IO clock. Change-Id: I66307ad96960ce7e4daaeab5cf6e191f0ee3778d Signed-off-by: Jon Lin --- drivers/spi/spi-rockchip-sfc.c | 44 +++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index 79bbd6b59a96..fdbfd8b7ba68 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -115,6 +115,7 @@ #define SFC_VER_5 0x5 #define SFC_VER_6 0x6 #define SFC_VER_8 0x8 +#define SFC_VER_10 0x10 /* Delay line controller resiter */ #define SFC_DLL_CTRL0 0x3C @@ -168,8 +169,10 @@ /* Maximum clock values from datasheet suggest keeping clock value under * 150MHz. No minimum or average value is suggested. */ -#define SFC_MAX_SPEED (150 * 1000 * 1000) -#define SFC_DLL_THRESHOLD_RATE (50 * 1000 * 1000) +#define SFC_MAX_SPEED (150 * 1000 * 1000) +#define SFC_DLL_THRESHOLD_RATE (50 * 1000 * 1000) +#define SFC_X2_MAX_SPEED (300 * 1000 * 1000) +#define SFC_X2_DLL_THRESHOLD_RATE (100 * 1000 * 1000) #define SFC_DLL_TRANING_STEP 10 /* Training step */ #define SFC_DLL_TRANING_VALID_WINDOW 80 /* Valid DLL winbow */ @@ -228,6 +231,22 @@ static u32 rockchip_sfc_get_max_iosize(struct rockchip_sfc *sfc) return SFC_MAX_IOSIZE_VER3; } +static u32 rockchip_sfc_get_max_rate(struct rockchip_sfc *sfc) +{ + if (sfc->version >= SFC_VER_8) + return SFC_X2_MAX_SPEED; + + return SFC_MAX_SPEED; +} + +static u32 rockchip_sfc_get_max_dll_threshold(struct rockchip_sfc *sfc) +{ + if (sfc->version >= SFC_VER_8) + return SFC_X2_DLL_THRESHOLD_RATE; + + return SFC_DLL_THRESHOLD_RATE; +} + static u32 rockchip_sfc_get_max_dll_cells(struct rockchip_sfc *sfc) { switch (rockchip_sfc_get_version(sfc)) { @@ -595,14 +614,14 @@ static void rockchip_sfc_delay_lines_tuning(struct rockchip_sfc *sfc, struct spi bool dll_valid = false; u8 cs = mem->spi->chip_select; - clk_set_rate(sfc->clk, SFC_DLL_THRESHOLD_RATE); + clk_set_rate(sfc->clk, rockchip_sfc_get_max_dll_threshold(sfc)); op.data.buf.in = &id; rockchip_sfc_exec_op_bypass(sfc, mem, &op); if ((0xFF == id[0] && 0xFF == id[1]) || (0x00 == id[0] && 0x00 == id[1])) { dev_dbg(sfc->dev, "no dev, dll by pass\n"); clk_set_rate(sfc->clk, sfc->speed[cs]); - sfc->speed[cs] = SFC_DLL_THRESHOLD_RATE; + sfc->speed[cs] = rockchip_sfc_get_max_dll_threshold(sfc); return; } @@ -654,11 +673,11 @@ static void rockchip_sfc_delay_lines_tuning(struct rockchip_sfc *sfc, struct spi dev_err(sfc->dev, "%d %d dll training failed in %dMHz, reduce the frequency\n", left, right, sfc->speed[cs]); rockchip_sfc_set_delay_lines(sfc, 0, cs); - clk_set_rate(sfc->clk, SFC_DLL_THRESHOLD_RATE); - mem->spi->max_speed_hz = SFC_DLL_THRESHOLD_RATE; - sfc->cur_speed = SFC_DLL_THRESHOLD_RATE; + clk_set_rate(sfc->clk, rockchip_sfc_get_max_dll_threshold(sfc)); + mem->spi->max_speed_hz = rockchip_sfc_get_max_dll_threshold(sfc); + sfc->cur_speed = rockchip_sfc_get_max_dll_threshold(sfc); sfc->cur_real_speed = clk_get_rate(sfc->clk); - sfc->speed[cs] = SFC_DLL_THRESHOLD_RATE; + sfc->speed[cs] = rockchip_sfc_get_max_dll_threshold(sfc); } } @@ -684,7 +703,7 @@ static int rockchip_sfc_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op sfc->cur_speed = mem->spi->max_speed_hz; sfc->cur_real_speed = clk_get_rate(sfc->clk); if (rockchip_sfc_get_version(sfc) >= SFC_VER_4) { - if (clk_get_rate(sfc->clk) > SFC_DLL_THRESHOLD_RATE) + if (clk_get_rate(sfc->clk) > rockchip_sfc_get_max_dll_threshold(sfc)) rockchip_sfc_delay_lines_tuning(sfc, mem); else rockchip_sfc_set_delay_lines(sfc, 0, cs); @@ -770,8 +789,6 @@ static int rockchip_sfc_probe(struct platform_device *pdev) master->flags = SPI_MASTER_HALF_DUPLEX; master->mem_ops = &rockchip_sfc_mem_ops; master->dev.of_node = pdev->dev.of_node; - master->mode_bits = SPI_TX_QUAD | SPI_TX_DUAL | SPI_RX_QUAD | SPI_RX_DUAL; - master->max_speed_hz = SFC_MAX_SPEED; master->num_chipselect = SFC_MAX_CHIPSELECT_NUM; sfc = spi_master_get_devdata(master); @@ -854,6 +871,11 @@ static int rockchip_sfc_probe(struct platform_device *pdev) sfc->version = rockchip_sfc_get_version(sfc); sfc->max_iosize = rockchip_sfc_get_max_iosize(sfc); + master->mode_bits = SPI_TX_QUAD | SPI_TX_DUAL | SPI_RX_QUAD | SPI_RX_DUAL; + master->max_speed_hz = rockchip_sfc_get_max_rate(sfc); + if (sfc->version >= SFC_VER_8) + master->mode_bits |= SPI_TX_OCTAL | SPI_RX_OCTAL; + pm_runtime_set_autosuspend_delay(dev, ROCKCHIP_AUTOSUSPEND_DELAY); pm_runtime_use_autosuspend(dev); pm_runtime_set_active(dev); From 784e52a797a936672fdcb4b5ae32feae5fe47e71 Mon Sep 17 00:00:00 2001 From: Jon Lin Date: Thu, 22 Feb 2024 20:54:55 +0800 Subject: [PATCH 03/28] spi: rockchip-sfc: Support sfc-cs-gpio Change-Id: I24bfbf39ed7bea0596f299d3413159c7e2038ba8 Signed-off-by: Jon Lin --- drivers/spi/spi-rockchip-sfc.c | 81 ++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index fdbfd8b7ba68..01dedba02fd7 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -23,6 +23,7 @@ #include #include #include +#include /* System control */ #define SFC_CTRL 0x0 @@ -195,6 +196,7 @@ struct rockchip_sfc { u32 max_iosize; u32 dll_cells[SFC_MAX_CHIPSELECT_NUM]; u16 version; + struct gpio_desc **cs_gpiods; }; static int rockchip_sfc_reset(struct rockchip_sfc *sfc) @@ -407,13 +409,13 @@ static int rockchip_sfc_xfer_setup(struct rockchip_sfc *sfc, /* set the Controller */ ctrl |= SFC_CTRL_PHASE_SEL_NEGETIVE; - cmd |= mem->spi->chip_select << SFC_CMD_CS_SHIFT; + cmd |= cs << SFC_CMD_CS_SHIFT; dev_dbg(sfc->dev, "sfc addr.nbytes=%x(x%d) dummy.nbytes=%x(x%d)\n", op->addr.nbytes, op->addr.buswidth, op->dummy.nbytes, op->dummy.buswidth); - dev_dbg(sfc->dev, "sfc ctrl=%x cmd=%x addr=%llx len=%x\n", - ctrl, cmd, op->addr.val, len); + dev_dbg(sfc->dev, "sfc ctrl=%x cmd=%x addr=%llx len=%x cs=%d\n", + ctrl, cmd, op->addr.val, len, cs); writel(ctrl, sfc->regbase + cs * SFC_CS1_REG_OFFSET + SFC_CTRL); writel(cmd, sfc->regbase + SFC_CMD); @@ -582,14 +584,27 @@ static int rockchip_sfc_xfer_done(struct rockchip_sfc *sfc, u32 timeout_us) return ret; } +static void rockchip_sfc_set_cs_gpio(struct rockchip_sfc *sfc, u8 cs, bool enable) +{ + if (sfc->cs_gpiods) { + if (has_acpi_companion(sfc->dev)) + gpiod_set_value_cansleep(sfc->cs_gpiods[cs], !enable); + else + /* Polarity handled by GPIO library */ + gpiod_set_value_cansleep(sfc->cs_gpiods[cs], enable); + } +} + static int rockchip_sfc_exec_op_bypass(struct rockchip_sfc *sfc, struct spi_mem *mem, const struct spi_mem_op *op) { u32 len = min_t(u32, op->data.nbytes, sfc->max_iosize); + u8 cs = mem->spi->chip_select; u32 ret; rockchip_sfc_adjust_op_work((struct spi_mem_op *)op); + rockchip_sfc_set_cs_gpio(sfc, cs, true); rockchip_sfc_xfer_setup(sfc, mem, op, len); ret = rockchip_sfc_xfer_data_poll(sfc, op, len); if (ret != len) { @@ -598,7 +613,10 @@ static int rockchip_sfc_exec_op_bypass(struct rockchip_sfc *sfc, return -EIO; } - return rockchip_sfc_xfer_done(sfc, 100000); + ret = rockchip_sfc_xfer_done(sfc, 100000); + rockchip_sfc_set_cs_gpio(sfc, cs, false); + + return ret; } static void rockchip_sfc_delay_lines_tuning(struct rockchip_sfc *sfc, struct spi_mem *mem) @@ -714,6 +732,7 @@ static int rockchip_sfc_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op } rockchip_sfc_adjust_op_work((struct spi_mem_op *)op); + rockchip_sfc_set_cs_gpio(sfc, cs, true); rockchip_sfc_xfer_setup(sfc, mem, op, len); if (len) { if (likely(sfc->use_dma) && len >= SFC_DMA_TRANS_THRETHOLD && !(len & 0x3)) { @@ -734,6 +753,7 @@ static int rockchip_sfc_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op ret = rockchip_sfc_xfer_done(sfc, 100000); out: + rockchip_sfc_set_cs_gpio(sfc, cs, false); pm_runtime_mark_last_busy(sfc->dev); pm_runtime_put_autosuspend(sfc->dev); @@ -773,6 +793,53 @@ static irqreturn_t rockchip_sfc_irq_handler(int irq, void *dev_id) return IRQ_NONE; } +static int rockchip_sfc_get_gpio_descs(struct spi_controller *ctlr, struct rockchip_sfc *sfc) +{ + int nb, i; + struct gpio_desc **cs; + struct device *dev = &ctlr->dev; + unsigned int num_cs_gpios = 0; + + nb = gpiod_count(dev, "sfc-cs"); + ctlr->num_chipselect = max_t(int, nb, ctlr->num_chipselect); + + if (nb == 0 || nb == -ENOENT) + return 0; + else if (nb < 0) + return nb; + + cs = devm_kcalloc(dev, ctlr->num_chipselect, sizeof(*cs), + GFP_KERNEL); + if (!cs) + return -ENOMEM; + sfc->cs_gpiods = cs; + + for (i = 0; i < nb; i++) { + cs[i] = devm_gpiod_get_index_optional(dev, "sfc-cs", i, + GPIOD_OUT_LOW); + if (IS_ERR(cs[i])) + return PTR_ERR(cs[i]); + + if (cs[i]) { + /* + * If we find a CS GPIO, name it after the device and + * chip select line. + */ + char *gpioname; + + gpioname = devm_kasprintf(dev, GFP_KERNEL, "%s CS%d", + dev_name(dev), i); + if (!gpioname) + return -ENOMEM; + gpiod_set_consumer_name(cs[i], gpioname); + num_cs_gpios++; + continue; + } + } + + return 0; +} + static int rockchip_sfc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -826,6 +893,12 @@ static int rockchip_sfc_probe(struct platform_device *pdev) sfc->use_dma = !of_property_read_bool(sfc->dev->of_node, "rockchip,sfc-no-dma"); + ret = rockchip_sfc_get_gpio_descs(master, sfc); + if (ret) { + dev_err(&pdev->dev, "Failed to get gpio_descs\n"); + return ret; + } + ret = clk_prepare_enable(sfc->hclk); if (ret) { dev_err(&pdev->dev, "Failed to enable ahb clk\n"); From a0cb6e977e30364a57719e19dd77540894f9cab9 Mon Sep 17 00:00:00 2001 From: Luo Wei Date: Wed, 21 Feb 2024 18:54:25 +0800 Subject: [PATCH 04/28] mfd: display-serdes: delete the interface that GKI not supported Signed-off-by: Luo Wei Change-Id: I01aa98e803b80229576b5c3a6f00649940f44ce9 --- drivers/mfd/display-serdes/serdes-bridge.c | 8 ++++++-- drivers/mfd/display-serdes/serdes-gpio.c | 13 ++----------- drivers/mfd/display-serdes/serdes-panel-split.c | 6 +++++- drivers/mfd/display-serdes/serdes-panel.c | 6 +++++- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/drivers/mfd/display-serdes/serdes-bridge.c b/drivers/mfd/display-serdes/serdes-bridge.c index bcb7ade23031..6a1662aadf66 100644 --- a/drivers/mfd/display-serdes/serdes-bridge.c +++ b/drivers/mfd/display-serdes/serdes-bridge.c @@ -187,7 +187,8 @@ static void serdes_bridge_enable(struct drm_bridge *bridge) if (!ret) { extcon_set_state_sync(serdes->extcon, EXTCON_JACK_VIDEO_OUT, true); - SERDES_DBG_MFD("%s: extcon is true\n", __func__); + SERDES_DBG_MFD("%s: %s-%s extcon is true\n", __func__, dev_name(serdes->dev), + serdes->chip_data->name); } SERDES_DBG_MFD("%s: %s-%s ret=%d\n", __func__, dev_name(serdes->dev), @@ -204,6 +205,9 @@ serdes_bridge_detect(struct drm_bridge *bridge) if (serdes->chip_data->bridge_ops->detect) status = serdes->chip_data->bridge_ops->detect(serdes); + SERDES_DBG_MFD("%s:%s %s, %s\n", __func__, dev_name(serdes->dev), + serdes->chip_data->name, + (status == connector_status_connected) ? "connected" : "disconnect"); return status; } @@ -223,7 +227,7 @@ static int serdes_bridge_get_modes(struct drm_bridge *bridge, if (serdes_bridge->panel) ret = drm_panel_get_modes(serdes_bridge->panel, connector); - SERDES_DBG_MFD("%s:name=%s, node=%s\n", __func__, + SERDES_DBG_MFD("%s:%s %s, node=%s\n", __func__, dev_name(serdes->dev), serdes->chip_data->name, serdes_bridge->dev->of_node->name); return ret; diff --git a/drivers/mfd/display-serdes/serdes-gpio.c b/drivers/mfd/display-serdes/serdes-gpio.c index 24b0a9b4f02f..4107ef99be58 100644 --- a/drivers/mfd/display-serdes/serdes-gpio.c +++ b/drivers/mfd/display-serdes/serdes-gpio.c @@ -108,18 +108,9 @@ static void serdes_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) for (i = 0; i < chip->ngpio; i++) { int gpio = i + chip->base; - const char *label, *level; + const char *level; - /* We report the GPIO even if it's not requested since - * we're also reporting things like alternate - * functions which apply even when the GPIO is not in - * use as a GPIO. - */ - label = gpiochip_is_requested(chip, i); - if (!label) - label = "Unrequested"; - - seq_printf(s, " %s-gpio-%02d ", label, gpio); + seq_printf(s, "gpio-%02d ", gpio); if (serdes->chip_data->gpio_ops->get_level) ret = serdes->chip_data->gpio_ops->get_level(serdes, i); diff --git a/drivers/mfd/display-serdes/serdes-panel-split.c b/drivers/mfd/display-serdes/serdes-panel-split.c index 537d28233ba4..374b82e1f66e 100644 --- a/drivers/mfd/display-serdes/serdes-panel-split.c +++ b/drivers/mfd/display-serdes/serdes-panel-split.c @@ -213,7 +213,7 @@ static int serdes_panel_split_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "failed to parse serdes DT\n"); - serdes_panel_split->backlight = devm_of_find_backlight(dev); + serdes_panel_split->backlight = of_find_backlight_by_node(dev->of_node); if (IS_ERR(serdes_panel_split->backlight)) return dev_err_probe(dev, PTR_ERR(serdes_panel_split->backlight), "failed to get serdes backlight\n"); @@ -236,6 +236,10 @@ static int serdes_panel_split_probe(struct platform_device *pdev) static int serdes_panel_split_remove(struct platform_device *pdev) { struct serdes_panel_split *serdes_panel_split = platform_get_drvdata(pdev); + struct backlight_device *backlight = serdes_panel_split->backlight; + + if (backlight) + put_device(&backlight->dev); drm_panel_remove(&serdes_panel_split->panel); diff --git a/drivers/mfd/display-serdes/serdes-panel.c b/drivers/mfd/display-serdes/serdes-panel.c index e2abfc0ba641..9815652829fb 100644 --- a/drivers/mfd/display-serdes/serdes-panel.c +++ b/drivers/mfd/display-serdes/serdes-panel.c @@ -219,7 +219,7 @@ static int serdes_panel_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "failed to parse serdes DT\n"); - serdes_panel->backlight = devm_of_find_backlight(dev); + serdes_panel->backlight = of_find_backlight_by_node(dev->of_node); if (IS_ERR(serdes_panel->backlight)) return dev_err_probe(dev, PTR_ERR(serdes_panel->backlight), "failed to get serdes backlight\n"); @@ -242,6 +242,10 @@ static int serdes_panel_probe(struct platform_device *pdev) static int serdes_panel_remove(struct platform_device *pdev) { struct serdes_panel *serdes_panel = platform_get_drvdata(pdev); + struct backlight_device *backlight = serdes_panel->backlight; + + if (backlight) + put_device(&backlight->dev); drm_panel_remove(&serdes_panel->panel); From 2586765a7c377372c608cdae9d22d0db5b7f1069 Mon Sep 17 00:00:00 2001 From: Jon Lin Date: Thu, 29 Feb 2024 17:01:28 +0800 Subject: [PATCH 05/28] arm64: dts: rockchip: rk356x boards: Fix to regulator to unique name Change-Id: I113c8f14c8386bd16d81775459b9ca4b51e47f68 Signed-off-by: Jon Lin --- arch/arm64/boot/dts/rockchip/rk3566-evb-mipitest-v10.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3566-evb1-ddr4-v10.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3566-evb2-lp4x-v10.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3566-evb5-lp4x-v10.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3567-evb2-lp4x-v10.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3568-evb2-lp4x-v10.dtsi | 4 ++-- arch/arm64/boot/dts/rockchip/rk3568-evb5-ddr4-v10.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3568-evb6-ddr3-v10.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3568-nvr-demo-v10.dtsi | 2 +- .../arm64/boot/dts/rockchip/rk3568-pcie-ep-lp4x-v10-linux.dts | 2 +- arch/arm64/boot/dts/rockchip/rk3568-toybrick-sd0.dtsi | 4 ++-- arch/arm64/boot/dts/rockchip/rk3568-toybrick-x0.dtsi | 4 ++-- 13 files changed, 16 insertions(+), 16 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3566-evb-mipitest-v10.dtsi b/arch/arm64/boot/dts/rockchip/rk3566-evb-mipitest-v10.dtsi index a68269f58d64..6ebad295cbfc 100644 --- a/arch/arm64/boot/dts/rockchip/rk3566-evb-mipitest-v10.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3566-evb-mipitest-v10.dtsi @@ -14,7 +14,7 @@ model = "Rockchip RK3566 EVB MIPITEST V10 Board"; compatible = "rockchip,rk3566-evb-mipitest-v10", "rockchip,rk3566"; - vcc3v3_pcie: gpio-regulator { + vcc3v3_pcie: vcc3v3-pcie { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie"; regulator-min-microvolt = <3300000>; diff --git a/arch/arm64/boot/dts/rockchip/rk3566-evb1-ddr4-v10.dtsi b/arch/arm64/boot/dts/rockchip/rk3566-evb1-ddr4-v10.dtsi index 8963446a1203..bf0e166e339f 100644 --- a/arch/arm64/boot/dts/rockchip/rk3566-evb1-ddr4-v10.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3566-evb1-ddr4-v10.dtsi @@ -14,7 +14,7 @@ model = "Rockchip RK3566 EVB1 DDR4 V10 Board"; compatible = "rockchip,rk3566-evb1-ddr4-v10", "rockchip,rk3566"; - vcc3v3_pcie: gpio-regulator { + vcc3v3_pcie: vcc3v3-pcie { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie"; regulator-min-microvolt = <3300000>; diff --git a/arch/arm64/boot/dts/rockchip/rk3566-evb2-lp4x-v10.dtsi b/arch/arm64/boot/dts/rockchip/rk3566-evb2-lp4x-v10.dtsi index 7b434c5e79ef..d981c7e2c938 100644 --- a/arch/arm64/boot/dts/rockchip/rk3566-evb2-lp4x-v10.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3566-evb2-lp4x-v10.dtsi @@ -27,7 +27,7 @@ regulator-boot-on; }; - vcc3v3_pcie: gpio-regulator { + vcc3v3_pcie: vcc3v3-pcie { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie"; regulator-min-microvolt = <3300000>; diff --git a/arch/arm64/boot/dts/rockchip/rk3566-evb5-lp4x-v10.dtsi b/arch/arm64/boot/dts/rockchip/rk3566-evb5-lp4x-v10.dtsi index 55bfa76c1bb1..936d6fdfe715 100644 --- a/arch/arm64/boot/dts/rockchip/rk3566-evb5-lp4x-v10.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3566-evb5-lp4x-v10.dtsi @@ -14,7 +14,7 @@ model = "Rockchip RK3566 EVB5 LP4X V10 Board"; compatible = "rockchip,rk3566-evb5-lp4x-v10", "rockchip,rk3566"; - vcc3v3_pcie: gpio-regulator { + vcc3v3_pcie: vcc3v3-pcie { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie"; regulator-min-microvolt = <3300000>; diff --git a/arch/arm64/boot/dts/rockchip/rk3567-evb2-lp4x-v10.dtsi b/arch/arm64/boot/dts/rockchip/rk3567-evb2-lp4x-v10.dtsi index 1dadc179b07d..eb8d1f297ca8 100644 --- a/arch/arm64/boot/dts/rockchip/rk3567-evb2-lp4x-v10.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3567-evb2-lp4x-v10.dtsi @@ -27,7 +27,7 @@ regulator-boot-on; }; - vcc3v3_pcie: gpio-regulator { + vcc3v3_pcie: vcc3v3-pcie { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie"; regulator-min-microvolt = <3300000>; diff --git a/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi b/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi index dc9ad4236dd3..14f35e1bd877 100644 --- a/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi @@ -55,7 +55,7 @@ vin-supply = <&vcc3v3_sys>; }; - vcc3v3_pcie: gpio-regulator { + vcc3v3_pcie: vcc3v3-pcie { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie"; regulator-min-microvolt = <3300000>; diff --git a/arch/arm64/boot/dts/rockchip/rk3568-evb2-lp4x-v10.dtsi b/arch/arm64/boot/dts/rockchip/rk3568-evb2-lp4x-v10.dtsi index 34b7f284bfd3..7a47fda80a28 100644 --- a/arch/arm64/boot/dts/rockchip/rk3568-evb2-lp4x-v10.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3568-evb2-lp4x-v10.dtsi @@ -32,7 +32,7 @@ vin-supply = <&vcc3v3_sys>; }; - vcc3v3_pcie: gpio-regulator { + vcc3v3_pcie: vcc3v3-pcie { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie"; regulator-min-microvolt = <3300000>; @@ -43,7 +43,7 @@ vin-supply = <&dc_12v>; }; - qsgmii_3v3: gpio-regulator { + qsgmii_3v3: qsgmii-3v3 { compatible = "regulator-gpio"; regulator-name = "qsgmii_3v3"; regulator-min-microvolt = <0100000>; diff --git a/arch/arm64/boot/dts/rockchip/rk3568-evb5-ddr4-v10.dtsi b/arch/arm64/boot/dts/rockchip/rk3568-evb5-ddr4-v10.dtsi index beba4ab09426..cb054e4932e1 100644 --- a/arch/arm64/boot/dts/rockchip/rk3568-evb5-ddr4-v10.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3568-evb5-ddr4-v10.dtsi @@ -52,7 +52,7 @@ vin-supply = <&vcc3v3_sys>; }; - vcc3v3_pcie: gpio-regulator { + vcc3v3_pcie: vcc3v3-pcie { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie"; regulator-min-microvolt = <3300000>; diff --git a/arch/arm64/boot/dts/rockchip/rk3568-evb6-ddr3-v10.dtsi b/arch/arm64/boot/dts/rockchip/rk3568-evb6-ddr3-v10.dtsi index c93e47356277..cd98ca1e97f1 100644 --- a/arch/arm64/boot/dts/rockchip/rk3568-evb6-ddr3-v10.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3568-evb6-ddr3-v10.dtsi @@ -20,7 +20,7 @@ pinctrl-0 = <&hp_det>; }; - vcc3v3_pcie: gpio-regulator { + vcc3v3_pcie: vcc3v3-pcie { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie"; regulator-min-microvolt = <3300000>; diff --git a/arch/arm64/boot/dts/rockchip/rk3568-nvr-demo-v10.dtsi b/arch/arm64/boot/dts/rockchip/rk3568-nvr-demo-v10.dtsi index 3f59acc227e8..0f2d15b65b42 100644 --- a/arch/arm64/boot/dts/rockchip/rk3568-nvr-demo-v10.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3568-nvr-demo-v10.dtsi @@ -54,7 +54,7 @@ vin-supply = <&vcc3v3_sys>; }; - vcc3v3_pcie: gpio-regulator { + vcc3v3_pcie: vcc3v3-pcie { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie"; regulator-min-microvolt = <3300000>; diff --git a/arch/arm64/boot/dts/rockchip/rk3568-pcie-ep-lp4x-v10-linux.dts b/arch/arm64/boot/dts/rockchip/rk3568-pcie-ep-lp4x-v10-linux.dts index 4f255e1a635c..3e23653980cc 100644 --- a/arch/arm64/boot/dts/rockchip/rk3568-pcie-ep-lp4x-v10-linux.dts +++ b/arch/arm64/boot/dts/rockchip/rk3568-pcie-ep-lp4x-v10-linux.dts @@ -94,7 +94,7 @@ status = "okay"; }; - vcc3v3_pcie: gpio-regulator { + vcc3v3_pcie: vcc3v3-pcie { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie"; regulator-min-microvolt = <3300000>; diff --git a/arch/arm64/boot/dts/rockchip/rk3568-toybrick-sd0.dtsi b/arch/arm64/boot/dts/rockchip/rk3568-toybrick-sd0.dtsi index 7526f292b716..89863c4115dc 100644 --- a/arch/arm64/boot/dts/rockchip/rk3568-toybrick-sd0.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3568-toybrick-sd0.dtsi @@ -51,7 +51,7 @@ vin-supply = <&vcc3v3_sys>; }; - pcie30_3v3: gpio-regulator { + pcie30_3v3: pcie30-3v3 { compatible = "regulator-gpio"; regulator-name = "pcie30_3v3"; regulator-min-microvolt = <100000>; @@ -80,7 +80,7 @@ vin-supply = <&vcc3v3_sys>; }; - vcc3v3_pcie: gpio-regulator { + vcc3v3_pcie: vcc3v3-pcie { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie"; regulator-min-microvolt = <3300000>; diff --git a/arch/arm64/boot/dts/rockchip/rk3568-toybrick-x0.dtsi b/arch/arm64/boot/dts/rockchip/rk3568-toybrick-x0.dtsi index c71eedafcaa2..08ac59108847 100644 --- a/arch/arm64/boot/dts/rockchip/rk3568-toybrick-x0.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3568-toybrick-x0.dtsi @@ -66,7 +66,7 @@ }; }; - pcie20_3v3: gpio-regulator { + pcie20_3v3: pcie20-3v3 { compatible = "regulator-gpio"; regulator-name = "pcie20_3v3"; regulator-min-microvolt = <100000>; @@ -97,7 +97,7 @@ vin-supply = <&vcc3v3_sys>; }; - pcie30_3v3: gpio-regulator { + pcie30_3v3: pcie30-3v3 { compatible = "regulator-gpio"; regulator-name = "pcie30_3v3"; regulator-min-microvolt = <100000>; From d7be109f40e88f91868a5635b239f2a2c1d6ba47 Mon Sep 17 00:00:00 2001 From: Felix Zeng Date: Mon, 26 Feb 2024 20:06:57 +0800 Subject: [PATCH 06/28] driver: rknpu: Update rknpu driver, version: 0.9.5 * Fix exception error caused by uninitialized atomic variables * Improve rknpu reset function Signed-off-by: Felix Zeng Change-Id: I952d4a679048fa7c295d37b72da64be95be8f5ee --- drivers/rknpu/include/rknpu_drv.h | 15 ++------ drivers/rknpu/rknpu_devfreq.c | 3 +- drivers/rknpu/rknpu_drv.c | 19 ---------- drivers/rknpu/rknpu_job.c | 3 ++ drivers/rknpu/rknpu_reset.c | 61 +++++++++++++++++-------------- 5 files changed, 42 insertions(+), 59 deletions(-) diff --git a/drivers/rknpu/include/rknpu_drv.h b/drivers/rknpu/include/rknpu_drv.h index 0e7ad2d7ddb0..ffb902856260 100644 --- a/drivers/rknpu/include/rknpu_drv.h +++ b/drivers/rknpu/include/rknpu_drv.h @@ -29,10 +29,10 @@ #define DRIVER_NAME "rknpu" #define DRIVER_DESC "RKNPU driver" -#define DRIVER_DATE "20240129" +#define DRIVER_DATE "20240226" #define DRIVER_MAJOR 0 #define DRIVER_MINOR 9 -#define DRIVER_PATCHLEVEL 4 +#define DRIVER_PATCHLEVEL 5 #define LOG_TAG "RKNPU" @@ -58,11 +58,6 @@ struct rknpu_irqs_data { irqreturn_t (*irq_hdl)(int irq, void *ctx); }; -struct rknpu_reset_data { - const char *srst_a_name; - const char *srst_h_name; -}; - struct rknpu_amount_data { uint16_t offset_clr_all; uint16_t offset_dt_wr; @@ -80,9 +75,7 @@ struct rknpu_config { __u32 pc_task_status_offset; __u32 pc_dma_ctrl; const struct rknpu_irqs_data *irqs; - const struct rknpu_reset_data *resets; int num_irqs; - int num_resets; __u64 nbuf_phyaddr; __u64 nbuf_size; __u64 max_submit_number; @@ -132,8 +125,8 @@ struct rknpu_device { void __iomem *bw_priority_base; struct rknpu_fence_context *fence_ctx; bool iommu_en; - struct reset_control *srst_a[RKNPU_MAX_CORES]; - struct reset_control *srst_h[RKNPU_MAX_CORES]; + struct reset_control **srsts; + int num_srsts; struct clk_bulk_data *clks; int num_clks; struct regulator *vdd; diff --git a/drivers/rknpu/rknpu_devfreq.c b/drivers/rknpu/rknpu_devfreq.c index a2a83a9ccd9e..9c5f9b3d256a 100644 --- a/drivers/rknpu/rknpu_devfreq.c +++ b/drivers/rknpu/rknpu_devfreq.c @@ -162,7 +162,6 @@ static int rk3588_npu_set_read_margin(struct device *dev, struct rockchip_opp_info *opp_info, u32 rm) { - struct rknpu_device *rknpu_dev = dev_get_drvdata(dev); u32 offset = 0, val = 0; int i, ret = 0; @@ -174,7 +173,7 @@ static int rk3588_npu_set_read_margin(struct device *dev, LOG_DEV_DEBUG(dev, "set rm to %d\n", rm); - for (i = 0; i < rknpu_dev->config->num_irqs; i++) { + for (i = 0; i < 3; i++) { ret = regmap_read(opp_info->grf, offset, &val); if (ret < 0) { LOG_DEV_ERROR(dev, "failed to get rm from 0x%x\n", diff --git a/drivers/rknpu/rknpu_drv.c b/drivers/rknpu/rknpu_drv.c index c5c41fef5678..88783bec35f5 100644 --- a/drivers/rknpu/rknpu_drv.c +++ b/drivers/rknpu/rknpu_drv.c @@ -81,15 +81,6 @@ static const struct rknpu_irqs_data rk3588_npu_irqs[] = { { "npu2_irq", rknpu_core2_irq_handler } }; -static const struct rknpu_reset_data rknpu_resets[] = { { "srst_a", - "srst_h" } }; - -static const struct rknpu_reset_data rk3588_npu_resets[] = { - { "srst_a0", "srst_h0" }, - { "srst_a1", "srst_h1" }, - { "srst_a2", "srst_h2" } -}; - static const struct rknpu_amount_data rknpu_old_top_amount = { .offset_clr_all = 0x8010, .offset_dt_wr = 0x8034, @@ -121,9 +112,7 @@ static const struct rknpu_config rk356x_rknpu_config = { .pc_task_status_offset = 0x3c, .pc_dma_ctrl = 0, .irqs = rknpu_irqs, - .resets = rknpu_resets, .num_irqs = ARRAY_SIZE(rknpu_irqs), - .num_resets = ARRAY_SIZE(rknpu_resets), .nbuf_phyaddr = 0, .nbuf_size = 0, .max_submit_number = (1 << 12) - 1, @@ -142,9 +131,7 @@ static const struct rknpu_config rk3588_rknpu_config = { .pc_task_status_offset = 0x3c, .pc_dma_ctrl = 0, .irqs = rk3588_npu_irqs, - .resets = rk3588_npu_resets, .num_irqs = ARRAY_SIZE(rk3588_npu_irqs), - .num_resets = ARRAY_SIZE(rk3588_npu_resets), .nbuf_phyaddr = 0, .nbuf_size = 0, .max_submit_number = (1 << 12) - 1, @@ -163,9 +150,7 @@ static const struct rknpu_config rk3583_rknpu_config = { .pc_task_status_offset = 0x3c, .pc_dma_ctrl = 0, .irqs = rk3588_npu_irqs, - .resets = rk3588_npu_resets, .num_irqs = 2, - .num_resets = 2, .nbuf_phyaddr = 0, .nbuf_size = 0, .max_submit_number = (1 << 12) - 1, @@ -184,9 +169,7 @@ static const struct rknpu_config rv1106_rknpu_config = { .pc_task_status_offset = 0x3c, .pc_dma_ctrl = 0, .irqs = rknpu_irqs, - .resets = rknpu_resets, .num_irqs = ARRAY_SIZE(rknpu_irqs), - .num_resets = ARRAY_SIZE(rknpu_resets), .nbuf_phyaddr = 0, .nbuf_size = 0, .max_submit_number = (1 << 16) - 1, @@ -205,9 +188,7 @@ static const struct rknpu_config rk3562_rknpu_config = { .pc_task_status_offset = 0x48, .pc_dma_ctrl = 1, .irqs = rknpu_irqs, - .resets = rknpu_resets, .num_irqs = ARRAY_SIZE(rknpu_irqs), - .num_resets = ARRAY_SIZE(rknpu_resets), .nbuf_phyaddr = 0xfe400000, .nbuf_size = 256 * 1024, .max_submit_number = (1 << 16) - 1, diff --git a/drivers/rknpu/rknpu_job.c b/drivers/rknpu/rknpu_job.c index fadd8e3b2413..e13488dfc6b3 100644 --- a/drivers/rknpu/rknpu_job.c +++ b/drivers/rknpu/rknpu_job.c @@ -128,6 +128,7 @@ static inline struct rknpu_job *rknpu_job_alloc(struct rknpu_device *rknpu_dev, struct rknpu_submit *args) { struct rknpu_job *job = NULL; + int i = 0; #ifdef CONFIG_ROCKCHIP_RKNPU_DRM_GEM struct rknpu_gem_object *task_obj = NULL; #endif @@ -143,6 +144,8 @@ static inline struct rknpu_job *rknpu_job_alloc(struct rknpu_device *rknpu_dev, ((args->core_mask & RKNPU_CORE2_MASK) >> 2); atomic_set(&job->run_count, job->use_core_num); atomic_set(&job->interrupt_count, job->use_core_num); + for (i = 0; i < rknpu_dev->config->num_irqs; i++) + atomic_set(&job->submit_count[i], 0); #ifdef CONFIG_ROCKCHIP_RKNPU_DRM_GEM task_obj = (struct rknpu_gem_object *)(uintptr_t)args->task_obj_addr; if (task_obj) diff --git a/drivers/rknpu/rknpu_reset.c b/drivers/rknpu/rknpu_reset.c index 91c9b75d68e7..36657a7852fe 100644 --- a/drivers/rknpu/rknpu_reset.c +++ b/drivers/rknpu/rknpu_reset.c @@ -28,27 +28,34 @@ static inline struct reset_control *rknpu_reset_control_get(struct device *dev, int rknpu_reset_get(struct rknpu_device *rknpu_dev) { #ifndef FPGA_PLATFORM - struct reset_control *srst_a = NULL; - struct reset_control *srst_h = NULL; int i = 0; + int num_srsts = 0; - for (i = 0; i < rknpu_dev->config->num_resets; i++) { - srst_a = rknpu_reset_control_get( - rknpu_dev->dev, - rknpu_dev->config->resets[i].srst_a_name); - if (IS_ERR(srst_a)) - return PTR_ERR(srst_a); - - rknpu_dev->srst_a[i] = srst_a; - - srst_h = rknpu_reset_control_get( - rknpu_dev->dev, - rknpu_dev->config->resets[i].srst_h_name); - if (IS_ERR(srst_h)) - return PTR_ERR(srst_h); - - rknpu_dev->srst_h[i] = srst_h; + num_srsts = of_count_phandle_with_args(rknpu_dev->dev->of_node, + "resets", "#reset-cells"); + if (num_srsts <= 0) { + LOG_DEV_ERROR(rknpu_dev->dev, + "failed to get rknpu resets from dtb\n"); + return num_srsts; } + + rknpu_dev->srsts = devm_kcalloc(rknpu_dev->dev, num_srsts, + sizeof(*rknpu_dev->srsts), GFP_KERNEL); + if (!rknpu_dev->srsts) + return -ENOMEM; + + for (i = 0; i < num_srsts; ++i) { + rknpu_dev->srsts[i] = devm_reset_control_get_exclusive_by_index( + rknpu_dev->dev, i); + if (IS_ERR(rknpu_dev->srsts[i])) { + rknpu_dev->num_srsts = i; + return PTR_ERR(rknpu_dev->srsts[i]); + } + } + + rknpu_dev->num_srsts = num_srsts; + + return num_srsts; #endif return 0; @@ -93,7 +100,7 @@ int rknpu_soft_reset(struct rknpu_device *rknpu_dev) #ifndef FPGA_PLATFORM struct iommu_domain *domain = NULL; struct rknpu_subcore_data *subcore_data = NULL; - int ret = -EINVAL, i = 0; + int ret = 0, i = 0; if (rknpu_dev->bypass_soft_reset) { LOG_WARN("bypass soft reset\n"); @@ -112,17 +119,17 @@ int rknpu_soft_reset(struct rknpu_device *rknpu_dev) wake_up(&subcore_data->job_done_wq); } - LOG_INFO("soft reset\n"); + LOG_INFO("soft reset, num: %d\n", rknpu_dev->num_srsts); - for (i = 0; i < rknpu_dev->config->num_resets; i++) { - ret = rknpu_reset_assert(rknpu_dev->srst_a[i]); - ret |= rknpu_reset_assert(rknpu_dev->srst_h[i]); + for (i = 0; i < rknpu_dev->num_srsts; ++i) + ret |= rknpu_reset_assert(rknpu_dev->srsts[i]); - udelay(10); + udelay(10); - ret |= rknpu_reset_deassert(rknpu_dev->srst_a[i]); - ret |= rknpu_reset_deassert(rknpu_dev->srst_h[i]); - } + for (i = 0; i < rknpu_dev->num_srsts; ++i) + ret |= rknpu_reset_deassert(rknpu_dev->srsts[i]); + + udelay(10); if (ret) { LOG_DEV_ERROR(rknpu_dev->dev, From d21a7378aa2a544e3aac7bee83772c31c763afee Mon Sep 17 00:00:00 2001 From: Zorro Liu Date: Tue, 20 Feb 2024 16:19:26 +0800 Subject: [PATCH 07/28] input: touch: cyttsp5: x,y max size for input use dts defined Change-Id: I5845d1b06968f4b923949a3f55956b7bf91fbd60 Signed-off-by: Zorro Liu --- .../input/touchscreen/cyttsp5/cyttsp5_core.h | 2 ++ .../touchscreen/cyttsp5/cyttsp5_devtree.c | 12 ++++++++++ .../touchscreen/cyttsp5/cyttsp5_mt_common.c | 24 ++++++++++++++----- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/drivers/input/touchscreen/cyttsp5/cyttsp5_core.h b/drivers/input/touchscreen/cyttsp5/cyttsp5_core.h index 5989613865c1..93f60d4b1088 100644 --- a/drivers/input/touchscreen/cyttsp5/cyttsp5_core.h +++ b/drivers/input/touchscreen/cyttsp5/cyttsp5_core.h @@ -157,6 +157,8 @@ struct cyttsp5_mt_platform_data { char const *inp_dev_name; int vkeys_x; int vkeys_y; + int max_x; + int max_y; int swap_x; int swap_y; int xy_exchange; diff --git a/drivers/input/touchscreen/cyttsp5/cyttsp5_devtree.c b/drivers/input/touchscreen/cyttsp5/cyttsp5_devtree.c index 9cb9050960b6..10f25ae45990 100644 --- a/drivers/input/touchscreen/cyttsp5/cyttsp5_devtree.c +++ b/drivers/input/touchscreen/cyttsp5/cyttsp5_devtree.c @@ -295,6 +295,18 @@ static void *create_and_get_mt_pdata(struct device_node *dev_node) if (!rc) pdata->vkeys_y = value; + rc = of_property_read_u32(dev_node, "cy,max_x", &value); + if (!rc) + pdata->max_x = value; + else + pdata->max_x = -1; + + rc = of_property_read_u32(dev_node, "cy,max_y", &value); + if (!rc) + pdata->max_y = value; + else + pdata->max_y = -1; + rc = of_property_read_u32(dev_node, "cy,revert_x", &value); if (!rc) pdata->swap_x = value; diff --git a/drivers/input/touchscreen/cyttsp5/cyttsp5_mt_common.c b/drivers/input/touchscreen/cyttsp5/cyttsp5_mt_common.c index eed583a17f4e..a70b733d1812 100644 --- a/drivers/input/touchscreen/cyttsp5/cyttsp5_mt_common.c +++ b/drivers/input/touchscreen/cyttsp5/cyttsp5_mt_common.c @@ -36,8 +36,8 @@ #define MT_PARAM_FUZZ(md, sig_ost) PARAM_FUZZ(md->pdata->frmwrk, sig_ost) #define MT_PARAM_FLAT(md, sig_ost) PARAM_FLAT(md->pdata->frmwrk, sig_ost) -static int screen_max_x = 1872; -static int screen_max_y = 1404; +static int screen_max_x = 0; +static int screen_max_y = 0; static int revert_x_flag = 0; static int revert_y_flag = 0; static int exchange_x_y_flag = 0; @@ -636,17 +636,24 @@ static int cyttsp5_setup_input_device(struct device *dev) max = max_y; else if (i == CY_ABS_P_OST) max = max_p; + /*change x-y max value*/ if(signal == ABS_MT_POSITION_X) { + if (screen_max_x > 0) + input_set_abs_params(md->input, signal, min, screen_max_x, + MT_PARAM_FUZZ(md, i), MT_PARAM_FLAT(md, i)); screen_max_x = max; dev_info(dev, "screen_max_x = %d\n", screen_max_x); - } - if(signal == ABS_MT_POSITION_Y) { + } else if (signal == ABS_MT_POSITION_Y) { + if (screen_max_y > 0) + input_set_abs_params(md->input, signal, min, screen_max_y, + MT_PARAM_FUZZ(md, i), MT_PARAM_FLAT(md, i)); screen_max_y = max; dev_info(dev, "screen_max_y = %d\n", screen_max_y); + } else { + input_set_abs_params(md->input, signal, min, max, + MT_PARAM_FUZZ(md, i), MT_PARAM_FLAT(md, i)); } - input_set_abs_params(md->input, signal, min, max, - MT_PARAM_FUZZ(md, i), MT_PARAM_FLAT(md, i)); parade_debug(dev, DEBUG_LEVEL_1, "%s: register signal=%02X min=%d max=%d\n", __func__, signal, min, max); @@ -747,6 +754,11 @@ int cyttsp5_mt_probe(struct device *dev) md->input->close = cyttsp5_mt_close; input_set_drvdata(md->input, md); + if (mt_pdata->max_x > 0) + screen_max_x = mt_pdata->max_x; + if (mt_pdata->max_y > 0) + screen_max_y = mt_pdata->max_y; + /* get sysinfo */ md->si = _cyttsp5_request_sysinfo(dev); From 44f70fc15bef6b5d4cc7aa4345697906fd5db77c Mon Sep 17 00:00:00 2001 From: Binyuan Lan Date: Sun, 28 Jan 2024 03:13:43 +0000 Subject: [PATCH 08/28] input: touchscreen: support tp chipone_9551r Signed-off-by: Binyuan Lan Change-Id: Ia62dfe3670af23c69f857ab0fcc251607187974d --- drivers/input/touchscreen/Kconfig | 2 + drivers/input/touchscreen/Makefile | 1 + .../input/touchscreen/chipone_tddi/Kconfig | 12 + .../input/touchscreen/chipone_tddi/Makefile | 17 + .../chipone_tddi/cts_builtin_firmware.h | 25 + .../chipone_tddi/cts_charger_detect.c | 1024 ++++++ .../chipone_tddi/cts_charger_detect.h | 45 + .../touchscreen/chipone_tddi/cts_config.h | 242 ++ .../input/touchscreen/chipone_tddi/cts_core.c | 2874 ++++++++++++++++ .../input/touchscreen/chipone_tddi/cts_core.h | 988 ++++++ .../touchscreen/chipone_tddi/cts_driver.c | 1231 +++++++ .../chipone_tddi/cts_earjack_detect.c | 649 ++++ .../chipone_tddi/cts_earjack_detect.h | 42 + .../touchscreen/chipone_tddi/cts_firmware.c | 1044 ++++++ .../touchscreen/chipone_tddi/cts_firmware.h | 33 + .../input/touchscreen/chipone_tddi/cts_oem.c | 2127 ++++++++++++ .../input/touchscreen/chipone_tddi/cts_oem.h | 12 + .../touchscreen/chipone_tddi/cts_platform.c | 1919 +++++++++++ .../touchscreen/chipone_tddi/cts_platform.h | 309 ++ .../touchscreen/chipone_tddi/cts_sfctrl.h | 38 + .../touchscreen/chipone_tddi/cts_sfctrlv2.c | 342 ++ .../touchscreen/chipone_tddi/cts_spi_flash.c | 536 +++ .../touchscreen/chipone_tddi/cts_spi_flash.h | 45 + .../touchscreen/chipone_tddi/cts_strerror.c | 175 + .../touchscreen/chipone_tddi/cts_strerror.h | 11 + .../touchscreen/chipone_tddi/cts_sysfs.c | 2904 ++++++++++++++++ .../touchscreen/chipone_tddi/cts_sysfs.h | 27 + .../input/touchscreen/chipone_tddi/cts_tcs.c | 1901 +++++++++++ .../input/touchscreen/chipone_tddi/cts_tcs.h | 127 + .../input/touchscreen/chipone_tddi/cts_test.c | 2927 +++++++++++++++++ .../input/touchscreen/chipone_tddi/cts_test.h | 113 + .../input/touchscreen/chipone_tddi/cts_tool.c | 1207 +++++++ 32 files changed, 22949 insertions(+) create mode 100644 drivers/input/touchscreen/chipone_tddi/Kconfig create mode 100644 drivers/input/touchscreen/chipone_tddi/Makefile create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_builtin_firmware.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_charger_detect.c create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_charger_detect.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_config.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_core.c create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_core.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_driver.c create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_earjack_detect.c create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_earjack_detect.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_firmware.c create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_firmware.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_oem.c create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_oem.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_platform.c create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_platform.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_sfctrl.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_sfctrlv2.c create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_spi_flash.c create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_spi_flash.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_strerror.c create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_strerror.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_sysfs.c create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_sysfs.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_tcs.c create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_tcs.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_test.c create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_test.h create mode 100644 drivers/input/touchscreen/chipone_tddi/cts_tool.c diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index eb5324b90a4f..6129ce0ac901 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -198,6 +198,8 @@ config TOUCHSCREEN_CHIPONE_ICN8505 To compile this driver as a module, choose M here: the module will be called chipone_icn8505. +source "drivers/input/touchscreen/chipone_tddi/Kconfig" + config TOUCHSCREEN_CY8CTMA140 tristate "cy8ctma140 touchscreen" depends on I2C diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index ecb3b041e762..311c707dbfba 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_TOUCHSCREEN_BU21013) += bu21013_ts.o obj-$(CONFIG_TOUCHSCREEN_BU21029) += bu21029_ts.o obj-$(CONFIG_TOUCHSCREEN_CHIPONE_ICN8318) += chipone_icn8318.o obj-$(CONFIG_TOUCHSCREEN_CHIPONE_ICN8505) += chipone_icn8505.o +obj-$(CONFIG_TOUCHSCREEN_CHIPONE_9551R) += chipone_tddi/ obj-$(CONFIG_TOUCHSCREEN_CY8CTMA140) += cy8ctma140.o obj-$(CONFIG_TOUCHSCREEN_CY8CTMG110) += cy8ctmg110_ts.o obj-$(CONFIG_TOUCHSCREEN_CYTTSP_CORE) += cyttsp_core.o diff --git a/drivers/input/touchscreen/chipone_tddi/Kconfig b/drivers/input/touchscreen/chipone_tddi/Kconfig new file mode 100644 index 000000000000..d5d30af6cd16 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# chipone Touchscreen driver configuration +# + +config TOUCHSCREEN_CHIPONE_9551R + tristate "chipone Touchscreen" + depends on I2C + default n + help + Say Y here if you have chipone touch panel. + If unsure, say N. diff --git a/drivers/input/touchscreen/chipone_tddi/Makefile b/drivers/input/touchscreen/chipone_tddi/Makefile new file mode 100644 index 000000000000..b8815b9558fb --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/Makefile @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +obj-$(CONFIG_TOUCHSCREEN_CHIPONE_9551R) += chipone-ts.o +chipone-ts-y += cts_driver.o +chipone-ts-y += cts_core.o +chipone-ts-y += cts_sfctrlv2.o +chipone-ts-y += cts_spi_flash.o +chipone-ts-y += cts_firmware.o +chipone-ts-y += cts_test.o +chipone-ts-y += cts_charger_detect.o +chipone-ts-y += cts_earjack_detect.o +chipone-ts-y += cts_tcs.o +chipone-ts-y += cts_platform.o +chipone-ts-y += cts_tool.o +chipone-ts-y += cts_sysfs.o +chipone-ts-y += cts_strerror.o +chipone-ts-y += cts_oem.o diff --git a/drivers/input/touchscreen/chipone_tddi/cts_builtin_firmware.h b/drivers/input/touchscreen/chipone_tddi/cts_builtin_firmware.h new file mode 100644 index 000000000000..8eb9d940f3a4 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_builtin_firmware.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +static u8 icnl9951_driver_builtin_firmware[] = { +}; + +static u8 icnl9951r_driver_builtin_firmware[] = { +}; + +static struct cts_firmware cts_driver_builtin_firmwares[] = { + { + .name = "OEM-Project", /* MUST set non-NULL */ + .hwid = CTS_DEV_HWID_ICNL9951, + .fwid = CTS_DEV_FWID_ICNL9951, + .data = icnl9951_driver_builtin_firmware, + .size = ARRAY_SIZE(icnl9951_driver_builtin_firmware), + }, + { + .name = "OEM-Project", /* MUST set non-NULL */ + .hwid = CTS_DEV_HWID_ICNL9951R, + .fwid = CTS_DEV_FWID_ICNL9951R, + .data = icnl9951r_driver_builtin_firmware, + .size = ARRAY_SIZE(icnl9951r_driver_builtin_firmware), + }, + +}; diff --git a/drivers/input/touchscreen/chipone_tddi/cts_charger_detect.c b/drivers/input/touchscreen/chipone_tddi/cts_charger_detect.c new file mode 100644 index 000000000000..952f03f163fe --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_charger_detect.c @@ -0,0 +1,1024 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define LOG_TAG "Charger" + +#include "cts_config.h" +#include "cts_platform.h" +#include "cts_core.h" +#include "cts_sysfs.h" + +#ifdef CONFIG_CTS_CHARGER_DETECT + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0) +/** + * match_string - matches given string in an array + * @array: array of strings + * @n: number of strings in the array or -1 for NULL terminated arrays + * @string: string to match with + * + * Return: + * index of a @string in the @array if matches, or %-EINVAL otherwise. + */ +int match_string(const char *const *array, size_t n, const char *string) +{ + int index; + const char *item; + + for (index = 0; index < n; index++) { + item = array[index]; + if (!item) + break; + if (!strcmp(item, string)) + return index; + } + + return -EINVAL; +} +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) +#define CFG_CTS_CHARGER_DETECT_PSY_NOTIFY +#endif +#define CFG_CTS_CHARGER_DETECT_PSY_POLL + +#include +#include + +enum cts_charger_detect_type { + CTS_CHGR_DET_TYPE_NONE = 0, + CTS_CHGR_DET_TYPE_PSY_NOTIFY, + CTS_CHGR_DET_TYPE_POLL_PSP, + CTS_CHGR_DET_TYPE_MAX +}; + +/* Over-written setting for DTS */ +/* #define CFG_CTS_DEF_CHGR_DET_ENABLE */ + +/* Default settings */ +#ifdef CFG_CTS_CHARGER_DETECT_PSY_NOTIFY +#define CFG_CTS_DEF_CHGR_DET_TYPE CTS_CHGR_DET_TYPE_PSY_NOTIFY +#else /* CFG_CTS_CHARGER_DETECT_PSY_NOTIFY */ +#define CFG_CTS_DEF_CHGR_DET_TYPE CTS_CHGR_DET_TYPE_POLL_PSP +#endif /* CFG_CTS_CHARGER_DETECT_PSY_NOTIFY */ + +#define CFG_CTS_DEF_CHGR_DET_PSY_NAME "usb" +#define CFG_CTS_DEF_CHGR_DET_PSY_PROP POWER_SUPPLY_PROP_ONLINE +#define CFG_CTS_DEF_CHGR_DET_PSP_POLL_INTERVAL 2000u + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) +/* Lower version has no power_supply_get_property() */ +#define POWER_SUPPLY_GET_PROPERTY(psy, psp, val) \ + power_supply_get_property(psy, psp, val) +#else +#define POWER_SUPPLY_GET_PROPERTY(psy, psp, val) \ + psy->get_property(psy, psp, val) +#endif + +struct cts_charger_detect_data { + bool enable; + bool running; + bool state; + + /* Parameter */ + enum cts_charger_detect_type type; + const char *psy_name; + enum power_supply_property psp; + u32 psp_poll_interval; /* Unit is ms */ + + /* Power supply notify */ +#ifdef CFG_CTS_CHARGER_DETECT_PSY_NOTIFY + bool notifier_registered; + struct notifier_block psy_notifier; +#endif + +#ifdef CFG_CTS_CHARGER_DETECT_PSY_POLL + /* Polling power supply property */ + struct delayed_work psp_poll_work; +#endif + + struct work_struct set_charger_state_work; + + /* Sysfs */ +#ifdef CONFIG_CTS_SYSFS + bool sysfs_created; +#endif + + struct chipone_ts_data *cts_data; + +}; + +#define POWER_SUPPLY_PROP_PREFIX "POWER_SUPPLY_PROP_" +#define POWER_SUPPLY_PROP_TOKEN(property) \ + {.prop = POWER_SUPPLY_PROP_ ##property, \ + .name = POWER_SUPPLY_PROP_PREFIX #property} + +const static struct { + enum power_supply_property prop; + const char *name; +} power_supply_prop_token[] = { + POWER_SUPPLY_PROP_TOKEN(STATUS), + POWER_SUPPLY_PROP_TOKEN(PRESENT), + POWER_SUPPLY_PROP_TOKEN(ONLINE), +}; + +static const char *power_supply_prop_str(enum power_supply_property prop) +{ + int i; + + for (i = 0; i < sizeof(power_supply_prop_token); i++) { + if (prop == power_supply_prop_token[i].prop) + return power_supply_prop_token[i].name; + } + + return "Unknown"; +} + +static enum power_supply_property power_supply_prop_from_name(const char *name) +{ + size_t offset = strlen(POWER_SUPPLY_PROP_PREFIX); + int i; + + for (i = 0; i < ARRAY_SIZE(power_supply_prop_token); i++) { + if (strcasecmp(power_supply_prop_token[i].name + offset, name) == 0 + || strcasecmp(power_supply_prop_token[i].name, name) == 0) { + return power_supply_prop_token[i].prop; + } + } + + return -ENOENT; +} + +static const char *charger_detect_type_text[] = { + "none", "notify", "poll", +}; + +static const char *charger_detect_type_str(enum cts_charger_detect_type type) +{ + return type < ARRAY_SIZE(charger_detect_type_text) ? + charger_detect_type_text[type] : "Unknown"; +} + +static int parse_charger_detect_dt(struct cts_charger_detect_data *cd_data, + struct device_node *np) +{ + const char *psy_name; + const char *type_str; + const char *psp_str; + int ret; + + cts_info("Parse dt"); + +#ifdef CFG_CTS_DEF_CHGR_DET_ENABLE + cd_data->enable = true; +#else /* CFG_CTS_DEF_CHGR_DET_ENABLE */ + cd_data->enable = + of_property_read_bool(np, "chipone,touch-charger-detect-enable"); +#endif /* CFG_CTS_DEF_CHGR_DET_ENABLE */ + + cd_data->type = CFG_CTS_DEF_CHGR_DET_TYPE; + ret = of_property_read_string(np, + "chipone,touch-charger-detect-type", &type_str); + if (ret) + cts_warn("Parse detect type failed %d", ret); + else { + int type = match_string(charger_detect_type_text, + ARRAY_SIZE(charger_detect_type_text), + type_str); + if (type < 0) + cts_err("Parse detect type '%s' invalid", type_str); + else + cd_data->type = type; + } + + ret = of_property_read_string(np, "chipone,touch-charger-detect-psy-name", + &psy_name); + if (ret) { + cts_warn("Parse detect psy name failed %d", ret); + psy_name = CFG_CTS_DEF_CHGR_DET_PSY_NAME; + } else { + if (power_supply_get_by_name(psy_name) == NULL) { + cts_warn("Power supply '%s' not found", psy_name); + psy_name = CFG_CTS_DEF_CHGR_DET_PSY_NAME; + } + } + cd_data->psy_name = kstrdup(psy_name, GFP_KERNEL); + if (cd_data->psy_name == NULL) { + cts_err("Alloc mem for psy name failed"); + return -ENOMEM; + } + + cd_data->psp = CFG_CTS_DEF_CHGR_DET_PSY_PROP; + ret = of_property_read_string(np, "chipone,touch-charger-detect-psp", + &psp_str); + if (ret) { + cts_warn("Parse detect psp failed %d", ret); + } else { + struct power_supply *psy; + enum power_supply_property psp; + union power_supply_propval val; + + psp = power_supply_prop_from_name(psp_str); + if (psp < 0) { + cts_warn("Parse detect psp: '%s' invalid", psp_str); + } else { + psy = power_supply_get_by_name(cd_data->psy_name); + if (psy != NULL && POWER_SUPPLY_GET_PROPERTY(psy, psp, &val) >= 0) { + cts_err("Parse detect psp invalid"); + } else { + cd_data->psp = psp; + } + } + } + + cd_data->psp_poll_interval = CFG_CTS_DEF_CHGR_DET_PSP_POLL_INTERVAL; + ret = of_property_read_u32(np, + "chipone,touch-charger-detect-psp-poll-interval", + &cd_data->psp_poll_interval); + if (ret) + cts_warn("Parse detect psp poll interval failed %d", ret); + + return 0; +} + +static int start_charger_detect(struct cts_charger_detect_data *cd_data) +{ + if (!cd_data->enable) { + cts_warn("Start detect while NOT enabled"); + return -EINVAL; + } + + if (cd_data->running) { + cts_warn("Start detect while already RUNNING"); + return 0; + } + + cts_info("Start detect type: %d(%s)", + cd_data->type, charger_detect_type_str(cd_data->type)); + +#ifdef CFG_CTS_CHARGER_DETECT_PSY_NOTIFY + if (cd_data->type == CTS_CHGR_DET_TYPE_PSY_NOTIFY) { + int ret = power_supply_reg_notifier(&cd_data->psy_notifier); + + if (ret) { + cts_err("Register notifier failed: %d", ret); + return ret; + } + cd_data->notifier_registered = true; + + cd_data->running = true; + + return 0; + } +#endif /* CFG_CTS_CHARGER_DETECT_PSY_NOTIFY */ + +#ifdef CFG_CTS_CHARGER_DETECT_PSY_POLL + if (cd_data->type == CTS_CHGR_DET_TYPE_POLL_PSP) { + if (!queue_delayed_work(cd_data->cts_data->workqueue, + &cd_data->psp_poll_work, + msecs_to_jiffies(cd_data->psp_poll_interval))) { + cts_warn("Queue detect work while already on the queue"); + } + cd_data->running = true; + + return 0; + } +#endif /* CFG_CTS_CHARGER_DETECT_PSY_POLL */ + + return -ENOTSUPP; +} + +static int stop_charger_detect(struct cts_charger_detect_data *cd_data) +{ + if (!cd_data->running) { + cts_warn("Stop detect while NOT running"); + return 0; + } + + cts_info("Stop detect type: %d(%s)", cd_data->type, + charger_detect_type_str(cd_data->type)); + +#ifdef CFG_CTS_CHARGER_DETECT_PSY_NOTIFY + if (cd_data->type == CTS_CHGR_DET_TYPE_PSY_NOTIFY) { + if (cd_data->notifier_registered) { + power_supply_unreg_notifier(&cd_data->psy_notifier); + cd_data->notifier_registered = false; + } + cd_data->running = false; + + return 0; + } +#endif /* CFG_CTS_CHARGER_DETECT_PSY_NOTIFY */ + +#ifdef CFG_CTS_CHARGER_DETECT_PSY_POLL + if (cd_data->type == CTS_CHGR_DET_TYPE_POLL_PSP) { + if (!cancel_delayed_work_sync(&cd_data->psp_poll_work)) + cts_warn("Cancel poll psp work while NOT pending"); + + cd_data->running = false; + + return 0; + } +#endif /* CFG_CTS_CHARGER_DETECT_PSY_POLL */ + + return 0; +} + +static int enable_charger_detect(struct cts_charger_detect_data *cd_data) +{ + cts_info("Enable detect type: %d(%s)", cd_data->type, + charger_detect_type_str(cd_data->type)); + + cd_data->enable = true; + + return 0; +} + +static int disable_charger_detect(struct cts_charger_detect_data *cd_data) +{ + int ret; + + cts_info("Disable detect type: %d(%s)", cd_data->type, + charger_detect_type_str(cd_data->type)); + + ret = stop_charger_detect(cd_data); + if (ret) + cts_err("Disable detect failed %d", ret); + + cd_data->enable = false; + + return 0; +} + +static int get_charger_state(struct cts_charger_detect_data *cd_data) +{ + struct power_supply *psy; + union power_supply_propval propval; + int ret; + + cts_dbg("Get state from psy: '%s' prop: %d(%s)", + cd_data->psy_name, cd_data->psp, + power_supply_prop_str(cd_data->psp)); + + psy = power_supply_get_by_name(cd_data->psy_name); + if (cd_data->psy_name == NULL || psy == NULL) { + cts_err("Get state from psy: '%s' not found", cd_data->psy_name); + return -EINVAL; + } + + ret = POWER_SUPPLY_GET_PROPERTY(psy, cd_data->psp, &propval); + if (ret < 0) { + cts_err("Get state from psy: '%s' prop: %d(%s) failed", + cd_data->psy_name, cd_data->psp, + power_supply_prop_str(cd_data->psp)); + return -EINVAL; + } + + /* ONLY for bool type */ + cd_data->state = !!propval.intval; + + cts_dbg("State: %s", cd_data->state ? "ATTACHED" : "DETACHED"); + + return 0; +} + +static int switch_charger_detect_type(struct cts_charger_detect_data *cd_data, + enum cts_charger_detect_type type) +{ + bool running; + + cts_info("Switch detect type to %d(%s)", + type, charger_detect_type_str(type)); + + if (type >= CTS_CHGR_DET_TYPE_MAX) { + cts_err("Switch detect type %d invalid", type); + return -EINVAL; + } + + if (cd_data->type == type) { + cts_warn("Switch detect type equal"); + return 0; + } + + running = cd_data->running; + + if (running) { + int ret = stop_charger_detect(cd_data); + if (ret) { + cts_err("Stop detect failed %d", ret); + return ret; + } + } + + cd_data->type = type; + + if (running) { + int ret = start_charger_detect(cd_data); + if (ret) { + cts_err("Start detect failed %d", ret); + cd_data->type = CTS_CHGR_DET_TYPE_NONE; + return ret; + } + } + + return 0; +} + +static void set_dev_charger_state_work(struct work_struct *work) +{ + struct cts_charger_detect_data *cd_data; + int ret; + + cd_data = container_of(work, struct cts_charger_detect_data, + set_charger_state_work); + + cts_lock_device(&cd_data->cts_data->cts_dev); + ret = cts_set_dev_charger_attached(&cd_data->cts_data->cts_dev, + cd_data->state); + cts_unlock_device(&cd_data->cts_data->cts_dev); + if (ret) { + cts_err("Set dev charger attached to %s failed %d", + cd_data->state ? "ATTACHED" : "DETATCHED", ret); + /* Set to previous state, try set again in next loop */ + cd_data->state = !cd_data->state; + } +} + +#ifdef CFG_CTS_CHARGER_DETECT_PSY_NOTIFY +static int psy_notify_callback(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct cts_charger_detect_data *cd_data; + struct power_supply *psy; + bool prev_state; + int ret; + + if (nb == NULL || data == NULL) { + cts_err("PSY notify callback with notifier %p or data %p = NULL", + nb, data); + return NOTIFY_DONE; + } + + if (action != PSY_EVENT_PROP_CHANGED) { + cts_dbg("Notify event %ld not care", action); + return NOTIFY_DONE; + } + + cd_data = container_of(nb, struct cts_charger_detect_data, psy_notifier); + + psy = (struct power_supply *)data; + if (strcmp(psy->desc->name, cd_data->psy_name) != 0) { + cts_dbg("Notify from power supply '%s' not care", psy->desc->name); + return NOTIFY_DONE; + } + + prev_state = cd_data->state; + + ret = get_charger_state(cd_data); + if (ret < 0) { + cts_err("Get state failed %d", ret); + return NOTIFY_DONE; + } + + cts_info("State changed: %s -> %s", prev_state ? "ATTACHED" : "DETACHED", + cd_data->state ? "ATTACHED" : "DETACHED"); + + if (!queue_work(cd_data->cts_data->workqueue, + &cd_data->set_charger_state_work)) { + cts_warn("Set device charger state work is PENDING"); + } + + return NOTIFY_OK; +} +#endif /* CFG_CTS_CHARGER_DETECT_PSY_NOTIFY */ + +#ifdef CFG_CTS_CHARGER_DETECT_PSY_POLL +static void poll_psp_work(struct work_struct *work) +{ + struct cts_charger_detect_data *cd_data; + bool prev_state; + int ret; + + cts_dbg("Poll psp work"); + + cd_data = container_of(to_delayed_work(work), + struct cts_charger_detect_data, psp_poll_work); + + prev_state = cd_data->state; + + ret = get_charger_state(cd_data); + if (ret < 0) { + cts_err("Get state failed %d", ret); + } else { + if (cd_data->state != prev_state) { + cts_info("State changed: %s -> %s", + prev_state ? "ATTACHED" : "DETACHED", + cd_data->state ? "ATTACHED" : "DETACHED"); + + if (!queue_work(cd_data->cts_data->workqueue, + &cd_data->set_charger_state_work)) { + cts_warn("Set device charger state work is PENDING"); + } + } + } + + if (!queue_delayed_work(cd_data->cts_data->workqueue, + &cd_data->psp_poll_work, + msecs_to_jiffies(cd_data->psp_poll_interval))) { + cts_warn("Queue detect work while already on the queue"); + } +} +#endif /* CFG_CTS_CHARGER_DETECT_PSY_POLL */ + +static int init_charger_detect(struct cts_charger_detect_data *cd_data) +{ + int ret; + +#ifdef CFG_CTS_CHARGER_DETECT_PSY_NOTIFY + cd_data->psy_notifier.notifier_call = psy_notify_callback; +#endif + +#ifdef CFG_CTS_CHARGER_DETECT_PSY_POLL + INIT_DELAYED_WORK(&cd_data->psp_poll_work, poll_psp_work); +#endif + + INIT_WORK(&cd_data->set_charger_state_work, set_dev_charger_state_work); + + ret = get_charger_state(cd_data); + if (ret) + cts_err("Get state failed %d", ret); + + return 0; +} + +/* Sysfs */ +#ifdef CONFIG_CTS_SYSFS + +#define CHARGER_DET_SYSFS_GROUP_NAME "charger-det" + +static ssize_t charger_detect_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data; + + cts_info("Read sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s'", + attr->attr.name); + + return scnprintf(buf, PAGE_SIZE, "Charger detect: %s\n", + cd_data->enable ? "ENABLED" : "DISABLED"); +} + +/* Echo 0/n/N/of/Of/Of/OF/1/y/Y/on/oN/On/ON > enable */ +static ssize_t charger_detect_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data; + bool enable; + int ret; + + cts_info("Write sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s' size %zu", + attr->attr.name, count); + + cts_parse_arg(buf, count); + + if (argc != 1) { + cts_err("Invalid num of args"); + return -EINVAL; + } + + ret = kstrtobool(argv[0], &enable); + if (ret) { + cts_err("Invalid param of enable"); + return ret; + } + + if (enable) + ret = enable_charger_detect(cd_data); + else + ret = disable_charger_detect(cd_data); + + if (ret) { + cts_err("%s charger detect failed %d", + enable ? "Enable" : "Disable", ret); + return ret; + } + + return count; +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + charger_detect_enable_show, charger_detect_enable_store); + +static ssize_t charger_detect_running_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data; + + cts_info("Read sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s'", + attr->attr.name); + + return scnprintf(buf, PAGE_SIZE, "Charger detect: %sRunning\n", + cd_data->running ? "" : "Not-"); +} + +/* Echo 0/n/N/of/Of/Of/OF/1/y/Y/on/oN/On/ON > runing */ +static ssize_t charger_detect_running_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data; + bool running; + int ret; + + cts_info("Write sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s' size %zu", + attr->attr.name, count); + + cts_parse_arg(buf, count); + + if (argc != 1) { + cts_err("Invalid num of args"); + return -EINVAL; + } + + ret = kstrtobool(argv[0], &running); + if (ret) { + cts_err("Invalid param of running"); + return ret; + } + + if (running) + ret = start_charger_detect(cd_data); + else + ret = stop_charger_detect(cd_data); + + if (ret) { + cts_err("%s charger detect failed %d", running ? "Start" : "Stop", ret); + return ret; + } + + return count; +} + +static DEVICE_ATTR(running, S_IWUSR | S_IRUGO, + charger_detect_running_show, charger_detect_running_store); + +static ssize_t charger_detect_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data; + + cts_info("Read sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s'", + attr->attr.name); + + return scnprintf(buf, PAGE_SIZE, "Charger detect type: %d(%s)\n", + cd_data->type, charger_detect_type_str(cd_data->type)); +} + +/* echo charger_detect_type > type */ +static ssize_t charger_detect_type_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data; + int type, ret; + + cts_info("Write sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s' size %zu", + attr->attr.name, count); + + cts_parse_arg(buf, count); + + if (argc != 1) { + cts_err(""); + return -EINVAL; + } + + type = match_string(charger_detect_type_text, + ARRAY_SIZE(charger_detect_type_text), argv[0]); + if (type < 0) { + cts_err("Invalid charger detect type: '%s'", argv[0]); + return -EINVAL; + } + + ret = switch_charger_detect_type(cd_data, type); + if (ret) { + cts_err("Switch charger detect type failed %d", ret); + return ret; + } + + return count; +} + +static DEVICE_ATTR(type, S_IWUSR | S_IRUGO, + charger_detect_type_show, charger_detect_type_store); + +static ssize_t charger_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data; + int ret; + + cts_info("Read sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s'", + attr->attr.name); + + ret = get_charger_state(cd_data); + if (ret) { + return scnprintf(buf, PAGE_SIZE, "Get charge state failed %d\n", ret); + } + + return scnprintf(buf, PAGE_SIZE, "Charger state: %s\n", + cd_data->state ? "ATTACHED" : "DETACHED"); +} + +static DEVICE_ATTR(state, S_IRUGO, charger_state_show, NULL); + +static ssize_t charger_detect_param_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data; + struct power_supply *psy; + + cts_info("Read sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s'", + attr->attr.name); + + psy = power_supply_get_by_name(cd_data->psy_name); + + return scnprintf(buf, PAGE_SIZE, + "Power supply name: %s(%sExist)\n" + "Power supply prop: %d(%s)\n" + "Poll interval : %dms\n", + cd_data->psy_name, psy == NULL ? "Non-" : "", + cd_data->psp, power_supply_prop_str(cd_data->psp), + cd_data->psp_poll_interval); +} + +/* echo psy_name psy_property [interval] > param */ +static ssize_t charger_detect_param_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data; + struct power_supply *psy; + union power_supply_propval propval; + enum power_supply_property psp; + u32 interval; + int ret; + + cts_info("Write sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s' size %zu", + attr->attr.name, count); + + cts_parse_arg(buf, count); + + if (argc < 2 || argc > 3) { + cts_err("Invalid num of args"); + return -EINVAL; + } + + psy = power_supply_get_by_name(argv[0]); + if (psy == NULL) { + cts_err("Power supply '%s' not found", argv[0]); + return -EINVAL; + } + + psp = power_supply_prop_from_name(argv[1]); + if (psp < 0 || POWER_SUPPLY_GET_PROPERTY(psy, psp, &propval) < 0) { + cts_err("Power supply '%s' property '%s' not valid", + argv[0], power_supply_prop_str(psp)); + return -EINVAL; + } + + interval = cd_data->psp_poll_interval; + if (argc > 2) { + ret = kstrtou32(argv[2], 0, &interval); + if (ret) { + cts_err("Arg interval is invalid"); + return -EINVAL; + } + } + + if (cd_data->psy_name) + kfree(cd_data->psy_name); + + cd_data->psy_name = kstrdup(argv[0], GFP_KERNEL); + if (cd_data->psy_name == NULL) { + cts_err("Dup power supply name failed"); + return -ENOMEM; + } + + cd_data->psp = psp; + cd_data->psp_poll_interval = interval; + + return count; +} + +static DEVICE_ATTR(param, S_IWUSR | S_IRUGO, + charger_detect_param_show, charger_detect_param_store); + +static struct attribute *charger_detect_attrs[] = { + &dev_attr_enable.attr, + &dev_attr_running.attr, + &dev_attr_type.attr, + &dev_attr_state.attr, + &dev_attr_param.attr, + NULL +}; + +static const struct attribute_group charger_detect_attr_group = { + .name = CHARGER_DET_SYSFS_GROUP_NAME, + .attrs = charger_detect_attrs, +}; +#endif /* CONFIG_CTS_SYSFS */ + +int cts_charger_detect_init(struct chipone_ts_data *cts_data) +{ + struct cts_charger_detect_data *cd_data; + int ret = 0; + + cts_info("Init detect"); + + if (cts_data == NULL) { + cts_err("Init detect while cts_data = NULL"); + return -EFAULT; + } + + cd_data = kzalloc(sizeof(*cd_data), GFP_KERNEL); + if (cd_data == NULL) { + cts_err("Alloc charger detect data failed"); + return -ENOMEM; + } + +#ifdef CONFIG_CTS_OF + ret = parse_charger_detect_dt(cd_data, cts_data->device->of_node); +#else /* CONFIG_CTS_OF */ +#ifdef CFG_CTS_DEF_CHGR_DET_ENABLE + cd_data->enable = true; +#else /* CFG_CTS_DEF_CHGR_DET_ENABLE */ + cd_data->enable = false; +#endif /* CFG_CTS_DEF_CHGR_DET_ENABLE */ + + cd_data->type = CFG_CTS_DEF_CHGR_DET_TYPE; + cd_data->psp = CFG_CTS_DEF_CHGR_DET_PSY_PROP; + cd_data->psp_poll_interval = CFG_CTS_DEF_CHGR_DET_PSP_POLL_INTERVAL; + cd_data->psy_name = kstrdup(CFG_CTS_DEF_CHGR_DET_PSY_NAME, GFP_KERNEL); + if (cd_data->psy_name == NULL) { + cts_err("Dup power supply name failed"); + ret = -ENOMEM; + } +#endif /* CONFIG_CTS_OF */ + if (ret) { + cts_err("Get detect param failed %d", ret); + goto free_cd_data; + } + + cts_info("Detect: %sABLED", cd_data->enable ? "EN" : "DIS"); + cts_info(" Type : %s", charger_detect_type_str(cd_data->type)); + cts_info(" PSY Name: %s, prop: %d(%s)", cd_data->psy_name, + cd_data->psp, power_supply_prop_str(cd_data->psp)); + cts_info(" Poll Int: %dms", cd_data->psp_poll_interval); + + ret = init_charger_detect(cd_data); + if (ret) { + cts_err("Init detect failed %d", ret); + goto free_psy_name; + } + +#ifdef CONFIG_CTS_SYSFS + cts_info("Create sysfs group '%s'", CHARGER_DET_SYSFS_GROUP_NAME); + ret = sysfs_create_group(&cts_data->device->kobj, + &charger_detect_attr_group); + if (ret) + cts_warn("Create sysfs group failed %d", ret); + else + cd_data->sysfs_created = true; + +#endif /* CONFIG_CTS_SYSFS */ + + cts_data->charger_detect_data = cd_data; + cd_data->cts_data = cts_data; + + return 0; + +free_psy_name: + kfree(cd_data->psy_name); +free_cd_data: + kfree(cd_data); + + return ret; +} + +int cts_charger_detect_deinit(struct chipone_ts_data *cts_data) +{ + struct cts_charger_detect_data *cd_data; + int ret; + + cts_info("Deinit detect"); + + if (cts_data == NULL) { + cts_err("Deinit detect with cts_data = NULL"); + return -EFAULT; + } + + cd_data = cts_data->charger_detect_data; + if (cd_data == NULL) { + cts_warn("Deinit detect with charger_detect_data = NULL"); + return 0; + } + + if (cd_data->running) { + ret = stop_charger_detect(cd_data); + if (ret) + cts_err("Stop detect failed %d", ret); + } + +#ifdef CONFIG_CTS_SYSFS + if (cd_data->sysfs_created) { + cts_info("Remove sysfs group '%s'", CHARGER_DET_SYSFS_GROUP_NAME); + sysfs_remove_group(&cts_data->device->kobj, + &charger_detect_attr_group); + cd_data->sysfs_created = false; + } +#endif /* CONFIG_CTS_SYSFS */ + + if (cd_data->psy_name) { + cts_info("Kfree power supply name"); + kfree(cd_data->psy_name); + cd_data->psy_name = NULL; + } + + kfree(cd_data); + cts_data->charger_detect_data = NULL; + + return 0; +} + +int cts_is_charger_attached(struct chipone_ts_data *cts_data, bool *attached) +{ + struct cts_charger_detect_data *cd_data; + int ret; + + if (cts_data == NULL) { + cts_err("Get state with cts_data = NULL"); + return -EFAULT; + } + + cd_data = cts_data->charger_detect_data; + if (cd_data == NULL) { + cts_err("Get state with charger_detect_data = NULL"); + return -ENODEV; + } + + cts_info("Get state using type %d(%s) param", + cd_data->type, charger_detect_type_str(cd_data->type)); + + ret = get_charger_state(cd_data); + if (ret) { + cts_err("Get state failed %d", ret); + return ret; + } + + *attached = cd_data->state; + + return 0; +} + +int cts_start_charger_detect(struct chipone_ts_data *cts_data) +{ + struct cts_charger_detect_data *cd_data; + + if (cts_data == NULL) { + cts_err("Start detect with cts_data = NULL"); + return -EFAULT; + } + + cd_data = cts_data->charger_detect_data; + if (cd_data == NULL) { + cts_err("Start detect with charger_detect_data = NULL"); + return -ENODEV; + } + + return start_charger_detect(cd_data); +} + +int cts_stop_charger_detect(struct chipone_ts_data *cts_data) +{ + struct cts_charger_detect_data *cd_data; + + if (cts_data == NULL) { + cts_err("Stop detect with cts_data = NULL"); + return -EFAULT; + } + + cd_data = cts_data->charger_detect_data; + if (cd_data == NULL) { + cts_err("Stop detect with charger_detect_data = NULL"); + return -ENODEV; + } + + return stop_charger_detect(cd_data); +} +#endif /* CONFIG_CTS_CHARGER_DETECT */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_charger_detect.h b/drivers/input/touchscreen/chipone_tddi/cts_charger_detect.h new file mode 100644 index 000000000000..8cf8d7d7cbfc --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_charger_detect.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CTS_CHARGER_DETECT_H +#define CTS_CHARGER_DETECT_H + +#include "cts_config.h" + +struct chipone_ts_data; + +#ifdef CONFIG_CTS_CHARGER_DETECT +extern int cts_charger_detect_init(struct chipone_ts_data *cts_data); +extern int cts_charger_detect_deinit(struct chipone_ts_data *cts_data); +extern int cts_start_charger_detect(struct chipone_ts_data *cts_data); +extern int cts_stop_charger_detect(struct chipone_ts_data *cts_data); +extern int cts_is_charger_attached(struct chipone_ts_data *cts_data, + bool *attached); +#else /* CONFIG_CTS_CHARGER_DETECT */ +static inline int cts_charger_detect_init(struct chipone_ts_data *cts_data) +{ + return -ENOTSUPP; +} + +static inline int cts_charger_detect_deinit(struct chipone_ts_data *cts_data) +{ + return -ENOTSUPP; +} + +static inline int cts_start_charger_detect(struct chipone_ts_data *cts_data) +{ + return -ENODEV; +} + +static inline int cts_stop_charger_detect(struct chipone_ts_data *cts_data) +{ + return -ENODEV; +} + +static inline int cts_is_charger_attached(struct chipone_ts_data *cts_data, + bool *attached) +{ + return -ENODEV; +} +#endif /* CONFIG_CTS_CHARGER_DETECT */ + +#endif /* CTS_CHARGER_DETECT_H */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_config.h b/drivers/input/touchscreen/chipone_tddi/cts_config.h new file mode 100644 index 000000000000..d9581056203c --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_config.h @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CTS_CONFIG_H +#define CTS_CONFIG_H + +/** Driver version */ +#define CFG_CTS_DRIVER_MAJOR_VERSION 3 +#define CFG_CTS_DRIVER_MINOR_VERSION 6 +#define CFG_CTS_DRIVER_PATCH_VERSION 3 +#define CFG_CTS_DRIVER_VERSION "v3.6.3" + +/* Support Mediatek TPD arch */ +/* #define CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED */ + +/* Support Spreadtrum platform */ +#define CFG_CTS_PLATFORM_SPRD_SUPPORTED + +/* Support cascade application */ +//#define CFG_CTS_CASCADE_SUPPORTED + +/* Support RK dsi extcon notifier for suspend and resume */ +#define CONFIG_PM_DSI_EXTCON_NOTIFIER + +/* Support both finger and stylus protocol, 11 */ +// #define CFG_CTS_FINGER_STYLUS_SUPPORTED + +/* #define CFG_CTS_STYLUS_BTN_SUPPORTED */ + +#define CONFIG_CTS_I2C_HOST + +/* For Goole Security */ +#define CFG_CTS_FOR_GKI + +/** Whether reset pin is used */ +#define CFG_CTS_HAS_RESET_PIN + +#ifndef CONFIG_CTS_I2C_HOST +#ifndef CFG_CTS_HAS_RESET_PIN +#define CFG_CTS_HAS_RESET_PIN +#endif +#define CFG_CTS_SPI_SPEED_KHZ 9600 +#endif /* CONFIG_CTS_I2C_HOST */ + +/* #define CFG_CTS_FORCE_UP */ + +/* #define CFG_CTS_HEARTBEAT_MECHANISM */ + +/* #define CFG_CTS_PALM_DETECT */ +#ifdef CFG_CTS_PALM_DETECT +#define CFG_CTS_PALM_EVENT 240 +#define CFG_CTS_PALM_ID 0x80 +#endif + +#define CONFIG_GENERIC_HARDIRQS + +/** Whether force download firmware to chip */ +/* #define CFG_CTS_FIRMWARE_FORCE_UPDATE */ + +/** Use build in firmware or firmware file in fs*/ +#define CFG_CTS_DRIVER_BUILTIN_FIRMWARE +#define CFG_CTS_FIRMWARE_IN_FS +#ifdef CFG_CTS_FIRMWARE_IN_FS +/* #define CFG_CTS_FW_UPDATE_SYS */ +#define CFG_CTS_FIRMWARE_FILENAME "chipone_firmware.bin" +#endif /* CFG_CTS_FIRMWARE_IN_FS */ + +#define CFG_CTS_FIRMWARE_SIZE (120 * 1024) +#define CFG_CTS_FACTORY_LIMIT_FILENAME "chipone_limit.bin" + +/* IC type support */ +#define CFG_CTS_CHIP_NAME "CHIPONE-TDDI" + +#ifdef CONFIG_PROC_FS +/* Proc FS for backward compatibility for APK tool com.ICN85xx */ +#define CONFIG_CTS_LEGACY_TOOL +#endif /* CONFIG_PROC_FS */ + +#ifdef CONFIG_SYSFS +/* Sys FS for gesture report, debug feature etc. */ +#define CONFIG_CTS_SYSFS +#endif /* CONFIG_SYSFS */ + +#define CFG_CTS_MAX_TOUCH_NUM (10) +#define CFG_CTS_MAX_STYLUS_NUM (4) + +/* Virtual key support */ +/* #define CONFIG_CTS_VIRTUALKEY */ +#ifdef CONFIG_CTS_VIRTUALKEY +#define CFG_CTS_MAX_VKEY_NUM (4) +#define CFG_CTS_NUM_VKEY (3) +#define CFG_CTS_VKEY_KEYCODES {KEY_BACK, KEY_HOME, KEY_MENU} +#endif /* CONFIG_CTS_VIRTUALKEY */ + +/* Gesture wakeup */ +#define CFG_CTS_GESTURE +#ifdef CFG_CTS_GESTURE +#define GESTURE_UP 0x11 +#define GESTURE_C 0x12 +#define GESTURE_O 0x13 +#define GESTURE_M 0x14 +#define GESTURE_W 0x15 +#define GESTURE_E 0x16 +#define GESTURE_S 0x17 +#define GESTURE_Z 0x1d +#define GESTURE_V 0x1e +#define GESTURE_D_TAP 0x50 +#define GESTURE_DOWN 0x22 +#define GESTURE_LEFT 0x23 +#define GESTURE_RIGHT 0x24 +#define GESTURE_TAP 0x7f + +#define CFG_CTS_NUM_GESTURE (14u) +#define CFG_CTS_GESTURE_REPORT_KEY +#define CFG_CTS_GESTURE_KEYMAP \ +{ \ + { GESTURE_C, KEY_C,}, \ + { GESTURE_W, KEY_W,}, \ + { GESTURE_V, KEY_V,}, \ + { GESTURE_D_TAP, KEY_F1,}, \ + { GESTURE_Z, KEY_Z,}, \ + { GESTURE_M, KEY_M,}, \ + { GESTURE_O, KEY_O,}, \ + { GESTURE_E, KEY_E,}, \ + { GESTURE_S, KEY_S,}, \ + { GESTURE_UP, KEY_UP,}, \ + { GESTURE_DOWN, KEY_DOWN,}, \ + { GESTURE_LEFT, KEY_LEFT,}, \ + { GESTURE_RIGHT, KEY_RIGHT,}, \ + { GESTURE_TAP, KEY_F2,}, \ +} +#define CFG_CTS_GESTURE_REPORT_TRACE 0 +#endif /* CFG_CTS_GESTURE */ + +#define CONFIG_CTS_GLOVE +//#define CONFIG_CTS_EARJACK_DETECT +//#define CONFIG_CTS_CHARGER_DETECT + +/* #define CONFIG_CTS_TP_PROXIMITY */ + +/* ESD protection */ +//#define CONFIG_CTS_ESD_PROTECTION +#ifdef CONFIG_CTS_ESD_PROTECTION +#define CFG_CTS_ESD_PROTECTION_CHECK_PERIOD (2 * HZ) +#define CFG_CTS_ESD_FAILED_CONFIRM_CNT 3 +#endif /* CONFIG_CTS_ESD_PROTECTION */ + +/* Use slot protocol (protocol B), comment it if use protocol A. */ +#define CONFIG_CTS_SLOTPROTOCOL + +/* #define CTS_CONFIG_MKDIR_FOR_CTS_TEST */ + +#ifdef CONFIG_CTS_LEGACY_TOOL +#define CFG_CTS_TOOL_PROC_FILENAME "cts_tool" +#endif /* CONFIG_CTS_LEGACY_TOOL */ + +#define CFG_CTS_UPDATE_CRCCHECK + + +#ifdef CONFIG_OF +#define CONFIG_CTS_OF +#endif + +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +/**************************************************************************** + * MTK Platform configurations + ****************************************************************************/ +#ifdef CONFIG_MTK_I2C_EXTENSION +#define TPD_SUPPORT_I2C_DMA +#define CFG_CTS_MAX_I2C_XFER_SIZE (250) +#define CFG_CTS_MAX_I2C_FIFO_XFER_SIZE (8) +#else +#define CFG_CTS_MAX_I2C_XFER_SIZE (128) +#endif /* CONFIG_MTK_I2C_EXTENSION */ + +#define CFG_CTS_MAX_I2C_READ_SIZE (8192u) +#define CFG_CTS_MAX_SPI_XFER_SIZE (8192u) +#define CFG_CTS_INT_DATA_MAX_SIZE (8192u) + +#define CTS_FW_LOG_REDIRECT_SIGN 0x60 +#define CTS_FW_LOG_BUF_LEN 128 + +//#define CFG_MTK_LEGEND_PLATFORM + +#define CFG_CTS_DEVICE_NAME TPD_DEVICE +#define CFG_CTS_DRIVER_NAME "chipone-tddi" + +#ifdef CONFIG_CTS_OF +#define CFG_CTS_OF_DEVICE_ID_NAME "mediatek,cap_touch" +#endif /* CONFIG_CTS_OF */ + +#else /* CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED */ +/**************************************************************************** + * QCOM Platform configurations + ****************************************************************************/ +#ifndef CFG_CTS_PLATFORM_SPRD_SUPPORTED +#define CONFIG_CTS_PM_FB_NOTIFIER +#endif +#ifdef CONFIG_CTS_PM_FB_NOTIFIER +#ifdef CONFIG_DRM +#define CFG_CTS_DRM_NOTIFIER +#endif /*CONFIG_DRM */ +#else /*CONFIG_CTS_PM_FB_NOTIFIER */ +#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_PM_SUSPEND) + /* #define CONFIG_CTS_PM_GENERIC */ +#endif /* CONFIG_PM_SLEEP */ + +#ifndef CONFIG_CTS_PM_GENERIC +#define CONFIG_CTS_PM_LEGACY +#endif /*CONFIG_CTS_PM_GENERIC */ +#endif /*CONFIG_CTS_PM_FB_NOTIFIER */ + +#define CFG_CTS_MAX_I2C_XFER_SIZE (48u) +#define CFG_CTS_MAX_I2C_READ_SIZE (8192u) +#define CFG_CTS_MAX_SPI_XFER_SIZE (8192u) +#define CFG_CTS_INT_DATA_MAX_SIZE (8192u) + +#define CTS_FW_LOG_REDIRECT_SIGN 0x60 +#define CTS_FW_LOG_BUF_LEN 128 + +#define CFG_CTS_DEVICE_NAME "chipone-tddi" +#define CFG_CTS_DRIVER_NAME "chipone-tddi" + +#ifdef CONFIG_CTS_OF +#define CFG_CTS_OF_DEVICE_ID_NAME "chipone-tddi" +#define CFG_CTS_OF_INT_GPIO_NAME "chipone,irq-gpio" +#define CFG_CTS_OF_RST_GPIO_NAME "chipone,rst-gpio" + +#ifdef CFG_CTS_MANUAL_CS +#define CFG_CTS_OF_CS_GPIO_NAME "chipone,cs-gpio" +#endif + +#define CFG_CTS_OF_X_RESOLUTION_NAME "chipone,x-res" +#define CFG_CTS_OF_Y_RESOLUTION_NAME "chipone,y-res" + +#ifdef CFG_CTS_FW_UPDATE_SYS +#define CFG_CTS_OF_PANEL_SUPPLIER "chipone,panel-supplier" +#endif +#endif /* CONFIG_CTS_OF */ + +#endif /* CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED */ + +#endif /* CTS_CONFIG_H */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_core.c b/drivers/input/touchscreen/chipone_tddi/cts_core.c new file mode 100644 index 000000000000..13866d43babd --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_core.c @@ -0,0 +1,2874 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define LOG_TAG "Core" + +#include "cts_config.h" +#include "cts_platform.h" +#include "cts_core.h" +#include "cts_sfctrl.h" +#include "cts_spi_flash.h" +#include "cts_sysfs.h" +#include "cts_firmware.h" +#include "cts_charger_detect.h" +#include "cts_earjack_detect.h" +#include "cts_tcs.h" +#include "cts_test.h" + +#ifdef CONFIG_CTS_I2C_HOST +int cts_i2c_writeb(struct cts_device *cts_dev, + u32 addr, u8 b, int retry, int delay) +{ + u8 buff[8]; + + cts_dbg("Write to slave_addr: 0x%02x reg: 0x%0*x val: 0x%02x", + cts_dev->rtdata.slave_addr, cts_dev->rtdata.addr_width * 2, + addr, b); + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, buff); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, buff); + else { + cts_err("Writeb invalid address width %u", cts_dev->rtdata.addr_width); + return -EINVAL; + } + buff[cts_dev->rtdata.addr_width] = b; + + return cts_plat_i2c_write(cts_dev->pdata, cts_dev->rtdata.slave_addr, + buff, cts_dev->rtdata.addr_width + 1, retry, delay); +} + +static int cts_i2c_writew(struct cts_device *cts_dev, + u32 addr, u16 w, int retry, int delay) +{ + u8 buff[8]; + + cts_dbg("Write to slave_addr: 0x%02x reg: 0x%0*x val: 0x%04x", + cts_dev->rtdata.slave_addr, cts_dev->rtdata.addr_width * 2, + addr, w); + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, buff); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, buff); + else { + cts_err("Writew invalid address width %u", cts_dev->rtdata.addr_width); + return -EINVAL; + } + + put_unaligned_le16(w, buff + cts_dev->rtdata.addr_width); + + return cts_plat_i2c_write(cts_dev->pdata, cts_dev->rtdata.slave_addr, + buff, cts_dev->rtdata.addr_width + 2, retry, delay); +} + +static int cts_i2c_writel(struct cts_device *cts_dev, + u32 addr, u32 l, int retry, int delay) +{ + u8 buff[8]; + + cts_dbg("Write to slave_addr: 0x%02x reg: 0x%0*x val: 0x%08x", + cts_dev->rtdata.slave_addr, cts_dev->rtdata.addr_width * 2, + addr, l); + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, buff); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, buff); + else { + cts_err("Writel invalid address width %u", cts_dev->rtdata.addr_width); + return -EINVAL; + } + + put_unaligned_le32(l, buff + cts_dev->rtdata.addr_width); + + return cts_plat_i2c_write(cts_dev->pdata, cts_dev->rtdata.slave_addr, + buff, cts_dev->rtdata.addr_width + 4, retry, delay); +} + +int cts_i2c_writesb(struct cts_device *cts_dev, u32 addr, + const u8 *src, size_t len, int retry, int delay) +{ + int ret; + u8 *data; + size_t max_xfer_size; + size_t payload_len; + size_t xfer_len; + + cts_dbg("Write to slave_addr: 0x%02x reg: 0x%0*x len: %zu", + cts_dev->rtdata.slave_addr, cts_dev->rtdata.addr_width * 2, + addr, len); + + max_xfer_size = cts_plat_get_max_i2c_xfer_size(cts_dev->pdata); + data = cts_plat_get_i2c_xfer_buf(cts_dev->pdata, len); + while (len) { + payload_len = + min((size_t)(max_xfer_size - cts_dev->rtdata.addr_width), len); + xfer_len = payload_len + cts_dev->rtdata.addr_width; + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, data); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, data); + else { + cts_err("Writesb invalid address width %u", + cts_dev->rtdata.addr_width); + return -EINVAL; + } + + memcpy(data + cts_dev->rtdata.addr_width, src, payload_len); + + ret = cts_plat_i2c_write(cts_dev->pdata, + cts_dev->rtdata.slave_addr, data, + xfer_len, retry, delay); + if (ret) { + cts_err("Platform i2c write failed %d", ret); + return ret; + } + + src += payload_len; + len -= payload_len; + addr += payload_len; + } + + return 0; +} + +int cts_i2c_readb(struct cts_device *cts_dev, + u32 addr, u8 *b, int retry, int delay) +{ + u8 addr_buf[4]; + + cts_dbg("Readb from slave_addr: 0x%02x reg: 0x%0*x", + cts_dev->rtdata.slave_addr, cts_dev->rtdata.addr_width * 2, addr); + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, addr_buf); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, addr_buf); + else { + cts_err("Readb invalid address width %u", cts_dev->rtdata.addr_width); + return -EINVAL; + } + + return cts_plat_i2c_read(cts_dev->pdata, cts_dev->rtdata.slave_addr, + addr_buf, cts_dev->rtdata.addr_width, b, 1, retry, delay); +} + +static int cts_i2c_readw(struct cts_device *cts_dev, + u32 addr, u16 *w, int retry, int delay) +{ + int ret; + u8 addr_buf[4]; + u8 buff[2]; + + cts_dbg("Readw from slave_addr: 0x%02x reg: 0x%0*x", + cts_dev->rtdata.slave_addr, cts_dev->rtdata.addr_width * 2, addr); + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, addr_buf); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, addr_buf); + else { + cts_err("Readw invalid address width %u", cts_dev->rtdata.addr_width); + return -EINVAL; + } + + ret = cts_plat_i2c_read(cts_dev->pdata, cts_dev->rtdata.slave_addr, + addr_buf, cts_dev->rtdata.addr_width, buff, 2, retry, delay); + if (ret == 0) + *w = get_unaligned_le16(buff); + + return ret; +} + +int cts_i2c_readl(struct cts_device *cts_dev, + u32 addr, u32 *l, int retry, int delay) +{ + int ret; + u8 addr_buf[4]; + u8 buff[4]; + + cts_dbg("Readl from slave_addr: 0x%02x reg: 0x%0*x", + cts_dev->rtdata.slave_addr, cts_dev->rtdata.addr_width * 2, addr); + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, addr_buf); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, addr_buf); + else { + cts_err("Readl invalid address width %u", cts_dev->rtdata.addr_width); + return -EINVAL; + } + + ret = cts_plat_i2c_read(cts_dev->pdata, cts_dev->rtdata.slave_addr, + addr_buf, cts_dev->rtdata.addr_width, buff, 4, retry, delay); + if (ret == 0) + *l = get_unaligned_le32(buff); + + return ret; +} + +static int cts_i2c_readsb(struct cts_device *cts_dev, + u32 addr, u8 *dst, size_t len, int retry, int delay) +{ + int ret; + u8 addr_buf[4]; + size_t max_xfer_size, xfer_len; + + cts_dbg("Readsb from slave_addr: 0x%02x reg: 0x%0*x len: %zu", + cts_dev->rtdata.slave_addr, cts_dev->rtdata.addr_width * 2, + addr, len); + + max_xfer_size = cts_plat_get_max_i2c_xfer_size(cts_dev->pdata); + while (len) { + xfer_len = min(max_xfer_size, len); + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, addr_buf); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, addr_buf); + else { + cts_err("Readsb invalid address width %u", + cts_dev->rtdata.addr_width); + return -EINVAL; + } + + ret = cts_plat_i2c_read(cts_dev->pdata, + cts_dev->rtdata.slave_addr, addr_buf, + cts_dev->rtdata.addr_width, dst, + xfer_len, retry, delay); + if (ret) { + cts_err("Platform i2c read failed %d", ret); + return ret; + } + + dst += xfer_len; + len -= xfer_len; + addr += xfer_len; + } + + return 0; +} +#else +static int cts_spi_writeb(struct cts_device *cts_dev, + u32 addr, u8 b, int retry, int delay) +{ + u8 buff[8]; + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, buff); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, buff); + else { + cts_err("Writeb invalid address width %u", cts_dev->rtdata.addr_width); + return -EINVAL; + } + buff[cts_dev->rtdata.addr_width] = b; + + return cts_plat_spi_write(cts_dev->pdata, cts_dev->rtdata.slave_addr, + buff, cts_dev->rtdata.addr_width + 1, retry, delay); + return 0; +} + +static int cts_spi_writew(struct cts_device *cts_dev, + u32 addr, u16 w, int retry, int delay) +{ + u8 buff[8]; + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, buff); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, buff); + else { + cts_err("Writew invalid address width %u", cts_dev->rtdata.addr_width); + return -EINVAL; + } + + put_unaligned_le16(w, buff + cts_dev->rtdata.addr_width); + + return cts_plat_spi_write(cts_dev->pdata, cts_dev->rtdata.slave_addr, + buff, cts_dev->rtdata.addr_width + 2, retry, delay); + return 0; +} + +static int cts_spi_writel(struct cts_device *cts_dev, + u32 addr, u32 l, int retry, int delay) +{ + u8 buff[8]; + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, buff); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, buff); + else { + cts_err("Writel invalid address width %u", cts_dev->rtdata.addr_width); + return -EINVAL; + } + + put_unaligned_le32(l, buff + cts_dev->rtdata.addr_width); + + return cts_plat_spi_write(cts_dev->pdata, cts_dev->rtdata.slave_addr, + buff, cts_dev->rtdata.addr_width + 4, retry, delay); + return 0; +} + +static int cts_spi_writesb(struct cts_device *cts_dev, u32 addr, + const u8 *src, size_t len, int retry, int delay) +{ + int ret; + u8 *data; + size_t max_xfer_size; + size_t payload_len; + size_t xfer_len; + + max_xfer_size = cts_plat_get_max_spi_xfer_size(cts_dev->pdata); + data = cts_plat_get_spi_xfer_buf(cts_dev->pdata, len); + while (len) { + payload_len = + min((size_t)(max_xfer_size - cts_dev->rtdata.addr_width), len); + xfer_len = payload_len + cts_dev->rtdata.addr_width; + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, data); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, data); + else { + cts_err("Writesb invalid address width %u", + cts_dev->rtdata.addr_width); + return -EINVAL; + } + + memcpy(data + cts_dev->rtdata.addr_width, src, payload_len); + + ret = cts_plat_spi_write(cts_dev->pdata, cts_dev->rtdata.slave_addr, + data, xfer_len, retry, delay); + if (ret) { + cts_err("Platform i2c write failed %d", ret); + return ret; + } + + src += payload_len; + len -= payload_len; + addr += payload_len; + } + return 0; +} + +static int cts_spi_readb(struct cts_device *cts_dev, + u32 addr, u8 *b, int retry, int delay) +{ + u8 addr_buf[4]; + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, addr_buf); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, addr_buf); + else { + cts_err("Readb invalid address width %u", cts_dev->rtdata.addr_width); + return -EINVAL; + } + + return cts_plat_spi_read(cts_dev->pdata, cts_dev->rtdata.slave_addr, + addr_buf, cts_dev->rtdata.addr_width, b, 1, retry, delay); +} + +static int cts_spi_readw(struct cts_device *cts_dev, + u32 addr, u16 *w, int retry, int delay) +{ + int ret; + u8 addr_buf[4]; + u8 buff[2]; + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, addr_buf); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, addr_buf); + else { + cts_err("Readw invalid address width %u", cts_dev->rtdata.addr_width); + return -EINVAL; + } + + ret = cts_plat_spi_read(cts_dev->pdata, cts_dev->rtdata.slave_addr, + addr_buf, cts_dev->rtdata.addr_width, buff, 2, retry, delay); + if (ret == 0) + *w = get_unaligned_le16(buff); + + return ret; +} + +static int cts_spi_readl(struct cts_device *cts_dev, + u32 addr, u32 *l, int retry, int delay) +{ + int ret; + u8 addr_buf[4]; + u8 buff[4]; + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, addr_buf); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, addr_buf); + else { + cts_err("Readl invalid address width %u", cts_dev->rtdata.addr_width); + return -EINVAL; + } + + ret = cts_plat_spi_read(cts_dev->pdata, cts_dev->rtdata.slave_addr, + addr_buf, cts_dev->rtdata.addr_width, buff, 4, retry, delay); + if (ret == 0) + *l = get_unaligned_le32(buff); + + return ret; +} + +static int cts_spi_readsb(struct cts_device *cts_dev, + u32 addr, u8 *dst, size_t len, int retry, int delay) +{ + int ret; + u8 addr_buf[4]; + size_t max_xfer_size, xfer_len; + + max_xfer_size = cts_plat_get_max_spi_xfer_size(cts_dev->pdata); + while (len) { + xfer_len = min(max_xfer_size, len); + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, addr_buf); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, addr_buf); + else { + cts_err("Readsb invalid address width %u", + cts_dev->rtdata.addr_width); + return -EINVAL; + } + + ret = cts_plat_spi_read(cts_dev->pdata, + cts_dev->rtdata.slave_addr, addr_buf, + cts_dev->rtdata.addr_width, dst, + xfer_len, retry, delay); + if (ret) { + cts_err("Platform i2c read failed %d", ret); + return ret; + } + + dst += xfer_len; + len -= xfer_len; + addr += xfer_len; + } + return 0; +} + +static int cts_spi_readsb_delay_idle(struct cts_device *cts_dev, + u32 addr, u8 *dst, size_t len, int retry, int delay, int idle) +{ + int ret; + u8 addr_buf[4]; + size_t max_xfer_size, xfer_len; + + max_xfer_size = cts_plat_get_max_spi_xfer_size(cts_dev->pdata); + while (len) { + xfer_len = min(max_xfer_size, len); + + if (cts_dev->rtdata.addr_width == 2) + put_unaligned_be16(addr, addr_buf); + else if (cts_dev->rtdata.addr_width == 3) + put_unaligned_be24(addr, addr_buf); + else { + cts_err("Readsb invalid address width %u", + cts_dev->rtdata.addr_width); + return -EINVAL; + } + + ret = cts_plat_spi_read_delay_idle(cts_dev->pdata, + cts_dev->rtdata.slave_addr, addr_buf, + cts_dev->rtdata.addr_width, + dst, xfer_len, retry, delay, idle); + if (ret) { + cts_err("Platform i2c read failed %d", ret); + return ret; + } + + dst += xfer_len; + len -= xfer_len; + addr += xfer_len; + } + return 0; +} + +#endif /* CONFIG_CTS_I2C_HOST */ + +int cts_dev_writeb(struct cts_device *cts_dev, + u32 addr, u8 b, int retry, int delay) +{ +#ifdef CONFIG_CTS_I2C_HOST + return cts_i2c_writeb(cts_dev, addr, b, retry, delay); +#else + return cts_spi_writeb(cts_dev, addr, b, retry, delay); +#endif +} + +static inline int cts_dev_writew(struct cts_device *cts_dev, + u32 addr, u16 w, int retry, int delay) +{ +#ifdef CONFIG_CTS_I2C_HOST + return cts_i2c_writew(cts_dev, addr, w, retry, delay); +#else + return cts_spi_writew(cts_dev, addr, w, retry, delay); +#endif +} + +static inline int cts_dev_writel(struct cts_device *cts_dev, + u32 addr, u32 l, int retry, int delay) +{ +#ifdef CONFIG_CTS_I2C_HOST + return cts_i2c_writel(cts_dev, addr, l, retry, delay); +#else + return cts_spi_writel(cts_dev, addr, l, retry, delay); +#endif +} + +static inline int cts_dev_writesb(struct cts_device *cts_dev, u32 addr, + const u8 *src, size_t len, int retry, int delay) +{ +#ifdef CONFIG_CTS_I2C_HOST + return cts_i2c_writesb(cts_dev, addr, src, len, retry, delay); +#else + return cts_spi_writesb(cts_dev, addr, src, len, retry, delay); +#endif +} + +int cts_dev_readb(struct cts_device *cts_dev, + u32 addr, u8 *b, int retry, int delay) +{ +#ifdef CONFIG_CTS_I2C_HOST + return cts_i2c_readb(cts_dev, addr, b, retry, delay); +#else + return cts_spi_readb(cts_dev, addr, b, retry, delay); +#endif +} + +static inline int cts_dev_readw(struct cts_device *cts_dev, + u32 addr, u16 *w, int retry, int delay) +{ +#ifdef CONFIG_CTS_I2C_HOST + return cts_i2c_readw(cts_dev, addr, w, retry, delay); +#else + return cts_spi_readw(cts_dev, addr, w, retry, delay); +#endif +} + +static inline int cts_dev_readl(struct cts_device *cts_dev, + u32 addr, u32 *l, int retry, int delay) +{ +#ifdef CONFIG_CTS_I2C_HOST + return cts_i2c_readl(cts_dev, addr, l, retry, delay); +#else + return cts_spi_readl(cts_dev, addr, l, retry, delay); +#endif +} + +static inline int cts_dev_readsb(struct cts_device *cts_dev, + u32 addr, void *dst, size_t len, int retry, int delay) +{ +#ifdef CONFIG_CTS_I2C_HOST + return cts_i2c_readsb(cts_dev, addr, dst, len, retry, delay); +#else + return cts_spi_readsb(cts_dev, addr, dst, len, retry, delay); +#endif +} + +static inline int cts_dev_readsb_delay_idle(struct cts_device *cts_dev, + u32 addr, void *dst, size_t len, int retry, int delay, int idle) +{ +#ifdef CONFIG_CTS_I2C_HOST + return cts_i2c_readsb(cts_dev, addr, dst, len, retry, delay); +#else + return cts_spi_readsb_delay_idle(cts_dev, addr, dst, len, retry, delay, + idle); +#endif +} + +#ifdef CFG_CTS_UPDATE_CRCCHECK +int cts_sram_writesb_boot_crc_retry(struct cts_device *cts_dev, + size_t len, u32 crc, int retry) +{ + int ret = 0, retries; + u32 addr[3]; + + addr[0] = 0x037ff0; + addr[1] = 0x037ff8; + addr[2] = 0x037ffc; + + retries = 0; + do { + ret = cts_dev_writel(cts_dev, addr[0], 0xCC33555A, 10, 1); + if (ret != 0) { + cts_err("SRAM writesb failed %d", ret); + continue; + } + + ret = cts_dev_writel(cts_dev, addr[1], crc, 10, 1); + if (ret != 0) { + cts_err("SRAM writesb failed %d", ret); + continue; + } + + ret = cts_dev_writel(cts_dev, addr[2], len, 10, 1); + if (ret != 0) { + cts_err("SRAM writesb failed %d", ret); + continue; + } + + break; + } while (retries++ < retry); + + return ret; +} +#endif + +static int cts_write_sram_normal_mode(struct cts_device *cts_dev, + u32 addr, const u8 *src, size_t len, int retry, int delay) +{ + int i, ret; + const u8 *psrc = src; + u8 buff[5]; + + for (i = 0; i < len; i++) { + put_unaligned_le32(addr, buff); + buff[4] = *(u8 *) psrc; + + addr++; + psrc++; + + ret = cts_dev_writesb(cts_dev, CTS_DEVICE_FW_REG_DEBUG_INTF, + buff, 5, retry, delay); + if (ret) { + cts_err("Write rDEBUG_INTF len=5B failed %d", ret); + return ret; + } + } + + return 0; +} + +int cts_sram_writeb_retry(struct cts_device *cts_dev, + u32 addr, u8 b, int retry, int delay) +{ + if (cts_dev->rtdata.program_mode) + return cts_dev_writeb(cts_dev, addr, b, retry, delay); + else + return cts_write_sram_normal_mode(cts_dev, addr, &b, 1, retry, delay); +} + +int cts_sram_writew_retry(struct cts_device *cts_dev, + u32 addr, u16 w, int retry, int delay) +{ + u8 buff[2]; + + if (cts_dev->rtdata.program_mode) + return cts_dev_writew(cts_dev, addr, w, retry, delay); + else { + put_unaligned_le16(w, buff); + + return cts_write_sram_normal_mode(cts_dev, addr, buff, 2, retry, delay); + } +} + +int cts_sram_writel_retry(struct cts_device *cts_dev, + u32 addr, u32 l, int retry, int delay) +{ + u8 buff[4]; + + if (cts_dev->rtdata.program_mode) + return cts_dev_writel(cts_dev, addr, l, retry, delay); + else { + put_unaligned_le32(l, buff); + + return cts_write_sram_normal_mode(cts_dev, addr, buff, 4, retry, delay); + } +} + +int cts_sram_writesb_retry(struct cts_device *cts_dev, + u32 addr, const void *src, size_t len, int retry, int delay) +{ + if (cts_dev->rtdata.program_mode) + return cts_dev_writesb(cts_dev, addr, src, len, retry, delay); + else + return cts_write_sram_normal_mode(cts_dev, addr, src, len, retry, delay); +} + +static int cts_calc_sram_crc(struct cts_device *cts_dev, + u32 sram_addr, size_t size, u32 *crc) +{ + cts_info("Calc crc from sram 0x%06x size %zu", sram_addr, size); + + return cts_dev->hwdata->sfctrl->ops->calc_sram_crc(cts_dev, + sram_addr, size, crc); +} + +int cts_sram_writesb_check_crc_retry(struct cts_device *cts_dev, + u32 addr, const void *src, size_t len, u32 crc, int retry) +{ + int ret, retries; + u32 fw_crc; + u8 tdata[16]; + u8 magic_num[] = { 0x5A, 0x55, 0x33, 0xCC, 0x00, 0x00, 0x00, 0x00 }; + + retries = 0; + do { + u32 crc_sram; + + retries++; + + ret = cts_sram_writesb(cts_dev, 0, src, len); + if (ret != 0) { + cts_err("SRAM writesb failed %d", ret); + continue; + } + + fw_crc = cts_crc32(src, len); + cts_info("fw crc: 0x%04x", fw_crc); + memcpy(tdata, magic_num, 8); + memcpy(tdata + 8, &fw_crc, 4); + memcpy(tdata + 12, &len, 4); + + ret = cts_sram_writesb_boot_crc_retry(cts_dev, len, fw_crc, 10); + if (ret != 0) { + cts_err("cts_hw_reg_writesb_retry %d", ret); + continue; + } + + ret = cts_calc_sram_crc(cts_dev, 0, len, &crc_sram); + if (ret != 0) { + cts_err("Get CRC for sram writesb failed %d retries %d", + ret, retries); + continue; + } + + if (crc == crc_sram) + return 0; + + cts_err("Check CRC for sram writesb mismatch %x != %x retries %d", + crc, crc_sram, retries); + ret = -EFAULT; + } while (retries < retry); + + return ret; +} + +int cts_read_sram_normal_mode(struct cts_device *cts_dev, + u32 addr, void *dst, size_t len, int retry, int delay) +{ + int i, ret; + + for (i = 0; i < len; i++) { + ret = cts_dev_writel(cts_dev, CTS_DEVICE_FW_REG_DEBUG_INTF, + addr, retry, delay); + if (ret) { + cts_err("Write addr to rDEBUG_INTF failed %d", ret); + return ret; + } + + ret = cts_dev_readb(cts_dev, CTS_DEVICE_FW_REG_DEBUG_INTF + 4, + (u8 *) dst, retry, delay); + if (ret) { + cts_err("Read value from rDEBUG_INTF + 4 failed %d", ret); + return ret; + } + + addr++; + dst++; + } + + return 0; +} + +int cts_sram_readb_retry(struct cts_device *cts_dev, + u32 addr, u8 *b, int retry, int delay) +{ + if (cts_dev->rtdata.program_mode) + return cts_dev_readb(cts_dev, addr, b, retry, delay); + else + return cts_read_sram_normal_mode(cts_dev, addr, b, 1, retry, delay); +} + +int cts_sram_readw_retry(struct cts_device *cts_dev, + u32 addr, u16 *w, int retry, int delay) +{ + int ret; + u8 buff[2]; + + if (cts_dev->rtdata.program_mode) + return cts_dev_readw(cts_dev, addr, w, retry, delay); + else { + ret = cts_read_sram_normal_mode(cts_dev, addr, buff, 2, retry, delay); + if (ret) { + cts_err("SRAM readw in normal mode failed %d", ret); + return ret; + } + + *w = get_unaligned_le16(buff); + return 0; + } +} + +int cts_sram_readl_retry(struct cts_device *cts_dev, + u32 addr, u32 *l, int retry, int delay) +{ + int ret; + u8 buff[4]; + + if (cts_dev->rtdata.program_mode) + return cts_dev_readl(cts_dev, addr, l, retry, delay); + else { + ret = cts_read_sram_normal_mode(cts_dev, addr, buff, 4, retry, delay); + if (ret) { + cts_err("SRAM readl in normal mode failed %d", ret); + return ret; + } + + *l = get_unaligned_le32(buff); + + return 0; + } +} + +int cts_sram_readsb_retry(struct cts_device *cts_dev, + u32 addr, void *dst, size_t len, int retry, int delay) +{ + if (cts_dev->rtdata.program_mode) + return cts_dev_readsb(cts_dev, addr, dst, len, retry, delay); + else + return cts_read_sram_normal_mode(cts_dev, addr, dst, len, retry, delay); +} + +int cts_fw_reg_writeb_retry(struct cts_device *cts_dev, + u32 reg_addr, u8 b, int retry, int delay) +{ + if (cts_dev->rtdata.program_mode) { + cts_err("Writeb to fw reg 0x%04x under program mode", reg_addr); + return -ENODEV; + } + + return cts_dev_writeb(cts_dev, reg_addr, b, retry, delay); +} + +int cts_fw_reg_writew_retry(struct cts_device *cts_dev, + u32 reg_addr, u16 w, int retry, int delay) +{ + if (cts_dev->rtdata.program_mode) { + cts_err("Writew to fw reg 0x%04x under program mode", reg_addr); + return -ENODEV; + } + + return cts_dev_writew(cts_dev, reg_addr, w, retry, delay); +} + +int cts_fw_reg_writel_retry(struct cts_device *cts_dev, + u32 reg_addr, u32 l, int retry, int delay) +{ + if (cts_dev->rtdata.program_mode) { + cts_err("Writel to fw reg 0x%04x under program mode", reg_addr); + return -ENODEV; + } + + return cts_dev_writel(cts_dev, reg_addr, l, retry, delay); +} + +int cts_fw_reg_writesb_retry(struct cts_device *cts_dev, + u32 reg_addr, const void *src, size_t len, int retry, int delay) +{ + if (cts_dev->rtdata.program_mode) { + cts_err("Writesb to fw reg 0x%04x under program mode", + reg_addr); + return -ENODEV; + } + + return cts_dev_writesb(cts_dev, reg_addr, src, len, retry, delay); +} + +int cts_fw_reg_readb_retry(struct cts_device *cts_dev, + u32 reg_addr, u8 *b, int retry, int delay) +{ + if (cts_dev->rtdata.program_mode) { + cts_err("Readb from fw reg under program mode"); + return -ENODEV; + } + + return cts_dev_readb(cts_dev, reg_addr, b, retry, delay); +} + +int cts_fw_reg_readw_retry(struct cts_device *cts_dev, + u32 reg_addr, u16 *w, int retry, int delay) +{ + if (cts_dev->rtdata.program_mode) { + cts_err("Readw from fw reg under program mode"); + return -ENODEV; + } + + return cts_dev_readw(cts_dev, reg_addr, w, retry, delay); +} + +int cts_fw_reg_readl_retry(struct cts_device *cts_dev, + u32 reg_addr, u32 *l, int retry, int delay) +{ + if (cts_dev->rtdata.program_mode) { + cts_err("Readl from fw reg under program mode"); + return -ENODEV; + } + + return cts_dev_readl(cts_dev, reg_addr, l, retry, delay); +} + +int cts_fw_reg_readsb_retry(struct cts_device *cts_dev, + u32 reg_addr, void *dst, size_t len, int retry, int delay) +{ + if (cts_dev->rtdata.program_mode) { + cts_err("Readsb from fw reg under program mode"); + return -ENODEV; + } + + return cts_dev_readsb(cts_dev, reg_addr, dst, len, retry, delay); +} + +int cts_fw_reg_readsb_retry_delay_idle(struct cts_device *cts_dev, + u32 reg_addr, void *dst, size_t len, int retry, int delay, int idle) +{ + if (cts_dev->rtdata.program_mode) { + cts_err("Readsb from fw reg under program mode"); + return -ENODEV; + } + + return cts_dev_readsb_delay_idle(cts_dev, reg_addr, dst, len, retry, + delay, idle); +} + +int cts_hw_reg_writeb_retry(struct cts_device *cts_dev, + u32 reg_addr, u8 b, int retry, int delay) +{ + return cts_sram_writeb_retry(cts_dev, reg_addr, b, retry, delay); +} + +int cts_hw_reg_writew_retry(struct cts_device *cts_dev, + u32 reg_addr, u16 w, int retry, int delay) +{ + return cts_sram_writew_retry(cts_dev, reg_addr, w, retry, delay); +} + +int cts_hw_reg_writel_retry(struct cts_device *cts_dev, + u32 reg_addr, u32 l, int retry, int delay) +{ + return cts_sram_writel_retry(cts_dev, reg_addr, l, retry, delay); +} + +int cts_hw_reg_writesb_retry(struct cts_device *cts_dev, + u32 reg_addr, const void *src, size_t len, int retry, int delay) +{ + return cts_sram_writesb_retry(cts_dev, reg_addr, src, len, retry, delay); +} + +int cts_hw_reg_readb_retry(struct cts_device *cts_dev, + u32 reg_addr, u8 *b, int retry, int delay) +{ + return cts_sram_readb_retry(cts_dev, reg_addr, b, retry, delay); +} + +int cts_hw_reg_readw_retry(struct cts_device *cts_dev, + u32 reg_addr, u16 *w, int retry, int delay) +{ + return cts_sram_readw_retry(cts_dev, reg_addr, w, retry, delay); +} + +int cts_hw_reg_readl_retry(struct cts_device *cts_dev, + u32 reg_addr, u32 *l, int retry, int delay) +{ + return cts_sram_readl_retry(cts_dev, reg_addr, l, retry, delay); +} + +int cts_hw_reg_readsb_retry(struct cts_device *cts_dev, + u32 reg_addr, void *dst, size_t len, int retry, int delay) +{ + return cts_sram_readsb_retry(cts_dev, reg_addr, dst, len, retry, delay); +} + +static int icnl9916_set_access_ddi_reg(struct cts_device *cts_dev, bool enable) +{ + int ret; + u8 access_flag; + + cts_info("ICNL9916 %s access ddi reg", enable ? "enable" : "disable"); + + ret = cts_hw_reg_readb(cts_dev, CTS_DEV_HW_REG_DDI_REG_CTRL, &access_flag); + if (ret) { + cts_err("Read HW_REG_DDI_REG_CTRL failed %d", ret); + return ret; + } + + access_flag = enable ? (access_flag | 0x01) : (access_flag & (~0x01)); + ret = cts_hw_reg_writeb(cts_dev, CTS_DEV_HW_REG_DDI_REG_CTRL, access_flag); + if (ret) { + cts_err("Write HW_REG_DDI_REG_CTRL %02x failed %d", access_flag, ret); + return ret; + } + + ret = cts_hw_reg_writeb(cts_dev, CTS_DEV_HW_REG_HPPROG, enable ? 1 : 0); + if (ret) { + cts_err("Write CTS_DEV_HW_REG_HPPROG failed %d", ret); + return ret; + } + + ret = cts_hw_reg_writew(cts_dev, 0x3DFF0, enable ? 0x595A : 0x5A5A); + if (ret) { + cts_err("Write password to F0 failed %d", ret); + return ret; + } + ret = cts_hw_reg_writew(cts_dev, 0x3DFF4, enable ? 0xA6A5 : 0x5A5A); + if (ret) { + cts_err("Write password to F1 failed %d", ret); + return ret; + } + + return 0; +} + +const static struct cts_sfctrl icnl9951_sfctrl = { + .reg_base = 0x74000, + .xchg_sram_base = 160 * 1024, + .xchg_sram_size = 64 * 1024, /* For non firmware programming */ + .ops = &cts_sfctrlv2_ops +}; + +const static struct cts_sfctrl icnl9951r_sfctrl = { + .reg_base = 0x74000, + .xchg_sram_base = 160 * 1024, + .xchg_sram_size = 64 * 1024, /* For non firmware programming */ + .ops = &cts_sfctrlv2_ops +}; + + +const static struct cts_device_hwdata cts_device_hwdatas[] = { + { + .name = "ICNL9951", + .hwid = CTS_DEV_HWID_ICNL9951, + .fwid = CTS_DEV_FWID_ICNL9951, + .num_row = 36, + .num_col = 22, + .sram_size = 128 * 1024, + + .program_addr_width = 3, + + .sfctrl = &icnl9951_sfctrl, + .enable_access_ddi_reg = icnl9916_set_access_ddi_reg, + }, + + { + .name = "ICNL9951R", + .hwid = CTS_DEV_HWID_ICNL9951R, + .fwid = CTS_DEV_FWID_ICNL9951R, + .num_row = 50, + .num_col = 32, + .sram_size = 128 * 1024, + + .program_addr_width = 3, + + .sfctrl = &icnl9951r_sfctrl, + .enable_access_ddi_reg = icnl9916_set_access_ddi_reg, + } +}; + +static int cts_init_device_hwdata(struct cts_device *cts_dev, + u32 hwid, u16 fwid) +{ + int i; + + cts_info("Init hardware data hwid: %06x fwid: %04x", hwid, fwid); + + for (i = 0; i < ARRAY_SIZE(cts_device_hwdatas); i++) { + if (hwid == cts_device_hwdatas[i].hwid || + fwid == cts_device_hwdatas[i].fwid) { + cts_dev->hwdata = &cts_device_hwdatas[i]; + return 0; + } + } + + return -EINVAL; +} + +void cts_lock_device(struct cts_device *cts_dev) +{ + cts_dbg("*** Lock ***"); + + mutex_lock(&cts_dev->pdata->dev_lock); +} + +void cts_unlock_device(struct cts_device *cts_dev) +{ + cts_dbg("### Un-Lock ###"); + + mutex_unlock(&cts_dev->pdata->dev_lock); +} + +int cts_send_command(struct cts_device *cts_dev, u8 cmd) +{ + cts_info("Send command 0x%02x", cmd); + + if (cts_dev->rtdata.program_mode) { + cts_warn("Send command %u while chip in program mode", cmd); + return -ENODEV; + } + + return cts_fw_reg_writeb_retry(cts_dev, CTS_DEVICE_FW_REG_CMD, cmd, 3, 0); +} + +static int cts_get_dev_boot_mode(struct cts_device *cts_dev, + u8 *boot_mode) +{ + int ret; + + if (cts_dev->rtdata.program_mode) + ret = cts_hw_reg_readb_retry(cts_dev, CTS_DEV_HW_REG_CURRENT_MODE, + boot_mode, 5, 10); + else + ret = cts_tcs_read_hw_reg(cts_dev, CTS_DEV_HW_REG_CURRENT_MODE, + boot_mode, 1); + + if (ret) { + cts_err("Read boot mode failed %d", ret); + return ret; + } + + *boot_mode &= CTS_DEV_BOOT_MODE_MASK; + + cts_info("Curr dev boot mode: %u(%s)", *boot_mode, + cts_dev_boot_mode2str(*boot_mode)); + return 0; +} + +static int cts_set_dev_boot_mode(struct cts_device *cts_dev, u8 boot_mode) +{ + int ret; + + cts_info("Set dev boot mode to %u(%s)", boot_mode, + cts_dev_boot_mode2str(boot_mode)); + + ret = cts_hw_reg_writeb_retry(cts_dev, CTS_DEV_HW_REG_BOOT_MODE, + boot_mode, 5, 5); + if (ret) { + cts_err("Write hw register BOOT_MODE failed %d", ret); + return ret; + } + + return 0; +} + +static int cts_init_fwdata(struct cts_device *cts_dev) +{ + + struct cts_device_fwdata *fwdata = &cts_dev->fwdata; + u8 info[16]; + int ret; + + cts_info("Init firmware data"); + + if (cts_dev->rtdata.program_mode) { + cts_err("Init firmware data while in program mode"); + return -EINVAL; + } + + memset(fwdata, 0, sizeof(*fwdata)); + + ret = cts_tcs_get_hwid_info(cts_dev, info); + if (ret < 0) { + cts_err("get_hwid_info failed"); + } else { + if (((info[5]<<16)|(info[6]<<8)|info[7]) == 0x55AA55) { + fwdata->ic_application = info[8]; + fwdata->pen_freq_num = info[9]; + fwdata->cascade_num = info[10]; + cts_info("ic_application=%d, pen_freq_num=%d, cascade_num=%d", info[8],info[9],info[10]); + } else { + fwdata->ic_application = 0; + } + } + + ret = cts_tcs_get_fw_ver(cts_dev, &fwdata->version); + if (ret < 0) { + cts_err("get_fw_ver failed"); + return -EINVAL; + } + ret = cts_tcs_get_lib_ver(cts_dev, &fwdata->lib_version); + if (ret < 0) { + cts_err("get_lib_ver failed"); + return -EINVAL; + } + ret = cts_tcs_get_ddi_ver(cts_dev, &fwdata->ddi_version); + if (ret < 0) { + cts_err("get_ddi_ver failed"); + return -EINVAL; + } + ret = cts_tcs_get_res_x(cts_dev, &fwdata->res_x); + if (ret < 0) { + cts_err("get_res_x failed"); + return -EINVAL; + } + ret = cts_tcs_get_res_y(cts_dev, &fwdata->res_y); + if (ret < 0) { + cts_err("get_res_y failed"); + return -EINVAL; + } + ret = cts_tcs_get_rows(cts_dev, &fwdata->rows); + if (ret < 0) { + cts_err("get_rows failed"); + return -EINVAL; + } + ret = cts_tcs_get_cols(cts_dev, &fwdata->cols); + if (ret < 0) { + cts_err("get_cols failed"); + return -EINVAL; + } + + ret = cts_tcs_get_rx_tx_info(cts_dev, info); + if (ret < 0) { + cts_err("get_rx_tx_info failed"); + } else { + fwdata->pc_cols = info[4]; + fwdata->pc_rows = info[5]; + fwdata->pr_cols = info[6]; + fwdata->pr_rows = info[7]; + } + + ret = cts_tcs_get_panel_rx_tx_info(cts_dev, info); + if (ret < 0) { + cts_err("get_panel_rx_tx_info failed"); + } else { + fwdata->pc_cols_used = info[11]; + fwdata->pc_rows_used = info[12]; + fwdata->pr_cols_used = info[13]; + fwdata->pr_rows_used = info[14]; + } + + ret = cts_tcs_get_flip_x(cts_dev, &fwdata->flip_x); + if (ret < 0) { + cts_err("get_flip_x failed"); + return -EINVAL; + } + ret = cts_tcs_get_flip_y(cts_dev, &fwdata->flip_y); + if (ret < 0) { + cts_err("get_flip_y failed"); + return -EINVAL; + } + ret = cts_tcs_get_swap_axes(cts_dev, &fwdata->swap_axes); + if (ret < 0) { + cts_err("get_swap_axes failed"); + return -EINVAL; + } + ret = cts_tcs_get_int_mode(cts_dev, &fwdata->int_mode); + if (ret < 0) { + cts_err("get_int_mode failed"); + return -EINVAL; + } + ret = cts_tcs_get_int_keep_time(cts_dev, &fwdata->int_keep_time); + if (ret < 0) { + cts_err("get_int_keep_time failed"); + return -EINVAL; + } + + ret = cts_tcs_get_rawdata_target(cts_dev, &fwdata->rawdata_target); + if (ret < 0) { + cts_err("get_rawdata_target failed"); + return -EINVAL; + } + + ret = cts_tcs_get_esd_method(cts_dev, &fwdata->esd_method); + if (ret < 0) { + cts_err("get_esd_method failed"); + return -EINVAL; + } + + ret = cts_tcs_get_has_int_data(cts_dev, + &fwdata->has_int_data); + if (ret < 0) { + cts_err("get_has_int_data failed: %d", ret); + return -EINVAL; + } + + if (fwdata->has_int_data) { + ret = cts_tcs_get_int_data_method(cts_dev, + &fwdata->int_data_method); + if (ret < 0) { + cts_err("get_int_data_method failed: %d", ret); + return -EINVAL; + } + if (fwdata->int_data_method >= INT_DATA_METHOD_CNT) { + cts_err("Invalid int data method: %d", + fwdata->int_data_method); + return -EINVAL; + } + + ret = cts_tcs_get_int_data_types(cts_dev, + &fwdata->int_data_types); + if (ret < 0) { + cts_err("get_int_data_types failed: %d", ret); + return -EINVAL; + } + fwdata->int_data_types &= INT_DATA_TYPE_MASK; + + ret = cts_tcs_calc_int_data_size(cts_dev); + if (ret < 0) { + cts_err("calc_int_data_size failed: %d", ret); + return -EINVAL; + } + } + + cts_err("fwver: %04x", fwdata->version); + cts_err("libver: %04x", fwdata->lib_version); + cts_err("ddi_version: %02x", fwdata->ddi_version); + cts_err("res_x: %d", fwdata->res_x); + cts_err("res_y: %d", fwdata->res_y); + cts_err("rows: %d", fwdata->rows); + cts_err("cols: %d", fwdata->cols); + cts_err("flip_x: %d", fwdata->flip_x); + cts_err("flip_y: %d", fwdata->flip_y); + cts_err("swap_axes: %d", fwdata->swap_axes); + cts_err("int_mode: %d", fwdata->int_mode); + cts_err("int_keep_time: %d", fwdata->int_keep_time); + cts_err("rawdata_target: %d", fwdata->rawdata_target); + cts_err("esd_method: %d", fwdata->esd_method); + cts_err("has_int_data: %d", fwdata->has_int_data); + cts_err("int_data_method: %d", fwdata->int_data_method); + cts_err("int_data_types: %d", fwdata->int_data_types); + cts_err("int_data_size: %ld", fwdata->int_data_size); + + cts_err("pc_cols: %ld", fwdata->pc_cols); + cts_err("pc_rows: %ld", fwdata->pc_rows); + cts_err("pr_cols: %ld", fwdata->pr_cols); + cts_err("pr_rows: %ld", fwdata->pr_rows); + cts_err("pc_cols_used: %ld", fwdata->pc_cols_used); + cts_err("pc_rows_used: %ld", fwdata->pc_rows_used); + cts_err("pr_cols_used: %ld", fwdata->pr_cols_used); + cts_err("pr_rows_used: %ld", fwdata->pr_rows_used); + + return 0; +} + +#ifdef CONFIG_CTS_TP_PROXIMITY +static bool cts_is_proximity_enable(struct cts_device *cts_dev) +{ + struct cts_firmware_status *status = (struct cts_firmware_status *) + &cts_dev->rtdata.firmware_status; + return !!status->proximity; +} + +static int cts_enable_proximity_mode(struct cts_device *cts_dev) +{ + return cts_tcs_set_proximity_mode(cts_dev, 1); +} + +static int cts_exit_proximity_mode(struct cts_device *cts_dev) +{ + return cts_tcs_set_proximity_mode(cts_dev, 0); +} + +static void cts_handle_proximity_event(bool status) +{ + cts_info("Handler proximity '%s' event", status ? "Near" : "Far"); +} +#endif + +int cts_irq_handler(struct cts_device *cts_dev) +{ + struct cts_device_touch_info *touch_info; + u8 pwrmode = 3; + int ret; + + cts_dbg("Enter IRQ handler"); + + touch_info = &cts_dev->rtdata.touch_info; + ret = cts_tcs_get_touchinfo(cts_dev, touch_info); + if (ret) { + cts_err("Get touch info failed %d", ret); + return ret; + } + + if (unlikely(cts_dev->rtdata.suspended)) { +#ifdef CFG_CTS_GESTURE + if (cts_dev->rtdata.gesture_wakeup_enabled) { + struct cts_device_gesture_info *gesture_info; + + gesture_info = &cts_dev->rtdata.gesture_info; + memcpy(gesture_info, touch_info, sizeof(struct cts_device_gesture_info)); + + ret = cts_plat_process_gesture_info(cts_dev->pdata, gesture_info); + + if (cts_dev->fwdata.int_data_method != INT_DATA_METHOD_HOST) { + if (ret) + cts_err("Process gesture info failed %d", ret); + + ret = cts_tcs_set_pwr_mode(cts_dev, pwrmode); + if (ret) + cts_warn("Reenter suspend with gesture wakeup failed %d", ret); + + if (cts_tcs_clr_gstr_ready_flag(cts_dev)) + cts_err("Clear gesture ready flag failed"); + } + } else { + cts_warn("IRQ triggered while device suspended " + "without gesture wakeup enable"); + } +#endif /* CFG_CTS_GESTURE */ + } else { + cts_dbg("Touch info: vkey_state %x, finger num_msg %u", + touch_info->vkey_state, touch_info->num_msg); + +#ifdef CFG_CTS_PALM_DETECT + if (touch_info->finger_or_stylus == FINGER_ONLY && + CFG_CTS_PALM_ID == touch_info->vkey_state) { + cts_report_palm_event(cts_dev->pdata); + cts_plat_release_all_touch(cts_dev->pdata); + return 0; + } +#endif + +#ifdef CONFIG_CTS_TP_PROXIMITY + if (touch_info->finger_or_stylus == FINGER_ONLY && + cts_is_proximity_enable(cts_dev)) { + cts_dbg("proximity status:%d", cts_dev->rtdata.proximity_status); + if (!cts_dev->rtdata.proximity_status + && touch_info->vkey_state == CTS_CMD_PROXIMITY_STATUS) { + cts_dev->rtdata.proximity_num++; + if (cts_dev->rtdata.proximity_num == 4) { + cts_dev->rtdata.proximity_num = 0; + cts_dev->rtdata.proximity_status = true; + cts_handle_proximity_event(true); + return 0; + } + } else if (cts_dev->rtdata.proximity_status + && (touch_info->vkey_state == 0)) { + cts_dev->rtdata.proximity_num = 0; + cts_dev->rtdata.proximity_status = false; + cts_handle_proximity_event(false); + cts_plat_release_all_touch(cts_dev->pdata); + return 0; + } + } +#endif + + if (touch_info->finger_or_stylus == FINGER_ONLY) { + ret = cts_plat_process_touch_msg(cts_dev->pdata, + touch_info->msgs, touch_info->num_msg); + } else if (touch_info->finger_or_stylus == STYLUS_ONLY) { + struct cts_device_stylus_info *stylus_info = + (struct cts_device_stylus_info *)touch_info; + ret = cts_plat_process_stylus_msg(cts_dev->pdata, + stylus_info->msgs, stylus_info->event_num); + } else if (touch_info->finger_or_stylus == FINGER_STYLUS) { +#ifdef CFG_CTS_FINGER_STYLUS_SUPPORTED + ret = cts_plat_process_touch_stylus(cts_dev->pdata, touch_info); +#endif + } else { + cts_err("Unsupported finger_or_stylus: %d", + touch_info->finger_or_stylus); + ret = -EINVAL; + } + if (ret) { + cts_err("Process touch msg failed %d", ret); + return ret; + } + +#ifdef CONFIG_CTS_VIRTUALKEY + ret = cts_plat_process_vkey(cts_dev->pdata, touch_info->vkey_state); + if (ret) { + cts_err("Process vkey failed %d", ret); + return ret; + } +#endif + } + + return 0; +} + +bool cts_is_device_suspended(struct cts_device *cts_dev) +{ + return cts_dev->rtdata.suspended; +} + +int cts_suspend_device(struct cts_device *cts_dev) +{ + int ret; + u8 buf; + + cts_info("Suspend device"); + +/* Disable this check for sleep/gesture switch */ +/* + if (cts_dev->rtdata.suspended) { + cts_warn("Suspend device while already suspended"); + return 0; + } +*/ + if (cts_dev->rtdata.program_mode) { + cts_info("Quit programming mode before suspend"); + ret = cts_enter_normal_mode(cts_dev); + if (ret) { + cts_err("Failed to exit program mode before suspend:%d", ret); + return ret; + } + } + + cts_info("Set suspend mode:%s", + cts_dev->rtdata.gesture_wakeup_enabled ? "gesture" : "sleep"); + + buf = cts_dev->rtdata.gesture_wakeup_enabled ? 3 : 2; + ret = cts_tcs_set_pwr_mode(cts_dev, buf); + if (ret) { + cts_err("Suspend device failed %d", ret); + return ret; + } + + cts_info("Device suspended ..."); + cts_dev->rtdata.suspended = true; + + return 0; +} + +int cts_resume_device(struct cts_device *cts_dev) +{ + int retries = 0; + int ret = 0; + + cts_info("Resume device"); + + cts_reset_device(cts_dev); + + if (!cts_dev->rtdata.gesture_wakeup_enabled) { +#ifdef CONFIG_CTS_I2C_HOST + cts_info("Resume from sleep mode with flash, do not update."); +#else + if(cts_plat_is_normal_mode(cts_dev->pdata)) { + cts_info("Resume from sleep mode without flash, and firmware online then to normal state."); + goto normal_exit; + } else { + cts_info("Resume from sleep mode without flash, update now."); + goto update_firmware; + } +#endif + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret < 0) { + cts_err("Wait current normal mode failed"); + goto update_firmware; + } else { + goto normal_exit; + } + +update_firmware: + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + return ret; + } + + do { + ret = cts_update_firmware(cts_dev); + if (ret) { + cts_err("Update firmware failed times: %d", retries); + cts_reset_device(cts_dev); + } else { + break; + } + } while (++retries < 3); + if (ret) { + cts_err("Update default firmware failed %d", ret); + goto err_set_program_mode; + } + +normal_exit: + +#ifdef CONFIG_CTS_CHARGER_DETECT + if (cts_is_charger_exist(cts_dev)) { + int r = cts_set_dev_charger_attached(cts_dev, true); + if (r) + cts_err("Set dev charger attached failed %d", r); + } +#endif + +#ifdef CONFIG_CTS_EARJACK_DETECT + if (cts_is_earjack_exist(cts_dev)) { + int r = cts_set_dev_earjack_attached(cts_dev, true); + if (r) { + cts_err("Set dev earjack attached failed %d", r); + } + } +#endif + +#ifdef CONFIG_CTS_GLOVE + if (cts_is_glove_enabled(cts_dev)) + cts_enter_glove_mode(cts_dev); +#endif + +#ifdef CONFIG_CTS_TP_PROXIMITY + if (cts_is_proximity_enable(cts_dev)) { + cts_enable_proximity_mode(cts_dev); + cts_info("after update firmware, enable proximity mode."); + } +#endif + + cts_dev->rtdata.suspended = false; + return 0; + +err_set_program_mode: + cts_dev->rtdata.program_mode = true; + cts_dev->rtdata.slave_addr = CTS_DEV_PROGRAM_MODE_I2CADDR; + cts_dev->rtdata.addr_width = CTS_DEV_PROGRAM_MODE_ADDR_WIDTH; + + return ret; +} + +bool cts_is_device_program_mode(struct cts_device *cts_dev) +{ + return cts_dev->rtdata.program_mode; +} + +static inline void cts_init_rtdata_with_normal_mode(struct cts_device *cts_dev) +{ + memset(&cts_dev->rtdata, 0, sizeof(cts_dev->rtdata)); + + cts_set_normal_addr(cts_dev); + cts_dev->rtdata.suspended = false; + cts_dev->rtdata.updating = false; + cts_dev->rtdata.testing = false; + cts_dev->rtdata.fw_log_redirect_enabled = false; + cts_dev->rtdata.glove_mode_enabled = false; + cts_dev->rtdata.gesture_d_tap_enabled = false; + cts_dev->rtdata.esd_count = 0; +#ifdef CONFIG_CTS_TP_PROXIMITY + cts_dev->rtdata.proximity_num = 0; + cts_dev->rtdata.proximity_status = false; +#endif +} + +#ifdef FIX_DEFAULT_ENABLE_DRW +static int cts_disable_drw(struct cts_device *cts_dev) +{ + int ret; + + u8 txbuf[] = { + 0x60, + 0x07, 0x80, 0x34, + 0x00, 0x00, 0x04, + 0xdd, 0xe8, + 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xdf, 0xfc, + 0x00, 0x00, 0x00 + }; + u8 rxbuf[] = { + 0x60, + 0x07, 0x80, 0x34, + 0x00, 0x00, 0x04, + 0xdd, 0xe8, + 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xdf, 0xfc, + 0x00, 0x00, 0x00 + }; + + ret = cts_spi_send_recv(cts_dev->pdata, sizeof(txbuf), txbuf, rxbuf); + if (ret) + cts_err("disable drw ret=%d", ret); + return ret; +} +#endif + +int cts_enter_program_mode(struct cts_device *cts_dev) +{ + const static u8 magic_num[] = { 0xCC, 0x33, 0x55, 0x5A }; + u8 boot_mode; + int ret; + + cts_info("Enter program mode"); + + if (cts_dev->rtdata.program_mode) { + cts_warn("Enter program mode while alredy in"); + /* return 0; */ + } + + if (cts_plat_is_normal_mode(cts_dev->pdata)) { + cts_tcs_wait_krang_stop(cts_dev); + } + +#ifdef CONFIG_CTS_I2C_HOST + ret = cts_plat_i2c_write(cts_dev->pdata, + CTS_DEV_PROGRAM_MODE_I2CADDR, magic_num, 4, 5, 10); + if (ret) { + cts_err("Write magic number to i2c_dev: 0x%02x failed %d", + CTS_DEV_PROGRAM_MODE_I2CADDR, ret); + return ret; + } + + cts_set_program_addr(cts_dev); +#else + cts_set_program_addr(cts_dev); + ret = cts_spi_send_recv(cts_dev->pdata, sizeof(magic_num), (u8 *)magic_num, NULL); + if (ret) { + cts_err("Write magic number to spi_dev: 0x%02x failed %d", + CTS_DEV_PROGRAM_MODE_SPIADDR, ret); + return ret; + } +#endif /* CONFIG_CTS_I2C_HOST */ + + cts_dev->rtdata.program_mode = true; + mdelay(5); + +#ifdef FIX_DEFAULT_ENABLE_DRW + cts_disable_drw(cts_dev); +#endif + + ret = cts_get_dev_boot_mode(cts_dev, &boot_mode); + if (ret) { + cts_err("Read BOOT_MODE failed %d", ret); + return ret; + } + /* Note: the following CTS_DEV_BOOT_MODE_TCH_PRG_9916 + is used by both ICNL9916 and ICNL9916C */ +#ifdef CONFIG_CTS_I2C_HOST + if ((boot_mode == CTS_DEV_BOOT_MODE_TCH_PRG_9916) || + (boot_mode == CTS_DEV_BOOT_MODE_I2C_PRG_9911C)) +#else + if ((boot_mode == CTS_DEV_BOOT_MODE_TCH_PRG_9916) || + (boot_mode == CTS_DEV_BOOT_MODE_SPI_PRG_9911C)) +#endif + { + return 0; + } + cts_err("BOOT_MODE readback %u != I2C/SPI PROMGRAM mode", boot_mode); + return -EFAULT; +} + +const char *cts_dev_boot_mode2str(u8 boot_mode) +{ + switch (boot_mode) { + case CTS_DEV_BOOT_MODE_IDLE: + return "IDLE-BOOT"; + case CTS_DEV_BOOT_MODE_FLASH: + return "FLASH-BOOT"; + case CTS_DEV_BOOT_MODE_SRAM: + return "SRAM-BOOT"; + /* case CTS_DEV_BOOT_MODE_I2C_PRG_9911C: */ + case CTS_DEV_BOOT_MODE_TCH_PRG_9916: + return "I2C-PRG-BOOT/TCH-PRG-BOOT"; + case CTS_DEV_BOOT_MODE_DDI_PRG: + return "DDI-PRG-BOOT"; + case CTS_DEV_BOOT_MODE_SPI_PRG_9911C: + return "SPI-PROG-BOOT/INVALID-BOOT"; + default: + return "INVALID"; + } +} + +int cts_enter_normal_mode(struct cts_device *cts_dev) +{ + int ret = 0; + u8 boot_mode; + int retries; + u16 fwid = CTS_DEV_FWID_INVALID; + u8 auto_boot = 0; + u8 first_boot = 1; + + cts_info("Enter normal mode"); + + if (!cts_dev->rtdata.program_mode) { + cts_warn("Enter normal mode while already in"); + return 0; + } + + if (cts_dev->rtdata.has_flash) + auto_boot = 1; + +#ifdef CFG_CTS_UPDATE_CRCCHECK + auto_boot = 1; +#endif + for (retries = 5; retries >= 0; retries--) { +#ifdef CFG_CTS_UPDATE_CRCCHECK + if (first_boot == 1) { +#else + if (first_boot == 1 || auto_boot == 0) { +#endif + cts_set_program_addr(cts_dev); + ret = cts_set_dev_boot_mode(cts_dev, CTS_DEV_BOOT_MODE_SRAM); + if (ret) { + cts_err("Set BOOT_MODE to SRAM failed %d," + "try to reset device", ret); + } + + mdelay(30); + } + first_boot = 0; + + cts_set_normal_addr(cts_dev); + + ret = cts_get_dev_boot_mode(cts_dev, &boot_mode); + if (ret) + cts_err("Get BOOT_MODE failed %d", ret); + if (boot_mode != CTS_DEV_BOOT_MODE_SRAM) + cts_err("Curr boot mode %u(%s) != SRAM_BOOT", + boot_mode, cts_dev_boot_mode2str(boot_mode)); + else + break; + + ret = cts_tcs_get_fw_id(cts_dev, &fwid); + if (ret) + cts_err("Get firmware id failed %d, retries %d", ret, retries); + else { + cts_info("Get firmware id: 0x%02x", fwid); + break; + } + cts_reset_device(cts_dev); + } + if (retries >= 0) { + ret = cts_init_fwdata(cts_dev); + if (ret) { + cts_err("Device init firmware data failed %d", ret); + return ret; + } + return 0; + } + cts_set_program_addr(cts_dev); + return ret; +} + +bool cts_is_device_enabled(struct cts_device *cts_dev) +{ + return cts_dev->enabled; +} + +int cts_start_device(struct cts_device *cts_dev) +{ +#if defined(CONFIG_CTS_ESD_PROTECTION) || \ + defined(CONFIG_CTS_CHARGER_DETECT) || \ + defined(CONFIG_CTS_EARJACK_DETECT) + struct chipone_ts_data *cts_data = + container_of(cts_dev, struct chipone_ts_data, cts_dev); +#endif + int ret; + + cts_info("Start device..."); + + if (cts_is_device_enabled(cts_dev)) { + cts_warn("Start device while already started"); + return 0; + } + +#ifdef CFG_CTS_HEARTBEAT_MECHANISM + cts_enable_heartbeat_mechanism(cts_data); +#endif + +#ifdef CONFIG_CTS_ESD_PROTECTION + cts_enable_esd_protection(cts_data); +#endif + +#ifdef CONFIG_CTS_CHARGER_DETECT + cts_start_charger_detect(cts_data); +#endif + +#ifdef CONFIG_CTS_EARJACK_DETECT + cts_start_earjack_detect(cts_data); +#endif + + ret = cts_plat_enable_irq(cts_dev->pdata); + if (ret < 0) { + cts_err("Enable IRQ failed %d", ret); + return ret; + } + + cts_dev->enabled = true; + + cts_info("Start device successfully"); + + return 0; +} + +int cts_stop_device(struct cts_device *cts_dev) +{ + struct chipone_ts_data *cts_data = + container_of(cts_dev, struct chipone_ts_data, cts_dev); + int ret; + + cts_info("Stop device..."); + + if (!cts_is_device_enabled(cts_dev)) { + cts_warn("Stop device while halted"); + return 0; + } + + if (cts_is_firmware_updating(cts_dev)) { + cts_warn("Stop device while firmware updating, please try again"); + return -EAGAIN; + } + + ret = cts_plat_disable_irq(cts_dev->pdata); + if (ret < 0) { + cts_err("Disable IRQ failed %d", ret); + return ret; + } + + cts_dev->enabled = false; + +#ifdef CFG_CTS_HEARTBEAT_MECHANISM + cts_disable_heartbeat_mechanism(cts_data); +#endif + +#ifdef CONFIG_CTS_ESD_PROTECTION + cts_disable_esd_protection(cts_data); +#endif + +#ifdef CONFIG_CTS_CHARGER_DETECT + cts_stop_charger_detect(cts_data); +#endif + +#ifdef CONFIG_CTS_EARJACK_DETECT + cts_stop_earjack_detect(cts_data); +#endif + +#ifndef CONFIG_GENERIC_HARDIRQS + if (work_pending(&cts_data->pdata->ts_irq_work)) { + cts_warn("IRQ work is pending .... flush it"); + flush_work(&cts_data->pdata->ts_irq_work); + } else + cts_info("None IRQ work is pending"); +#endif + // cts_info("Flush workqueue..."); + // flush_workqueue(cts_data->workqueue); + + ret = cts_plat_release_all_touch(cts_data->pdata); + if (ret) { + cts_err("Release all touch failed %d", ret); + return ret; + } + +#ifdef CONFIG_CTS_VIRTUALKEY + ret = cts_plat_release_all_vkey(cts_dev->pdata); + if (ret) { + cts_err("Release all vkey failed %d", ret); + return ret; + } +#endif + + return 0; +} + +#ifdef CONFIG_CTS_ESD_PROTECTION +int cts_start_device_esdrecover(struct cts_device *cts_dev) +{ + int ret; + + cts_info("Start device..."); + + if (cts_is_device_enabled(cts_dev)) { + cts_warn("Start device while already started"); + return 0; + } + + ret = cts_plat_enable_irq(cts_dev->pdata); + if (ret < 0) { + cts_err("Enable IRQ failed %d", ret); + return ret; + } + + cts_dev->enabled = true; + + cts_info("Start device successfully"); + + return 0; +} + +int cts_stop_device_esdrecover(struct cts_device *cts_dev) +{ + struct chipone_ts_data *cts_data = + container_of(cts_dev, struct chipone_ts_data, cts_dev); + int ret; + + cts_info("Stop device..."); + + if (!cts_is_device_enabled(cts_dev)) { + cts_warn("Stop device while halted"); + return 0; + } + + if (cts_is_firmware_updating(cts_dev)) { + cts_warn("Stop device while firmware updating, please try again"); + return -EAGAIN; + } + + ret = cts_plat_disable_irq(cts_dev->pdata); + if (ret < 0) { + cts_err("Disable IRQ failed %d", ret); + return ret; + } + + cts_dev->enabled = false; + + flush_workqueue(cts_data->workqueue); + + ret = cts_plat_release_all_touch(cts_dev->pdata); + if (ret) { + cts_err("Release all touch failed %d", ret); + return ret; + } +#ifdef CONFIG_CTS_VIRTUALKEY + ret = cts_plat_release_all_vkey(cts_dev->pdata); + if (ret) { + cts_err("Release all vkey failed %d", ret); + return ret; + } +#endif /* CONFIG_CTS_VIRTUALKEY */ + + return 0; +} +#endif + +bool cts_is_fwid_valid(u16 fwid) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cts_device_hwdatas); i++) { + if (cts_device_hwdatas[i].fwid == fwid) + return true; + } + + return false; +} + +static bool cts_is_hwid_valid(u32 hwid) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cts_device_hwdatas); i++) { + if (cts_device_hwdatas[i].hwid == hwid) + return true; + } + + return false; +} + +static int cts_get_hwid(struct cts_device *cts_dev, u32 *hwid) +{ + int ret; + + cts_info("Get device hardware id"); + + if (cts_dev->hwdata) { + *hwid = cts_dev->hwdata->hwid; + return 0; + } + + if (!cts_dev->rtdata.program_mode) { + ret = cts_enter_program_mode(cts_dev); + if (ret) { + cts_err("Enter program mode failed %d", ret); + goto err_out; + } + } + + ret = cts_hw_reg_readl_retry(cts_dev, CTS_DEV_HW_REG_HARDWARE_ID, + hwid, 5, 0); + if (ret) + goto err_out; + + *hwid = le32_to_cpu(*hwid); + *hwid &= 0XFFFFFFF0; + cts_info("Device hardware id: %04x", *hwid); + + if (!cts_is_hwid_valid(*hwid)) { + cts_warn("Device hardware id %04x invalid", *hwid); + ret = -EINVAL; + goto err_out; + } + + return 0; + +err_out: + *hwid = CTS_DEV_HWID_INVALID; + return ret; +} + +int cts_probe_device(struct cts_device *cts_dev) +{ + int ret, retries = 0; + u16 fwid = CTS_DEV_FWID_INVALID; + u32 hwid = CTS_DEV_HWID_INVALID; + + cts_dev->fwdata.version = 0; + + cts_info("Probe device"); + + cts_init_rtdata_with_normal_mode(cts_dev); + if (!cts_plat_is_normal_mode(cts_dev->pdata)) { + cts_warn("Normal mode spi addr is offline"); + } else { + ret = cts_tcs_get_fw_id(cts_dev, &fwid); + if (ret) { + cts_err("Get firmware id failed %d", ret); + } else { + cts_info("Firmware id: 0x%04x", fwid); + ret = cts_tcs_get_fw_ver(cts_dev, &cts_dev->fwdata.version); + if (ret) { + cts_err("Read firmware version failed %d", ret); + cts_dev->fwdata.version = 0; + } else { + cts_info("Firmware version: 0x%04x", cts_dev->fwdata.version); + } + goto init_hwdata; + } + } + +read_hwid: + ret = cts_get_hwid(cts_dev, &hwid); + if (ret || hwid == CTS_DEV_HWID_INVALID) { + retries++; + + cts_err("Get hardware id failed %d retries %d", ret, retries); + + if (retries < 3) { + cts_reset_device(cts_dev); + goto read_hwid; + } else + return -ENODEV; + } + +init_hwdata: + ret = cts_init_device_hwdata(cts_dev, hwid, fwid); + if (ret) { + cts_err("Device hwid: %06x fwid: %04x not found", hwid, fwid); + return -ENODEV; + } + + cts_info("Touch info size:%zu", sizeof(struct cts_device_touch_info)); + + return ret; +} + +#ifdef CFG_CTS_GESTURE +void cts_enable_gesture_wakeup(struct cts_device *cts_dev) +{ + cts_info("Enable gesture wakeup"); + cts_dev->rtdata.gesture_wakeup_enabled = true; +} + +void cts_disable_gesture_wakeup(struct cts_device *cts_dev) +{ + cts_info("Disable gesture wakeup"); + cts_dev->rtdata.gesture_wakeup_enabled = false; +} + +bool cts_is_gesture_wakeup_enabled(struct cts_device *cts_dev) +{ + return cts_dev->rtdata.gesture_wakeup_enabled; +} + +int cts_get_gesture_info(struct cts_device *cts_dev, void *gesture_info) +{ + int ret; + + cts_info("Get gesture info"); + + if (cts_dev->rtdata.program_mode) { + cts_warn("Get gesture info in program mode"); + return -ENODEV; + } + + if (!cts_dev->rtdata.suspended) { + cts_warn("Get gesture info while not suspended"); + return -ENODEV; + } + + if (!cts_dev->rtdata.gesture_wakeup_enabled) { + cts_warn("Get gesture info while gesture wakeup not enabled"); + return -ENODEV; + } + + ret = cts_tcs_get_gestureinfo(cts_dev, gesture_info); + if (ret) { + cts_err("Read gesture info header failed %d", ret); + return ret; + } + + return 0; +} + +bool cts_is_d_tap_supported(struct cts_device *cts_dev) +{ + return true; +} + +int cts_set_d_tap_enabled(struct cts_device *cts_dev, bool enabled) +{ + cts_dev->rtdata.gesture_d_tap_enabled = enabled; + return 0; +} +#else +bool cts_is_d_tap_supported(struct cts_device *cts_dev) +{ + return false; +} + +int cts_set_d_tap_enabled(struct cts_device *cts_dev, bool enabled) +{ + return 0; +} +#endif /* CFG_CTS_GESTURE */ + +int cts_set_int_data_types(struct cts_device *cts_dev, u16 types) +{ + int ret; + u16 realtypes = types & INT_DATA_TYPE_MASK; + + cts_info("Set int data types: %#06x, mask to %#06x", types, realtypes); + + ret = cts_tcs_set_int_data_types(cts_dev, realtypes); + if (ret) { + cts_err("Set int data type failed: %d", ret); + return -EIO; + } + cts_dev->fwdata.int_data_types = realtypes; + + cts_tcs_calc_int_data_size(cts_dev); + + return ret; +} + +int cts_set_int_data_method(struct cts_device *cts_dev, u8 method) +{ + int ret = 0; + + cts_info("Set int data method: %d", method); + + if (method >= INT_DATA_METHOD_CNT) { + cts_err("Invalid int data method"); + return -EINVAL; + } + + ret = cts_tcs_set_int_data_method(cts_dev, method); + if (ret) { + cts_err("Set int data method failed: %d", ret); + return -EIO; + } + cts_dev->fwdata.int_data_method = method; + + cts_tcs_calc_int_data_size(cts_dev); + + return ret; +} + +#ifdef CONFIG_CTS_ESD_PROTECTION +static void cts_esd_protection_work(struct work_struct *work) +{ + struct chipone_ts_data *cts_data; + struct cts_device *cts_dev; + int ret = 0; + + cts_dbg("ESD protection work"); + cts_data = container_of(work, struct chipone_ts_data, esd_work.work); + cts_dev = &cts_data->cts_dev; + + cts_lock_device(cts_dev); + if (!cts_plat_is_normal_mode(cts_data->pdata)) { + cts_data->esd_check_fail_cnt++; + /*reset chip next time */ + if ((cts_data->esd_check_fail_cnt % 2) == 0) { + cts_err("ESD protection read normal mode failed, reset chip!"); + ret = cts_reset_device(cts_dev); + if (ret) + cts_err("ESD protection reset chip failed %d", ret); + } + } else { + cts_data->esd_check_fail_cnt = 0; + } + + if (cts_data->esd_check_fail_cnt >= CFG_CTS_ESD_FAILED_CONFIRM_CNT) { + cts_warn("ESD protection check failed, update firmware!!!"); + cts_stop_device_esdrecover(cts_dev); + cts_reset_device(cts_dev); + ret = cts_update_firmware(cts_dev); + if (ret) + cts_err("Update default firmware failed %d", ret); + + cts_start_device_esdrecover(cts_dev); + cts_data->esd_check_fail_cnt = 0; + } + queue_delayed_work(cts_data->esd_workqueue, &cts_data->esd_work, + CFG_CTS_ESD_PROTECTION_CHECK_PERIOD); + + cts_unlock_device(cts_dev); +} + +void cts_enable_esd_protection(struct chipone_ts_data *cts_data) +{ + if (cts_data->esd_workqueue && !cts_data->esd_enabled) { + cts_info("ESD protection enable"); + + cts_data->esd_enabled = true; + cts_data->esd_check_fail_cnt = 0; + queue_delayed_work(cts_data->esd_workqueue, + &cts_data->esd_work, + CFG_CTS_ESD_PROTECTION_CHECK_PERIOD); + } +} + +void cts_disable_esd_protection(struct chipone_ts_data *cts_data) +{ + if (cts_data->esd_workqueue && cts_data->esd_enabled) { + cts_info("ESD protection disable"); + + cts_data->esd_enabled = false; + cancel_delayed_work(&cts_data->esd_work); + flush_workqueue(cts_data->esd_workqueue); + } +} + +void cts_init_esd_protection(struct chipone_ts_data *cts_data) +{ + cts_info("Init ESD protection"); + + INIT_DELAYED_WORK(&cts_data->esd_work, cts_esd_protection_work); + + cts_data->esd_enabled = false; + cts_data->esd_check_fail_cnt = 0; +} + +void cts_deinit_esd_protection(struct chipone_ts_data *cts_data) +{ + cts_info("De-Init ESD protection"); + + if (cts_data->esd_workqueue && cts_data->esd_enabled) { + cts_data->esd_enabled = false; + cancel_delayed_work(&cts_data->esd_work); + } +} +#endif /* CONFIG_CTS_ESD_PROTECTION */ + + +#ifdef CFG_CTS_HEARTBEAT_MECHANISM +void cts_show_touch_debug_msg(void *debug) +{ + struct cts_touch_debug_msg *msg = (struct cts_touch_debug_msg *)debug; + struct cts_firmware_status *status = (struct cts_firmware_status *) + &msg->firmware_status; + + cts_info("FW_Ver : 0x%02X", msg->fw_version); + cts_info("Reset_Flag : 0x%08X", msg->reset_flag); + + cts_info("Charger : %d", status->charger); + cts_info("Proxmty : %d", status->proximity); + cts_info("Earjack : %d", status->earjack); + cts_info("Knuckle : %d", status->knuckle); + cts_info("Glove : %d", status->glove); + cts_info("Pocket : %d", status->pocket); + cts_info("Game : %d", status->game); + + cts_info("High_Temp : %d", status->high_temperature); + cts_info("Low_Temp : %d", status->low_temperature); + cts_info("Bend : %d", status->bend_mode); + cts_info("Palm : %d", status->palm_status); + cts_info("Noise : %d", status->noise_mode); + cts_info("Base_Trace : %d", status->base_trace); + cts_info("Water : %d", status->water_mode); + cts_info("Ground : %d", status->ground); + + cts_info("Proxmity_Sts: 0x%02x", msg->proximity); + + cts_info("Work_Mode : %d", msg->work_mode); + cts_info("Power_Mode : %d", msg->power_mode); + cts_info("Freq : %d", msg->curr_freq); + cts_info("Esd_0a : %d", msg->esd_0a_status); + cts_info("Fw_Esd : %d", msg->fw_esd_status); + cts_info("Landscape : %d", msg->landscape_mode); + + cts_info("Freq_Noise : %d", msg->curr_freq_noise); + cts_info("Max_Diff : %d", msg->max_diff); + cts_info("Row : %d", msg->max_diff_row); + cts_info("Col : %d", msg->max_diff_col); + cts_info("Max_Neg : -%d", msg->max_neg_diff); + cts_info("Neg_Row : %d", msg->max_neg_diff_row); + cts_info("Neg_Col : %d", msg->max_neg_diff_col); +} + +void cts_heartbeat_mechanism_work(struct work_struct *work) +{ + struct chipone_ts_data *cts_data; + struct cts_device *cts_dev; + struct cts_device_touch_info info; + u8 curr_status; + int ret = 0; + + cts_data = container_of(work, struct chipone_ts_data, heart_work.work); + cts_dev = &cts_data->cts_dev; + + cts_dbg("heartbeat mechanism work"); + + cts_lock_device(cts_dev); + ret = cts_tcs_get_touch_status(cts_dev); + if (ret) { + cts_err("get touch status failed"); + + cts_reset_device(cts_dev); + + if (++cts_dev->rtdata.esd_count < 3) + goto end; + + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + return ret; + } + + ret = cts_update_firmware(cts_dev); + if (ret) + cts_err("Update default firmware failed %d", ret); + + cts_tcs_reinit_fw_status(cts_dev); + goto end; + } + + memcpy(&info, cts_dev->int_data, sizeof(info)); + + curr_status = info.debug_msg.firmware_status; + + cts_dev->rtdata.esd_count = 0; + + /* 9922C reset_flag:0xFFFFFF; 9922 9916 9916C reset_flag:0xFFFF */ + if ((info.debug_msg.reset_flag & 0xFFFFFF) != 0xFFFFFF + || (info.debug_msg.reset_flag & 0xFFFF) != 0xFFFF) { + cts_err("heartbeat reset flag:0x%08x error", info.debug_msg.reset_flag); + cts_show_touch_debug_msg(&info.debug_msg); + } + + if (curr_status != cts_dev->rtdata.firmware_status) { + cts_err("firmware status error"); + cts_show_touch_debug_msg(&info.debug_msg); + cts_tcs_reinit_fw_status(cts_dev); + } + +end: + queue_delayed_work(cts_data->heart_workqueue, + &cts_data->heart_work, msecs_to_jiffies(2000)); + cts_unlock_device(cts_dev); +} + +void cts_enable_heartbeat_mechanism(struct chipone_ts_data *cts_data) +{ + queue_delayed_work(cts_data->heart_workqueue, + &cts_data->heart_work, msecs_to_jiffies(2000)); +} + +void cts_disable_heartbeat_mechanism(struct chipone_ts_data *cts_data) +{ + cancel_delayed_work_sync(&cts_data->heart_work); +} +#endif + +#ifdef CONFIG_CTS_GLOVE +int cts_enter_glove_mode(struct cts_device *cts_dev) +{ + int ret; + + cts_info("Enter glove mode"); + + ret = cts_tcs_set_glove_mode(cts_dev, 1); + if (ret) + cts_err("Enable Glove mode err"); + else + cts_dev->rtdata.glove_mode_enabled = true; + + return ret; +} + +int cts_exit_glove_mode(struct cts_device *cts_dev) +{ + int ret; + + cts_info("Exit glove mode"); + + ret = cts_tcs_set_glove_mode(cts_dev, 0); + if (ret) + cts_err("Exit Glove mode err"); + else + cts_dev->rtdata.glove_mode_enabled = false; + + return ret; +} + +int cts_is_glove_enabled(struct cts_device *cts_dev) +{ + return cts_dev->rtdata.glove_mode_enabled; +} + +int cts_set_glove_enabled(struct cts_device *cts_dev, bool enabled) +{ + cts_dev->rtdata.glove_mode_enabled = enabled; + return 0; +} +#endif /* CONFIG_CTS_GLOVE */ + +#ifdef CONFIG_CTS_CHARGER_DETECT +bool cts_is_charger_exist(struct cts_device *cts_dev) +{ + struct chipone_ts_data *cts_data; + bool attached = false; + int ret; + + cts_data = container_of(cts_dev, struct chipone_ts_data, cts_dev); + + ret = cts_is_charger_attached(cts_data, &attached); + if (ret) + cts_err("Get charger state failed %d", ret); + + cts_dev->rtdata.charger_exist = attached; + + return attached; +} + +int cts_set_dev_charger_attached(struct cts_device *cts_dev, bool attached) +{ + int ret; + u8 buf; + + cts_info("Set dev charger %s", attached ? "ATTACHED" : "DETATCHED"); + buf = attached ? 1 : 0; + + ret = cts_tcs_set_charger_plug(cts_dev, buf); + if (ret) + cts_err("Set failed %d", ret); + + return ret; +} +#endif /* CONFIG_CTS_CHARGER_DETECT */ + +#ifdef CONFIG_CTS_EARJACK_DETECT +bool cts_is_earjack_exist(struct cts_device *cts_dev) +{ + struct chipone_ts_data *cts_data; + bool attached = false; + int ret; + + cts_data = container_of(cts_dev, struct chipone_ts_data, cts_dev); + + ret = cts_is_earjack_attached(cts_data, &attached); + if (ret) { + cts_err("Get earjack state failed %d", ret); + } + + return attached; +} + +int cts_set_dev_earjack_attached(struct cts_device *cts_dev, bool attached) +{ + int ret; + u8 buf; + + cts_info("Set dev earjack %s", attached ? "ATTACHED" : "DETATCHED"); + buf = attached ? 1 : 0; + ret = cts_tcs_set_earjack_plug(cts_dev, buf); + if (ret) + cts_err("Set failed %d", ret); + + return ret; +} +#endif /* CONFIG_CTS_EARJACK_DETECT */ + +#if 0 +int cts_enable_fw_log_redirect(struct cts_device *cts_dev) +{ + int ret; + + cts_info("Fw log redirect enable"); + ret = cts_send_command(cts_dev, CTS_CMD_ENABLE_FW_LOG_REDIRECT); + if (ret) + cts_err("Send CTS_CMD_ENABLE_FW_LOG_REDIRECT failed %d", ret); + else + cts_dev->rtdata.fw_log_redirect_enabled = true; + + return 0; +} + +int cts_disable_fw_log_redirect(struct cts_device *cts_dev) +{ + int ret; + + cts_info("Fw log redirect disable"); + ret = cts_send_command(cts_dev, CTS_CMD_DISABLE_FW_LOG_REDIRECT); + if (ret) + cts_err("Send CTS_CMD_DISABLE_FW_LOG_REDIRECT failed %d", ret); + else + cts_dev->rtdata.fw_log_redirect_enabled = false; + + return 0; +} + +bool cts_is_fw_log_redirect(struct cts_device *cts_dev) +{ + return cts_dev->rtdata.fw_log_redirect_enabled; +} + +int cts_fw_log_show_finish(struct cts_device *cts_dev) +{ + int ret; + + ret = cts_send_command(cts_dev, CTS_CMD_FW_LOG_SHOW_FINISH); + if (ret) + cts_err("Send CTS_CMD_FW_LOG_SHOW_FINISH failed %d", ret); + + return ret; +} +#endif + +void cts_firmware_upgrade_work(struct work_struct *work) +{ + struct chipone_ts_data *cts_data; + struct cts_device *cts_dev; + u32 hwid; + int retries; + int ret = 0; + + cts_info("Firmware upgrade work"); + + cts_data = container_of(work, struct chipone_ts_data, fw_upgrade_work.work); + cts_dev = &cts_data->cts_dev; + hwid = cts_dev->hwdata->hwid; + + ret = cts_request_firmware(cts_dev, hwid, CTS_DEV_FWID_ANY, 0); + if (ret) { + cts_err("Get firmware failed!!!, Please add builtin or bin firmware!"); + return; + } + + cts_lock_device(cts_dev); + if (cts_dev->fwdata.version >= FIRMWARE_VERSION(cts_dev->firmware)) { + cts_info("Do not update, (curr) 0x%04x >= 0x%04x (local)", + cts_dev->fwdata.version, FIRMWARE_VERSION(cts_dev->firmware)); + cts_set_program_addr(cts_dev); + cts_enter_normal_mode(cts_dev); + cts_start_device(cts_dev); + cts_unlock_device(cts_dev); + return; + } + + for(retries = 0; retries < 3; retries++) { + cts_reset_device(cts_dev); + ret = cts_update_firmware(cts_dev); + if (ret) { + cts_err("Update firmware failed times: %d", retries); + } else { + cts_info("Update firmware successfully"); + break; + } + } + cts_start_device(cts_dev); + cts_unlock_device(cts_dev); + + return; +} + +int cts_reset_device(struct cts_device *cts_dev) +{ + return cts_plat_reset_device(cts_dev->pdata); +} + +int cts_wait_current_mode(struct cts_device *cts_dev, u8 tar_mode) +{ + int i = 0; + int ret; + u8 work_mode; + + do { + mdelay(100); + ret = cts_tcs_get_krang_current_workmode(cts_dev, &work_mode); + if (ret == 0 && work_mode == tar_mode) { + cts_info("wait work_mode:%d okay", work_mode); + return ret; + } + } while (++i < 10); + + cts_err("wait tar_mode:%d failed, retries:%d",tar_mode, i); + return -ETIMEDOUT; +} + +static struct file *cts_log_filp; +static int cts_log_to_file_level; + +static char *cts_log_buffer; +static int cts_log_buf_size; +static int cts_log_buf_wr_size; + +static bool cts_log_redirect; + +int cts_start_driver_log_redirect(const char *filepath, bool append_to_file, + char *log_buffer, int log_buf_size, int log_level) +{ +#define START_BANNER \ + ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" + + int ret = 0; + + cts_info("Start driver log redirect"); + + cts_log_to_file_level = log_level; + + if (log_buffer && log_buf_size) { + cts_info(" - Start driver log to buffer: %p size: %d level: %d", + log_buffer, log_buf_size, log_level); + cts_log_buffer = log_buffer; + cts_log_buf_size = log_buf_size; + cts_log_buf_wr_size = 0; + } + + if (filepath && filepath[0]) { + cts_info(" - Start driver log to file : '%s' level: %d", + filepath, log_level); +#ifdef CFG_CTS_FOR_GKI + cts_info("%s(): filp_open is forbiddon with GKI Version!", __func__); +#else + cts_log_filp = filp_open(filepath, + O_WRONLY | O_CREAT | (append_to_file ? O_APPEND : O_TRUNC), + S_IRUGO | S_IWUGO); + if (IS_ERR(cts_log_filp)) { + ret = PTR_ERR(cts_log_filp); + cts_log_filp = NULL; + cts_err("Open file '%s' for driver log failed %d", + filepath, ret); + } else { + cts_write_file(cts_log_filp, START_BANNER, strlen(START_BANNER)); + } +#endif + } + + cts_log_redirect = true; + + return ret; +#undef START_BANNER +} + +void cts_stop_driver_log_redirect(void) +{ +#define END_BANNER \ + "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n" +#ifndef CFG_CTS_FOR_GKI + int ret; +#endif + + cts_log_redirect = false; + + cts_info("Stop driver log redirect"); + + if (cts_log_filp) { + cts_info(" - Stop driver log to file"); + + cts_write_file(cts_log_filp, END_BANNER, strlen(END_BANNER)); +#ifndef CFG_CTS_FOR_GKI + ret = filp_close(cts_log_filp, NULL); + if (ret) { + cts_err("Close driver log file failed %d", ret); + } +#endif + cts_log_filp = NULL; + } + + if (cts_log_buffer) { + cts_info(" - Stop driver log to buffer"); + + cts_log_buffer = NULL; + cts_log_buf_size = 0; + cts_log_buf_wr_size = 0; + } + +#undef END_BANNER +} + +int cts_get_driver_log_redirect_size(void) +{ + if (cts_log_redirect && cts_log_buffer && cts_log_buf_wr_size) { + return cts_log_buf_wr_size; + } else { + return 0; + } +} + +void cts_log(int level, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + + if (cts_log_redirect) { + if (cts_log_buffer && + cts_log_buf_wr_size < cts_log_buf_size && + level <= cts_log_to_file_level) { + cts_log_buf_wr_size += vscnprintf(cts_log_buffer + cts_log_buf_wr_size, + cts_log_buf_size - cts_log_buf_wr_size, fmt, args); + } + + if (cts_log_filp != NULL && level <= cts_log_to_file_level) { + char buf[512]; + int count = vscnprintf(buf, sizeof(buf), fmt, args); + + cts_write_file(cts_log_filp, buf, count); + } + } + + if (level < CTS_DRIVER_LOG_DEBUG || cts_show_debug_log) { + vprintk(fmt, args); + } + + va_end(args); +} + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0) +/* From lib/kstrtox.c */ +/** + * kstrtobool - convert common user inputs into boolean values + * @s: input string + * @res: result + * + * This routine returns 0 iff the first character is one of 'Yy1Nn0', or + * [oO][NnFf] for "on" and "off". Otherwise it will return -EINVAL. Value + * pointed to by res is updated upon finding a match. + */ +int kstrtobool(const char *s, bool *res) +{ + if (!s) + return -EINVAL; + + switch (s[0]) { + case 'y': + case 'Y': + case '1': + *res = true; + return 0; + case 'n': + case 'N': + case '0': + *res = false; + return 0; + case 'o': + case 'O': + switch (s[1]) { + case 'n': + case 'N': + *res = true; + return 0; + case 'f': + case 'F': + *res = false; + return 0; + default: + break; + } + default: + break; + } + + return -EINVAL; +} +#endif diff --git a/drivers/input/touchscreen/chipone_tddi/cts_core.h b/drivers/input/touchscreen/chipone_tddi/cts_core.h new file mode 100644 index 000000000000..955491378292 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_core.h @@ -0,0 +1,988 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CTS_CORE_H +#define CTS_CORE_H + +#include "cts_config.h" + +enum cts_dev_hw_reg { + CTS_DEV_HW_REG_HARDWARE_ID = 0x70000u, + CTS_DEV_HW_REG_CLOCK_GATING = 0x70004u, + CTS_DEV_HW_REG_RESET_CONFIG = 0x70008u, + CTS_DEV_HW_REG_SW_RST_CTRL = 0x7000Au, + CTS_DEV_HW_REG_BOOT_MODE = 0x70010u, + CTS_DEV_HW_REG_CURRENT_MODE = 0x70011u, + CTS_DEV_HW_REG_DDI_REG_CTRL = 0x7002Cu, + CTS_DEV_HW_REG_CLK_DIV_CFG = 0x70033u, + CTS_DEV_HW_REG_HPPROG = 0x70074u, + CTS_DEV_HW_REG_SET_RESET = 0x700F8u, + CTS_DEV_HW_REG_HW_STATUS = 0x7401Bu, + CTS_DEV_HW_REG_I2C_CFG = 0x77001u, + CTS_DEV_HW_REG_SPI_CFG = 0x78438u, +}; + +enum cts_dev_boot_mode { + CTS_DEV_BOOT_MODE_IDLE = 0, + CTS_DEV_BOOT_MODE_FLASH = 1, + CTS_DEV_BOOT_MODE_I2C_PRG_9911C = 2, + CTS_DEV_BOOT_MODE_TCH_PRG_9916 = 2, + CTS_DEV_BOOT_MODE_SRAM = 3, + CTS_DEV_BOOT_MODE_DDI_PRG = 4, + CTS_DEV_BOOT_MODE_SPI_PRG_9911C = 5, + CTS_DEV_BOOT_MODE_MASK = 7, +}; + +/* ICNL9951/ICNL9951R */ +#define CTS_PWR_MODE_ACTIVE (0x00) +#define CTS_PWR_MODE_MNT (0x01) +#define CTS_PWR_MODE_HIBERNATE (0x02) +#define CTS_PWR_MODE_GSTR_WAKEUP (0x03) +#define CTS_PWR_MODE_MNT_DBG (0x04) +#define CTS_PWR_MODE_GSTR_DBG (0x05) +#define CTS_PWR_MODE_WAIT_TO_HIBERNATE (0x06) +#define CTS_PWR_MODE_WAIT_TO_GSTR (0x07) + +//Krang work mode value +#define CTS_KRANG_NORMAL_MODE (0x00) +#define CTS_KRANG_STY_DBG_MODE (0x01) +#define CTS_KRANG_FIN_DBG_MODE (0x02) +#define CTS_KRANG_MNT_DBG_MODE (0x03) +#define CTS_KRANG_GS_DBG_MODE (0x04) +#define CTS_KRANG_OPEN_SHORT_DET_MODE (0x05) +#define CTS_KRANG_CFG_MODE (0x06) +#define CTS_KRANG_TEST_MODE (0x07) +#define CTS_KRANG_DEF_MODE (0x08) + +/** I2C addresses(7bits), transfer size and bitrate */ +#define CTS_DEV_NORMAL_MODE_I2CADDR (0x48) +#define CTS_DEV_PROGRAM_MODE_I2CADDR (0x30) +#define CTS_DEV_NORMAL_MODE_ADDR_WIDTH (2) +#define CTS_DEV_PROGRAM_MODE_ADDR_WIDTH (3) +#define CTS_DEV_NORMAL_MODE_SPIADDR (0xF0) +#define CTS_DEV_PROGRAM_MODE_SPIADDR (0x60) + +/** Chipone firmware register addresses under normal mode */ +enum cts_device_fw_reg { + CTS_DEVICE_FW_REG_WORK_MODE = 0x0000, + CTS_DEVICE_FW_REG_SYS_BUSY = 0x0001, + CTS_DEVICE_FW_REG_DATA_READY = 0x0002, + CTS_DEVICE_FW_REG_CMD = 0x0004, + CTS_DEVICE_FW_REG_POWER_MODE = 0x0005, + CTS_DEVICE_FW_REG_FW_LIB_MAIN_VERSION = 0x0009, + CTS_DEVICE_FW_REG_CHIP_TYPE = 0x000A, + CTS_DEVICE_FW_REG_VERSION = 0x000C, + CTS_DEVICE_FW_REG_DDI_VERSION = 0x0010, + CTS_DEVICE_FW_REG_GET_WORK_MODE = 0x003F, + CTS_DEVICE_FW_REG_FW_LIB_SUB_VERSION = 0x0047, + CTS_DEVICE_FW_REG_COMPENSATE_CAP_READY = 0x004E, + + CTS_DEVICE_FW_REG_TOUCH_INFO = 0x1000, + CTS_DEVICE_FW_REG_RAW_DATA = 0x2000, + CTS_DEVICE_FW_REG_DIFF_DATA = 0x3000, + CTS_DEVICE_FW_REG_GESTURE_INFO = 0x7000, + CTS_DEVICE_FW_REG_PANEL_PARAM = 0x8000, + CTS_DEVICE_FW_REG_NUM_TX = 0x8007, + CTS_DEVICE_FW_REG_NUM_RX = 0x8008, + CTS_DEVICE_FW_REG_INT_KEEP_TIME = 0x8047, + CTS_DEVICE_FW_REG_RAWDATA_TARGET = 0x8049, + CTS_DEVICE_FW_REG_X_RESOLUTION = 0x8090, + CTS_DEVICE_FW_REG_Y_RESOLUTION = 0x8092, + CTS_DEVICE_FW_REG_SWAP_AXES = 0x8094, + CTS_DEVICE_FW_REG_GLOVE_MODE = 0x8095, + CTS_DEVICE_FW_REG_TEST_WITH_DISPLAY_ON = 0x80A3, + CTS_DEVICE_FW_REG_INT_MODE = 0x80D8, + CTS_DEVICE_FW_REG_EARJACK_DETECT_SUPP = 0x8113, + CTS_DEVICE_FW_REG_AUTO_COMPENSATE_EN = 0x8114, + CTS_DEVICE_FW_REG_ESD_PROTECTION = 0x8156, + CTS_DEVICE_FW_REG_FLAG_BITS = 0x8158, + + CTS_DEVICE_FW_REG_COMPENSATE_CAP = 0xA000, + CTS_DEVICE_FW_REG_DEBUG_INTF = 0xF000, +}; + +/** Hardware IDs, read from hardware id register */ +enum cts_dev_hwid { + CTS_DEV_HWID_ICNL9951 = 0x990510u, + CTS_DEV_HWID_ICNL9951R = 0x991510u, + + CTS_DEV_HWID_ANY = 0, + CTS_DEV_HWID_INVALID = 0xFFFFFFFFu, +}; + +/* Firmware IDs, read from firmware register @ref CTS_DEV_FW_REG_CHIP_TYPE + * under normal mode + */ +enum cts_dev_fwid { + CTS_DEV_FWID_ICNL9951 = 0x9951u, + CTS_DEV_FWID_ICNL9951R = 0x99a3u, + + CTS_DEV_FWID_ANY = 0u, + CTS_DEV_FWID_INVALID = 0xFFFFu +}; + +/** Commands written to firmware register @ref CTS_DEVICE_FW_REG_CMD under normal mode */ +enum cts_firmware_cmd { + CTS_CMD_RESET = 1, + CTS_CMD_SUSPEND = 2, + CTS_CMD_ENTER_WRITE_PARA_TO_FLASH_MODE = 3, + CTS_CMD_WRITE_PARA_TO_FLASH = 4, + CTS_CMD_WRTITE_INT_HIGH = 5, + CTS_CMD_WRTITE_INT_LOW = 6, + CTS_CMD_RELASE_INT_TEST = 7, + CTS_CMD_RECOVERY_TX_VOL = 0x10, + CTS_CMD_DEC_TX_VOL_1 = 0x11, + CTS_CMD_DEC_TX_VOL_2 = 0x12, + CTS_CMD_DEC_TX_VOL_3 = 0x13, + CTS_CMD_DEC_TX_VOL_4 = 0x14, + CTS_CMD_DEC_TX_VOL_5 = 0x15, + CTS_CMD_DEC_TX_VOL_6 = 0x16, + CTS_CMD_ENABLE_READ_RAWDATA = 0x20, + CTS_CMD_DISABLE_READ_RAWDATA = 0x21, + CTS_CMD_SUSPEND_WITH_GESTURE = 0x40, + CTS_CMD_QUIT_GESTURE_MONITOR = 0x41, + CTS_CMD_CHARGER_ATTACHED = 0x55, + CTS_CMD_EARJACK_ATTACHED = 0x57, + CTS_CMD_EARJACK_DETACHED = 0x58, + CTS_CMD_CHARGER_DETACHED = 0x66, + CTS_CMD_ENABLE_FW_LOG_REDIRECT = 0x86, + CTS_CMD_DISABLE_FW_LOG_REDIRECT = 0x87, + CTS_CMD_ENABLE_READ_CNEG = 0x88, + CTS_CMD_DISABLE_READ_CNEG = 0x89, + CTS_CMD_FW_LOG_SHOW_FINISH = 0xE0, + +#ifdef CONFIG_CTS_TP_PROXIMITY + CTS_CMD_PROXIMITY_STATUS = 0x80, + /* For vivo code, make a distinction between palm func and proximity func */ + CTS_CMD_PROXIMITY_NEAR_VIVO = 0x30, + CTS_CMD_PROXIMITY_FAR_VIVO = 0x31, +#endif +}; + +#pragma pack(1) +/** Touch message read back from chip */ +struct cts_device_touch_msg { + u8 event:4; + u8 id:4; + + u8 xh:4; + u8 yh:4; + + u8 xl; + u8 yl; + + u8 pressure; + +#define CTS_DEVICE_TOUCH_EVENT_NONE (0) +#define CTS_DEVICE_TOUCH_EVENT_DOWN (1) +#define CTS_DEVICE_TOUCH_EVENT_MOVE (2) +#define CTS_DEVICE_TOUCH_EVENT_STAY (3) +#define CTS_DEVICE_TOUCH_EVENT_UP (4) + +}; + +/** Stylus message read back from chip */ +struct cts_device_stylus_msg { + u8 event:4; + u8 id:4; + + u8 tip_xh:4; + u8 tip_yh:4; + u8 tip_xl; + u8 tip_yl; + + u8 ring_xh:4; + u8 ring_yh:4; + u8 ring_xl; + u8 ring_yl; + + u8 battary; + u8 tilt; + + u8 pressure_l; + u8 pressure_h:5;//use low 5 bits + u8 btn0:1; + u8 btn1:1; + u8 btn2:1; + + u8 tiltx; + u8 tilty; +}; + +struct cts_firmware_status { + uint16_t charger:1; + uint16_t proximity:1; + uint16_t earjack:1; + uint16_t knuckle:1; + uint16_t glove:1; + uint16_t pocket:1; + uint16_t game:1; + uint16_t reserved:1; + uint16_t high_temperature:1; + uint16_t low_temperature:1; + uint16_t bend_mode:1; + uint16_t palm_status:1; + uint16_t noise_mode:1; + uint16_t base_trace:1; + uint16_t water_mode:1; + uint16_t ground:1; +}; +/** Touch debug messages read back from chip [40 bytes] */ +struct cts_touch_debug_msg { + u8 protocol_version; + u8 fw_version; + u32 reset_flag; + u16 frame_index; + u16 firmware_status; + + u8 proximity; + u8 work_mode; + u8 power_mode; + u8 curr_freq; + u8 esd_0a_status; + u8 fw_esd_status; + u8 landscape_mode; + u16 curr_freq_noise; + u16 max_diff; + u8 max_diff_row; + u8 max_diff_col; + u16 max_neg_diff; + u8 max_neg_diff_row; + u8 max_neg_diff_col; + u8 data_method; + u8 data_type; + u8 reserved[11]; +}; + +/** Touch information read back from chip */ +struct cts_device_touch_info { + u8 vkey_state; + + u8 num_msg:4; +#define PROTO_NORMAL 0x00 +#define PROTO_COMPRESSED 0x01 + u8 compressed_proto:2; +#define FINGER_ONLY 0x01 +#define STYLUS_ONLY 0x02 +#define FINGER_STYLUS 0x03 + u8 finger_or_stylus:2; + + u8 reserved0; + + struct cts_device_touch_msg msgs[CFG_CTS_MAX_TOUCH_NUM]; + + u8 proximity; + u8 palm_major; + u8 palm_minor; + u8 palm_flags; + +#ifdef CFG_CTS_FINGER_STYLUS_SUPPORTED + u8 stylus_num:4; + u8 stylus_vernum:4; + + struct cts_device_stylus_msg smsgs[CFG_CTS_MAX_STYLUS_NUM]; + + u8 reserved1; + u8 reserved2; +#endif +}; + +struct cts_device_stylus_info { + u8 vkey_state; + + u8 num_msg:4; + u8 compressed_proto:2; + u8 finger_or_stylus:2; + + u8 reserved0; + + u8 event_num:4; + u8 stylus_vernum:4; + + struct cts_device_stylus_msg msgs[CFG_CTS_MAX_STYLUS_NUM]; + + u8 reserved1; +}; + + +/** Gesture trace point read back from chip */ +struct cts_device_gesture_point { + __le16 x; + __le16 y; +}; + +/** Gesture information read back from chip */ +/* total size 112 bytes */ +struct cts_device_gesture_info { + u8 gesture_id; +#define CTS_GESTURE_UP (0x11) +#define CTS_GESTURE_C (0x12) +#define CTS_GESTURE_O (0x13) +#define CTS_GESTURE_M (0x14) +#define CTS_GESTURE_W (0x15) +#define CTS_GESTURE_E (0x16) +#define CTS_GESTURE_S (0x17) +#define CTS_GESTURE_B (0x18) +#define CTS_GESTURE_T (0x19) +#define CTS_GESTURE_H (0x1A) +#define CTS_GESTURE_F (0x1B) +#define CTS_GESTURE_X (0x1C) +#define CTS_GESTURE_Z (0x1D) +#define CTS_GESTURE_V (0x1E) +#define CTS_GESTURE_D_TAP (0x50) +#define CTS_GESTURE_TAP (0x7F) + + u8 num_points; + +#define CTS_CHIP_MAX_GESTURE_TRACE_POINT (27u) + struct cts_device_gesture_point points[CTS_CHIP_MAX_GESTURE_TRACE_POINT]; + u8 reserved[2]; + +}; + +struct cts_tcs_cmd { + uint16_t cmd_id : 8; + uint16_t class_id : 5; + uint16_t is_write : 1; + uint16_t is_read : 1; + uint16_t base_flag : 1; +}; +#pragma pack() + +struct cts_device; + +enum cts_crc_type { + CTS_CRC16 = 1, + CTS_CRC32 = 2, +}; + +/** Chip hardware data, will never change */ +struct cts_device_hwdata { + const char *name; + u32 hwid; + u16 fwid; + u8 num_row; + u8 num_col; + u32 sram_size; + + /* Address width under program mode */ + u8 program_addr_width; + + const struct cts_sfctrl *sfctrl; + int (*enable_access_ddi_reg)(struct cts_device *cts_dev, bool enable); +}; + +enum int_data_type { + INT_DATA_TYPE_NONE = 0, + INT_DATA_TYPE_RAWDATA = BIT(0), + INT_DATA_TYPE_MANUAL_DIFF = BIT(1), + INT_DATA_TYPE_REAL_DIFF = BIT(2), + INT_DATA_TYPE_NOISE_DIFF = BIT(3), + INT_DATA_TYPE_BASEDATA = BIT(4), + INT_DATA_TYPE_CNEGDATA = BIT(5), + INT_DATA_TYPE_MASK = 0xF03F, +}; + +enum int_data_method { + INT_DATA_METHOD_NONE = 0, + INT_DATA_METHOD_HOST = 1, + INT_DATA_METHOD_POLLING = 2, + INT_DATA_METHOD_DEBUG = 3, + INT_DATA_METHOD_CNT = 4, +}; + +/** Chip firmware data */ +struct cts_device_fwdata { + u16 version; + u16 res_x; + u16 res_y; + u8 rows; + u8 cols; + + bool flip_x; + bool flip_y; + bool swap_axes; + u8 ddi_version; + u8 int_mode; + u8 esd_method; + u16 lib_version; + u16 int_keep_time; + u16 rawdata_target; + bool has_int_data; + u8 int_data_method; + u16 int_data_types; + size_t int_data_size; + + u8 ic_application; + u8 pen_freq_num; + u8 cascade_num; + + u8 pc_cols; + u8 pc_rows; + u8 pr_cols; + u8 pr_rows; + + u8 pc_cols_used; + u8 pc_rows_used; + u8 pr_cols_used; + u8 pr_rows_used; +}; + +/** Chip runtime data */ +struct cts_device_rtdata { + u8 slave_addr; + int addr_width; + bool program_mode; + bool has_flash; + + bool suspended; + bool updating; + bool testing; + + bool gesture_wakeup_enabled; + bool gesture_d_tap_enabled; + bool charger_exist; + bool fw_log_redirect_enabled; + bool glove_mode_enabled; + + u8 esd_count; + u16 firmware_status; + + struct cts_device_touch_info touch_info; + struct cts_device_gesture_info gesture_info; + +#ifdef CONFIG_CTS_TP_PROXIMITY + bool proximity_status; + int proximity_num; +#endif + + u16 tcscmd[ALIGN(PAGE_SIZE + 10, 4)]; + int tcscmd_len; + u16 curr_cmd; + int curr_len; +}; + +struct cts_firmware { + const char *name; /* MUST set to non-NULL if driver builtin firmware */ + u32 hwid; + u16 fwid; + + u8 *data; + size_t size; + const struct firmware *fw; +}; + +struct cts_device { + struct cts_platform_data *pdata; + + const struct cts_device_hwdata *hwdata; + struct cts_device_fwdata fwdata; + struct cts_device_rtdata rtdata; + struct cts_firmware *firmware; + const struct cts_flash *flash; + bool enabled; + u8 int_data[ALIGN(CFG_CTS_INT_DATA_MAX_SIZE + 10, 4)]; +}; + +struct cts_platform_data; + +struct chipone_ts_data { +#ifdef CONFIG_CTS_I2C_HOST + struct i2c_client *i2c_client; +#else + struct spi_device *spi_client; +#endif + struct device *device; + struct cts_device cts_dev; + struct cts_platform_data *pdata; + struct workqueue_struct *workqueue; + struct delayed_work fw_upgrade_work; + struct work_struct ts_resume_work; +#ifdef CONFIG_CTS_CHARGER_DETECT + void *charger_detect_data; +#endif + +#ifdef CONFIG_CTS_EARJACK_DETECT + void *earjack_detect_data; +#endif + +#ifdef CONFIG_CTS_ESD_PROTECTION + struct workqueue_struct *esd_workqueue; + struct delayed_work esd_work; + bool esd_enabled; + int esd_check_fail_cnt; +#endif + +#ifdef CFG_CTS_HEARTBEAT_MECHANISM + struct workqueue_struct *heart_workqueue; + struct delayed_work heart_work; +#endif + +#ifdef CONFIG_CTS_LEGACY_TOOL + struct proc_dir_entry *procfs_entry; +#endif + + void *oem_data; + + bool force_reflash; + // struct kobject *suspend_kobj; +#ifdef CONFIG_PM_DSI_EXTCON_NOTIFIER + struct extcon_dev *edev; + struct notifier_block extcon_nb; +#endif +}; + +/*static inline u32 get_unaligned_le24(const void *p) +{ + const u8 *puc = (const u8 *)p; + return (puc[0] | (puc[1] << 8) | (puc[2] << 16)); +} + +static inline u32 get_unaligned_be24(const void *p) +{ + const u8 *puc = (const u8 *)p; + return (puc[2] | (puc[1] << 8) | (puc[0] << 16)); +} + +static inline void put_unaligned_be24(u32 v, void *p) +{ + u8 *puc = (u8 *) p; + + puc[0] = (v >> 16) & 0xFF; + puc[1] = (v >> 8) & 0xFF; + puc[2] = (v >> 0) & 0xFF; +}*/ + +#define wrap(max, x) ((max) - 1 - (x)) + +extern void cts_lock_device(struct cts_device *cts_dev); +extern void cts_unlock_device(struct cts_device *cts_dev); + +extern int cts_dev_readb(struct cts_device *cts_dev, + u32 addr, u8 *b, int retry, int delay); +extern int cts_dev_writeb(struct cts_device *cts_dev, + u32 addr, u8 b, int retry, int delay); + +extern int cts_sram_writeb_retry(struct cts_device *cts_dev, + u32 addr, u8 b, int retry, int delay); +extern int cts_sram_writew_retry(struct cts_device *cts_dev, + u32 addr, u16 w, int retry, int delay); +extern int cts_sram_writel_retry(struct cts_device *cts_dev, + u32 addr, u32 l, int retry, int delay); +extern int cts_sram_writesb_retry(struct cts_device *cts_dev, + u32 addr, const void *src, size_t len, int retry, int delay); +extern int cts_sram_writesb_check_crc_retry(struct cts_device *cts_dev, + u32 addr, const void *src, size_t len, u32 crc, int retry); + +extern int cts_sram_readb_retry(struct cts_device *cts_dev, + u32 addr, u8 *b, int retry, int delay); +extern int cts_sram_readw_retry(struct cts_device *cts_dev, + u32 addr, u16 *w, int retry, int delay); +extern int cts_sram_readl_retry(struct cts_device *cts_dev, + u32 addr, u32 *l, int retry, int delay); +extern int cts_sram_readsb_retry(struct cts_device *cts_dev, + u32 addr, void *dst, size_t len, int retry, int delay); + +extern int cts_fw_reg_writeb_retry(struct cts_device *cts_dev, + u32 reg_addr, u8 b, int retry, int delay); +extern int cts_fw_reg_writew_retry(struct cts_device *cts_dev, + u32 reg_addr, u16 w, int retry, int delay); +extern int cts_fw_reg_writel_retry(struct cts_device *cts_dev, + u32 reg_addr, u32 l, int retry, int delay); +extern int cts_fw_reg_writesb_retry(struct cts_device *cts_dev, + u32 reg_addr, const void *src, size_t len, int retry, int delay); + +extern int cts_fw_reg_readb_retry(struct cts_device *cts_dev, + u32 reg_addr, u8 *b, int retry, int delay); +extern int cts_fw_reg_readw_retry(struct cts_device *cts_dev, + u32 reg_addr, u16 *w, int retry, int delay); +extern int cts_fw_reg_readl_retry(struct cts_device *cts_dev, + u32 reg_addr, u32 *l, int retry, int delay); +extern int cts_fw_reg_readsb_retry(struct cts_device *cts_dev, + u32 reg_addr, void *dst, size_t len, + int retry, int delay); +extern int cts_fw_reg_readsb_retry_delay_idle(struct cts_device *cts_dev, + u32 reg_addr, void *dst, size_t len, int retry, int delay, int idle); + +extern int cts_hw_reg_writeb_retry(struct cts_device *cts_dev, + u32 reg_addr, u8 b, int retry, int delay); +extern int cts_hw_reg_writew_retry(struct cts_device *cts_dev, + u32 reg_addr, u16 w, int retry, int delay); +extern int cts_hw_reg_writel_retry(struct cts_device *cts_dev, + u32 reg_addr, u32 l, int retry, int delay); +extern int cts_hw_reg_writesb_retry(struct cts_device *cts_dev, + u32 reg_addr, const void *src, size_t len, int retry, int delay); + +extern int cts_hw_reg_readb_retry(struct cts_device *cts_dev, + u32 reg_addr, u8 *b, int retry, int delay); +extern int cts_hw_reg_readw_retry(struct cts_device *cts_dev, + u32 reg_addr, u16 *w, int retry, int delay); +extern int cts_hw_reg_readl_retry(struct cts_device *cts_dev, + u32 reg_addr, u32 *l, int retry, int delay); +extern int cts_hw_reg_readsb_retry(struct cts_device *cts_dev, + u32 reg_addr, void *dst, size_t len, int retry, int delay); + +static inline int cts_hw_reg_readb(struct cts_device *cts_dev, + u32 reg_addr, u8 *b) +{ + return cts_hw_reg_readb_retry(cts_dev, reg_addr, b, 1, 0); +} + +static inline int cts_hw_reg_writeb(struct cts_device *cts_dev, + u32 reg_addr, u8 b) +{ + return cts_hw_reg_writeb_retry(cts_dev, reg_addr, b, 1, 0); +} + +static inline int cts_hw_reg_writew(struct cts_device *cts_dev, + u32 reg_addr, u16 w) +{ + return cts_hw_reg_writew_retry(cts_dev, reg_addr, w, 1, 0); +} + +static inline int cts_fw_reg_writeb(struct cts_device *cts_dev, + u32 reg_addr, u8 b) +{ + return cts_fw_reg_writeb_retry(cts_dev, reg_addr, b, 1, 0); +} + +static inline int cts_fw_reg_writew(struct cts_device *cts_dev, + u32 reg_addr, u16 w) +{ + return cts_fw_reg_writew_retry(cts_dev, reg_addr, w, 1, 0); +} + +static inline int cts_fw_reg_writel(struct cts_device *cts_dev, + u32 reg_addr, u32 l) +{ + return cts_fw_reg_writel_retry(cts_dev, reg_addr, l, 1, 0); +} + +static inline int cts_fw_reg_writesb(struct cts_device *cts_dev, + u32 reg_addr, const void *src, size_t len) +{ + return cts_fw_reg_writesb_retry(cts_dev, reg_addr, src, len, 1, 0); +} + +static inline int cts_fw_reg_readb(struct cts_device *cts_dev, + u32 reg_addr, u8 *b) +{ + return cts_fw_reg_readb_retry(cts_dev, reg_addr, b, 1, 0); +} + +static inline int cts_fw_reg_readw(struct cts_device *cts_dev, + u32 reg_addr, u16 *w) +{ + return cts_fw_reg_readw_retry(cts_dev, reg_addr, w, 1, 0); +} + +static inline int cts_fw_reg_readl(struct cts_device *cts_dev, + u32 reg_addr, u32 *l) +{ + return cts_fw_reg_readl_retry(cts_dev, reg_addr, l, 1, 0); +} + +static inline int cts_fw_reg_readsb(struct cts_device *cts_dev, + u32 reg_addr, void *dst, size_t len) +{ + return cts_fw_reg_readsb_retry(cts_dev, reg_addr, dst, len, 1, 0); +} + +static inline int cts_fw_reg_readsb_delay_idle(struct cts_device *cts_dev, + u32 reg_addr, void *dst, size_t len, int idle) +{ + return cts_fw_reg_readsb_retry_delay_idle(cts_dev, reg_addr, dst, len, + 1, 0, idle); +} + +static inline int cts_hw_reg_writeb_relaxed(struct cts_device *cts_dev, + u32 reg_addr, u8 b) +{ + return cts_hw_reg_writeb_retry(cts_dev, reg_addr, b, 1, 0); +} + +static inline int cts_hw_reg_writew_relaxed(struct cts_device *cts_dev, + u32 reg_addr, u16 w) +{ + return cts_hw_reg_writew_retry(cts_dev, reg_addr, w, 1, 0); +} + +static inline int cts_hw_reg_writel_relaxed(struct cts_device *cts_dev, + u32 reg_addr, u32 l) +{ + return cts_hw_reg_writel_retry(cts_dev, reg_addr, l, 1, 0); +} + +static inline int cts_hw_reg_writesb(struct cts_device *cts_dev, + u32 reg_addr, const void *src, size_t len) +{ + return cts_hw_reg_writesb_retry(cts_dev, reg_addr, src, len, 1, 0); +} + +static inline int cts_hw_reg_readb_relaxed(struct cts_device *cts_dev, + u32 reg_addr, u8 *b) +{ + return cts_hw_reg_readb_retry(cts_dev, reg_addr, b, 1, 0); +} + +static inline int cts_hw_reg_readw_relaxed(struct cts_device *cts_dev, + u32 reg_addr, u16 *w) +{ + return cts_hw_reg_readw_retry(cts_dev, reg_addr, w, 1, 0); +} + +static inline int cts_hw_reg_readl_relaxed(struct cts_device *cts_dev, + u32 reg_addr, u32 *l) +{ + return cts_hw_reg_readl_retry(cts_dev, reg_addr, l, 1, 0); +} + +static inline int cts_hw_reg_readsb(struct cts_device *cts_dev, + u32 reg_addr, void *dst, size_t len) +{ + return cts_hw_reg_readsb_retry(cts_dev, reg_addr, dst, len, 1, 0); +} + +static inline int cts_sram_writeb(struct cts_device *cts_dev, + u32 addr, u8 b) +{ + return cts_sram_writeb_retry(cts_dev, addr, b, 1, 0); +} + +static inline int cts_sram_writew(struct cts_device *cts_dev, + u32 addr, u16 w) +{ + return cts_sram_writew_retry(cts_dev, addr, w, 1, 0); +} + +static inline int cts_sram_writel(struct cts_device *cts_dev, + u32 addr, u32 l) +{ + return cts_sram_writel_retry(cts_dev, addr, l, 1, 0); +} + +static inline int cts_sram_writesb(struct cts_device *cts_dev, u32 addr, + const void *src, size_t len) +{ + return cts_sram_writesb_retry(cts_dev, addr, src, len, 10, 0); +} + +static inline int cts_sram_readb(struct cts_device *cts_dev, + u32 addr, u8 *b) +{ + return cts_sram_readb_retry(cts_dev, addr, b, 1, 0); +} + +static inline int cts_sram_readw(struct cts_device *cts_dev, + u32 addr, u16 *w) +{ + return cts_sram_readw_retry(cts_dev, addr, w, 1, 0); +} + +static inline int cts_sram_readl(struct cts_device *cts_dev, + u32 addr, u32 *l) +{ + return cts_sram_readl_retry(cts_dev, addr, l, 1, 0); +} + +static inline int cts_sram_readsb(struct cts_device *cts_dev, + u32 addr, void *dst, size_t len) +{ + return cts_sram_readsb_retry(cts_dev, addr, dst, len, 1, 0); +} + +#ifdef CONFIG_CTS_I2C_HOST +static inline void cts_set_program_addr(struct cts_device *cts_dev) +{ + cts_dev->rtdata.slave_addr = CTS_DEV_PROGRAM_MODE_I2CADDR; + cts_dev->rtdata.program_mode = true; + cts_dev->rtdata.addr_width = CTS_DEV_PROGRAM_MODE_ADDR_WIDTH; +} + +static inline void cts_set_normal_addr(struct cts_device *cts_dev) +{ + cts_dev->rtdata.slave_addr = CTS_DEV_NORMAL_MODE_I2CADDR; + cts_dev->rtdata.program_mode = false; + cts_dev->rtdata.addr_width = CTS_DEV_NORMAL_MODE_ADDR_WIDTH; +} + +int cts_i2c_writeb(struct cts_device *cts_dev, + u32 addr, u8 b, int retry, int delay); +int cts_i2c_writeb(struct cts_device *cts_dev, + u32 addr, u8 b, int retry, int delay); +int cts_i2c_writesb(struct cts_device *cts_dev, u32 addr, + const u8 *src, size_t len, int retry, int delay); +int cts_i2c_readb(struct cts_device *cts_dev, + u32 addr, u8 *b, int retry, int delay); +int cts_i2c_readl(struct cts_device *cts_dev, + u32 addr, u32 *l, int retry, int delay); + +#else +static inline void cts_set_program_addr(struct cts_device *cts_dev) +{ + cts_dev->rtdata.slave_addr = CTS_DEV_PROGRAM_MODE_SPIADDR; + cts_dev->rtdata.program_mode = true; + cts_dev->rtdata.addr_width = CTS_DEV_PROGRAM_MODE_ADDR_WIDTH; +} + +static inline void cts_set_normal_addr(struct cts_device *cts_dev) +{ + cts_dev->rtdata.slave_addr = CTS_DEV_NORMAL_MODE_SPIADDR; + cts_dev->rtdata.program_mode = false; + cts_dev->rtdata.addr_width = CTS_DEV_NORMAL_MODE_ADDR_WIDTH; +} +#endif + +extern int cts_irq_handler(struct cts_device *cts_dev); +extern void cts_firmware_upgrade_work(struct work_struct *work); + +extern bool cts_is_device_suspended(struct cts_device *cts_dev); +extern int cts_suspend_device(struct cts_device *cts_dev); +extern int cts_resume_device(struct cts_device *cts_dev); + +extern bool cts_is_device_program_mode(struct cts_device *cts_dev); +extern int cts_enter_program_mode(struct cts_device *cts_dev); +extern int cts_enter_normal_mode(struct cts_device *cts_dev); + +extern int cts_probe_device(struct cts_device *cts_dev); +extern int cts_send_command(struct cts_device *cts_dev, u8 cmd); + +extern int cts_read_sram_normal_mode(struct cts_device *cts_dev, + u32 addr, void *dst, size_t len, int retry, int delay); + +#ifdef CFG_CTS_GESTURE +extern void cts_enable_gesture_wakeup(struct cts_device *cts_dev); +extern void cts_disable_gesture_wakeup(struct cts_device *cts_dev); +extern bool cts_is_gesture_wakeup_enabled(struct cts_device *cts_dev); +extern int cts_get_gesture_info(struct cts_device *cts_dev, void *gesture_info); +#endif /* CFG_CTS_GESTURE */ +extern bool cts_is_d_tap_supported(struct cts_device *cts_dev); +extern int cts_set_d_tap_enabled(struct cts_device *cts_dev, bool enabled); + +extern int cts_set_int_data_types(struct cts_device *cts_dev, u16 types); +extern int cts_set_int_data_method(struct cts_device *cts_dev, u8 method); + +#ifdef CONFIG_CTS_ESD_PROTECTION +extern void cts_init_esd_protection(struct chipone_ts_data *cts_data); +extern void cts_enable_esd_protection(struct chipone_ts_data *cts_data); +extern void cts_disable_esd_protection(struct chipone_ts_data *cts_data); +extern void cts_deinit_esd_protection(struct chipone_ts_data *cts_data); +#else /* CONFIG_CTS_ESD_PROTECTION */ +static inline void cts_init_esd_protection(struct chipone_ts_data *cts_data) +{ +} + +static inline void cts_enable_esd_protection(struct chipone_ts_data *cts_data) +{ +} + +static inline void cts_disable_esd_protection(struct chipone_ts_data *cts_data) +{ +} + +static inline void cts_deinit_esd_protection(struct chipone_ts_data *cts_data) +{ +} +#endif /* CONFIG_CTS_ESD_PROTECTION */ + +#ifdef CFG_CTS_HEARTBEAT_MECHANISM +void cts_show_touch_debug_msg(void *debug); +void cts_heartbeat_mechanism_work(struct work_struct *work); +void cts_enable_heartbeat_mechanism(struct chipone_ts_data *cts_data); +void cts_disable_heartbeat_mechanism(struct chipone_ts_data *cts_data); +#endif + +#ifdef CONFIG_CTS_GLOVE +extern int cts_enter_glove_mode(struct cts_device *cts_dev); +extern int cts_exit_glove_mode(struct cts_device *cts_dev); +int cts_is_glove_enabled(struct cts_device *cts_dev); +int cts_set_glove_enabled(struct cts_device *cts_dev, bool enabled); +#else +static inline int cts_enter_glove_mode(struct cts_device *cts_dev) +{ + return 0; +} + +static inline int cts_exit_glove_mode(struct cts_device *cts_dev) +{ + return 0; +} + +static inline int cts_is_glove_enabled(struct cts_device *cts_dev) +{ + return 0; +} + +static inline int cts_set_glove_enabled(struct cts_device *cts_dev, bool enabled) +{ + return 0; +} +#endif + +#ifdef CONFIG_CTS_CHARGER_DETECT +extern bool cts_is_charger_exist(struct cts_device *cts_dev); +extern int cts_set_dev_charger_attached(struct cts_device *cts_dev, + bool attached); +#else /* CONFIG_CTS_CHARGER_DETECT */ +static inline bool cts_is_charger_exist(struct cts_device *cts_dev) +{ + return false; +} + +static inline int cts_set_dev_charger_attached(struct cts_device *cts_dev, + bool attached) +{ + return 0; +} +#endif /* CONFIG_CTS_CHARGER_DETECT */ + +#ifdef CONFIG_CTS_EARJACK_DETECT +extern bool cts_is_earjack_exist(struct cts_device *cts_dev); +extern int cts_set_dev_earjack_attached(struct cts_device *cts_dev, bool attached); +#else /* CONFIG_CTS_EARJACK_DETECT */ +static inline bool cts_is_earjack_exist(struct cts_device *cts_dev) +{ + return false; +} +static inline int cts_set_dev_earjack_attached(struct cts_device *cts_dev, bool attached) +{ + return 0; +} +#endif /* CONFIG_CTS_EARJACK_DETECT */ + +#ifdef CONFIG_CTS_LEGACY_TOOL +extern int cts_tool_init(struct chipone_ts_data *cts_data); +extern void cts_tool_deinit(struct chipone_ts_data *data); +#else /* CONFIG_CTS_LEGACY_TOOL */ +static inline int cts_tool_init(struct chipone_ts_data *cts_data) +{ + return 0; +} + +static inline void cts_tool_deinit(struct chipone_ts_data *data) +{ +} +#endif /* CONFIG_CTS_LEGACY_TOOL */ + +extern bool cts_is_device_enabled(struct cts_device *cts_dev); +extern int cts_start_device(struct cts_device *cts_dev); +extern int cts_stop_device(struct cts_device *cts_dev); + +#ifdef CFG_CTS_UPDATE_CRCCHECK +extern int cts_sram_writesb_boot_crc_retry(struct cts_device *cts_dev, + size_t len, u32 crc, int retry); +#endif + +extern const char *cts_dev_boot_mode2str(u8 boot_mode); +extern bool cts_is_fwid_valid(u16 fwid); + +extern int cts_reset_device(struct cts_device *cts_dev); +extern int cts_wait_current_mode(struct cts_device *cts_dev, u8 tar_mode); + +extern int kstrtobool(const char *s, bool *res); + +extern int cts_suspend(struct chipone_ts_data *cts_data); +extern int cts_resume(struct chipone_ts_data *cts_data); + +extern struct chipone_ts_data *g_cts_data; + +#endif /* CTS_CORE_H */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_driver.c b/drivers/input/touchscreen/chipone_tddi/cts_driver.c new file mode 100644 index 000000000000..3ebd444b2eb0 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_driver.c @@ -0,0 +1,1231 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef CONFIG_CTS_I2C_HOST +#define LOG_TAG "I2CDrv" +#else +#define LOG_TAG "SPIDrv" +#endif +#include +#include "cts_config.h" +#include "cts_platform.h" +#include "cts_core.h" +#include "cts_sysfs.h" +#include "cts_charger_detect.h" +#include "cts_earjack_detect.h" +#include "cts_oem.h" +#include "cts_strerror.h" +#include "cts_firmware.h" + +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +#include "tpd.h" +#include "cts_config.h" +#include "cts_platform.h" +#include "cts_core.h" +#else /* CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED */ +#ifdef CFG_CTS_DRM_NOTIFIER +#include +static struct drm_panel *active_panel; +static int check_dt(struct device_node *np); +#endif /* CFG_CTS_DRM_NOTIFIER */ +#endif /* CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED */ + +static void cts_resume_work_func(struct work_struct *work); + +bool cts_show_debug_log; +#ifdef CTS_MTK_GET_PANEL +static char *active_panel_name; +#endif + +module_param_named(debug_log, cts_show_debug_log, bool, 0660); +MODULE_PARM_DESC(debug_log, "Show debug log control"); + +struct chipone_ts_data *g_cts_data = NULL; + +int cts_suspend(struct chipone_ts_data *cts_data) +{ + int ret; + + cts_info("Suspend"); + + cts_lock_device(&cts_data->cts_dev); + ret = cts_suspend_device(&cts_data->cts_dev); + cts_unlock_device(&cts_data->cts_dev); + + if (ret) + cts_err("Suspend device failed %d", ret); + + ret = cts_stop_device(&cts_data->cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + return ret; + } +#ifdef CFG_CTS_GESTURE + /* Enable IRQ wake if gesture wakeup enabled */ + if (cts_is_gesture_wakeup_enabled(&cts_data->cts_dev)) { + ret = cts_plat_enable_irq_wake(cts_data->pdata); + if (ret) { + cts_err("Enable IRQ wake failed %d", ret); + return ret; + } + ret = cts_plat_enable_irq(cts_data->pdata); + if (ret) { + cts_err("Enable IRQ failed %d", ret); + return ret; + } + } +#endif /* CFG_CTS_GESTURE */ + +/** - To avoid waking up while not sleeping, + *delay 20ms to ensure reliability + */ + msleep(20); + + return 0; +} + +int cts_resume(struct chipone_ts_data *cts_data) +{ + int ret; + + cts_info("Resume"); + +#ifdef CFG_CTS_GESTURE + if (cts_is_gesture_wakeup_enabled(&cts_data->cts_dev)) { + ret = cts_plat_disable_irq_wake(cts_data->pdata); + if (ret) + cts_warn("Disable IRQ wake failed %d", ret); + ret = cts_plat_disable_irq(cts_data->pdata); + if (ret < 0) + cts_err("Disable IRQ failed %d", ret); + } +#endif /* CFG_CTS_GESTURE */ + + cts_lock_device(&cts_data->cts_dev); + ret = cts_resume_device(&cts_data->cts_dev); + cts_unlock_device(&cts_data->cts_dev); + if (ret) { + cts_warn("Resume device failed %d", ret); + return ret; + } + + ret = cts_start_device(&cts_data->cts_dev); + if (ret) { + cts_err("Start device failed %d", ret); + return ret; + } + + return 0; +} + +static void cts_resume_work_func(struct work_struct *work) +{ + struct chipone_ts_data *cts_data = + container_of(work, struct chipone_ts_data, ts_resume_work); + cts_info("%s", __func__); + cts_resume(cts_data); +} + +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +#ifdef CONFIG_CTS_PM_FB_NOTIFIER +#ifdef CFG_CTS_DRM_NOTIFIER +static int fb_notifier_callback(struct notifier_block *nb, + unsigned long action, void *data) +{ + volatile int blank; + const struct cts_platform_data *pdata = + container_of(nb, struct cts_platform_data, fb_notifier); + struct chipone_ts_data *cts_data = + container_of(pdata->cts_dev, struct chipone_ts_data, cts_dev); + struct drm_panel_notifier *evdata = data; + + cts_info("FB notifier callback"); + if (!evdata || !evdata->data || !cts_data) + return 0; + + blank = *(int *)evdata->data; + cts_info("action=%lu, blank=%d", action, blank); + + if (action == DRM_PANEL_EARLY_EVENT_BLANK) { + if (blank == DRM_PANEL_BLANK_POWERDOWN) + cts_suspend(cts_data); + } else { + blank = *(int *)evdata->data; + if (action == DRM_PANEL_EVENT_BLANK) { + if (blank == DRM_PANEL_BLANK_UNBLANK) + /* cts_resume(cts_data); */ + queue_work(cts_data->workqueue, + &cts_data->ts_resume_work); + } + } + + return 0; +} +#else +static int fb_notifier_callback(struct notifier_block *nb, + unsigned long action, void *data) +{ + volatile int blank; + const struct cts_platform_data *pdata = + container_of(nb, struct cts_platform_data, fb_notifier); + struct chipone_ts_data *cts_data = + container_of(pdata->cts_dev, struct chipone_ts_data, cts_dev); + struct fb_event *evdata = data; + + cts_info("FB notifier callback"); + + if (evdata && evdata->data) { + if (action == FB_EVENT_BLANK) { + blank = *(int *)evdata->data; + if (blank == FB_BLANK_UNBLANK) { + /* cts_resume(cts_data); */ + queue_work(cts_data->workqueue, + &cts_data->ts_resume_work); + return NOTIFY_OK; + } + } else if (action == FB_EARLY_EVENT_BLANK) { + blank = *(int *)evdata->data; + if (blank == FB_BLANK_POWERDOWN) { + cts_suspend(cts_data); + return NOTIFY_OK; + } + } + } + + return NOTIFY_DONE; +} +#endif + +static int cts_init_pm_fb_notifier(struct chipone_ts_data *cts_data) +{ + cts_info("Init FB notifier"); + + cts_data->pdata->fb_notifier.notifier_call = fb_notifier_callback; + +#ifdef CFG_CTS_DRM_NOTIFIER + { + int ret = -ENODEV; + + if (active_panel) { + ret =drm_panel_notifier_register(active_panel, + &cts_data->pdata->fb_notifier); + if (ret) + cts_err("register drm_notifier failed. ret=%d\n", ret); + } + return ret; + } +#else + return fb_register_client(&cts_data->pdata->fb_notifier); +#endif +} + +static int cts_deinit_pm_fb_notifier(struct chipone_ts_data *cts_data) +{ + cts_info("Deinit FB notifier"); +#ifdef CFG_CTS_DRM_NOTIFIER + { + int ret = 0; + + if (active_panel) { + ret = drm_panel_notifier_unregister(active_panel, + &cts_data->pdata->fb_notifier); + if (ret) + cts_err("Error occurred while unregistering drm_notifier.\n"); + } + return ret; + } +#else + return fb_unregister_client(&cts_data->pdata->fb_notifier); +#endif +} +#endif /* CONFIG_CTS_PM_FB_NOTIFIER */ + +#ifdef CFG_CTS_DRM_NOTIFIER +static int check_dt(struct device_node *np) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + cts_info("check active_panel"); + active_panel = panel; + return 0; + } + } + if (node) + cts_err("%s: %s not actived", __func__, node->name); + return -ENODEV; +} + +static int check_default_tp(struct device_node *dt, const char *prop) +{ + const char *active_tp; + const char *compatible; + char *start; + int ret; + + ret = of_property_read_string(dt->parent, prop, &active_tp); + if (ret) { + cts_err("%s:fail to read %s %d", __func__, prop, ret); + return -ENODEV; + } + + ret = of_property_read_string(dt, "compatible", &compatible); + if (ret < 0) { + cts_err("%s:fail to read %s %d", __func__, "compatible", ret); + return -ENODEV; + } + + start = strnstr(active_tp, compatible, strlen(active_tp)); + if (start == NULL) { + cts_err("no match compatible, %s, %s", compatible, active_tp); + ret = -ENODEV; + } + + return ret; +} +#endif +#endif + +#ifdef CTS_MTK_GET_PANEL +char panel_name[50] = { 0 }; + +static int cts_get_panel(void) +{ + int ret = -1; + + cts_info("Enter cts_get_panel"); + if (saved_command_line) { + char *sub; + char key_prefix[] = "mipi_mot_vid_"; + char ic_prefix[] = "icnl"; + + cts_info("saved_command_line is %s", saved_command_line); + sub = strstr(saved_command_line, key_prefix); + if (sub) { + char *d; + int n, len, len_max = 50; + + d = strstr(sub, " "); + if (d) + n = strlen(sub) - strlen(d); + else + n = strlen(sub); + + if (n > len_max) + len = len_max; + else + len = n; + + strncpy(panel_name, sub, len); + active_panel_name = panel_name; + if (strstr(active_panel_name, ic_prefix)) + cts_info("active_panel_name=%s", active_panel_name); + else { + cts_info("Not chipone panel!"); + return ret; + } + + } else { + cts_info("chipone active panel not found!"); + return ret; + } + } else { + cts_info("saved_command_line null!"); + return ret; + } + + return 0; +} +#endif + +#ifdef CONFIG_PM_DSI_EXTCON_NOTIFIER +static int ts_extcon_notifier(struct notifier_block *self, + unsigned long event, void *data) +{ + struct chipone_ts_data *cts_data = + container_of(self, struct chipone_ts_data, extcon_nb); + if (extcon_get_state(cts_data->edev, EXTCON_JACK_VIDEO_OUT)){ + pr_info("%s %d\n", __func__, __LINE__); + //cts_plat_set_reset(cts_data->pdata, 1); + //mdelay(1); + //cts_plat_set_reset(cts_data->pdata, 0); + //mdelay(5);/* 1ms */ + //cts_plat_set_reset(cts_data->pdata, 1); + cts_resume(cts_data); + } else { + cts_suspend(cts_data); + pr_info("%s %d\n", __func__, __LINE__); + //cts_plat_set_reset(cts_data->pdata, 0); + } + + return 0; +} +#endif + +#ifdef CONFIG_CTS_I2C_HOST +static int cts_driver_probe(struct i2c_client *client, + const struct i2c_device_id *id) +#else +static int cts_driver_probe(struct spi_device *client) +#endif +{ + struct chipone_ts_data *cts_data = NULL; + struct cts_device *cts_dev = NULL; + struct cts_firmware *firmware = NULL; + int ret = 0; + +#ifdef CTS_MTK_GET_PANEL + ret = cts_get_panel(); + if (ret) { + cts_info("MTK get chipone panel error"); + return ret; + } +#endif + +#ifdef CFG_CTS_DRM_NOTIFIER + { + struct device_node *dp = client->dev.of_node; + + if (check_dt(dp)) { + if (!check_default_tp(dp, "qcom,i2c-touch-active")) + ret = -EPROBE_DEFER; + else + ret = -ENODEV; + + cts_err("%s: %s not actived\n", __func__, dp->name); + return ret; + } + } +#endif + +#ifdef CONFIG_CTS_I2C_HOST + if (client == NULL) { + cts_err("Probe i2c client = NULL"); + return -EINVAL; + } + cts_info("Probe i2c client: name='%s' addr=0x%02x flags=0x%02x irq=%d", + client->name, client->addr, client->flags, client->irq); + +#if !defined(CONFIG_MTK_PLATFORM) + if (client->addr != CTS_DEV_NORMAL_MODE_I2CADDR) { + cts_err("Probe i2c addr 0x%02x != driver config addr 0x%02x", + client->addr, CTS_DEV_NORMAL_MODE_I2CADDR); + return -ENODEV; + }; +#endif + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + cts_err("Check functionality failed"); + return -ENODEV; + } +#else + if (client == NULL) { + cts_info("Probe spi client = NULL"); + return -EINVAL; + } + cts_info("Probe spi device '%s': " + "mode='%u' speed=%u bits_per_word=%u " + "chip_select=%u, cs_gpio=%d irq=%d", + client->modalias, client->mode, client->max_speed_hz, client->bits_per_word, + client->chip_select, client->cs_gpio, client->irq); +#endif + + cts_data = kzalloc(sizeof(struct chipone_ts_data), GFP_KERNEL); + if (cts_data == NULL) { + cts_err("Allocate chipone_ts_data failed"); + return -ENOMEM; + } + + cts_data->pdata = kzalloc(sizeof(struct cts_platform_data), GFP_KERNEL); + if (cts_data->pdata == NULL) { + cts_err("Allocate cts_platform_data failed"); + ret = -ENOMEM; + goto err_free_cts_data; + } + +#ifdef CONFIG_CTS_I2C_HOST + i2c_set_clientdata(client, cts_data); + cts_data->i2c_client = client; + cts_data->device = &client->dev; +#else + spi_set_drvdata(client, cts_data); + cts_data->spi_client = client; + cts_data->device = &client->dev; +#endif + + ret = cts_init_platform_data(cts_data->pdata, client); + if (ret) { + cts_err("Init platform data failed %d", ret); + goto err_free_pdata; + } + + cts_data->cts_dev.pdata = cts_data->pdata; + cts_data->pdata->cts_dev = &cts_data->cts_dev; + cts_dev = cts_data->pdata->cts_dev; + g_cts_data = cts_data; + + firmware = kzalloc(sizeof(struct cts_firmware), GFP_KERNEL); + if (firmware == NULL) { + cts_err("Create firmware buffer failed"); + goto err_free_firmware_buffer; + } + + firmware->data = kzalloc(CFG_CTS_FIRMWARE_SIZE, GFP_KERNEL); + if (firmware->data == NULL) { + cts_err("Create firmware data buffer failed"); + goto err_free_firmware_data_buffer; + } + + cts_dev->firmware = firmware; + + cts_data->workqueue = + create_singlethread_workqueue(CFG_CTS_DEVICE_NAME "-workqueue"); + if (cts_data->workqueue == NULL) { + cts_err("Create workqueue failed"); + ret = -ENOMEM; + goto err_deinit_platform_data; + } + +#ifdef CONFIG_CTS_ESD_PROTECTION + cts_data->esd_workqueue = + create_singlethread_workqueue(CFG_CTS_DEVICE_NAME "-esd_workqueue"); + if (cts_data->esd_workqueue == NULL) { + cts_err("Create esd workqueue failed"); + ret = -ENOMEM; + goto err_destroy_workqueue; + } +#endif + +#ifdef CFG_CTS_HEARTBEAT_MECHANISM + cts_data->heart_workqueue = + create_singlethread_workqueue(CFG_CTS_DEVICE_NAME "-heart_workqueue"); + if (cts_data->heart_workqueue == NULL) { + cts_err("Create heart workqueue failed"); + ret = -ENOMEM; + goto err_destroy_esd_workqueue; + } +#endif + + ret = cts_plat_request_resource(cts_data->pdata); + if (ret < 0) { + cts_err("Request resource failed %d", ret); + goto err_destroy_heart_workqueue; + } + + ret = cts_reset_device(cts_dev); + if (ret < 0) { + cts_err("Reset device failed %d", ret); + goto err_free_resource; + } + + ret = cts_probe_device(cts_dev); + if (ret) { + cts_err("Probe device failed %d", ret); + goto err_free_resource; + } + + ret = cts_plat_init_touch_device(cts_data->pdata); + if (ret < 0) { + cts_err("Init touch device failed %d", ret); + goto err_free_resource; + } + + ret = cts_plat_init_vkey_device(cts_data->pdata); + if (ret < 0) { + cts_err("Init vkey device failed %d", ret); + goto err_deinit_touch_device; + } + + ret = cts_plat_init_gesture(cts_data->pdata); + if (ret < 0) { + cts_err("Init gesture failed %d", ret); + goto err_deinit_vkey_device; + } + + cts_init_esd_protection(cts_data); + + ret = cts_tool_init(cts_data); + if (ret < 0) + cts_warn("Init tool node failed %d", ret); + + ret = cts_sysfs_add_device(&client->dev); + if (ret < 0) + cts_warn("Add sysfs entry for device failed %d", ret); + +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +#ifdef CONFIG_CTS_PM_FB_NOTIFIER + ret = cts_init_pm_fb_notifier(cts_data); + if (ret) { + cts_err("Init FB notifier failed %d", ret); + goto err_deinit_sysfs; + } +#endif +#endif + + ret = cts_plat_request_irq(cts_data->pdata); + if (ret < 0) { + cts_err("Request IRQ failed %d", ret); + goto err_register_fb; + } + +#ifdef CONFIG_CTS_CHARGER_DETECT + ret = cts_charger_detect_init(cts_data); + if (ret) + cts_err("Init charger detect failed %d", ret); + /* Ignore this error */ +#endif + +#ifdef CONFIG_CTS_EARJACK_DETECT + ret = cts_earjack_detect_init(cts_data); + if (ret) { + cts_err("Init earjack detect failed %d", ret); + // Ignore this error + } +#endif + + ret = cts_oem_init(cts_data); + if (ret < 0) { + cts_warn("Init oem specific faild %d", ret); + goto err_deinit_oem; + } + +#ifdef CFG_CTS_HEARTBEAT_MECHANISM + INIT_DELAYED_WORK(&cts_data->heart_work, cts_heartbeat_mechanism_work); +#endif + INIT_WORK(&cts_data->ts_resume_work, cts_resume_work_func); + + /* Init firmware upgrade work and schedule */ + INIT_DELAYED_WORK(&cts_data->fw_upgrade_work, cts_firmware_upgrade_work); + queue_delayed_work(cts_data->workqueue, &cts_data->fw_upgrade_work, + msecs_to_jiffies(5 * 1000)); + +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +#ifdef CONFIG_MTK_PLATFORM + tpd_load_status = 1; +#endif /* CONFIG_MTK_PLATFORM */ +#endif + +#ifdef CONFIG_PM_DSI_EXTCON_NOTIFIER + cts_data->edev = extcon_get_edev_by_phandle(&client->dev, 0); + if (IS_ERR(cts_data->edev)) { + if (PTR_ERR(cts_data->edev) != -EPROBE_DEFER) + dev_err(&client->dev, "Invalid or missing extcon\n"); + pr_info("register notifier...but extcon_get_edev error\n"); + return 0; + } + + cts_data->extcon_nb.notifier_call = ts_extcon_notifier; + ret = extcon_register_notifier(cts_data->edev, EXTCON_JACK_VIDEO_OUT, + &cts_data->extcon_nb); + pr_info("register notifier...\n"); + if (ret < 0) { + pr_info("fail to register notifier...\n"); + dev_err(&client->dev, "failed to register notifier for TP\n"); + return ret; + } +#endif + + return 0; + +err_deinit_oem: + cts_oem_deinit(cts_data); + +#ifdef CONFIG_CTS_CHARGER_DETECT + cts_charger_detect_deinit(cts_data); +#endif +#ifdef CONFIG_CTS_EARJACK_DETECT + cts_earjack_detect_deinit(cts_data); +#endif + + cts_plat_free_irq(cts_data->pdata); + +err_register_fb: +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +#ifdef CONFIG_CTS_PM_FB_NOTIFIER + cts_deinit_pm_fb_notifier(cts_data); +err_deinit_sysfs: +#endif +#endif + + cts_sysfs_remove_device(&client->dev); +#ifdef CONFIG_CTS_LEGACY_TOOL + cts_tool_deinit(cts_data); +#endif + +#ifdef CONFIG_CTS_ESD_PROTECTION + cts_deinit_esd_protection(cts_data); +#endif + +#ifdef CFG_CTS_GESTURE + cts_plat_deinit_gesture(cts_data->pdata); +#endif + +err_deinit_vkey_device: +#ifdef CONFIG_CTS_VIRTUALKEY + cts_plat_deinit_vkey_device(cts_data->pdata); +#endif + +err_deinit_touch_device: + cts_plat_deinit_touch_device(cts_data->pdata); + +err_free_resource: + cts_plat_free_resource(cts_data->pdata); + +err_destroy_heart_workqueue: +#ifdef CFG_CTS_HEARTBEAT_MECHANISM + destroy_workqueue(cts_data->heart_workqueue); +err_destroy_esd_workqueue: +#endif + +#ifdef CONFIG_CTS_ESD_PROTECTION + destroy_workqueue(cts_data->esd_workqueue); +err_destroy_workqueue: +#endif + destroy_workqueue(cts_data->workqueue); + +err_deinit_platform_data: + if (firmware->data) { + kfree(firmware->data); + firmware->data = NULL; + } + +err_free_firmware_data_buffer: + if (firmware) { + kfree(firmware); + firmware = NULL; + } + +err_free_firmware_buffer: +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + cts_deinit_platform_data(cts_data->pdata); +#endif +err_free_pdata: + kfree(cts_data->pdata); +err_free_cts_data: + kfree(cts_data); + + cts_err("Probe failed %d", ret); + + return ret; +} + +#ifdef CONFIG_CTS_I2C_HOST +static void cts_driver_remove(struct i2c_client *client) +#else +static void cts_driver_remove(struct spi_device *client) +#endif +{ + struct chipone_ts_data *cts_data; + int ret = 0; + + cts_info("Remove"); + +#ifdef CONFIG_CTS_I2C_HOST + cts_data = (struct chipone_ts_data *)i2c_get_clientdata(client); +#else + cts_data = (struct chipone_ts_data *)spi_get_drvdata(client); +#endif + if (cts_data) { + ret = cts_stop_device(&cts_data->cts_dev); + if (ret) + cts_warn("Stop device failed %d", ret); + +#ifdef CONFIG_CTS_CHARGER_DETECT + cts_charger_detect_deinit(cts_data); +#endif + +#ifdef CONFIG_CTS_EARJACK_DETECT + cts_earjack_detect_deinit(cts_data); +#endif + + cts_plat_free_irq(cts_data->pdata); + +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +#ifdef CONFIG_CTS_PM_FB_NOTIFIER + cts_deinit_pm_fb_notifier(cts_data); +#endif +#endif + + cts_tool_deinit(cts_data); + + cts_sysfs_remove_device(&client->dev); + + cts_deinit_esd_protection(cts_data); + + cts_plat_deinit_touch_device(cts_data->pdata); + + cts_plat_deinit_vkey_device(cts_data->pdata); + + cts_plat_deinit_gesture(cts_data->pdata); + + cts_plat_free_resource(cts_data->pdata); + + cts_oem_deinit(cts_data); + +#ifdef CFG_CTS_HEARTBEAT_MECHANISM + if (cts_data->heart_workqueue) + destroy_workqueue(cts_data->heart_workqueue); +#endif + +#ifdef CONFIG_CTS_ESD_PROTECTION + if (cts_data->esd_workqueue) + destroy_workqueue(cts_data->esd_workqueue); +#endif + + if (cts_data->workqueue) + destroy_workqueue(cts_data->workqueue); + + if (cts_data->cts_dev.firmware->data) { + kfree(cts_data->cts_dev.firmware->data); + cts_data->cts_dev.firmware->data = NULL; + } + + if (cts_data->cts_dev.firmware) { + kfree(cts_data->cts_dev.firmware); + cts_data->cts_dev.firmware = NULL; + } + +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + cts_deinit_platform_data(cts_data->pdata); +#endif + if (cts_data->pdata) + kfree(cts_data->pdata); + + kfree(cts_data); + } else { + cts_warn("Chipone i2c driver remove while NULL chipone_ts_data"); + } + +} + +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +#ifdef CONFIG_CTS_PM_LEGACY +static int cts_i2c_driver_suspend(struct device *dev, pm_message_t state) +{ + cts_info("Suspend by legacy power management"); + return cts_suspend(dev_get_drvdata(dev)); +} + +static int cts_i2c_driver_resume(struct device *dev) +{ + cts_info("Resume by legacy power management"); + return cts_resume(dev_get_drvdata(dev)); +} +#endif /* CONFIG_CTS_PM_LEGACY */ + +#ifdef CONFIG_CTS_PM_GENERIC +static int cts_i2c_driver_pm_suspend(struct device *dev) +{ + cts_info("Suspend by bus power management"); + return cts_suspend(dev_get_drvdata(dev)); +} + +static int cts_i2c_driver_pm_resume(struct device *dev) +{ + cts_info("Resume by bus power management"); + return cts_resume(dev_get_drvdata(dev)); +} + +/* bus control the suspend/resume procedure */ +static const struct dev_pm_ops cts_i2c_driver_pm_ops = { + .suspend = cts_i2c_driver_pm_suspend, + .resume = cts_i2c_driver_pm_resume, +}; +#endif /* CONFIG_CTS_PM_GENERIC */ +#endif + +#ifdef CONFIG_CTS_SYSFS +static ssize_t reset_pin_show(struct device_driver *driver, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "CFG_CTS_HAS_RESET_PIN: %c\n", +#ifdef CFG_CTS_HAS_RESET_PIN + 'Y' +#else + 'N' +#endif + ); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +static DRIVER_ATTR(reset_pin, S_IRUGO, reset_pin_show, NULL); +#else +static DRIVER_ATTR_RO(reset_pin); +#endif + +static ssize_t force_update_show(struct device_driver *dev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "CFG_CTS_HAS_RESET_PIN: %c\n", +#ifdef CFG_CTS_FIRMWARE_FORCE_UPDATE + 'Y' +#else + 'N' +#endif + ); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +static DRIVER_ATTR(force_update, S_IRUGO, force_update_show, NULL); +#else +static DRIVER_ATTR_RO(force_update); +#endif + +static ssize_t max_touch_num_show(struct device_driver *dev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "CFG_CTS_MAX_TOUCH_NUM: %d\n", + CFG_CTS_MAX_TOUCH_NUM); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +static DRIVER_ATTR(max_touch_num, S_IRUGO, max_touch_num_show, NULL); +#else +static DRIVER_ATTR_RO(max_touch_num); +#endif + +static ssize_t vkey_show(struct device_driver *dev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "CONFIG_CTS_VIRTUALKEY: %c\n", +#ifdef CONFIG_CTS_VIRTUALKEY + 'Y' +#else + 'N' +#endif + ); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +static DRIVER_ATTR(vkey, S_IRUGO, vkey_show, NULL); +#else +static DRIVER_ATTR_RO(vkey); +#endif + +static ssize_t gesture_show(struct device_driver *dev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "CFG_CTS_GESTURE: %c\n", +#ifdef CFG_CTS_GESTURE + 'Y' +#else + 'N' +#endif + ); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +static DRIVER_ATTR(gesture, S_IRUGO, gesture_show, NULL); +#else +static DRIVER_ATTR_RO(gesture); +#endif + +static ssize_t esd_protection_show(struct device_driver *dev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "CONFIG_CTS_ESD_PROTECTION: %c\n", +#ifdef CONFIG_CTS_ESD_PROTECTION + 'Y' +#else + 'N' +#endif + ); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +static DRIVER_ATTR(esd_protection, S_IRUGO, esd_protection_show, NULL); +#else +static DRIVER_ATTR_RO(esd_protection); +#endif + +static ssize_t slot_protocol_show(struct device_driver *dev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "CONFIG_CTS_SLOTPROTOCOL: %c\n", +#ifdef CONFIG_CTS_SLOTPROTOCOL + 'Y' +#else + 'N' +#endif + ); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +static DRIVER_ATTR(slot_protocol, S_IRUGO, slot_protocol_show, NULL); +#else +static DRIVER_ATTR_RO(slot_protocol); +#endif + +static ssize_t max_xfer_size_show(struct device_driver *dev, char *buf) +{ +#ifdef CONFIG_CTS_I2C_HOST + return snprintf(buf, PAGE_SIZE, "CFG_CTS_MAX_I2C_XFER_SIZE: %d\n", + CFG_CTS_MAX_I2C_XFER_SIZE); +#else + return snprintf(buf, PAGE_SIZE, "CFG_CTS_MAX_SPI_XFER_SIZE: %d\n", + CFG_CTS_MAX_SPI_XFER_SIZE); +#endif +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +static DRIVER_ATTR(max_xfer_size, S_IRUGO, max_xfer_size_show, NULL); +#else +static DRIVER_ATTR_RO(max_xfer_size); +#endif + +static ssize_t driver_info_show(struct device_driver *dev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "Driver version: %s\n", + CFG_CTS_DRIVER_VERSION); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +static DRIVER_ATTR(driver_info, S_IRUGO, driver_info_show, NULL); +#else +static DRIVER_ATTR_RO(driver_info); +#endif + +static struct attribute *cts_driver_config_attrs[] = { + &driver_attr_reset_pin.attr, + &driver_attr_force_update.attr, + &driver_attr_max_touch_num.attr, + &driver_attr_vkey.attr, + &driver_attr_gesture.attr, + &driver_attr_esd_protection.attr, + &driver_attr_slot_protocol.attr, + &driver_attr_max_xfer_size.attr, + &driver_attr_driver_info.attr, + NULL +}; + +static const struct attribute_group cts_driver_config_group = { + .name = "config", + .attrs = cts_driver_config_attrs, +}; + +static const struct attribute_group *cts_driver_config_groups[] = { + &cts_driver_config_group, + NULL, +}; +#endif /* CONFIG_CTS_SYSFS */ + +#ifdef CONFIG_CTS_OF +static const struct of_device_id cts_driver_of_match_table[] = { + {.compatible = CFG_CTS_OF_DEVICE_ID_NAME, }, + { }, +}; + +MODULE_DEVICE_TABLE(of, cts_driver_of_match_table); +#endif /* CONFIG_CTS_OF */ + +#ifdef CONFIG_CTS_I2C_HOST +static const struct i2c_device_id cts_device_id_table[] = { + { CFG_CTS_DEVICE_NAME, 0 }, + { } +}; +#else +static const struct spi_device_id cts_device_id_table[] = { + { CFG_CTS_DEVICE_NAME, 0 }, + { } +}; +#endif + +#ifdef CONFIG_CTS_I2C_HOST +static struct i2c_driver cts_i2c_driver = { +#else +static struct spi_driver cts_spi_driver = { +#endif + .probe = cts_driver_probe, + .remove = cts_driver_remove, + .driver = { + .name = CFG_CTS_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_CTS_OF + .of_match_table = of_match_ptr(cts_driver_of_match_table), +#endif /* CONFIG_CTS_OF */ +#ifdef CONFIG_CTS_SYSFS + .groups = cts_driver_config_groups, +#endif /* CONFIG_CTS_SYSFS */ +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +#ifdef CONFIG_CTS_PM_LEGACY + .suspend = cts_i2c_driver_suspend, + .resume = cts_i2c_driver_resume, +#endif /* CONFIG_CTS_PM_LEGACY */ +#ifdef CONFIG_CTS_PM_GENERIC + .pm = &cts_i2c_driver_pm_ops, +#endif /* CONFIG_CTS_PM_GENERIC */ +#endif + }, + .id_table = cts_device_id_table, +}; + +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +static int cts_driver_init(void) +#else +static int __init cts_driver_init(void) +#endif +{ + int ret = 0; + + cts_info("Chipone touch driver init, version: "CFG_CTS_DRIVER_VERSION); + +#ifdef CONFIG_CTS_I2C_HOST + cts_info(" - Register i2c driver"); + ret = i2c_add_driver(&cts_i2c_driver); + if (ret) { + cts_info("Register i2c driver failed %d(%s)", ret, cts_strerror(ret)); + return ret; + } +#else + cts_info(" - Register spi driver"); + ret = spi_register_driver(&cts_spi_driver); + if (ret) { + cts_info("Register spi driver failed %d(%s)", ret, cts_strerror(ret)); + return ret; + } +#endif + + cts_info(" - Register touch driver successfully"); + + return 0; +} + +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +static void cts_driver_exit(void) +#else +static void __exit cts_driver_exit(void) +#endif +{ + cts_info("Exit"); + +#ifdef CONFIG_CTS_I2C_HOST + cts_info(" - Delete i2c driver"); + i2c_del_driver(&cts_i2c_driver); +#else + cts_info(" - Delete spi driver"); + spi_unregister_driver(&cts_spi_driver); +#endif +} + +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +module_init(cts_driver_init); +module_exit(cts_driver_exit); + +#if KERNEL_VERSION(5, 4, 0) <= LINUX_VERSION_CODE +MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver); +#endif +MODULE_DESCRIPTION("Chipone TDDI touchscreen Driver for QualComm platform"); +MODULE_VERSION(CFG_CTS_DRIVER_VERSION); +MODULE_AUTHOR("Miao Defang "); +MODULE_LICENSE("GPL"); + +#else /* CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED */ + +static int chipone_tpd_local_init(void) +{ +#if (defined(TPD_WARP_START) && defined(TPD_WARP_END)) + static int tpd_wb_start_local[TPD_WARP_CNT] = TPD_WARP_START; + static int tpd_wb_end_local[TPD_WARP_CNT] = TPD_WARP_END; +#endif + +#if (defined(TPD_HAVE_CALIBRATION) && !defined(TPD_CUSTOM_CALIBRATION)) + static int tpd_def_calmat_local[8] = TPD_CALIBRATION_MATRIX; +#endif + + int ret; + + if ((ret = cts_driver_init()) != 0) { + cts_err("Init driver failed %d", ret); + return ret; + } + + if (tpd_load_status == 0) { + cts_err("Driver not load successfully, exit."); + cts_driver_exit(); + return -1; + } + +#if (defined(TPD_WARP_START) && defined(TPD_WARP_END)) + TPD_DO_WARP = 1; + memcpy(tpd_wb_start, tpd_wb_start_local, TPD_WARP_CNT * 4); + memcpy(tpd_wb_end, tpd_wb_start_local, TPD_WARP_CNT * 4); +#endif + +#if (defined(CONFIG_TPD_HAVE_CALIBRATION) && !defined(CONFIG_TPD_CUSTOM_CALIBRATION)) + memcpy(tpd_calmat, tpd_def_calmat_local, 8 * 4); + memcpy(tpd_def_calmat, tpd_def_calmat_local, 8 * 4); +#endif + + tpd_type_cap = 1; + + return 0; +} + +/* TPD pass dev = NULL */ +static void chipone_tpd_suspend(struct device *dev) +{ + cts_suspend(g_cts_data); +} + +/* TPD pass dev = NULL */ +static void chipone_tpd_resume(struct device *dev) +{ + cts_resume(g_cts_data); +} + +static struct tpd_driver_t chipone_tpd_driver = { + .tpd_device_name = CFG_CTS_DRIVER_NAME, + .tpd_local_init = chipone_tpd_local_init, + .suspend = chipone_tpd_suspend, + .resume = chipone_tpd_resume, + .tpd_have_button = 0, + .attrs = { + .attr = NULL, + .num = 0, + } +}; + +static int __init chipone_tpd_driver_init(void) +{ + int ret; + + cts_info("Chipone TDDI TPD driver %s", CFG_CTS_DRIVER_VERSION); + + tpd_get_dts_info(); + if (tpd_dts_data.touch_max_num < 2) { + tpd_dts_data.touch_max_num = 2; + } else if (tpd_dts_data.touch_max_num > CFG_CTS_MAX_TOUCH_NUM) { + tpd_dts_data.touch_max_num = CFG_CTS_MAX_TOUCH_NUM; + } + + if((ret = tpd_driver_add(&chipone_tpd_driver)) < 0) { + cts_err("Add TPD driver failed %d", ret); + return ret; + } + + return 0; +} + +static void __exit chipone_tpd_driver_exit(void) +{ + cts_info("Chipone TPD driver exit"); + + tpd_driver_remove(&chipone_tpd_driver); +} + +module_init(chipone_tpd_driver_init); +module_exit(chipone_tpd_driver_exit); + +#ifdef CFG_CTS_KERNEL_BUILTIN_FIRMWARE +MODULE_FIRMWARE(CFG_CTS_FIRMWARE_FILENAME_9916); +#endif /* CFG_CTS_KERNEL_BUILTIN_FIRMWARE */ + +MODULE_DESCRIPTION("Chipone TDDI TPD Driver for MTK platform"); +MODULE_VERSION(CFG_CTS_DRIVER_VERSION); +MODULE_AUTHOR("Miao Defang "); +MODULE_LICENSE("GPL"); + +#endif /* CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_earjack_detect.c b/drivers/input/touchscreen/chipone_tddi/cts_earjack_detect.c new file mode 100644 index 000000000000..393ed5a165e2 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_earjack_detect.c @@ -0,0 +1,649 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define LOG_TAG "Earjack" + +#include "cts_config.h" +#include "cts_platform.h" +#include "cts_core.h" +#include "cts_sysfs.h" +#include "cts_strerror.h" + +#ifdef CONFIG_CTS_EARJACK_DETECT +struct cts_earjack_detect_data { + bool enable; + bool running; + bool state; + + const char *earjack_state_filepath; + struct delayed_work poll_work; + u32 poll_interval; + +#ifdef CONFIG_CTS_SYSFS + bool sysfs_attr_group_created; +#endif /* CONFIG_CTS_SYSFS */ + + struct chipone_ts_data *cts_data; +}; + +/* Over-ride setting for DTS */ +//#define CFG_CTS_DEF_EARJACK_DET_ENABLE + +#define CFG_CTS_DEF_EARJACK_POLL_INTERVAL 2000u +#if defined(CONFIG_MTK_PLATFORM) +#define CFG_CTS_DEF_EARJACK_STATE_FILEPATH \ + "/sys/bus/platform/drivers/Accdet_Driver/state" +#elif defined(CONFIG_ARCH_SPREADRUM) +#define CFG_CTS_DEF_EARJACK_STATE_FILEPATH \ + "/sys/kernel/headset/state" +#else +/* Current QCOM NOT support earjack detect, please disable it or define the file path */ +#define CFG_CTS_DEF_EARJACK_STATE_FILEPATH "" +#endif + +static const char *earjack_state_str(bool state) +{ + return state ? "ATTACHED" : "DETACHED"; +} + +static int parse_earjack_detect_dt(struct cts_earjack_detect_data *ed_data, + struct device_node *np) +{ + const char *filepath; + int ret; + + cts_info("Parse dt"); + +#ifdef CFG_CTS_DEF_EARJACK_DET_ENABLE + ed_data->enable = true; +#else /* CFG_CTS_DEF_EARJACK_DET_ENABLE */ + ed_data->enable = + of_property_read_bool(np, "chipone,touch-earjack-detect-enable"); +#endif /* CFG_CTS_DEF_EARJACK_DET_ENABLE */ + + ret = of_property_read_string(np, + "chipone,touch-earjack-state-filepath", &filepath); + if (ret) { + cts_warn("Parse state filepath failed %d(%s)", ret, cts_strerror(ret)); + filepath = CFG_CTS_DEF_EARJACK_STATE_FILEPATH; + } + ed_data->earjack_state_filepath = kstrdup(filepath, GFP_KERNEL); + if (ed_data->earjack_state_filepath == NULL) { + cts_err("Dup earjack state filepath failed"); + return -ENOMEM; + } + + ed_data->poll_interval = CFG_CTS_DEF_EARJACK_POLL_INTERVAL; + ret = of_property_read_u32(np, + "chipone,touch-earjack-poll-interval", &ed_data->poll_interval); + if (ret) { + cts_warn("Parse poll interval failed %d(%s)", ret, cts_strerror(ret)); + } + + return 0; +} + +static int start_earjack_detect(struct cts_earjack_detect_data *ed_data) +{ + if (!ed_data->enable) { + cts_warn("Start detect while NOT enabled"); + return -EINVAL; + } + + if (ed_data->running) { + cts_warn("Start detect while already RUNNING"); + return 0; + } + + if (ed_data->earjack_state_filepath == NULL || + ed_data->earjack_state_filepath[0] == '\n') { + cts_warn("Start detect with filepath = NULL/NUL"); + return -EINVAL; + } + + cts_info("Start detect check file: '%s'", ed_data->earjack_state_filepath); + + if (!queue_delayed_work(ed_data->cts_data->workqueue, + &ed_data->poll_work, + msecs_to_jiffies(ed_data->poll_interval))) { + cts_warn("Queue detect work while already on the queue"); + } + + ed_data->running = true; + + return 0; +} + +static int stop_earjack_detect(struct cts_earjack_detect_data *ed_data) +{ + if (!ed_data->running) { + cts_warn("Stop detect while NOT running"); + return 0; + } + + cts_info("Stop detect"); + + if (!cancel_delayed_work_sync(&ed_data->poll_work)) { + cts_warn("Cancel poll work while NOT pending"); + } + ed_data->running = false; + + return 0; +} + +static int get_earjack_state(struct cts_earjack_detect_data *ed_data) +{ + int ret; + char buff[10]; + u32 state; +#ifndef CFG_CTS_FOR_GKI + struct file *file = NULL; + loff_t pos = 0; + int read_size; +#endif + + cts_dbg("Get state from file '%s'", ed_data->earjack_state_filepath); + +#ifdef CFG_CTS_FOR_GKI + cts_info("%s(): filp_open is forbiddon with GKI Version!", __func__); +#else + file = filp_open(ed_data->earjack_state_filepath, O_RDONLY, 0); + if (IS_ERR(file)) { + ret = (int)PTR_ERR(file); + cts_err("Open file '%s' failed %d(%s)", + ed_data->earjack_state_filepath, ret, cts_strerror(ret)); + return PTR_ERR(file); + } + + memset(buff, 0, sizeof(buff)); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) + read_size = kernel_read(file, buff, sizeof(buff), &pos); +#else + read_size = kernel_read(file, pos, buff, sizeof(buff)); +#endif + if (read_size < 0) { + cts_err("Read state file '%s' failed %d(%s)", + ed_data->earjack_state_filepath, + read_size, cts_strerror(read_size)); + filp_close(file, NULL); + return read_size; + } + + cts_dbg("Read state file content: '%s'", buff); + + ret = filp_close(file, NULL); + if (ret) { + cts_warn("Close file '%s' failed %d(%s)", + ed_data->earjack_state_filepath, ret, cts_strerror(ret)); + } +#endif + + ret = kstrtou32(buff, 0, &state); + if (ret) { + cts_err("Invalid string from state file: '%s'", buff); + return ret; + } + + /* Assume "0" means detached */ + ed_data->state = !!state; + + cts_dbg("State: %s", earjack_state_str(ed_data->state)); + + return 0; +} + +/* Sysfs */ +#ifdef CONFIG_CTS_SYSFS + +#define EARJACK_DET_SYSFS_GROUP_NAME "earjack-det" + +static int enable_earjack_detect(struct cts_earjack_detect_data *ed_data) +{ + cts_info("Enable detect"); + + ed_data->enable = true; + + return 0; +} + +static int disable_earjack_detect(struct cts_earjack_detect_data *ed_data) +{ + int ret; + + cts_info("Disable detect"); + + ret = stop_earjack_detect(ed_data); + if (ret) { + cts_err("Stop detect failed %d(%s)", ret, cts_strerror(ret)); + } + + ed_data->enable = false; + + return 0; +} + +static ssize_t earjack_detect_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_earjack_detect_data *ed_data = cts_data->earjack_detect_data; + + cts_info("Read sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s'", attr->attr.name); + + return scnprintf(buf, PAGE_SIZE, + "Earjack detect: %s\n", + ed_data->enable ? "ENABLED" : "DISABLED"); +} + +/* Echo 0/n/N/of/Of/Of/OF/1/y/Y/on/oN/On/ON > enable */ +static ssize_t earjack_detect_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_earjack_detect_data *ed_data = cts_data->earjack_detect_data; + bool enable; + int ret; + + cts_info("Write sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s' size %zu", + attr->attr.name, count); + + cts_parse_arg(buf, count); + + if (cts_argc != 1) { + cts_err("Invalid num of args"); + return -EINVAL; + } + + ret = kstrtobool(cts_argv[0], &enable); + if (ret) { + cts_err("Invalid param of enable"); + return ret; + } + + if (enable) { + ret = enable_earjack_detect(ed_data); + } else { + ret = disable_earjack_detect(ed_data); + } + if (ret) { + cts_err("%s earjack detect failed %d(%s)", + enable ? "Enable" : "Disable", ret, cts_strerror(ret)); + return ret; + } + + return count; +} +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + earjack_detect_enable_show, earjack_detect_enable_store); + +static ssize_t earjack_detect_running_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_earjack_detect_data *ed_data = cts_data->earjack_detect_data; + + cts_info("Read sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s'", + attr->attr.name); + + return scnprintf(buf, PAGE_SIZE, + "Earjack detect: %sRunning\n", + ed_data->running ? "" : "Not-"); +} + +/* Echo 0/n/N/of/Of/Of/OF/1/y/Y/on/oN/On/ON > runing */ +static ssize_t earjack_detect_running_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_earjack_detect_data *ed_data = cts_data->earjack_detect_data; + bool running; + int ret; + + cts_info("Write sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s' size %zu", + attr->attr.name, count); + + cts_parse_arg(buf, count); + + if (cts_argc != 1) { + cts_err("Invalid num of args"); + return -EINVAL; + } + + ret = kstrtobool(cts_argv[0], &running); + if (ret) { + cts_err("Invalid param of running"); + return ret; + } + + if (running) { + ret = start_earjack_detect(ed_data); + } else { + ret = stop_earjack_detect(ed_data); + } + if (ret) { + cts_err("%s earjack detect failed %d(%s)", + running ? "Start" : "Stop", ret, cts_strerror(ret)); + return ret; + } + + return count; +} +static DEVICE_ATTR(running, S_IWUSR | S_IRUGO, + earjack_detect_running_show, earjack_detect_running_store); + +static ssize_t earjack_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_earjack_detect_data *ed_data = cts_data->earjack_detect_data; + int ret; + + cts_info("Read sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s'", + attr->attr.name); + + ret = get_earjack_state(ed_data); + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Get earjack state failed %d(%s)\n", + ret, cts_strerror(ret)); + } + + return scnprintf(buf, PAGE_SIZE, + "Earjack state: %s\n", earjack_state_str(ed_data->state)); +} +static DEVICE_ATTR(state, S_IRUGO, earjack_state_show, NULL); + +static ssize_t earjack_detect_param_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_earjack_detect_data *ed_data = cts_data->earjack_detect_data; + + cts_info("Read sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s'", + attr->attr.name); + + return scnprintf(buf, PAGE_SIZE, + "Filepath: '%s'\n" + "Poll int: %dms\n", + ed_data->earjack_state_filepath, ed_data->poll_interval); +} + +/* echo filepath [interval] > param */ +static ssize_t earjack_detect_param_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_earjack_detect_data *ed_data = cts_data->earjack_detect_data; + u32 interval; + int ret; + + cts_info("Write sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s' size %zu", + attr->attr.name, count); + + cts_parse_arg(buf, count); + + if (cts_argc < 1 || cts_argc > 2) { + cts_err("Invalid num of args"); + return -EINVAL; + } + + interval = ed_data->poll_interval; + if (cts_argc > 1) { + ret = kstrtou32(cts_argv[1], 0, &interval); + if (ret) { + cts_err("Arg interval is invalid"); + return -EINVAL; + } + } + + if (ed_data->earjack_state_filepath) { + kfree(ed_data->earjack_state_filepath); + } + ed_data->earjack_state_filepath = kstrdup(cts_argv[0], GFP_KERNEL); + if (ed_data->earjack_state_filepath == NULL) { + cts_err("Dup earjack state filepath failed"); + return -ENOMEM; + } + ed_data->poll_interval = interval; + + return count; +} +static DEVICE_ATTR(param, S_IWUSR | S_IRUGO, + earjack_detect_param_show, earjack_detect_param_store); + +static struct attribute *earjack_detect_attrs[] = { + &dev_attr_enable.attr, + &dev_attr_running.attr, + &dev_attr_state.attr, + &dev_attr_param.attr, + NULL +}; + +static const struct attribute_group earjack_detect_attr_group = { + .name = EARJACK_DET_SYSFS_GROUP_NAME, + .attrs = earjack_detect_attrs, +}; +#endif /* CONFIG_CTS_SYSFS */ + +static void poll_earjack_state_work(struct work_struct *work) +{ + struct cts_earjack_detect_data *ed_data; + bool prev_state = false; + int ret; + + cts_dbg("Poll earjack state work"); + + ed_data = container_of(to_delayed_work(work), + struct cts_earjack_detect_data, poll_work); + + prev_state = ed_data->state; + + ret = get_earjack_state(ed_data); + if (ret) { + cts_err("Get state failed %d(%s)", ret, cts_strerror(ret)); + } else { + if (ed_data->state != prev_state) { + cts_info("State changed: %s -> %s", + earjack_state_str(prev_state), + earjack_state_str(ed_data->state)); + + cts_lock_device(&ed_data->cts_data->cts_dev); + ret = cts_set_dev_earjack_attached( + &ed_data->cts_data->cts_dev, ed_data->state); + cts_unlock_device(&ed_data->cts_data->cts_dev); + if (ret) { + cts_err("Set dev earjack attached to %s failed %d(%s)", + earjack_state_str(ed_data->state), + ret, cts_strerror(ret)); + /* Set to previous state, try set again in next loop */ + ed_data->state = prev_state; + } + } + } + + if (!queue_delayed_work(ed_data->cts_data->workqueue, + &ed_data->poll_work, + msecs_to_jiffies(ed_data->poll_interval))) { + cts_warn("Queue detect work while already on the queue"); + } +} + +int cts_earjack_detect_init(struct chipone_ts_data *cts_data) +{ + struct cts_earjack_detect_data *ed_data; + int ret = 0; + + if (cts_data == NULL) { + cts_err("Init detect with cts_data = NULL"); + return -EFAULT; + } + + ed_data = kzalloc(sizeof(*ed_data), GFP_KERNEL); + if (ed_data == NULL) { + cts_err("Alloc earjack detect data failed"); + return -ENOMEM; + } + + cts_info("Init detect"); + +#ifdef CONFIG_CTS_OF + ret = parse_earjack_detect_dt(ed_data, cts_data->device->of_node); +#else /* CONFIG_CTS_OF */ +#ifdef CFG_CTS_DEF_EARJACK_DET_ENABLE + ed_data->enable = true; +#else /* CFG_CTS_DEF_EARJACK_DET_ENABLE */ + ed_data->enable = false; +#endif /* CFG_CTS_DEF_EARJACK_DET_ENABLE */ + ed_data->poll_interval = CFG_CTS_DEF_EARJACK_POLL_INTERVAL; + ed_data->earjack_state_filepath = kstrdup( + CFG_CTS_DEF_EARJACK_STATE_FILEPATH, GFP_KERNEL); + if (ed_data->earjack_state_filepath == NULL) { + cts_err("Dup earjack state filepath failed"); + ret = -ENOMEM; + } +#endif /* CONFIG_CTS_OF */ + if (ret) { + cts_err("Get detect param failed %d(%s)", + ret, cts_strerror(ret)); + goto free_ed_data; + } + + cts_info("Detect: %sABLED", ed_data->enable ? "EN" : "DIS"); + cts_info(" Filepath: '%s'", ed_data->earjack_state_filepath); + cts_info(" Poll Int: %dms", ed_data->poll_interval); + + INIT_DELAYED_WORK(&ed_data->poll_work, poll_earjack_state_work); + +#ifdef CONFIG_CTS_SYSFS + cts_info("Create sysfs attr group '%s'", EARJACK_DET_SYSFS_GROUP_NAME); + ret = sysfs_create_group(&cts_data->device->kobj, + &earjack_detect_attr_group); + if (ret) { + cts_warn("Create sysfs attr group '%s' failed %d(%s)", + EARJACK_DET_SYSFS_GROUP_NAME, ret, cts_strerror(ret)); + } else { + ed_data->sysfs_attr_group_created = true; + } +#endif /* CONFIG_CTS_SYSFS */ + + cts_data->earjack_detect_data = ed_data; + ed_data->cts_data = cts_data; + + return 0; + +free_ed_data: + kfree(ed_data); + + return ret; +} + +int cts_earjack_detect_deinit(struct chipone_ts_data *cts_data) +{ + struct cts_earjack_detect_data *ed_data; + int ret; + + if (cts_data == NULL) { + cts_err("Deinit detect with cts_data = NULL"); + return -EFAULT; + } + + ed_data = cts_data->earjack_detect_data; + if (ed_data == NULL) { + cts_warn("Deinit detect with earjack_detect_data = NULL"); + return 0; + } + + cts_info("Deinit detect"); + + if (ed_data->running) { + ret = stop_earjack_detect(ed_data); + if (ret) { + cts_err("Stop detect failed %d(%s)", + ret, cts_strerror(ret)); + } + } + +#ifdef CONFIG_CTS_SYSFS + if (ed_data->sysfs_attr_group_created) { + cts_info("Remove sysfs attr group '%s'", EARJACK_DET_SYSFS_GROUP_NAME); + sysfs_remove_group(&cts_data->device->kobj, &earjack_detect_attr_group); + ed_data->sysfs_attr_group_created = false; + } +#endif /* CONFIG_CTS_SYSFS */ + + if(ed_data->earjack_state_filepath) { + kfree(ed_data->earjack_state_filepath); + ed_data->earjack_state_filepath = NULL; + } + + kfree(ed_data); + cts_data->earjack_detect_data = NULL; + + return 0; +} + +int cts_is_earjack_attached(struct chipone_ts_data *cts_data, bool *attached) +{ + struct cts_earjack_detect_data *ed_data; + int ret; + + if (cts_data == NULL) { + cts_err("Get state with cts_data = NULL"); + return -EFAULT; + } + + ed_data = cts_data->earjack_detect_data; + if (ed_data == NULL) { + cts_err("Get state with earjack_detect_data = NULL"); + return -ENODEV; + } + + ret = get_earjack_state(ed_data); + if (ret) { + cts_err("Get state failed %d(%s)", ret, cts_strerror(ret)); + return ret; + } + + cts_info("Get curr state: %s", earjack_state_str(ed_data->state)); + + *attached = ed_data->state; + + return 0; +} + +int cts_start_earjack_detect(struct chipone_ts_data *cts_data) +{ + struct cts_earjack_detect_data *ed_data; + + if (cts_data == NULL) { + cts_err("Start detect with cts_data = NULL"); + return -EFAULT; + } + + ed_data = cts_data->earjack_detect_data; + if (ed_data == NULL) { + cts_err("Start detect with earjack_detect_data = NULL"); + return -ENODEV; + } + + return start_earjack_detect(ed_data); +} + +int cts_stop_earjack_detect(struct chipone_ts_data *cts_data) +{ + struct cts_earjack_detect_data *ed_data; + + if (cts_data == NULL) { + cts_err("Stop detect with cts_data = NULL"); + return -EFAULT; + } + + ed_data = cts_data->earjack_detect_data; + if (ed_data == NULL) { + cts_err("Stop detect with earjack_detect_data = NULL"); + return -ENODEV; + } + + return stop_earjack_detect(ed_data); +} +#endif /* CONFIG_CTS_EARJACK_DETECT */ + diff --git a/drivers/input/touchscreen/chipone_tddi/cts_earjack_detect.h b/drivers/input/touchscreen/chipone_tddi/cts_earjack_detect.h new file mode 100644 index 000000000000..051a38b8acf9 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_earjack_detect.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CTS_EARJACK_DETECT_H +#define CTS_EARJACK_DETECT_H + +#include "cts_config.h" + +//struct chipone_ts_data; + +#ifdef CONFIG_CTS_EARJACK_DETECT +extern int cts_earjack_detect_init(struct chipone_ts_data *cts_data); +extern int cts_earjack_detect_deinit(struct chipone_ts_data *cts_data); +extern int cts_start_earjack_detect(struct chipone_ts_data *cts_data); +extern int cts_stop_earjack_detect(struct chipone_ts_data *cts_data); +extern int cts_is_earjack_attached(struct chipone_ts_data *cts_data, + bool *attached); +#else /* CONFIG_CTS_EARJACK_DETECT */ +static inline int cts_earjack_detect_init(struct chipone_ts_data *cts_data) +{ + return -ENOTSUPP; +} +static inline int cts_earjack_detect_deinit(struct chipone_ts_data *cts_data) +{ + return -ENOTSUPP; +} +static inline int cts_start_earjack_detect(struct chipone_ts_data *cts_data) +{ + return -ENODEV; +} +static inline int cts_stop_earjack_detect(struct chipone_ts_data *cts_data) +{ + return -ENODEV; +} +static inline int cts_is_earjack_attached(struct chipone_ts_data *cts_data, + bool *attached) +{ + return -ENODEV; +} +#endif /* CONFIG_CTS_EARJACK_DETECT */ + +#endif /* CTS_EARJACK_DETECT_H */ + diff --git a/drivers/input/touchscreen/chipone_tddi/cts_firmware.c b/drivers/input/touchscreen/chipone_tddi/cts_firmware.c new file mode 100644 index 000000000000..5d78aad5ff38 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_firmware.c @@ -0,0 +1,1044 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define LOG_TAG "Firmware" + +#include "cts_config.h" +#include "cts_platform.h" +#include "cts_core.h" +#include "cts_sfctrl.h" +#include "cts_spi_flash.h" +#include "cts_firmware.h" +#include "cts_strerror.h" +#include "cts_tcs.h" +#include +#include +#include + + +#ifdef CFG_CTS_DRIVER_BUILTIN_FIRMWARE +#include "cts_builtin_firmware.h" +#define NUM_DRIVER_BUILTIN_FIRMWARE ARRAY_SIZE(cts_driver_builtin_firmwares) +#endif /* CFG_CTS_DRIVER_BUILTIN_FIRMWARE */ + +#define CTS_FIRMWARE_MULTI_SECTION_FILE_SIZE (0x20000) +#define CTS_SECTION_ENABLE_FLAG (0x0000C35A) + +enum cts_firmware_section_offset { + CTS_FIRMWARE_SECTION_OFFSET = 0x00000000, + CTS_FIRMWARE_CRC_SECTION_OFFSET = 0x30000, + CTS_DDIPARAM_SECTION_OFFSET = 0x00031000, + CTS_DDIPARAM_CRC_SECTION_OFFSET = 0x33000, +}; + +struct cts_firmware_sect_info { + const u8 *firmware_sect; + size_t firmware_sect_size; + const u8 *firmware_crc_sect; + size_t firmware_crc_sect_size; + u32 firmware_sect_crc; + + const u8 *ddiparam_sect; + size_t ddiparam_sect_size; + const u8 *ddiparam_crc_sect; + size_t ddiparam_crc_sect_size; + u32 ddiparam_sect_crc; + +}; + +#define FIRMWARE_SECTION(firmware) \ + ((firmware)->data) +#define FIRMWARE_CRC_SECTION(firmware) \ + ((firmware)->data + CTS_FIRMWARE_CRC_SECTION_OFFSET) +#define DDIPARAM_SECTION(firmware) \ + ((firmware)->data + CTS_DDIPARAM_SECTION_OFFSET) +#define DDIPARAM_CRC_SECTION(firmware) \ + ((firmware)->data + CTS_DDIPARAM_CRC_SECTION_OFFSET) + +#define FIRMWARE_SECTION_CRC(firmware) \ + (get_unaligned_le32(FIRMWARE_CRC_SECTION(firmware))) +#define FIRMWARE_SECTION_SIZE(firmware) \ + (get_unaligned_le32(FIRMWARE_CRC_SECTION(firmware) + 4)) +#define FIRMWARE_SECTION_CRC_ENABLE(firmware) \ + (get_unaligned_le32(FIRMWARE_CRC_SECTION(firmware) + 8)) +#define FIRMWARE_CRC_SECTION_SIZE (20) + +#define DDIPARAM_SECTION_ENABLE(firmware) \ + (get_unaligned_le32(DDIPARAM_CRC_SECTION(firmware))) +#define DDIPARAM_SECTION_CRC_ENABLE(firmware) \ + (get_unaligned_le32(DDIPARAM_CRC_SECTION(firmware) + 4)) +#define DDIPARAM_SECTION_CRC(firmware) \ + (get_unaligned_le32(DDIPARAM_CRC_SECTION(firmware) + 8)) +#define DDIPARAM_SECTION_SIZE(firmware) \ + (get_unaligned_le32(DDIPARAM_CRC_SECTION(firmware) + 12)) +#define DDIPARAM_CRC_SECTION_SIZE (17) + +u16 cts_crc16(const u8 *data, size_t len) +{ + u16 crc = 0; + + const static u16 crc16_table[] = { + 0x0000, 0x8005, 0x800F, 0x000A, 0x801B, 0x001E, 0x0014, 0x8011, + 0x8033, 0x0036, 0x003C, 0x8039, 0x0028, 0x802D, 0x8027, 0x0022, + 0x8063, 0x0066, 0x006C, 0x8069, 0x0078, 0x807D, 0x8077, 0x0072, + 0x0050, 0x8055, 0x805F, 0x005A, 0x804B, 0x004E, 0x0044, 0x8041, + 0x80C3, 0x00C6, 0x00CC, 0x80C9, 0x00D8, 0x80DD, 0x80D7, 0x00D2, + 0x00F0, 0x80F5, 0x80FF, 0x00FA, 0x80EB, 0x00EE, 0x00E4, 0x80E1, + 0x00A0, 0x80A5, 0x80AF, 0x00AA, 0x80BB, 0x00BE, 0x00B4, 0x80B1, + 0x8093, 0x0096, 0x009C, 0x8099, 0x0088, 0x808D, 0x8087, 0x0082, + 0x8183, 0x0186, 0x018C, 0x8189, 0x0198, 0x819D, 0x8197, 0x0192, + 0x01B0, 0x81B5, 0x81BF, 0x01BA, 0x81AB, 0x01AE, 0x01A4, 0x81A1, + 0x01E0, 0x81E5, 0x81EF, 0x01EA, 0x81FB, 0x01FE, 0x01F4, 0x81F1, + 0x81D3, 0x01D6, 0x01DC, 0x81D9, 0x01C8, 0x81CD, 0x81C7, 0x01C2, + 0x0140, 0x8145, 0x814F, 0x014A, 0x815B, 0x015E, 0x0154, 0x8151, + 0x8173, 0x0176, 0x017C, 0x8179, 0x0168, 0x816D, 0x8167, 0x0162, + 0x8123, 0x0126, 0x012C, 0x8129, 0x0138, 0x813D, 0x8137, 0x0132, + 0x0110, 0x8115, 0x811F, 0x011A, 0x810B, 0x010E, 0x0104, 0x8101, + 0x8303, 0x0306, 0x030C, 0x8309, 0x0318, 0x831D, 0x8317, 0x0312, + 0x0330, 0x8335, 0x833F, 0x033A, 0x832B, 0x032E, 0x0324, 0x8321, + 0x0360, 0x8365, 0x836F, 0x036A, 0x837B, 0x037E, 0x0374, 0x8371, + 0x8353, 0x0356, 0x035C, 0x8359, 0x0348, 0x834D, 0x8347, 0x0342, + 0x03C0, 0x83C5, 0x83CF, 0x03CA, 0x83DB, 0x03DE, 0x03D4, 0x83D1, + 0x83F3, 0x03F6, 0x03FC, 0x83F9, 0x03E8, 0x83ED, 0x83E7, 0x03E2, + 0x83A3, 0x03A6, 0x03AC, 0x83A9, 0x03B8, 0x83BD, 0x83B7, 0x03B2, + 0x0390, 0x8395, 0x839F, 0x039A, 0x838B, 0x038E, 0x0384, 0x8381, + 0x0280, 0x8285, 0x828F, 0x028A, 0x829B, 0x029E, 0x0294, 0x8291, + 0x82B3, 0x02B6, 0x02BC, 0x82B9, 0x02A8, 0x82AD, 0x82A7, 0x02A2, + 0x82E3, 0x02E6, 0x02EC, 0x82E9, 0x02F8, 0x82FD, 0x82F7, 0x02F2, + 0x02D0, 0x82D5, 0x82DF, 0x02DA, 0x82CB, 0x02CE, 0x02C4, 0x82C1, + 0x8243, 0x0246, 0x024C, 0x8249, 0x0258, 0x825D, 0x8257, 0x0252, + 0x0270, 0x8275, 0x827F, 0x027A, 0x826B, 0x026E, 0x0264, 0x8261, + 0x0220, 0x8225, 0x822F, 0x022A, 0x823B, 0x023E, 0x0234, 0x8231, + 0x8213, 0x0216, 0x021C, 0x8219, 0x0208, 0x820D, 0x8207, 0x0202, + }; + + while (len) { + crc = (crc << 8) ^ crc16_table[((crc >> 8) ^ *data) & 0xFF]; + data++; + len--; + } + + return crc; +} + +u32 cts_crc32(const u8 *data, size_t len) +{ + const static u32 crc32_table[] = { + 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, + 0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, + 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, 0x4C11DB70, 0x48D0C6C7, + 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75, + 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, + 0x709F7B7A, 0x745E66CD, 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, + 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 0xBE2B5B58, 0xBAEA46EF, + 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D, + 0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB, + 0xCEB42022, 0xCA753D95, 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, + 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 0x34867077, 0x30476DC0, + 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072, + 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4, + 0x0808D07D, 0x0CC9CDCA, 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE, + 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08, + 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, + 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, 0xBB60ADFC, + 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6, + 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A, 0xE0B41DE7, 0xE4750050, + 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, + 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, + 0xDC3ABDED, 0xD8FBA05A, 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637, + 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, 0x4F040D56, 0x4BC510E1, + 0x46863638, 0x42472B8F, 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53, + 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, + 0x3F9B762C, 0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, + 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 0xF12F560E, 0xF5EE4BB9, + 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B, + 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, + 0xCDA1F604, 0xC960EBB3, 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, + 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71, + 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3, + 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2, + 0x470CDD2B, 0x43CDC09C, 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8, + 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, 0x119B4BE9, 0x155A565E, + 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, + 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, 0x2056CD3A, + 0x2D15EBE3, 0x29D4F654, 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0, + 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C, 0xE3A1CBC1, 0xE760D676, + 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, + 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, + 0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668, + 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4 + }; + + u32 crc = 0; + + while (len) { + crc = (crc << 8) ^ crc32_table[((crc >> 24) ^ *data) & 0xFF]; + + data++; + len--; + } + + return crc; +} + +static bool is_multi_section_firmware(const struct cts_firmware *firmware) +{ + return (firmware->size == CTS_FIRMWARE_MULTI_SECTION_FILE_SIZE); +} + +static bool is_single_section_firmware(const struct cts_firmware *firmware) +{ + return !(is_multi_section_firmware(firmware)); +} + +static bool is_firmware_size_valid(const struct cts_firmware *firmware) +{ + return (firmware->size > 0x102 && + firmware->size <= CTS_FIRMWARE_MULTI_SECTION_FILE_SIZE); +} + +static bool is_multi_section_firmware_valid(const struct cts_firmware *firmware) +{ + u32 crc; + + crc = cts_crc32(FIRMWARE_SECTION(firmware), FIRMWARE_SECTION_SIZE(firmware)); + if (crc != FIRMWARE_SECTION_CRC(firmware)) { + cts_err("Firmware-section crc mismatch crc-section %08x != %08x" + ", File maybe broken!!!", crc, + FIRMWARE_SECTION_CRC(firmware)); + return false; + } + + if (DDIPARAM_SECTION_ENABLE(firmware) == CTS_SECTION_ENABLE_FLAG) { + crc = cts_crc32(DDIPARAM_SECTION(firmware), + DDIPARAM_SECTION_SIZE(firmware)); + if (crc != DDIPARAM_SECTION_CRC(firmware)) { + cts_err("DDIParam-section crc mismatch crc-section %08x != %08x" + ", File maybe broken!!!", crc, + DDIPARAM_SECTION_CRC(firmware)); + return false; + } + } else + cts_info("DDIParam-section is NOT enabled"); + + return true; +} + +static bool is_firmware_valid(const struct cts_firmware *firmware) +{ + if (firmware && firmware->data && is_firmware_size_valid(firmware)) { + if (is_single_section_firmware(firmware) + || is_multi_section_firmware_valid(firmware)) + return true; + } + + return false; +} + +static void parse_single_section_firmware(const struct cts_firmware *firmware, + struct cts_firmware_sect_info *info) +{ + static u8 crc_sect[20] = {0xff}; + + info->firmware_sect = firmware->data; + info->firmware_sect_size = firmware->size; + info->firmware_sect_crc = cts_crc32(firmware->data, firmware->size); + + put_unaligned_le32(info->firmware_sect_crc, crc_sect); + put_unaligned_le32(info->firmware_sect_size, crc_sect + 4); + put_unaligned_le32(~0x0000C35A, crc_sect + 8); /* Enable CRC check */ + put_unaligned_le32(0x7473756E, crc_sect + 16); + info->firmware_crc_sect = crc_sect; + info->firmware_crc_sect_size = sizeof(crc_sect); +} + +static void parse_multi_section_firmware(const struct cts_firmware *firmware, + struct cts_firmware_sect_info *info) +{ + info->firmware_sect = FIRMWARE_SECTION(firmware); + info->firmware_sect_size = FIRMWARE_SECTION_SIZE(firmware); + info->firmware_crc_sect = FIRMWARE_CRC_SECTION(firmware); + info->firmware_crc_sect_size = FIRMWARE_CRC_SECTION_SIZE; + info->firmware_sect_crc = FIRMWARE_SECTION_CRC(firmware); + + if (DDIPARAM_SECTION_ENABLE(firmware) == CTS_SECTION_ENABLE_FLAG) { + info->ddiparam_sect = DDIPARAM_SECTION(firmware); + info->ddiparam_sect_size = DDIPARAM_SECTION_SIZE(firmware); + info->ddiparam_crc_sect = DDIPARAM_CRC_SECTION(firmware); + info->ddiparam_crc_sect_size = DDIPARAM_CRC_SECTION_SIZE; + info->ddiparam_sect_crc = DDIPARAM_SECTION_CRC(firmware); + } +} + +static int parse_firmware(const struct cts_firmware *firmware, + struct cts_firmware_sect_info *info) +{ + memset(info, 0, sizeof(*info)); + + if (is_multi_section_firmware(firmware)) + parse_multi_section_firmware(firmware, info); + else + parse_single_section_firmware(firmware, info); + + cts_info(" Firmware section size: %zu", info->firmware_sect_size); + if (info->ddiparam_crc_sect) + cts_info(" DDIParam section size: %zu", info->ddiparam_sect_size); + + return 0; +} + +#ifdef CFG_CTS_DRIVER_BUILTIN_FIRMWARE +static int cts_request_newer_driver_builtin_firmware(struct cts_device *cts_dev, + u32 hwid, u16 fwid, u16 device_fw_ver) +{ +#define MATCH_HWID(firmware, hwid) \ + ((hwid) == CTS_DEV_HWID_ANY || (firmware)->hwid == (hwid)) +#define MATCH_FWID(firmware, fwid) \ + ((fwid) == CTS_DEV_FWID_ANY || (firmware)->fwid == (fwid)) + + struct cts_firmware *firmware = NULL; + int i; + + cts_info("Request driver builtin if match hwid: %06x fwid: %04x && ver > %04x", + hwid, fwid, device_fw_ver); + + firmware = cts_driver_builtin_firmwares; + for (i = 0; i < ARRAY_SIZE(cts_driver_builtin_firmwares); i++, firmware++) { + if (MATCH_HWID(firmware, hwid) && MATCH_FWID(firmware, fwid)) { + if (!is_firmware_valid(firmware)) { + cts_err("Found driver builtin '%s' " + "hwid: %06x fwid: %04x data: %p size: %zu INVALID", + firmware->name, firmware->hwid, + firmware->fwid, firmware->data, + firmware->size); + continue; + } + + if (FIRMWARE_VERSION(firmware) > device_fw_ver) { + cts_info("Found newer driver builtin '%s' " + "hwid: %06x fwid: %04x size: %zu ver: %04x > %04x", + firmware->name, firmware->hwid, + firmware->fwid, firmware->size, + FIRMWARE_VERSION(firmware), + device_fw_ver); + memcpy(cts_dev->firmware->data, firmware->data, firmware->size); + cts_dev->firmware->size = firmware->size; + return 0; + } + } + } + + cts_info("No newer driver builtin found"); + + return -EINVAL; + +#undef MATCH_HWID +#undef MATCH_FWID +} +#endif /* CFG_CTS_DRIVER_BUILTIN_FIRMWARE */ + +// #ifdef CFG_CTS_FIRMWARE_IN_FS +// bool is_filesystem_mounted(const char *filepath) +// { +// #ifdef CFG_CTS_FOR_GKI +// cts_info("%s(): some functions are forbiddon with GKI Version!", __func__); +// return false; +// #else +// struct path root_path; +// struct path path; +// int ret; + +// ret = kern_path("/", LOOKUP_FOLLOW, &root_path); +// if (ret) +// return false; + +// ret = kern_path(filepath, LOOKUP_FOLLOW, &path); +// if (ret) +// goto err_put_root_path; + +// if (path.mnt->mnt_sb == root_path.mnt->mnt_sb) +// /* not mounted */ +// ret = false; +// else +// ret = true; + +// path_put(&path); +// err_put_root_path: +// path_put(&root_path); + +// return !!ret; +// #endif +// } + +#ifndef CFG_CTS_FOR_GKI +static int cts_request_firmware_full_filepath(struct cts_firmware *firmware, + const char *filepath, int curr_version) +{ + struct file *file; + u16 version; + u8 buff[2]; + loff_t pos = 0; + int read_size; + int ret = 0; + + file = filp_open(filepath, O_RDONLY, 0); + if (IS_ERR(file)) { + cts_err("Open file '%s' failed %ld", filepath, PTR_ERR(file)); + return PTR_ERR(file); + } + + firmware->size = file_inode(file)->i_size; + if (!is_firmware_size_valid(firmware)) { + cts_info("File '%s' size: %zu invalid", filepath, firmware->size); + ret = -EINVAL; + goto err_close_file; + } + + pos = FIRMWARE_VERSION_OFFSET; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) + read_size = kernel_read(file, buff, 2, &pos); +#else + read_size = kernel_read(file, pos, buff, 2); +#endif + if (read_size < 0) { + cts_err("Read version from offset 0x100 failed"); + ret = -EIO; + goto err_close_file; + } + version = get_unaligned_le16(buff); + + if (version <= curr_version) { + cts_info("File '%s' size: %zu version: %04x <= %04x", + filepath, firmware->size, version, curr_version); + cts_info("Do not update"); + ret = -EINVAL; + goto err_close_file; + } + + cts_info("File '%s' size: %zu version: %04x", + filepath, firmware->size, version); + + pos = 0; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) + read_size = kernel_read(file, firmware->data, firmware->size, &pos); +#else + read_size = kernel_read(file, pos, firmware->data, firmware->size); +#endif + if (read_size < 0 || read_size != firmware->size) { + cts_err("Request from fs read whole file failed %d", read_size); + ret = -EIO; + firmware = NULL; + goto err_close_file; + } + + filp_close(file, NULL); + + return 0; + +err_close_file: + filp_close(file, NULL); + return ret; +} +#endif + +#ifdef CFG_CTS_FIRMWARE_IN_FS +static int cts_wrap_request_firmware(struct cts_firmware *firmware, + const char *name, struct device *device, int curr_version) +{ + struct cts_firmware *local = NULL; + int ret; + + local = kzalloc(sizeof(struct cts_firmware), GFP_KERNEL); + if (local == NULL) { + cts_err("Alloc local buffer failed"); + return -ENOMEM; + } + + ret = request_firmware(&local->fw, name, device); + if (ret) { + cts_err("Could not load firmware from %s: %d", name, ret); + return ret; + } + + local->data = (u8 *) local->fw->data; + local->size = local->fw->size; + + if (!is_firmware_valid(local)) { + cts_err("Request firmware file INVALID, size %zu", local->size); + ret = -EINVAL; + goto err_release_firmware; + } + + if (curr_version > 0 && curr_version >= FIRMWARE_VERSION(local)) { + cts_info("Request firmware file version 0x%04x <= 0x%04x curr", + FIRMWARE_VERSION(local), curr_version); + ret = -EINVAL; + goto err_release_firmware; + } + + if (firmware == NULL) { + cts_err("firmware was null"); + ret = -EINVAL; + } else { + memcpy(firmware->data, (u8 *) local->fw->data, local->size); + firmware->size = local->size; + cts_info("Update firmware buffer successfully"); + } + +err_release_firmware: + release_firmware(local->fw); + local->data = NULL; + kfree(local); + return ret; +} + +int cts_request_newer_firmware_from_fs(struct cts_device *cts_dev, + const char *filepath, u16 curr_version) +{ + int ret = 0; + + cts_info("Request from file '%s' if version > %04x", filepath, curr_version); + + if (filepath == NULL || filepath[0] == '\0') { + cts_err("Request from file path INVALID %p", filepath); + return -EINVAL; + } + + if (strchr(filepath, '/') != NULL) { + cts_info("Filepath is fullpath, direct read it out"); +#ifdef CFG_CTS_FOR_GKI + cts_err("Google GKI unsupport kernel_read, please use request_firmware"); + ret = -EPERM; +#else + /* Sometimes vendor/firmware path was not have permission, then use sdcard */ + ret = cts_request_firmware_full_filepath(cts_dev->firmware, filepath, + curr_version); +#endif + } else { + ret = cts_wrap_request_firmware(cts_dev->firmware, filepath, + &cts_dev->pdata->ts_input_dev->dev, curr_version); + } + + return ret; +} + +int cts_update_firmware_from_file(struct cts_device *cts_dev, + const char *filepath) +{ + int ret; + + cts_info("Update from file '%s'", filepath); + + cts_reset_device(cts_dev); + + ret = cts_request_newer_firmware_from_fs(cts_dev, filepath, 0); + if (ret) { + cts_err("Request from file '%s' failed", filepath); + return -EFAULT; + } + + ret = cts_update_firmware(cts_dev); + if (ret) { + cts_err("Update from file failed %d", ret); + return ret; + } + + cts_info("Update from file success"); + + return ret; +} +#endif /*CFG_CTS_FIRMWARE_IN_FS */ + +int cts_request_firmware(struct cts_device *cts_dev, + u32 hwid, u16 fwid, u16 curr_firmware_ver) +{ + int builtin = -1, bin = -1; + + if (hwid == CTS_DEV_HWID_INVALID) + hwid = CTS_DEV_HWID_ANY; + if (fwid == CTS_DEV_FWID_INVALID) + fwid = CTS_DEV_FWID_ANY; + + cts_info("Request newer if match hwid: %06x fwid: %04x && ver > %04x", + hwid, fwid, curr_firmware_ver); + +#ifdef CFG_CTS_DRIVER_BUILTIN_FIRMWARE + builtin = cts_request_newer_driver_builtin_firmware(cts_dev, + hwid, fwid, curr_firmware_ver); + if (builtin == 0) { + curr_firmware_ver = FIRMWARE_VERSION(cts_dev->firmware); + } +#endif + +#ifdef CFG_CTS_FIRMWARE_IN_FS + bin = cts_request_newer_firmware_from_fs(cts_dev, + CFG_CTS_FIRMWARE_FILENAME, curr_firmware_ver); + if (bin) { + cts_err("Request firmware from file failed"); + } +#endif + + if (bin == 0 || builtin == 0) + return 0; + else + return -1; +} + +#ifdef CONFIG_CTS_I2C_HOST +static int calc_crc_in_flash(struct cts_device *cts_dev, + u32 flash_addr, size_t size, u32 *crc) +{ + return cts_dev->hwdata->sfctrl->ops->calc_flash_crc(cts_dev, + flash_addr, size, crc); +} + +static int validate_flash_data(struct cts_device *cts_dev, u32 flash_addr, + const u8 *data, size_t size, u8 *buf, bool calc_crc, u32 crc) +{ + int ret, i; + bool free_data = false; + u32 crc_flash; + + cts_info("Validate flash data from 0x%06x size %zu by %s", + flash_addr, size, calc_crc ? "check-crc" : "direct-readback"); + + if (calc_crc) { + ret = calc_crc_in_flash(cts_dev, flash_addr, size, &crc_flash); + if (ret) { + cts_err("Calc data in flash from 0x%06x size %zu crc failed %d, " + "try to validate by direct readback", flash_addr, size, ret); + /* FALL through by direct compare data read back */ + } else { + if (crc_flash != crc) { + cts_err("Crc in flash from 0x%06x size %zu mismatch 0x%08x != 0x%08x", + flash_addr, size, crc_flash, crc); + /* FALL through by direct compare data read back */ + } else { + cts_info("Flash data crc correct"); + return 0; + } + } + } + + if (buf == NULL) { + buf = (u8 *) kmalloc(size, GFP_KERNEL); + if (buf == NULL) { + cts_err("Validate flash data allocate mem failed"); + return -ENOMEM; + } + + free_data = true; + } + + ret = cts_read_flash(cts_dev, flash_addr, buf, size); + if (ret) { + cts_err("Read flash from 0x%06x size %zu failed %d", + flash_addr, size, ret); + goto err_free_buf; + } + + for (i = 0; i < size; i++) { + if (buf[i] != data[i]) { + if (ret == 0) { + cts_err("Flash data from 0x%06x size %zu first bytes diff:", + flash_addr, size); + } + + if (ret < 100) { + cts_err(" 0x%06x: %02x %02x", i, buf[i], data[i]); + } else if (ret == 100) { + cts_err(" ..."); + } + ret++; + } + } + +err_free_buf: + if (free_data) + kfree(buf); + + return ret; +} + +static int cts_program_firmware_from_sram_to_flash(struct cts_device *cts_dev, + const struct cts_firmware_sect_info *firmware_info) +{ + int ret; + u8 crc_sect_buf[FIRMWARE_CRC_SECTION_SIZE]; + + cts_info("Program firmware size %zu", + firmware_info->firmware_sect_size); + + ret = cts_program_flash_from_sram(cts_dev, 4, 4, + firmware_info->firmware_sect_size - 4); + if (ret) { + cts_err("Program firmware section from sram failed %d", ret); + return ret; + } + + ret = cts_program_flash(cts_dev, CTS_FIRMWARE_CRC_SECTION_OFFSET, + firmware_info->firmware_crc_sect, + firmware_info->firmware_crc_sect_size); + if (ret) { + cts_err("Program firmware crc section failed %d", ret); + return ret; + } + + ret = validate_flash_data(cts_dev, CTS_FIRMWARE_CRC_SECTION_OFFSET, + firmware_info->firmware_crc_sect, + firmware_info->firmware_crc_sect_size, + crc_sect_buf, false, 0); + if (ret) { + cts_err("Validate Firmware-CRC section failed %d", ret); + return ret; + } + + ret = cts_program_flash(cts_dev, 0, firmware_info->firmware_sect, 4); + if (ret) { + cts_err("Program firmware section fist 4bytes failed %d", ret); + return ret; + } + + ret = validate_flash_data(cts_dev, CTS_FIRMWARE_SECTION_OFFSET, + firmware_info->firmware_sect, + firmware_info->firmware_sect_size, NULL, true, + firmware_info->firmware_sect_crc); + if (ret) { + cts_err("Validate firmware section failed %d", ret); + return ret; + } + + return 0; +} + +static int cts_program_ddiparam(struct cts_device *cts_dev, + const struct cts_firmware_sect_info *firmware_info) +{ + int ret; + u8 crc_sect_buf[DDIPARAM_CRC_SECTION_SIZE]; + + cts_info("Program DDIParam size: %zu", firmware_info->ddiparam_sect_size); + + ret = cts_program_flash(cts_dev, CTS_DDIPARAM_SECTION_OFFSET, + firmware_info->ddiparam_sect, firmware_info->ddiparam_sect_size); + if (ret) { + cts_err("Program DDIParam section failed %d", ret); + return ret; + } + + ret = validate_flash_data(cts_dev, CTS_DDIPARAM_SECTION_OFFSET, + firmware_info->ddiparam_sect, + firmware_info->ddiparam_sect_size, NULL, true, + firmware_info->ddiparam_sect_crc); + if (ret) { + cts_err("Validate DDIParam section failed %d", ret); + return ret; + } + + ret = cts_program_flash(cts_dev, CTS_DDIPARAM_CRC_SECTION_OFFSET, + firmware_info->ddiparam_crc_sect, + firmware_info->ddiparam_crc_sect_size); + if (ret) { + cts_err("Program DDIParam-CRC section failed %d", ret); + return ret; + } + + ret = validate_flash_data(cts_dev, CTS_DDIPARAM_CRC_SECTION_OFFSET, + firmware_info->ddiparam_crc_sect, + firmware_info->ddiparam_crc_sect_size, + crc_sect_buf, false, 0); + if (ret) { + cts_err("Validate DDIParam-CRC section failed %d", ret); + return ret; + } + + return 0; +} + +static int cts_update_firmware_to_flash(struct cts_device *cts_dev, + struct cts_firmware_sect_info firmware_info) +{ + int retries; + int ret; + + ret = cts_prepare_flash_operation(cts_dev); + if (ret) { + cts_warn("Prepare flash operation failed %d", ret); + /* Go through and try */ + } + + if (!cts_dev->rtdata.has_flash) { + cts_err("Update firmware to flash is UNKNOWN/NON-EXIST"); + ret = -ENODEV; + goto post_flash_operation; + } + + retries = 0; + do { + retries++; + + ret = cts_erase_flash(cts_dev, CTS_FIRMWARE_CRC_SECTION_OFFSET, + firmware_info.firmware_crc_sect_size); + if (ret) { + cts_err("Erase firmware crc section failed %d retries %d", + ret, retries); + continue; + } + + ret = cts_erase_flash(cts_dev, 0, firmware_info.firmware_sect_size); + if (ret) { + cts_err("Erase firmware section failed %d retries %d", + ret, retries); + continue; + } + + ret = cts_program_firmware_from_sram_to_flash(cts_dev, &firmware_info); + if (ret) { + cts_err("Program firmware & crc section failed %d retries %d", + ret, retries); + } + } while (ret && retries < 3); + + if (ret == 0 && firmware_info.ddiparam_sect_size != 0) { + retries = 0; + do { + retries++; + + ret = cts_erase_flash(cts_dev, CTS_DDIPARAM_CRC_SECTION_OFFSET, + firmware_info.ddiparam_crc_sect_size); + if (ret) { + cts_err("Erase DDIParam crc secction failed %d, retries %d", + ret, retries); + continue; + } + + ret = cts_erase_flash(cts_dev, CTS_DDIPARAM_SECTION_OFFSET, + firmware_info.ddiparam_sect_size); + if (ret) { + cts_err("Erase DDIParam section failed %d, retries %d", + ret, retries); + continue; + } + + ret = cts_program_ddiparam(cts_dev, &firmware_info); + if (ret) { + cts_err("Program DDIParam & crc section failed %d retries %d", + ret, retries); + } + } while (ret && retries < 3); + } + +post_flash_operation: + cts_post_flash_operation(cts_dev); + return ret; +} +#else /* CONFIG_CTS_I2C_HOST */ +#ifdef CFG_CTS_CASCADE_SUPPORTED +static int cts_switch_to_ic(struct cts_device *cts_dev, uint8_t type) +{ + uint8_t dat = 0x83; + int count = 0; + int retries = 10; + int rc; + + if (type == 1) { + dat = 0x81; + } else if (type == 2) { + dat = 0x82; + } else if (type == 3) { + dat = 0x83; + } + + while (retries--) { + rc = cts_spi_send_recv(cts_dev->pdata, sizeof(dat), &dat, NULL); + if (rc) + cts_err("Send %d to ic failed: rc=%d", dat, rc); + else { + count++; + if (count == 2) + return 0; + } + } + + return -EIO; +} + +int cts_switch_to_master(struct cts_device *cts_dev) +{ + return cts_switch_to_ic(cts_dev, 1); +} + +int cts_switch_to_slave(struct cts_device *cts_dev) +{ + return cts_switch_to_ic(cts_dev, 2); +} + +int cts_switch_to_all(struct cts_device *cts_dev) +{ + return cts_switch_to_ic(cts_dev, 3); +} +#endif /* CFG_CTS_CASCADE_SUPPORTED */ +#endif /* CONFIG_CTS_I2C_HOST */ + +#if defined(CFG_CTS_CASCADE_SUPPORTED) && !defined(CONFIG_CTS_I2C_HOST) +static int cts_slave_enter_prog_mode(struct cts_device *cts_dev) +{ + u8 magic_num[] = { 0xCC, 0x33, 0x55, 0x5A }; + int ret; + +#ifdef CONFIG_CTS_I2C_HOST + ret = cts_plat_i2c_write(cts_dev->pdata, + CTS_DEV_PROGRAM_MODE_I2CADDR, (const u8 *)magic_num, 4, 5, 10); + if (ret) { + cts_err("Write slave magic number to i2c_dev: 0x%02x failed %d", + CTS_DEV_PROGRAM_MODE_I2CADDR, ret); + } +#else + ret = cts_spi_send_recv(cts_dev->pdata, sizeof(magic_num), magic_num, NULL); + if (ret) { + cts_err("Write slave magic number to spi_dev: 0x%02x failed %d", + CTS_DEV_PROGRAM_MODE_SPIADDR, ret); + } +#endif + + return ret; +} +#endif + +static int cts_prepare_update_firmware(struct cts_device *cts_dev, + struct cts_firmware_sect_info firmware_info) +{ + int ret; + +#if defined(CFG_CTS_CASCADE_SUPPORTED) && !defined(CONFIG_CTS_I2C_HOST) + ret = cts_switch_to_master(cts_dev); + if (ret) { + cts_err("Switch to master faild %d", ret); + return ret; + } + + ret = cts_enter_program_mode(cts_dev); + if (ret) { + cts_err("Device enter program mode failed %d", ret); + return ret; + } + + ret = cts_switch_to_slave(cts_dev); + if (ret) { + cts_err("Switch to slave field %d", ret); + return ret; + } + + ret = cts_hw_reg_writeb_retry(cts_dev, CTS_DEV_HW_REG_SPI_CFG, 0x12, 5, 0); + if (ret) { + cts_err("Disable Slave Spi Tx forward failed %d", ret); + return ret; + } + + ret = cts_slave_enter_prog_mode(cts_dev); + if (ret) { + cts_err("Slave device enter program mode failed %d", ret); + return ret; + } + + ret = cts_switch_to_all(cts_dev); + if (ret) { + cts_err("Switch to all failed %d", ret); + return ret; + } +#else + ret = cts_enter_program_mode(cts_dev); + if (ret) { + cts_err("Device enter program mode failed %d", ret); + return ret; + } +#endif + return ret; +} + +int cts_update_firmware(struct cts_device *cts_dev) +{ + struct cts_firmware_sect_info firmware_info; + struct cts_firmware *firmware = cts_dev->firmware; + int ret; + + cts_info("Update firmware ver: %04x size: %zu", + FIRMWARE_VERSION(firmware), firmware->size); + + if (parse_firmware(firmware, &firmware_info)) { + cts_err("Parse firmware failed"); + return -EINVAL; + } + + cts_dev->rtdata.updating = true; + + ret = cts_prepare_update_firmware(cts_dev, firmware_info); + if (ret) { + cts_err("Prepare update firmware failed, ret=%d", ret); + goto out; + } + + cts_info("Write firmware section to sram size %zu", + firmware_info.firmware_sect_size); + ret = cts_sram_writesb_check_crc_retry(cts_dev, 0, + firmware_info.firmware_sect, + firmware_info.firmware_sect_size, + firmware_info.firmware_sect_crc, 10); + if (ret) { + cts_err("Write firmware section to sram failed %d", ret); + } + +#ifdef CFG_CTS_UPDATE_CRCCHECK + cts_sram_writesb_boot_crc_retry(cts_dev, + firmware_info.firmware_sect_size, + firmware_info.firmware_sect_crc, 3); +#endif + +#ifdef CONFIG_CTS_I2C_HOST + ret = cts_update_firmware_to_flash(cts_dev, firmware_info); + if (ret) { + cts_err("Update firmware to flash failed"); + } +#endif + +out: + cts_dev->rtdata.updating = false; + + if (ret == 0) { + if (firmware_info.firmware_sect_size <= + cts_dev->hwdata->sfctrl->xchg_sram_base) { + ret = cts_enter_normal_mode(cts_dev); + if (ret) { + cts_err("Enter normal mode failed %d", ret); + /* return ret; */ + } + } + } + +#ifdef CONFIG_CTS_CHARGER_DETECT + if (cts_is_charger_exist(cts_dev)) { + int r = cts_set_dev_charger_attached(cts_dev, true); + if (r) + cts_err("Set dev charger attached failed %d", r); + } +#endif + +#ifdef CONFIG_CTS_EARJACK_DETECT + if (cts_is_earjack_exist(cts_dev)) { + int r = cts_set_dev_earjack_attached(cts_dev, true); + if (r) + cts_err("Set dev earjack attached failed %d", r); + } +#endif + +#ifdef CONFIG_CTS_GLOVE + if (cts_is_glove_enabled(cts_dev)) + cts_enter_glove_mode(cts_dev); +#endif + + return ret; +} + +bool cts_is_firmware_updating(struct cts_device *cts_dev) +{ + return cts_dev->rtdata.updating; +} diff --git a/drivers/input/touchscreen/chipone_tddi/cts_firmware.h b/drivers/input/touchscreen/chipone_tddi/cts_firmware.h new file mode 100644 index 000000000000..55033becacd8 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_firmware.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CTS_FIRMWARE_H +#define CTS_FIRMWARE_H + +#include "cts_config.h" +#include +#include + + + +#define FIRMWARE_VERSION_OFFSET 0x100 +#define FIRMWARE_VERSION(firmware) \ + get_unaligned_le16((firmware)->data + FIRMWARE_VERSION_OFFSET) + +struct cts_device; +extern int cts_request_newer_firmware_from_fs(struct cts_device *cts_dev, + const char *filepath, u16 curr_version); +extern bool cts_is_firmware_updating(struct cts_device *cts_dev); +extern int cts_request_firmware(struct cts_device *cts_dev, + u32 hwid, u16 fwid, u16 device_fw_ver); +extern int cts_update_firmware(struct cts_device *cts_dev); + +#ifdef CFG_CTS_FIRMWARE_IN_FS +extern int cts_update_firmware_from_file(struct cts_device *cts_dev, + const char *filepath); + +#endif /* CFG_CTS_FIRMWARE_IN_FS */ + +extern u16 cts_crc16(const u8 *data, size_t len); +extern u32 cts_crc32(const u8 *data, size_t len); + +#endif /* CTS_FIRMWARE_H */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_oem.c b/drivers/input/touchscreen/chipone_tddi/cts_oem.c new file mode 100644 index 000000000000..0b9aacda61b9 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_oem.c @@ -0,0 +1,2127 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define LOG_TAG "Oem" + +#include "cts_config.h" +#include "cts_platform.h" +#include "cts_core.h" +#include "cts_oem.h" +#include "cts_test.h" +#include "cts_tcs.h" +#include "cts_firmware.h" + +#include +#include +#include + +/* Following options override device tree settings */ +#define OEM_OF_DEF_PROPVAL_TEST_RESET_PIN true +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +#define OEM_OF_DEF_PROPVAL_TEST_INT_PIN false +#else +#define OEM_OF_DEF_PROPVAL_TEST_INT_PIN true +#endif +#define OEM_OF_DEF_PROPVAL_TEST_RAWDATA true +#define OEM_OF_DEF_PROPVAL_TEST_NOISE true +#define OEM_OF_DEF_PROPVAL_TEST_OPEN true +#define OEM_OF_DEF_PROPVAL_TEST_SHORT true +#define OEM_OF_DEF_PROPVAL_TEST_COMP_CAP true + +/* Default settings if device tree NOT exist */ +#define OEM_OF_DEF_PROPVAL_RAWDATA_FRAMES 1 +#define OEM_OF_DEF_PROPVAL_RAWDATA_MIN 1000 +#define OEM_OF_DEF_PROPVAL_RAWDATA_MAX 2000 +#define OEM_OF_DEF_PROPVAL_NOISE_FRAMES 50 +#define OEM_OF_DEF_PROPVAL_NOISE_MAX 80 +#define OEM_OF_DEF_PROPVAL_OPEN_MIN 1000 +#define OEM_OF_DEF_PROPVAL_SHORT_MIN 500 +#define OEM_OF_DEF_PROPVAL_COMP_CAP_MIN 1 +#define OEM_OF_DEF_PROPVAL_COMP_CAP_MAX 126 + +#define OEM_OF_PROPNAME_PREFIX "chipone," //modify + +#define OEM_OF_PROPNAME_TEST_RESET_PIN OEM_OF_PROPNAME_PREFIX"test-reset-pin" +#define OEM_OF_PROPNAME_TEST_INT_PIN OEM_OF_PROPNAME_PREFIX"test-int-pin" +#define OEM_OF_PROPNAME_TEST_RAWDATA OEM_OF_PROPNAME_PREFIX"test-rawdata" +#define OEM_OF_PROPNAME_RAWDATA_FRAMES OEM_OF_PROPNAME_PREFIX"test-rawdata-frames" +#define OEM_OF_PROPNAME_RAWDATA_MIN OEM_OF_PROPNAME_PREFIX"rawdata-min" +#define OEM_OF_PROPNAME_RAWDATA_MAX OEM_OF_PROPNAME_PREFIX"rawdata-max" +#define OEM_OF_PROPNAME_TEST_NOISE OEM_OF_PROPNAME_PREFIX"test-noise" +#define OEM_OF_PROPNAME_NOISE_FRAMES OEM_OF_PROPNAME_PREFIX"test-noise-frames" +#define OEM_OF_PROPNAME_NOISE_MAX OEM_OF_PROPNAME_PREFIX"noise-max" +#define OEM_OF_PROPNAME_TEST_OPEN OEM_OF_PROPNAME_PREFIX"test-open" +#define OEM_OF_PROPNAME_OPEN_MIN OEM_OF_PROPNAME_PREFIX"open-min" +#define OEM_OF_PROPNAME_TEST_SHORT OEM_OF_PROPNAME_PREFIX"test-short" +#define OEM_OF_PROPNAME_SHORT_MIN OEM_OF_PROPNAME_PREFIX"short-min" +#define OEM_OF_PROPNAME_TEST_COMP_CAP OEM_OF_PROPNAME_PREFIX"test-compensate-cap" +#define OEM_OF_PROPNAME_COMP_CAP_MIN OEM_OF_PROPNAME_PREFIX"compensate-cap-min" +#define OEM_OF_PROPNAME_COMP_CAP_MAX OEM_OF_PROPNAME_PREFIX"compensate-cap-max" + +#define OEM_SELFTEST_PROC_FILENAME "cts_selftest" +#define OEM_LIMIT_PROC_FILENAME "cts_limit" +#define OEM_FACTORY_TEST_PROC_FILENAME "cts_factory_test" +#define OEM_RAWDATA_PROC_FILENAME "cts_rawdata" +#define OEM_MANUAL_PROC_FILENAME "cts_manual" +#define OEM_DIFFDATA_PROC_FILENAME "cts_diffdata" +#define OEM_CNEGDATA_PROC_FILENAME "cts_compensate_cap" + +#define OEM_TEST_DATA_DIR "/sdcard" +#define OEM_RAWDATA_TEST_DATA_FILEPATH OEM_TEST_DATA_DIR"/FWMutualTest.csv" +#define OEM_NOISE_TEST_DATA_FILEPATH OEM_TEST_DATA_DIR"/NoiseTest.csv" +#define OEM_OPEN_TEST_DATA_FILEPATH OEM_TEST_DATA_DIR"/OpenTest.csv" +#define OEM_SHORT_TEST_DATA_FILEPATH OEM_TEST_DATA_DIR"/ShortTest.csv" +#define OEM_COMP_CAP_TEST_DATA_FILEPATH OEM_TEST_DATA_DIR"/FWCCTest.csv" + +#define TOUCH_DATA_DIRECTORY_PREFIX "/sdcard" +#define RAWDATA_TEST_DATA_FILENAME "rawdata-test-data.txt" +#define NOISE_TEST_DATA_FILENAME "noise-test-data.txt" +#define OPEN_TEST_DATA_FILENAME "open-test-data.txt" +#define SHORT_TEST_DATA_FILENAME "short-test-data.txt" +#define COMP_CAP_TEST_DATA_FILENAME "comp-cap-test-data.txt" +#define STYLUS_RAWDATA_TEST_DATA_FILENAME "stylus-rawdata-test-data.txt" +#define STYLUS_NOISE_TEST_DATA_FILENAME "stylus-noise-test-data.txt" +#define STYLUS_MNT_NOISE_TEST_DATA_FILENAME "stylus-mnt-noise-test-data.txt" + +#pragma pack(1) +struct cts_limit { + u8 header[7]; + u8 version; + u16 int_item : 1; + u16 reset_item : 1; + u16 normal_rawdata_item : 1; + u16 normal_noise_item : 1; + u16 open_item : 1; + u16 short_item : 1; + u16 comp_cap_item : 1; + u16 gstr_rawdata_item : 1; + u16 gstrlp_rawdata_item : 1; + u16 gstr_noise_item : 1; + u16 gstrlp_noise_item : 1; + u16 stylus_rawdata_item : 1; + u16 stylus_noise_item : 1; + u16 stylus_mnt_rawdata_item : 1; + u16 type : 2; + u16 normal_rawdata_frames; + u16 normal_rawdata_min; + u16 normal_rawdata_max; + u16 normal_noise_frames; + u16 normal_noise_max; + u16 normal_open_min; + u16 normal_short_min; + u16 normal_compcap_min; + u16 normal_compcap_max; + u16 gstr_rawdata_frames; + u16 gstr_rawdata_min; + u16 gstr_rawdata_max; + u16 gstrlp_rawdata_frames; + u16 gstrlp_rawdata_min; + u16 gstrlp_rawdata_max; + u16 gstr_noise_frames; + u16 gstr_noise_max; + u16 gstrlp_noise_frames; + u16 gstrlp_noise_max; + u16 stylus_rawdata_frames; + u16 stylus_rawdata_min; + u16 stylus_rawdata_max; + u16 stylus_noise_frames; + u16 stylus_noise_max; + u16 stylus_mnt_rawdata_frames; + u16 stylus_mnt_rawdata_min; + u16 stylus_mnt_rawdata_max; +}; +#pragma pack() + +struct cts_oem_data { + struct proc_dir_entry *selftest_proc_entry; + struct proc_dir_entry *limit_entry; + struct proc_dir_entry *factory_test_entry; + struct proc_dir_entry *manual_proc_entry; + struct proc_dir_entry *diffdata_proc_entry; + struct proc_dir_entry *rawdata_proc_entry; + struct proc_dir_entry *cnegdata_proc_entry; + + bool test_config_from_dt_has_parsed; + + /* Test configuration from device tree */ + bool test_reset_pin; + int reset_pin_test_result; + + bool test_int_pin; + int int_pin_test_result; + + bool test_rawdata; + u32 rawdata_test_frames; + int rawdata_test_result; + u16 *rawdata_test_data; + int rawdata_test_data_buff_size; + int rawdata_test_data_wr_size; + int rawdata_min; + int rawdata_max; + + bool test_noise; + u32 noise_test_frames; + int noise_test_result; + u16 *noise_test_data; + int noise_test_data_buff_size; + int noise_test_data_wr_size; + int noise_max; + + bool test_open; + int open_test_result; + u16 *open_test_data; + int open_test_data_buff_size; + int open_test_data_wr_size; + int open_min; + + bool test_short; + int short_test_result; + u16 *short_test_data; + int short_test_data_buff_size; + int short_test_data_wr_size; + int short_min; + + bool test_comp_cap; + int comp_cap_test_result; + u8 *comp_cap_test_data; + int comp_cap_test_data_buff_size; + int comp_cap_test_data_wr_size; + int comp_cap_min; + int comp_cap_max; + + bool test_stylus_rawdata; + u32 stylus_rawdata_test_frames; + int stylus_rawdata_test_result; + u8 *stylus_rawdata_test_data; + int stylus_rawdata_test_data_buff_size; + int stylus_rawdata_test_data_wr_size; + int stylus_rawdata_min; + int stylus_rawdata_max; + + bool test_stylus_noise; + u32 stylus_noise_test_frames; + int stylus_noise_test_result; + u8 *stylus_noise_test_data; + int stylus_noise_test_data_buff_size; + int stylus_noise_test_data_wr_size; + int stylus_noise_max; + + bool test_stylus_mnt_rawdata; + u32 stylus_mnt_rawdata_test_frames; + int stylus_mnt_rawdata_test_result; + u8 *stylus_mnt_rawdata_test_data; + int stylus_mnt_rawdata_test_data_buff_size; + int stylus_mnt_rawdata_test_data_wr_size; + int stylus_mnt_rawdata_min; + int stylus_mnt_rawdata_max; + + struct chipone_ts_data *cts_data; +}; + +/* struct proc_dir_entry *cts_tp_work_proc; */ + +#define ALLOC_TEST_DATA_MEM(type, size) \ + do { \ + if (oem_data->test_##type) { \ + if (oem_data->type##_test_data == NULL) { \ + cts_info(" - Alloc " #type " test data mem size %d", size); \ + oem_data->type##_test_data = kmalloc(size, GFP_KERNEL); \ + if (oem_data->type##_test_data == NULL) { \ + cts_err("Alloc " #type " test data mem failed"); \ + return -ENOMEM; \ + } \ + oem_data->type##_test_data_buff_size = size; \ + } \ + memset(oem_data->type##_test_data, 0, size); \ + } \ + } while (0) + +/* NOTE: Any test data mem alloc failed will NOT clean other mem */ +static int alloc_selftest_data_mem(struct cts_oem_data *oem_data, int nodes) +{ + cts_info("Alloc selftest data"); + + ALLOC_TEST_DATA_MEM(rawdata, + nodes * 2 * oem_data->rawdata_test_frames); + ALLOC_TEST_DATA_MEM(noise, + nodes * 2 * (oem_data->noise_test_frames + 3)); + ALLOC_TEST_DATA_MEM(open, nodes * 2); + ALLOC_TEST_DATA_MEM(short, nodes * 2 * 5); + ALLOC_TEST_DATA_MEM(comp_cap, nodes); + return 0; +} + +#define FREE_TEST_DATA_MEM(type) \ + do { \ + if (oem_data->type##_test_data) { \ + cts_info("- Free " #type " test data mem"); \ + kfree(oem_data->type##_test_data); \ + oem_data->type##_test_data = NULL; \ + oem_data->type##_test_data_buff_size = 0; \ + } \ + } while(0) +static void free_selftest_data_mem(struct cts_oem_data *oem_data) +{ + cts_info("Free selftest data"); + + FREE_TEST_DATA_MEM(rawdata); + FREE_TEST_DATA_MEM(noise); + FREE_TEST_DATA_MEM(open); + FREE_TEST_DATA_MEM(short); + FREE_TEST_DATA_MEM(comp_cap); +} + +/* NOTE: Any test data mem alloc failed will NOT clean other mem +static int alloc_stylus_test_data_mem(struct cts_oem_data *oem_data, int nodes) +{ + cts_info("Alloc stylus selftest data"); + + ALLOC_TEST_DATA_MEM(stylus_rawdata, + nodes * 2 * oem_data->stylus_rawdata_test_frames); + ALLOC_TEST_DATA_MEM(stylus_noise, + nodes * 2 * (oem_data->stylus_noise_test_frames + 3)); + ALLOC_TEST_DATA_MEM(stylus_mnt_rawdata, + nodes * 2 * oem_data->stylus_mnt_rawdata_test_frames); + return 0; +} + +static void free_stylus_test_data_mem(struct cts_oem_data *oem_data) +{ + cts_info("Free stylus selftest data"); + + FREE_TEST_DATA_MEM(stylus_rawdata); + FREE_TEST_DATA_MEM(stylus_noise); + FREE_TEST_DATA_MEM(stylus_mnt_rawdata); +} +*/ +#undef ALLOC_TEST_DATA_MEM +#undef FREE_TEST_DATA_MEM + +static int parse_selftest_dt(struct cts_oem_data *oem_data, + struct device_node *np) +{ + int ret; + + cts_info("Parse selftest dt"); + + /** reset pin **/ + oem_data->test_reset_pin = OEM_OF_DEF_PROPVAL_TEST_RESET_PIN || + of_property_read_bool(np, OEM_OF_PROPNAME_TEST_RESET_PIN); + + /** int pin **/ + oem_data->test_int_pin = OEM_OF_DEF_PROPVAL_TEST_INT_PIN || + of_property_read_bool(np, OEM_OF_PROPNAME_TEST_INT_PIN); + + /** rawdata **/ + oem_data->test_rawdata = OEM_OF_DEF_PROPVAL_TEST_RAWDATA || + of_property_read_bool(np, OEM_OF_PROPNAME_TEST_RAWDATA); + if (oem_data->test_rawdata) { + oem_data->rawdata_test_frames = OEM_OF_DEF_PROPVAL_RAWDATA_FRAMES; + ret = of_property_read_u32(np, OEM_OF_PROPNAME_RAWDATA_FRAMES, + &oem_data->rawdata_test_frames); + if (ret) { + cts_warn("Parse '"OEM_OF_PROPNAME_RAWDATA_FRAMES"' failed %d", ret); + } + oem_data->rawdata_min = OEM_OF_DEF_PROPVAL_RAWDATA_MIN; + ret = of_property_read_u32(np, OEM_OF_PROPNAME_RAWDATA_MIN, + (u32 *)&oem_data->rawdata_min); + if (ret) { + cts_warn("Parse '"OEM_OF_PROPNAME_RAWDATA_MIN"' failed %d", ret); + } + oem_data->rawdata_max = OEM_OF_DEF_PROPVAL_RAWDATA_MAX; + ret = of_property_read_u32(np, OEM_OF_PROPNAME_RAWDATA_MAX, + (u32 *)&oem_data->rawdata_max); + if (ret) { + cts_warn("Parse '"OEM_OF_PROPNAME_RAWDATA_MAX"' failed %d", ret); + } + } + + /** nosie **/ + oem_data->test_noise = OEM_OF_DEF_PROPVAL_TEST_NOISE || + of_property_read_bool(np, OEM_OF_PROPNAME_TEST_NOISE); + if (oem_data->test_noise) { + oem_data->noise_test_frames = OEM_OF_DEF_PROPVAL_NOISE_FRAMES; + ret = of_property_read_u32(np, OEM_OF_PROPNAME_NOISE_FRAMES, + &oem_data->noise_test_frames); + if (ret) { + cts_warn("Parse '"OEM_OF_PROPNAME_NOISE_FRAMES"' failed %d", ret); + } + oem_data->noise_max = OEM_OF_DEF_PROPVAL_NOISE_MAX; + ret = of_property_read_u32(np, OEM_OF_PROPNAME_NOISE_MAX, + (u32 *)&oem_data->noise_max); + if (ret) { + cts_warn("Parse '"OEM_OF_PROPNAME_NOISE_MAX"' failed %d", ret); + } + } + + /** open **/ + oem_data->test_open = OEM_OF_DEF_PROPVAL_TEST_OPEN || + of_property_read_bool(np, OEM_OF_PROPNAME_TEST_OPEN); + if (oem_data->test_open) { + oem_data->open_min = OEM_OF_DEF_PROPVAL_OPEN_MIN; + ret = of_property_read_u32(np, OEM_OF_PROPNAME_OPEN_MIN, + (u32 *)&oem_data->open_min); + if (ret) { + cts_warn("Parse '"OEM_OF_PROPNAME_OPEN_MIN"' failed %d", ret); + } + } + + /** short **/ + oem_data->test_short = OEM_OF_DEF_PROPVAL_TEST_SHORT || + of_property_read_bool(np, OEM_OF_PROPNAME_TEST_SHORT); + if (oem_data->test_short) { + oem_data->short_min = OEM_OF_DEF_PROPVAL_SHORT_MIN; + ret = of_property_read_u32(np, OEM_OF_PROPNAME_SHORT_MIN, + (u32 *)&oem_data->short_min); + if (ret) { + cts_warn("Parse '"OEM_OF_PROPNAME_SHORT_MIN"' failed %d", ret); + } + } + + /** comp cap **/ + oem_data->test_comp_cap = OEM_OF_DEF_PROPVAL_TEST_COMP_CAP || + of_property_read_bool(np, OEM_OF_PROPNAME_TEST_COMP_CAP); + if (oem_data->test_comp_cap) { + oem_data->comp_cap_min = OEM_OF_DEF_PROPVAL_COMP_CAP_MIN; + ret = of_property_read_u32(np, OEM_OF_PROPNAME_COMP_CAP_MIN, + (u32 *)&oem_data->comp_cap_min); + if (ret) { + cts_warn("Parse '"OEM_OF_PROPNAME_COMP_CAP_MIN"' failed %d", ret); + } + oem_data->comp_cap_max = OEM_OF_DEF_PROPVAL_COMP_CAP_MAX; + ret = of_property_read_u32(np, OEM_OF_PROPNAME_COMP_CAP_MAX, + (u32 *)&oem_data->comp_cap_max); + if (ret) { + cts_warn("Parse '"OEM_OF_PROPNAME_COMP_CAP_MAX"' failed %d", ret); + } + } + + oem_data->test_config_from_dt_has_parsed = true; + + return 0; +} + +static void print_selftest_config(const struct cts_oem_data *oem_data) +{ + cts_info("Seltest configuration:"); + + /** reset pin **/ + cts_info(" - %-32s = %c", OEM_OF_PROPNAME_TEST_RESET_PIN, + oem_data->test_reset_pin ? 'Y' : 'N'); + + /** int pin **/ + cts_info(" - %-32s = %c", OEM_OF_PROPNAME_TEST_INT_PIN, + oem_data->test_int_pin ? 'Y' : 'N'); + + /** rawdata **/ + cts_info(" - %-32s = %c", OEM_OF_PROPNAME_TEST_RAWDATA, + oem_data->test_rawdata ? 'Y' : 'N'); + if (oem_data->test_rawdata) { + cts_info(" - %-32s = %u", OEM_OF_PROPNAME_RAWDATA_FRAMES, + oem_data->rawdata_test_frames); + cts_info(" - %-32s = %d", OEM_OF_PROPNAME_RAWDATA_MIN, + oem_data->rawdata_min); + cts_info(" - %-32s = %d", OEM_OF_PROPNAME_RAWDATA_MAX, + oem_data->rawdata_max); + } + + /** noise **/ + cts_info(" - %-32s = %c", OEM_OF_PROPNAME_TEST_NOISE, + oem_data->test_noise ? 'Y' : 'N'); + if (oem_data->test_noise) { + cts_info(" - %-32s = %u", OEM_OF_PROPNAME_NOISE_FRAMES , + oem_data->noise_test_frames); + cts_info(" - %-32s = %d", OEM_OF_PROPNAME_NOISE_MAX, + oem_data->noise_max); + } + + /** open **/ + cts_info(" - %-32s = %c", OEM_OF_PROPNAME_TEST_OPEN, + oem_data->test_open ? 'Y' : 'N'); + if (oem_data->test_open) { + cts_info(" - %-32s = %d", OEM_OF_PROPNAME_OPEN_MIN, + oem_data->open_min); + } + + /** short **/ + cts_info(" - %-32s = %c", OEM_OF_PROPNAME_TEST_SHORT, + oem_data->test_short ? 'Y' : 'N'); + if (oem_data->test_short) { + cts_info(" - %-32s = %d", OEM_OF_PROPNAME_SHORT_MIN, + oem_data->short_min); + } + + /** comp cap **/ + cts_info(" - %-32s = %c", OEM_OF_PROPNAME_TEST_COMP_CAP, + oem_data->test_comp_cap ? 'Y' : 'N'); + if (oem_data->test_comp_cap) { + cts_info(" - %-32s = %d", OEM_OF_PROPNAME_COMP_CAP_MIN, + oem_data->comp_cap_min); + cts_info(" - %-32s = %d", OEM_OF_PROPNAME_COMP_CAP_MAX, + oem_data->comp_cap_max); + } +} + +static void do_selftest(struct cts_oem_data *oem_data) +{ + struct cts_test_param test_param; + int retry = 3; + + cts_info("Do selftest"); + + /** reset pin test **/ + if (oem_data->test_reset_pin) { + memset(&test_param, 0, sizeof(test_param)); + test_param.test_item = CTS_TEST_RESET_PIN; + oem_data->reset_pin_test_result = + cts_test_reset_pin(&oem_data->cts_data->cts_dev, &test_param); + if (oem_data->reset_pin_test_result) { + cts_err("Test reset pin failed %d", oem_data->reset_pin_test_result); + } + } + + /** int pin test **/ + if (oem_data->test_int_pin) { + memset(&test_param, 0, sizeof(test_param)); + test_param.test_item = CTS_TEST_INT_PIN; + oem_data->int_pin_test_result = + cts_test_int_pin(&oem_data->cts_data->cts_dev, &test_param); + if (oem_data->int_pin_test_result) { + cts_err("Test int pin failed %d", oem_data->int_pin_test_result); + } + } + + /** rawdata test **/ + if (oem_data->test_rawdata) { + struct cts_rawdata_test_priv_param priv_param = {0}; + memset(&test_param, 0, sizeof(test_param)); + oem_data->rawdata_test_data_wr_size = 0; + + test_param.test_item = CTS_TEST_RAWDATA; + test_param.flags = + CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE; + test_param.min = &oem_data->rawdata_min; + test_param.max = &oem_data->rawdata_max; + test_param.test_data_buf = oem_data->rawdata_test_data; + test_param.test_data_buf_size = oem_data->rawdata_test_data_buff_size; + test_param.test_data_wr_size = &oem_data->rawdata_test_data_wr_size; + + priv_param.frames = oem_data->rawdata_test_frames; + test_param.priv_param = &priv_param; + test_param.priv_param_size = sizeof(priv_param); + + retry = 3; + do { + oem_data->rawdata_test_result = + cts_test_rawdata(&oem_data->cts_data->cts_dev, &test_param); + } while (oem_data->rawdata_test_result < 0 && retry--); + if (oem_data->rawdata_test_result) { + cts_err("Test rawdata failed %d", oem_data->rawdata_test_result); + } + } + + /** noise test **/ + if (oem_data->test_noise) { + struct cts_noise_test_priv_param priv_param = {0}; + memset(&test_param, 0, sizeof(test_param)); + oem_data->noise_test_data_wr_size = 0; + + test_param.test_item = CTS_TEST_NOISE; + test_param.flags = + CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE; + test_param.max = &oem_data->noise_max; + test_param.test_data_buf = oem_data->noise_test_data; + test_param.test_data_buf_size = oem_data->noise_test_data_buff_size; + test_param.test_data_wr_size = &oem_data->noise_test_data_wr_size; + + priv_param.frames = oem_data->noise_test_frames; + test_param.priv_param = &priv_param; + test_param.priv_param_size = sizeof(priv_param); + + retry = 3; + do { + oem_data->noise_test_result = + cts_test_noise(&oem_data->cts_data->cts_dev, &test_param); + } while (oem_data->noise_test_result < 0 && retry--); + if (oem_data->noise_test_result) { + cts_err("Test noise failed %d", oem_data->noise_test_result); + } + } + + /** open test **/ + if (oem_data->test_open) { + memset(&test_param, 0, sizeof(test_param)); + oem_data->open_test_data_wr_size = 0; + test_param.test_item = CTS_TEST_OPEN; + test_param.flags = + CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE; + test_param.min = &oem_data->open_min; + test_param.test_data_buf = oem_data->open_test_data; + test_param.test_data_buf_size = oem_data->open_test_data_buff_size; + test_param.test_data_wr_size = &oem_data->open_test_data_wr_size; + + retry = 3; + do { + oem_data->open_test_result = + cts_test_open(&oem_data->cts_data->cts_dev, &test_param); + } while (oem_data->open_test_result < 0 && retry--); + if (oem_data->open_test_result) { + cts_err("Test open failed %d", oem_data->open_test_result); + } + } + + /** short test **/ + if (oem_data->test_short) { + memset(&test_param, 0, sizeof(test_param)); + oem_data->short_test_data_wr_size = 0; + test_param.test_item = CTS_TEST_SHORT; + test_param.flags = + CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE; + test_param.min = &oem_data->short_min; + test_param.test_data_buf = oem_data->short_test_data; + test_param.test_data_buf_size = oem_data->short_test_data_buff_size; + test_param.test_data_wr_size = &oem_data->short_test_data_wr_size; + + retry = 3; + do { + oem_data->short_test_result = + cts_test_short(&oem_data->cts_data->cts_dev, &test_param); + } while (oem_data->short_test_result < 0 && retry--); + if (oem_data->short_test_result) { + cts_err("Test short failed %d", oem_data->short_test_result); + } + } + + /** comp cap test **/ + if (oem_data->test_comp_cap) { + memset(&test_param, 0, sizeof(test_param)); + oem_data->comp_cap_test_data_wr_size = 0; + test_param.test_item = CTS_TEST_COMPENSATE_CAP; + test_param.flags = + CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE; + test_param.min = &oem_data->comp_cap_min; + test_param.max = &oem_data->comp_cap_max; + test_param.test_result = &oem_data->comp_cap_test_result; + test_param.test_data_buf = oem_data->comp_cap_test_data; + test_param.test_data_buf_size = oem_data->comp_cap_test_data_buff_size; + test_param.test_data_wr_size = &oem_data->comp_cap_test_data_wr_size; + + retry = 3; + do { + oem_data->comp_cap_test_result = + cts_test_compensate_cap(&oem_data->cts_data->cts_dev, &test_param); + } while (oem_data->comp_cap_test_result < 0 && retry--); + if (oem_data->comp_cap_test_result) { + cts_err("Test compensate cap failed %d", oem_data->comp_cap_test_result); + } + } + +/* + if (oem_data->test_stylus_rawdata) { + struct cts_rawdata_test_priv_param priv_param = {0}; + memset(&test_param, 0, sizeof(test_param)); + oem_data->stylus_rawdata_test_data_wr_size = 0; + + test_param.test_item = CTS_TEST_STYLUS_RAWDATA; + test_param.flags = + CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE; + test_param.min = &oem_data->stylus_rawdata_min; + test_param.max = &oem_data->stylus_rawdata_max; + test_param.test_data_buf = oem_data->stylus_rawdata_test_data; + test_param.test_data_buf_size = oem_data->stylus_rawdata_test_data_buff_size; + test_param.test_data_wr_size = &oem_data->stylus_rawdata_test_data_wr_size; + + priv_param.frames = oem_data->stylus_rawdata_test_frames; + test_param.priv_param = &priv_param; + test_param.priv_param_size = sizeof(priv_param); + + retry = 3; + do { + oem_data->stylus_rawdata_test_result = + cts_test_stylus_rawdata(&oem_data->cts_data->cts_dev, &test_param); + } while (oem_data->stylus_rawdata_test_result < 0 && retry--); + if (oem_data->stylus_rawdata_test_result) { + cts_err("stylus test rawdata failed %d", oem_data->stylus_rawdata_test_result); + } + } + + if (oem_data->stylus_test_noise) { + struct cts_noise_test_priv_param priv_param = {0}; + memset(&test_param, 0, sizeof(test_param)); + oem_data->stylus_noise_test_data_wr_size = 0; + + test_param.test_item = CTS_TEST_STYLUS_NOISE; + test_param.flags = + CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE; + test_param.max = &oem_data->stylus_noise_max; + test_param.test_data_buf = oem_data->stylus_noise_test_data; + test_param.test_data_buf_size = oem_data->stylus_noise_test_data_buff_size; + test_param.test_data_wr_size = &oem_data->stylus_noise_test_data_wr_size; + + priv_param.frames = oem_data->stylus_noise_test_frames; + test_param.priv_param = &priv_param; + test_param.priv_param_size = sizeof(priv_param); + + retry = 3; + do { + oem_data->stylus_noise_test_result = + cts_test_stylus_noise(&oem_data->cts_data->cts_dev, &test_param); + } while (oem_data->stylus_noise_test_result < 0 && retry--); + if (oem_data->stylus_noise_test_result) { + cts_err("stylus test noise failed %d", oem_data->stylus_noise_test_result); + } + } +*/ +} + +static int dump_tsdata_row_to_buffer(char *buf, size_t size, const u16 *data, + int cols, const char *prefix, const char *suffix, char seperator) +{ + int c, count = 0; + + if (prefix) { + count += scnprintf(buf, size, "%s", prefix); + } + + for (c = 0; c < cols; c++) { + count += scnprintf(buf + count, size - count, + "%4u%c ", data[c], seperator); + } + + if (suffix) { + count += scnprintf(buf + count, size - count, "%s", suffix); + } + + return count; +} + +static int dump_tsdata_to_csv_file(const char *filepath, + int flags, const u16 *data, int frames, int rows, int cols) +{ +#ifdef CFG_CTS_FOR_GKI + cts_info("%s(): some functions are forbiddon with GKI Version!", __func__); + return -EPERM; +#else + loff_t pos = 0; + int i, r, ret; + struct file *file; + + cts_info("Dump tsdata to csv file: '%s' flags: 0x%x data: %p frames: %d row: %d col: %d", + filepath, flags, data, frames, cols, rows); + + file = filp_open(filepath, flags, 0666); + if (IS_ERR(file)) { + cts_err("Open file '%s' failed %ld", filepath, PTR_ERR(file)); + return PTR_ERR(file); + } + + for (i = 0; i < frames; i++) { + for (r = 0; r < rows; r++) { + char linebuf[256]; + int len; + + len = dump_tsdata_row_to_buffer(linebuf, sizeof(linebuf), + data, cols, NULL, "\n", ','); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) + ret = kernel_write(file, linebuf, len, &pos); +#else + ret = kernel_write(file, linebuf, len, pos); + pos += len; +#endif + if (ret != len) { + cts_err("Write to file '%s' failed %d", filepath, ret); + goto close_file; + } + data += cols; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) + ret = kernel_write(file, "\n", 1, &pos); +#else + ret = kernel_write(file, "\n", 1, pos); + pos ++; +#endif + if (ret != 1) { + cts_err("Write newline to file '%s' failed %d", filepath, ret); + goto close_file; + } + } + +close_file: { + int r = filp_close(file, NULL); + if (r) { + cts_err("Close file '%s' failed %d", filepath, r); + } + } + + return ret; +#endif +} + +static void dump_tsdata_to_seq_file(struct seq_file *m, + const u16 *data, int rows, int cols) +{ + int r; + + for (r = 0; r < rows; r++) { + char linebuf[256]; + int len; + + len = dump_tsdata_row_to_buffer(linebuf, sizeof(linebuf), + data, cols, NULL, "\n", ','); + seq_puts(m, linebuf); + + data += cols; + } +} + +static int dump_comp_cap_row_to_buffer(char *buf, size_t size, const u8 *cap, + int cols, const char *prefix, const char *suffix, char seperator) +{ + int c, count = 0; + + if (prefix) { + count += scnprintf(buf, size, "%s", prefix); + } + + for (c = 0; c < cols; c++) { + count += scnprintf(buf + count, size - count, + "%3u%c ", cap[c], seperator); + } + + if (suffix) { + count += scnprintf(buf + count, size - count, "%s", suffix); + } + + return count; +} + +static int dump_comp_cap_to_csv_file(const char *filepath, + int flags, const u8 *cap, int rows, int cols) +{ +#ifdef CFG_CTS_FOR_GKI + cts_info("%s(): some functions are forbiddon with GKI Version!", __func__); + return -EPERM; +#else + struct file *file; + int r, ret = 0; + loff_t pos = 0; + + cts_info("Dump compensate cap to csv file: '%s' flags: 0x%x row: %d col: %d", + filepath, flags, rows, cols); + + file = filp_open(filepath, flags, 0666); + if (IS_ERR(file)) { + cts_err("Open file '%s' failed %ld", filepath, PTR_ERR(file)); + return PTR_ERR(file); + } + + for (r = 0; r < rows; r++) { + char linebuf[256]; + int len; + + len = dump_comp_cap_row_to_buffer(linebuf, sizeof(linebuf), + cap, cols, NULL, "\n", ','); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) + ret = kernel_write(file, linebuf, len, &pos); +#else + ret = kernel_write(file, linebuf, len, pos); + pos += len; +#endif + if (ret != len) { + cts_err("Write to file '%s' failed %d", filepath, ret); + goto close_file; + } + + cap += cols; + } + +close_file: { + int r = filp_close(file, NULL); + if (r) { + cts_err("Close file '%s' failed %d", filepath, ret); + } + } + + return ret; +#endif +} + +static void dump_comp_cap_to_seq_file(struct seq_file *m, + const u8 *data, int rows, int cols) +{ + int r; + + for (r = 0; r < rows; r++) { + char linebuf[256]; + int len; + + len = dump_comp_cap_row_to_buffer(linebuf, sizeof(linebuf), + data, cols, NULL, "\n", ','); + seq_puts(m, linebuf); + + data += cols; + } +} + +static int save_selftest_data_to_file(struct cts_oem_data *oem_data) +{ + int rows, cols; + int ret; + + cts_info("Save selftest data to file"); + + rows = oem_data->cts_data->cts_dev.fwdata.rows; + cols = oem_data->cts_data->cts_dev.fwdata.cols; + + if (oem_data->test_rawdata) { + ret = dump_tsdata_to_csv_file(OEM_RAWDATA_TEST_DATA_FILEPATH, + O_RDWR | O_CREAT | O_TRUNC, oem_data->rawdata_test_data, + oem_data->rawdata_test_frames, cols, rows); + if (ret < 0) { + cts_err("Dump rawdata test data to file failed"); + return ret; + } + } + + if (oem_data->test_noise) { + ret = dump_tsdata_to_csv_file(OEM_NOISE_TEST_DATA_FILEPATH, + O_RDWR | O_CREAT | O_TRUNC, oem_data->noise_test_data, + oem_data->noise_test_frames + 3, cols, rows); + if (ret < 0) { + cts_err("Dump noise test data to file failed"); + return ret; + } + } + + if (oem_data->test_open) { + ret = dump_tsdata_to_csv_file(OEM_OPEN_TEST_DATA_FILEPATH, + O_RDWR | O_CREAT | O_TRUNC, oem_data->open_test_data, + 1, cols, rows); + if (ret < 0) { + cts_err("Dump open test data to file failed"); + return ret; + } + } + + if (oem_data->test_short) { + ret = dump_tsdata_to_csv_file(OEM_SHORT_TEST_DATA_FILEPATH, + O_RDWR | O_CREAT | O_TRUNC, oem_data->short_test_data, + 5, cols, rows); + if (ret < 0) { + cts_err("Dump short test data to file failed"); + return ret; + } + } + + if (oem_data->test_comp_cap) { + ret = dump_comp_cap_to_csv_file(OEM_COMP_CAP_TEST_DATA_FILEPATH, + O_RDWR | O_CREAT | O_TRUNC, + oem_data->comp_cap_test_data, cols, rows); + if (ret < 0) { + cts_err("Dump compensate cap test data to file failed"); + return ret; + } + } + + return 0; +} + +static void *selftest_seq_start(struct seq_file *m, loff_t *pos) +{ + return *pos < 1 ? (void *)1 : NULL; +} + +static void *selftest_seq_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return NULL; +} + +static void selftest_seq_stop(struct seq_file *m, void *v) +{ + return; +} + +static int selftest_seq_show(struct seq_file *m, void *v) +{ + struct chipone_ts_data *cts_data = (struct chipone_ts_data *)m->private; + struct cts_oem_data *oem_data = NULL; + int i, rows, cols; + + cts_info("Show seq selftest"); + + if (cts_data == NULL) { + cts_err("Selftest seq file private data = NULL"); + return -EFAULT; + } + + oem_data = cts_data->oem_data; + rows = cts_data->cts_dev.fwdata.rows; + cols = cts_data->cts_dev.fwdata.cols; + + seq_printf(m, "FW Version %04x!\n\n", cts_data->cts_dev.fwdata.version); + + if (oem_data->test_reset_pin) { + seq_printf(m, "Reset-Pin Test %s!\n\n", + oem_data->reset_pin_test_result == 0 ? "PASS" : "FAIL"); + } + if (oem_data->test_int_pin) { + seq_printf(m, "Int-Pin Test %s!\n\n", + oem_data->int_pin_test_result == 0 ? "PASS" : "FAIL"); + } + if (oem_data->test_rawdata) { + seq_printf(m, "FW Rawdata Test"); + if (oem_data->rawdata_test_result == 0) { + seq_printf(m, " PASS!\n\n"); + } else if (oem_data->rawdata_test_result > 0) { + seq_printf(m, " FAIL!\n"); + for(i = 0; i < oem_data->rawdata_test_frames; i++) { + dump_tsdata_to_seq_file(m, oem_data->rawdata_test_data + + i * rows * cols, cols, rows); + seq_putc(m, '\n'); + } + } else { + seq_printf(m, " ERROR(%d)!\n\n", oem_data->rawdata_test_result); + } + } + if (oem_data->test_noise) { + seq_printf(m, "Noise Test"); + if (oem_data->noise_test_result == 0) { + seq_printf(m, " PASS!\n\n"); + } else if (oem_data->noise_test_result > 0) { + seq_printf(m, " FAIL!\n"); + for(i = 0; i < oem_data->noise_test_frames; i++) { + dump_tsdata_to_seq_file(m, oem_data->noise_test_data + + i * rows * cols, rows, cols); + seq_putc(m, '\n'); + } + } else { + seq_printf(m, " ERROR(%d)!\n\n", oem_data->noise_test_result); + } + } + if (oem_data->test_open) { + seq_printf(m, "Open Test"); + if (oem_data->open_test_result == 0) { + seq_printf(m, " PASS!\n\n"); + } else if (oem_data->open_test_result > 0) { + seq_printf(m, " FAIL!\n"); + dump_tsdata_to_seq_file(m, oem_data->open_test_data, + rows, cols); + } else { + seq_printf(m, " ERROR(%d)!\n\n", oem_data->open_test_result); + } + } + if (oem_data->test_short) { + seq_printf(m, "Short Test"); + if (oem_data->short_test_result == 0) { + seq_printf(m, " PASS!\n\n"); + } else if (oem_data->short_test_result > 0) { + seq_printf(m, " FAIL!\n"); + for (i = 0; i < 10; i++) { + dump_tsdata_to_seq_file(m, oem_data->short_test_data + + i * rows * cols, cols, rows); + seq_putc(m, '\n'); + } + } else { + seq_printf(m, " ERROR(%d)!\n\n", oem_data->short_test_result); + } + } + if (oem_data->test_comp_cap) { + seq_printf(m, "Compensate-Cap Test"); + if (oem_data->comp_cap_test_result == 0) { + seq_printf(m, " PASS!\n\n"); + } else if (oem_data->comp_cap_test_result > 0) { + seq_printf(m, " FAIL!\n"); + dump_comp_cap_to_seq_file(m, oem_data->comp_cap_test_data, + rows, cols); + } else { + seq_printf(m, " ERROR(%d)!\n\n", oem_data->comp_cap_test_result); + } + } + + return 0; +} + +static const struct seq_operations selftest_seq_ops = { + .start = selftest_seq_start, + .next = selftest_seq_next, + .stop = selftest_seq_stop, + .show = selftest_seq_show, +}; + +static int32_t selftest_proc_open(struct inode *inode, struct file *file) +{ + struct chipone_ts_data *cts_data = pde_data(inode); + struct cts_oem_data *oem_data = NULL; + int ret; + + if (cts_data == NULL) { + cts_err("Open selftest proc with cts_data = NULL"); + return -EFAULT; + } + + oem_data = cts_data->oem_data; + if (oem_data == NULL) { + cts_err("Open selftest proc with oem_data = NULL"); + return -EFAULT; + } + + cts_info("Open '/proc/" OEM_SELFTEST_PROC_FILENAME "'"); + + if (!oem_data->test_config_from_dt_has_parsed) { +#ifndef CONFIG_CTS_I2C_HOST + ret = parse_selftest_dt(oem_data, cts_data->pdata->spi_client->dev.of_node); +#else + ret = parse_selftest_dt(oem_data, cts_data->device->of_node); +#endif + if (ret) { + cts_err("Parse selftest dt failed %d", ret); + return ret; + } + } + + print_selftest_config(oem_data); + + ret = alloc_selftest_data_mem(oem_data, + cts_data->cts_dev.fwdata.rows * cts_data->cts_dev.fwdata.cols); + if (ret) { + cts_err("Alloc test data mem failed"); + return ret; + } + + do_selftest(oem_data); + + ret = save_selftest_data_to_file(oem_data); + if (ret) { + cts_err("Save selftest data to file failed %d", ret); + } + + ret = seq_open(file, &selftest_seq_ops); + if (ret) { + cts_err("Open selftest seq file failed %d", ret); + return ret; + } + + ((struct seq_file *)file->private_data)->private = cts_data; + + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) +static const struct file_operations selftest_proc_fops = { + .owner = THIS_MODULE, + .open = selftest_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; +#else +static const struct proc_ops selftest_proc_fops = { + .proc_open = selftest_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = seq_release, +}; +#endif + +static int cts_rawdata_show(struct seq_file *m, void *v) +{ + struct chipone_ts_data *cts_data = m->private; + struct cts_device *cts_dev; + u8 *rawdata; + s16 *data; + u8 hwrows, hwcols, fwrows, fwcols; + u8 i, j; + + cts_dev = &cts_data->cts_dev; + hwrows = cts_dev->hwdata->num_row; + hwcols = cts_dev->hwdata->num_col; + fwrows = cts_dev->fwdata.rows; + fwcols = cts_dev->fwdata.cols; + + rawdata = kzalloc(hwrows * hwcols * 2, GFP_KERNEL); + if (rawdata == NULL) { + cts_err("Allocate rawdata failed"); + return -ENOMEM; + } + + cts_lock_device(cts_dev); + cts_tcs_top_get_rawdata(cts_dev, rawdata, hwrows * hwcols * 2, 0); + cts_unlock_device(cts_dev); + + data = (s16 *)rawdata; + for (i = 0; i < fwcols; i++) { + for (j = 0; j < fwrows; j++) { + seq_printf(m, "%5d", *data++); + } + seq_printf(m, "\n"); + } + + kfree(rawdata); + return 0; +} +static int cts_rawdata_open(struct inode *inode, struct file *file) +{ + return single_open(file, cts_rawdata_show, pde_data(inode)); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) +static const struct file_operations cts_rawdata_ops = { + .owner = THIS_MODULE, + .open = cts_rawdata_open, + .read = seq_read, +}; +#else +static const struct proc_ops cts_rawdata_ops = { + .proc_open = cts_rawdata_open, + .proc_read = seq_read, +}; +#endif + +static int cts_diffdata_show(struct seq_file *m, void *v) +{ + struct chipone_ts_data *cts_data = m->private; + struct cts_device *cts_dev; + u8 *rawdata; + s16 *data; + u8 hwrows, hwcols, fwrows, fwcols; + u8 i, j; + + cts_dev = &cts_data->cts_dev; + hwrows = cts_dev->hwdata->num_row; + hwcols = cts_dev->hwdata->num_col; + fwrows = cts_dev->fwdata.rows; + fwcols = cts_dev->fwdata.cols; + + rawdata = kzalloc(hwrows * hwcols * 2, GFP_KERNEL); + if (rawdata == NULL) { + cts_err("Allocate rawdata failed"); + return -ENOMEM; + } + + cts_lock_device(cts_dev); + cts_tcs_top_get_real_diff(cts_dev, rawdata, hwrows * hwcols * 2, 0); + cts_unlock_device(cts_dev); + + data = (s16 *)rawdata; + for (i = 0; i < fwcols; i++) { + for (j = 0; j < fwrows; j++) { + seq_printf(m, "%5d", *data++); + } + seq_printf(m, "\n"); + } + + kfree(rawdata); + return 0; +} +static int cts_diffdata_open(struct inode *inode, struct file *file) +{ + return single_open(file, cts_diffdata_show, pde_data(inode)); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) +static const struct file_operations cts_diffdata_ops = { + .owner = THIS_MODULE, + .open = cts_diffdata_open, + .read = seq_read, +}; +#else +static const struct proc_ops cts_diffdata_ops = { + .proc_open = cts_diffdata_open, + .proc_read = seq_read, +}; +#endif + +static int cts_manual_show(struct seq_file *m, void *v) +{ + struct chipone_ts_data *cts_data = m->private; + struct cts_device *cts_dev; + u8 *rawdata; + s16 *data; + u8 hwrows, hwcols, fwrows, fwcols; + u8 i, j; + + cts_dev = &cts_data->cts_dev; + hwrows = cts_dev->hwdata->num_row; + hwcols = cts_dev->hwdata->num_col; + fwrows = cts_dev->fwdata.rows; + fwcols = cts_dev->fwdata.cols; + + rawdata = kzalloc(hwrows * hwcols * 2, GFP_KERNEL); + if (rawdata == NULL) { + cts_err("Allocate rawdata failed"); + return -ENOMEM; + } + + cts_lock_device(cts_dev); + cts_tcs_top_get_manual_diff(cts_dev, rawdata, hwrows * hwcols * 2, 0); + cts_unlock_device(cts_dev); + + data = (s16 *)rawdata; + for (i = 0; i < fwcols; i++) { + for (j = 0; j < fwrows; j++) { + seq_printf(m, "%5d", *data++); + } + seq_printf(m, "\n"); + } + + kfree(rawdata); + return 0; +} +static int cts_manual_open(struct inode *inode, struct file *file) +{ + return single_open(file, cts_manual_show, pde_data(inode)); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) +static const struct file_operations cts_manual_ops = { + .owner = THIS_MODULE, + .open = cts_manual_open, + .read = seq_read, +}; +#else +static const struct proc_ops cts_manual_ops = { + .proc_open = cts_manual_open, + .proc_read = seq_read, +}; +#endif + +static int cts_cnegdata_show(struct seq_file *m, void *v) +{ + struct chipone_ts_data *cts_data = m->private; + struct cts_device *cts_dev; + u8 *rawdata; + u8 *data; + u8 hwrows, hwcols, fwrows, fwcols; + u8 i, j; + + cts_dev = &cts_data->cts_dev; + hwrows = cts_dev->hwdata->num_row; + hwcols = cts_dev->hwdata->num_col; + fwrows = cts_dev->fwdata.rows; + fwcols = cts_dev->fwdata.cols; + + rawdata = kzalloc(hwrows * hwcols, GFP_KERNEL); + if (rawdata == NULL) { + cts_err("Allocate rawdata failed"); + return -ENOMEM; + } + + cts_lock_device(cts_dev); + cts_tcs_top_get_cnegdata(cts_dev, rawdata, hwrows * hwcols, 0); + cts_unlock_device(cts_dev); + + data = (u8 *)rawdata; + for (i = 0; i < fwcols; i++) { + for (j = 0; j < fwrows; j++) { + seq_printf(m, "%5d", *data++); + } + seq_printf(m, "\n"); + } + + kfree(rawdata); + return 0; +} + +static int cts_cnegdata_open(struct inode *inode, struct file *file) +{ + return single_open(file, cts_cnegdata_show, pde_data(inode)); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) +static const struct file_operations cts_cnegdata_ops = { + .owner = THIS_MODULE, + .open = cts_cnegdata_open, + .read = seq_read, +}; +#else +static const struct proc_ops cts_cnegdata_ops = { + .proc_open = cts_cnegdata_open, + .proc_read = seq_read, +}; +#endif + +/* Use limit bin */ +static int cts_limit_show(struct seq_file *m, void *v) +{ + struct chipone_ts_data *cts_data = m->private; + struct cts_device *cts_dev = &cts_data->cts_dev; + const struct firmware *limit_fw = NULL; + struct cts_limit limit; + char header[7] = {'C', 'h', 'i', 'p', 'o', 'n', 'e'}; + int ret; + + ret = request_firmware(&limit_fw, CFG_CTS_FACTORY_LIMIT_FILENAME, + &cts_dev->pdata->ts_input_dev->dev); + if (ret) { + seq_printf(m, "Request %s failed!\n", CFG_CTS_FACTORY_LIMIT_FILENAME); + return 0; + } + + ret = memcmp(header, limit_fw->data, sizeof(header)); + if (ret) { + seq_printf(m, "LIMIT file was not matched\n"); + release_firmware(limit_fw); + return 0; + } else { + cts_info("Matched limit file!"); + } + + memcpy(&limit, limit_fw->data, limit_fw->size); + release_firmware(limit_fw); + + seq_printf(m, "Limit version: %x, type:%d\n", limit.version, limit.type); + seq_printf(m, "\n"); + seq_printf(m, "Switch status:\n"); + seq_printf(m, " int_item : %d\n", limit.int_item); + seq_printf(m, " reset_item : %d\n", limit.reset_item); + seq_printf(m, " normal_rawdata_item : %d\n", limit.normal_rawdata_item); + seq_printf(m, " normal_noise_item : %d\n", limit.normal_noise_item); + seq_printf(m, " open_item : %d\n", limit.open_item); + seq_printf(m, " short_item : %d\n", limit.short_item); + seq_printf(m, " comp_cap_item : %d\n", limit.comp_cap_item); + seq_printf(m, " gstr_rawdata_item : %d\n", limit.gstr_rawdata_item); + seq_printf(m, " gstrlp_rawdata_item : %d\n", limit.gstrlp_rawdata_item); + seq_printf(m, " gstr_noise_item : %d\n", limit.gstr_noise_item); + seq_printf(m, " gstrlp_noise_item : %d\n", limit.gstrlp_noise_item); + seq_printf(m, " stylus_rawdata_item : %d\n", limit.stylus_rawdata_item); + seq_printf(m, " stylus_noise_item : %d\n", limit.stylus_noise_item); + seq_printf(m, " stylus_mnt_rawdata_item : %d\n", limit.stylus_mnt_rawdata_item); + seq_printf(m, "\n"); + seq_printf(m, "Threshold:\n"); + seq_printf(m, " normal_rawdata_frames : %d\n", limit.normal_rawdata_frames); + seq_printf(m, " normal_rawdata_min : %d\n", limit.normal_rawdata_min); + seq_printf(m, " normal_rawdata_max : %d\n", limit.normal_rawdata_max); + seq_printf(m, " normal_noise_frames : %d\n", limit.normal_noise_frames); + seq_printf(m, " normal_noise_max : %d\n", limit.normal_noise_max); + seq_printf(m, " normal_open_min : %d\n", limit.normal_open_min); + seq_printf(m, " normal_short_min : %d\n", limit.normal_short_min); + seq_printf(m, " normal_compcap_min : %d\n", limit.normal_compcap_min); + seq_printf(m, " normal_compcap_max : %d\n", limit.normal_compcap_max); + seq_printf(m, " gstr_rawdata_frames : %d\n", limit.gstr_rawdata_frames); + seq_printf(m, " gstr_rawdata_min : %d\n", limit.gstr_rawdata_min); + seq_printf(m, " gstr_rawdata_max : %d\n", limit.gstr_rawdata_max); + seq_printf(m, " gstrlp_rawdata_frames : %d\n", limit.gstrlp_rawdata_frames); + seq_printf(m, " gstrlp_rawdata_min : %d\n", limit.gstrlp_rawdata_min); + seq_printf(m, " gstrlp_rawdata_max : %d\n", limit.gstrlp_rawdata_max); + seq_printf(m, " gstr_noise_frames : %d\n", limit.gstr_noise_frames); + seq_printf(m, " gstr_noise_max : %d\n", limit.gstr_noise_max); + seq_printf(m, " gstrlp_noise_frames : %d\n", limit.gstrlp_noise_frames); + seq_printf(m, " gstrlp_noise_max : %d\n", limit.gstrlp_noise_max); + + seq_printf(m, " stylus_rawdata_frames : %d\n", limit.stylus_rawdata_frames); + seq_printf(m, " stylus_rawdata_min : %d\n", limit.stylus_rawdata_min); + seq_printf(m, " stylus_rawdata_max : %d\n", limit.stylus_rawdata_max); + seq_printf(m, " stylus_noise_frames : %d\n", limit.stylus_noise_frames); + seq_printf(m, " stylus_noise_max : %d\n", limit.stylus_noise_max); + seq_printf(m, " stylus_mnt_rawdata_frames : %d\n", limit.stylus_mnt_rawdata_frames); + seq_printf(m, " stylus_mnt_rawdata_min : %d\n", limit.stylus_mnt_rawdata_min); + seq_printf(m, " stylus_mnt_rawdata_max : %d\n", limit.stylus_mnt_rawdata_max); + + return 0; +} +static int cts_limit_open(struct inode *inode, struct file *file) +{ + return single_open(file, cts_limit_show, pde_data(inode)); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) +static const struct file_operations cts_limit_ops = { + .owner = THIS_MODULE, + .open = cts_limit_open, + .read = seq_read, +}; +#else +static const struct proc_ops cts_limit_ops = { + .proc_open = cts_limit_open, + .proc_read = seq_read, +}; +#endif + +static void cts_print_limit_value(struct cts_limit limit) +{ + cts_info("limit version : %x", limit.version); + cts_info("int_item : %d", limit.int_item); + cts_info("reset_item : %d", limit.reset_item); + cts_info("normal_rawdata_item : %d", limit.normal_rawdata_item); + cts_info("normal_noise_item : %d", limit.normal_noise_item); + cts_info("open_item : %d", limit.open_item); + cts_info("short_item : %d", limit.short_item); + cts_info("comp_cap_item : %d", limit.comp_cap_item); + cts_info("gstr_rawdata_item : %d", limit.gstr_rawdata_item); + cts_info("gstrlp_rawdata_item : %d", limit.gstrlp_rawdata_item); + cts_info("gstr_noise_item : %d", limit.gstr_noise_item); + cts_info("gstrlp_noise_item : %d", limit.gstrlp_noise_item); + cts_info("stylus_rawdata_item : %d", limit.stylus_rawdata_item); + cts_info("stylus_noise_item : %d", limit.stylus_noise_item); + cts_info("stylus_mnt_raw_item : %d", limit.stylus_mnt_rawdata_item); + cts_info("type : %d", limit.type); + cts_info("----------------------------"); + cts_info("normal_rawdata_frames : %d", limit.normal_rawdata_frames); + cts_info("normal_rawdata_min : %d", limit.normal_rawdata_min); + cts_info("normal_rawdata_max : %d", limit.normal_rawdata_max); + cts_info("normal_noise_frames : %d", limit.normal_noise_frames); + cts_info("normal_noise_max : %d", limit.normal_noise_max); + cts_info("normal_open_min : %d", limit.normal_open_min); + cts_info("normal_short_min : %d", limit.normal_short_min); + cts_info("normal_compcap_min : %d", limit.normal_compcap_min); + cts_info("normal_compcap_max : %d", limit.normal_compcap_max); + cts_info("gstr_rawdata_frames : %d", limit.gstr_rawdata_frames); + cts_info("gstr_rawdata_min : %d", limit.gstr_rawdata_min); + cts_info("gstr_rawdata_max : %d", limit.gstr_rawdata_max); + cts_info("gstrlp_rawdata_frames : %d", limit.gstrlp_rawdata_frames); + cts_info("gstrlp_rawdata_min : %d", limit.gstrlp_rawdata_min); + cts_info("gstrlp_rawdata_max : %d", limit.gstrlp_rawdata_max); + cts_info("gstr_noise_frames : %d", limit.gstr_noise_frames); + cts_info("gstr_noise_max : %d", limit.gstr_noise_max); + cts_info("gstrlp_noise_frames : %d", limit.gstrlp_noise_frames); + cts_info("gstrlp_noise_max : %d", limit.gstrlp_noise_max); + cts_info("stylus_rawdata_frames : %d", limit.stylus_rawdata_frames); + cts_info("stylus_rawdata_min : %d", limit.stylus_rawdata_min); + cts_info("stylus_rawdata_max : %d", limit.stylus_rawdata_max); + cts_info("stylus_noise_frames : %d", limit.stylus_noise_frames); + cts_info("stylus_noise_max : %d", limit.stylus_noise_max); + cts_info("stylus_mnt_raw_frames : %d", limit.stylus_mnt_rawdata_frames); + cts_info("stylus_mnt_raw_min : %d", limit.stylus_mnt_rawdata_min); + cts_info("stylus_mnt_raw_max : %d", limit.stylus_mnt_rawdata_max); +} + + +static int cts_touch_test(struct seq_file *m, void *v, struct cts_limit limit) +{ + struct chipone_ts_data *cts_data = m->private; + struct cts_device *cts_dev = &cts_data->cts_dev; + + s64 reset_pin_test_elapsed_time = 0; + struct cts_test_param reset_pin_test_param = { + .test_item = CTS_TEST_RESET_PIN, + .flags = 0, + .elapsed_time_ms = &reset_pin_test_elapsed_time, + }; + s64 int_pin_test_elapsed_time = 0; + struct cts_test_param int_pin_test_param = { + .test_item = CTS_TEST_INT_PIN, + .flags = 0, + .elapsed_time_ms = &int_pin_test_elapsed_time, + }; + struct cts_rawdata_test_priv_param rawdata_test_priv_param = { + .frames = 16, + //.work_mode = 0, + }; + s64 rawdata_test_elapsed_time = 0; + struct cts_test_param rawdata_test_param = { + .test_item = CTS_TEST_RAWDATA, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = NULL, + .num_invalid_node = 0, + .invalid_nodes = NULL, + .elapsed_time_ms = &rawdata_test_elapsed_time, + .priv_param = &rawdata_test_priv_param, + .priv_param_size = sizeof(rawdata_test_priv_param), + }; + struct cts_noise_test_priv_param noise_test_priv_param = { + .frames = 50, + //.work_mode = 0, + }; + s64 noise_test_elapsed_time = 0; + struct cts_test_param noise_test_param = { + .test_item = CTS_TEST_NOISE, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = NULL, + .num_invalid_node = 0, + .invalid_nodes = NULL, + .elapsed_time_ms = &noise_test_elapsed_time, + .priv_param = &noise_test_priv_param, + .priv_param_size = sizeof(noise_test_priv_param), + }; + s64 open_test_elapsed_time = 0; + struct cts_test_param open_test_param = { + .test_item = CTS_TEST_OPEN, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = NULL, + .num_invalid_node = 0, + .invalid_nodes = NULL, + .elapsed_time_ms = &open_test_elapsed_time, + }; + s64 short_test_elapsed_time = 0; + struct cts_test_param short_test_param = { + .test_item = CTS_TEST_SHORT, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = NULL, + .num_invalid_node = 0, + .invalid_nodes = NULL, + .elapsed_time_ms = &short_test_elapsed_time, + }; + s64 comp_cap_test_elapsed_time = 0; + struct cts_test_param comp_cap_test_param = { + .test_item = CTS_TEST_COMPENSATE_CAP, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = NULL, + .num_invalid_node = 0, + .invalid_nodes = NULL, + .elapsed_time_ms = &comp_cap_test_elapsed_time, + }; + + int normal_rawdata_frames; + int normal_rawdata_min; + int normal_rawdata_max; + int normal_noise_frames; + int normal_noise_max; + int normal_open_min; + int normal_short_min; + int normal_compcap_min; + int normal_compcap_max; + + int rawdata_test_result = 0; + int noise_test_result = 0; + int open_test_result = 0; + int short_test_result = 0; + int comp_cap_test_result = 0; + + char touch_data_filepath[256]; + int retry; + int result_all = 0; + + snprintf(touch_data_filepath, sizeof(touch_data_filepath), + TOUCH_DATA_DIRECTORY_PREFIX"/"RAWDATA_TEST_DATA_FILENAME); + rawdata_test_param.test_data_filepath = + kstrdup(touch_data_filepath, GFP_KERNEL); + snprintf(touch_data_filepath, sizeof(touch_data_filepath), + TOUCH_DATA_DIRECTORY_PREFIX"/"NOISE_TEST_DATA_FILENAME); + noise_test_param.test_data_filepath = + kstrdup(touch_data_filepath, GFP_KERNEL); + snprintf(touch_data_filepath, sizeof(touch_data_filepath), + TOUCH_DATA_DIRECTORY_PREFIX"/"OPEN_TEST_DATA_FILENAME); + open_test_param.test_data_filepath = + kstrdup(touch_data_filepath, GFP_KERNEL); + snprintf(touch_data_filepath, sizeof(touch_data_filepath), + TOUCH_DATA_DIRECTORY_PREFIX"/"SHORT_TEST_DATA_FILENAME); + short_test_param.test_data_filepath = + kstrdup(touch_data_filepath, GFP_KERNEL); + snprintf(touch_data_filepath, sizeof(touch_data_filepath), + TOUCH_DATA_DIRECTORY_PREFIX"/"COMP_CAP_TEST_DATA_FILENAME); + comp_cap_test_param.test_data_filepath = + kstrdup(touch_data_filepath, GFP_KERNEL); + + normal_rawdata_frames = limit.normal_rawdata_frames; + normal_rawdata_min = limit.normal_rawdata_min; + normal_rawdata_max = limit.normal_rawdata_max; + normal_noise_frames = limit.normal_noise_frames; + normal_noise_max = limit.normal_noise_max; + normal_open_min = limit.normal_open_min; + normal_short_min = limit.normal_short_min; + normal_compcap_min = limit.normal_compcap_min; + normal_compcap_max = limit.normal_compcap_max; + + rawdata_test_priv_param.frames = normal_rawdata_frames; + rawdata_test_param.min = &normal_rawdata_min; + rawdata_test_param.max = &normal_rawdata_max; + noise_test_priv_param.frames = normal_noise_frames; + noise_test_param.max = &normal_noise_max; + open_test_param.min = &normal_open_min; + short_test_param.min = &normal_short_min; + comp_cap_test_param.min = &normal_compcap_min; + comp_cap_test_param.max = &normal_compcap_max; + + if (limit.int_item && cts_test_int_pin(cts_dev, &int_pin_test_param)) { + result_all++; + } + if (limit.reset_item && cts_test_reset_pin(cts_dev, &reset_pin_test_param)) { + result_all++; + } + if (limit.normal_rawdata_item) { + retry = 3; + do { + rawdata_test_result = cts_test_rawdata(cts_dev, &rawdata_test_param); + } while (rawdata_test_result < 0 && retry--); + if (rawdata_test_result) { + result_all++; + } + } + if (limit.normal_noise_item) { + retry = 3; + do { + noise_test_result = cts_test_noise(cts_dev, &noise_test_param); + } while (noise_test_result < 0 && retry--); + if (noise_test_result) { + result_all++; + } + } + if (limit.open_item) { + retry = 3; + do { + open_test_result = cts_test_open(cts_dev, &open_test_param); + } while (open_test_result < 0 && retry--); + if (open_test_result) { + result_all++; + } + } + if (limit.short_item) { + retry = 3; + do { + short_test_result = cts_test_short(cts_dev, &short_test_param); + } while (short_test_result < 0 && retry--); + if (short_test_result) { + result_all++; + } + } + if (limit.comp_cap_item) { + retry = 3; + do { + comp_cap_test_result = + cts_test_compensate_cap(cts_dev, &comp_cap_test_param); + } while (comp_cap_test_result < 0 && retry--); + if (comp_cap_test_result) { + result_all++; + } + } + + if (rawdata_test_param.test_data_filepath) { + kfree(rawdata_test_param.test_data_filepath); + } + if (noise_test_param.test_data_filepath) { + kfree(noise_test_param.test_data_filepath); + } + if (open_test_param.test_data_filepath) { + kfree(open_test_param.test_data_filepath); + } + if (short_test_param.test_data_filepath) { + kfree(short_test_param.test_data_filepath); + } + if (comp_cap_test_param.test_data_filepath) { + kfree(comp_cap_test_param.test_data_filepath); + } + + return result_all; +} + +static int cts_stylus_test(struct seq_file *m, void *v, struct cts_limit limit) +{ + struct chipone_ts_data *cts_data = m->private; + struct cts_device *cts_dev = &cts_data->cts_dev; + + struct cts_rawdata_test_priv_param stylus_rawdata_test_priv_param = { + .frames = 16, + //.work_mode = 0, + }; + s64 stylus_rawdata_test_elapsed_time = 0; + struct cts_test_param stylus_rawdata_test_param = { + .test_item = CTS_TEST_STYLUS_RAWDATA, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = NULL, + .num_invalid_node = 0, + .invalid_nodes = NULL, + .elapsed_time_ms = &stylus_rawdata_test_elapsed_time, + .priv_param = &stylus_rawdata_test_priv_param, + .priv_param_size = sizeof(stylus_rawdata_test_priv_param), + }; + + struct cts_noise_test_priv_param stylus_noise_test_priv_param = { + .frames = 50, + //.work_mode = 0, + }; + s64 stylus_noise_test_elapsed_time = 0; + struct cts_test_param stylus_noise_test_param = { + .test_item = CTS_TEST_NOISE, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = NULL, + .num_invalid_node = 0, + .invalid_nodes = NULL, + .elapsed_time_ms = &stylus_noise_test_elapsed_time, + .priv_param = &stylus_noise_test_priv_param, + .priv_param_size = sizeof(stylus_noise_test_priv_param), + }; + + struct cts_rawdata_test_priv_param stylus_mnt_rawdata_test_priv_param = { + .frames = 16, + //.work_mode = 0, + }; + s64 stylus_mnt_rawdata_test_elapsed_time = 0; + struct cts_test_param stylus_mnt_rawdata_test_param = { + .test_item = CTS_TEST_STYLUS_RAWDATA, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = NULL, + .num_invalid_node = 0, + .invalid_nodes = NULL, + .elapsed_time_ms = &stylus_mnt_rawdata_test_elapsed_time, + .priv_param = &stylus_mnt_rawdata_test_priv_param, + .priv_param_size = sizeof(stylus_mnt_rawdata_test_priv_param), + }; + + int stylus_rawdata_frames; + int stylus_rawdata_min; + int stylus_rawdata_max; + int stylus_noise_frames; + int stylus_noise_max; + int stylus_mnt_rawdata_frames; + int stylus_mnt_rawdata_min; + int stylus_mnt_rawdata_max; + + int stylus_rawdata_test_result = 0; + int stylus_noise_test_result = 0; + int stylus_mnt_rawdata_test_result = 0; + + char touch_data_filepath[256]; + int retry; + int result_all = 0; + + snprintf(touch_data_filepath, sizeof(touch_data_filepath), + TOUCH_DATA_DIRECTORY_PREFIX"/"STYLUS_RAWDATA_TEST_DATA_FILENAME); + stylus_rawdata_test_param.test_data_filepath = + kstrdup(touch_data_filepath, GFP_KERNEL); + snprintf(touch_data_filepath, sizeof(touch_data_filepath), + TOUCH_DATA_DIRECTORY_PREFIX"/"STYLUS_NOISE_TEST_DATA_FILENAME); + stylus_noise_test_param.test_data_filepath = + kstrdup(touch_data_filepath, GFP_KERNEL); + snprintf(touch_data_filepath, sizeof(touch_data_filepath), + TOUCH_DATA_DIRECTORY_PREFIX"/"STYLUS_MNT_NOISE_TEST_DATA_FILENAME); + stylus_mnt_rawdata_test_param.test_data_filepath = + kstrdup(touch_data_filepath, GFP_KERNEL); + + stylus_rawdata_frames = limit.stylus_rawdata_frames; + stylus_rawdata_min = limit.stylus_rawdata_min; + stylus_rawdata_max = limit.stylus_rawdata_max; + stylus_noise_frames = limit.stylus_noise_frames; + stylus_noise_max = limit.stylus_noise_max; + stylus_mnt_rawdata_frames = limit.stylus_mnt_rawdata_frames; + stylus_mnt_rawdata_min = limit.stylus_mnt_rawdata_min; + stylus_mnt_rawdata_max = limit.stylus_mnt_rawdata_max; + + stylus_rawdata_test_priv_param.frames = stylus_rawdata_frames; + stylus_rawdata_test_param.min = &stylus_rawdata_min; + stylus_rawdata_test_param.max = &stylus_rawdata_max; + stylus_noise_test_priv_param.frames = stylus_noise_frames; + stylus_noise_test_param.max = &stylus_noise_max; + stylus_mnt_rawdata_test_priv_param.frames = stylus_mnt_rawdata_frames; + stylus_mnt_rawdata_test_param.min = &stylus_mnt_rawdata_min; + stylus_mnt_rawdata_test_param.max = &stylus_mnt_rawdata_max; + + if (limit.stylus_rawdata_item) { + retry = 3; + do { + stylus_rawdata_test_result = + cts_test_stylus_rawdata(cts_dev, &stylus_rawdata_test_param); + } while (stylus_rawdata_test_result < 0 && retry--); + if (stylus_rawdata_test_result) { + result_all++; + } + } + if (limit.stylus_noise_item) { + retry = 3; + do { + stylus_noise_test_result = + cts_test_stylus_noise(cts_dev, &stylus_noise_test_param); + } while (stylus_noise_test_result < 0 && retry--); + if (stylus_noise_test_result) { + result_all++; + } + } + if (limit.stylus_mnt_rawdata_item) { + retry = 3; + do { + stylus_mnt_rawdata_test_result = + cts_test_stylus_mnt_rawdata(cts_dev, &stylus_mnt_rawdata_test_param); + } while (stylus_mnt_rawdata_test_result < 0 && retry--); + if (stylus_mnt_rawdata_test_result) { + result_all++; + } + } + + if (stylus_rawdata_test_param.test_data_filepath) { + kfree(stylus_rawdata_test_param.test_data_filepath); + } + if (stylus_noise_test_param.test_data_filepath) { + kfree(stylus_noise_test_param.test_data_filepath); + } + if (stylus_mnt_rawdata_test_param.test_data_filepath) { + kfree(stylus_mnt_rawdata_test_param.test_data_filepath); + } + + return result_all; +} + + +static int cts_factory_test_show(struct seq_file *m, void *v) +{ + struct chipone_ts_data *cts_data = m->private; + struct cts_device *cts_dev = &cts_data->cts_dev; + const struct firmware *limit_fw = NULL; + struct cts_limit limit; + char header[7] = {'C', 'h', 'i', 'p', 'o', 'n', 'e'}; + + ktime_t start_time, end_time, delta_time; + u16 fw_version; + int result_all = 0; + int ret; + + ret = request_firmware(&limit_fw, CFG_CTS_FACTORY_LIMIT_FILENAME, + &cts_dev->pdata->ts_input_dev->dev); + if (ret) { + cts_err("Request %s failed!", CFG_CTS_FACTORY_LIMIT_FILENAME); + return 0; + } + + ret = memcmp(header, limit_fw->data, sizeof(header)); + if (ret) { + cts_err("LIMIT file was not matched"); + release_firmware(limit_fw); + return 0; + } else { + cts_info("Matched limit file!"); + } + + memcpy(&limit, limit_fw->data, limit_fw->size); + release_firmware(limit_fw); + + cts_print_limit_value(limit); + + start_time = ktime_get(); + + ret = cts_tcs_get_fw_ver(cts_dev, &fw_version); + if (ret) { + cts_err("Factory test get firmware version failed"); + fw_version = 0; + } + cts_info("Firmware Version: 0x%04X", fw_version); + + result_all += cts_touch_test(m, v, limit); + result_all += cts_stylus_test(m, v, limit); + + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + + cts_info("Factory test, total ELAPSED TIME: %lldms", + ktime_to_ms(delta_time)); + + seq_printf(m, "%s\n", result_all ? "FAIL" : "PASS"); + return 0; +} + +static int cts_factory_test_open(struct inode *inode, struct file *file) +{ + return single_open(file, cts_factory_test_show, pde_data(inode)); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) +static const struct file_operations cts_factory_test_ops = { + .owner = THIS_MODULE, + .open = cts_factory_test_open, + .read = seq_read, +}; +#else +static const struct proc_ops cts_factory_test_ops = { + .proc_open = cts_factory_test_open, + .proc_read = seq_read, +}; +#endif + + +int cts_oem_init(struct chipone_ts_data *cts_data) +{ + struct cts_oem_data *oem_data = NULL; + int ret; + + if (cts_data == NULL) { + cts_err("Init with cts_data = NULL"); + return -EINVAL; + } + + cts_info("Init"); + + cts_data->oem_data = NULL; + + oem_data = kzalloc(sizeof(*oem_data), GFP_KERNEL); + if (oem_data == NULL) { + cts_err("Alloc oem data failed"); + return -ENOMEM; + } + + cts_info(" - Create '/proc/"OEM_SELFTEST_PROC_FILENAME"'"); + oem_data->selftest_proc_entry = + proc_create_data(OEM_SELFTEST_PROC_FILENAME, + S_IRUGO, NULL, &selftest_proc_fops, cts_data); + if (oem_data->selftest_proc_entry == NULL) { + cts_err("Create '/proc/"OEM_SELFTEST_PROC_FILENAME"' failed"); + ret = -EFAULT; + goto free_oem_data; + } + + oem_data->limit_entry = + proc_create_data(OEM_LIMIT_PROC_FILENAME, + S_IRUGO, NULL, &cts_limit_ops, cts_data); + if (oem_data->limit_entry == NULL) { + cts_err("Create '/proc/"OEM_LIMIT_PROC_FILENAME"' failed"); + ret = -EFAULT; + goto free_oem_data; + } + + oem_data->factory_test_entry = + proc_create_data(OEM_FACTORY_TEST_PROC_FILENAME, + S_IRUGO, NULL, &cts_factory_test_ops, cts_data); + if (oem_data->factory_test_entry == NULL) { + cts_err("Create '/proc/"OEM_FACTORY_TEST_PROC_FILENAME"' failed"); + ret = -EFAULT; + goto free_oem_data; + } + + oem_data->rawdata_proc_entry = proc_create_data(OEM_RAWDATA_PROC_FILENAME, + S_IRUGO, NULL, &cts_rawdata_ops, cts_data); + if (oem_data->rawdata_proc_entry == NULL) { + cts_err("Create '/proc/"OEM_RAWDATA_PROC_FILENAME"' failed"); + ret = -EFAULT; + goto free_oem_data; + } + + oem_data->manual_proc_entry = proc_create_data(OEM_MANUAL_PROC_FILENAME, + S_IRUGO, NULL, &cts_manual_ops, cts_data); + if (oem_data->manual_proc_entry == NULL) { + cts_err("Create '/proc/"OEM_MANUAL_PROC_FILENAME"' failed"); + ret = -EFAULT; + goto free_oem_data; + } + + oem_data->diffdata_proc_entry = proc_create_data(OEM_DIFFDATA_PROC_FILENAME, + S_IRUGO, NULL, &cts_diffdata_ops, cts_data); + if (oem_data->diffdata_proc_entry == NULL) { + cts_err("Create '/proc/"OEM_DIFFDATA_PROC_FILENAME"' failed"); + ret = -EFAULT; + goto free_oem_data; + } + + oem_data->cnegdata_proc_entry = proc_create_data(OEM_CNEGDATA_PROC_FILENAME, + S_IRUGO, NULL, &cts_cnegdata_ops, cts_data); + if (oem_data->cnegdata_proc_entry == NULL) { + cts_err("Create '/proc/"OEM_CNEGDATA_PROC_FILENAME"' failed"); + ret = -EFAULT; + goto free_oem_data; + } + + cts_data->oem_data = oem_data; + oem_data->cts_data = cts_data; + return 0; + +free_oem_data: + kfree(oem_data); + return ret; +} + +int cts_oem_deinit(struct chipone_ts_data *cts_data) +{ + struct cts_oem_data *oem_data = NULL; + + if (cts_data == NULL) { + cts_err("Deinit with cts_data = NULL"); + return -EINVAL; + } + + if (cts_data->oem_data == NULL) { + cts_warn("Deinit with oem_data = NULL"); + return 0; + } + + cts_info("Deinit"); + + oem_data = cts_data->oem_data; + + if (oem_data->cnegdata_proc_entry) { + cts_info(" Remove '/proc/"OEM_CNEGDATA_PROC_FILENAME"'"); + remove_proc_entry(OEM_CNEGDATA_PROC_FILENAME, NULL); + } + + if (oem_data->selftest_proc_entry) { + cts_info(" Remove '/proc/"OEM_SELFTEST_PROC_FILENAME"'"); + remove_proc_entry(OEM_SELFTEST_PROC_FILENAME, NULL); + } + + if (oem_data->limit_entry) { + cts_info(" Remove '/proc/"OEM_LIMIT_PROC_FILENAME"'"); + remove_proc_entry(OEM_LIMIT_PROC_FILENAME, NULL); + } + if (oem_data->factory_test_entry) { + cts_info(" Remove '/proc/"OEM_FACTORY_TEST_PROC_FILENAME"'"); + remove_proc_entry(OEM_FACTORY_TEST_PROC_FILENAME, NULL); + } + + if (oem_data->rawdata_proc_entry) { + cts_info(" Remove '/proc/"OEM_RAWDATA_PROC_FILENAME"'"); + remove_proc_entry(OEM_RAWDATA_PROC_FILENAME, NULL); + } + + if (oem_data->manual_proc_entry) { + cts_info(" Remove '/proc/"OEM_MANUAL_PROC_FILENAME"'"); + remove_proc_entry(OEM_MANUAL_PROC_FILENAME, NULL); + } + + if (oem_data->diffdata_proc_entry) { + cts_info(" Remove '/proc/"OEM_DIFFDATA_PROC_FILENAME"'"); + remove_proc_entry(OEM_DIFFDATA_PROC_FILENAME, NULL); + } + + free_selftest_data_mem(oem_data); + + kfree(cts_data->oem_data); + cts_data->oem_data = NULL; + + return 0; +} + diff --git a/drivers/input/touchscreen/chipone_tddi/cts_oem.h b/drivers/input/touchscreen/chipone_tddi/cts_oem.h new file mode 100644 index 000000000000..ced0349f96c0 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_oem.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CTS_OEM_H +#define CTS_OEM_H + +struct chipone_ts_data; + +extern int cts_oem_init(struct chipone_ts_data *cts_data); +extern int cts_oem_deinit(struct chipone_ts_data *cts_data); + +#endif /* CTS_VENDOR_H */ + diff --git a/drivers/input/touchscreen/chipone_tddi/cts_platform.c b/drivers/input/touchscreen/chipone_tddi/cts_platform.c new file mode 100644 index 000000000000..4b4dd1bc535c --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_platform.c @@ -0,0 +1,1919 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define LOG_TAG "Plat" + +#include "cts_config.h" +#include "cts_platform.h" +#include "cts_core.h" +#include "cts_firmware.h" +#include "cts_sysfs.h" +#include "cts_tcs.h" + +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +int tpd_rst_gpio_index = 0; +int tpd_int_gpio_index = 1; +static int tpd_history_x, tpd_history_y; +#endif + +#ifdef CFG_CTS_HAS_RESET_PIN +int cts_plat_set_reset(struct cts_platform_data *pdata, int val) +{ + cts_info("Set Reset to %s", val ? "HIGH" : "LOW"); +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + if (val) + tpd_gpio_output(tpd_rst_gpio_index, 1); + else + tpd_gpio_output(tpd_rst_gpio_index, 0); +#else + if (val) + gpio_set_value(pdata->rst_gpio, 1); + else + gpio_set_value(pdata->rst_gpio, 0); + +#endif + return 0; +} + +int cts_plat_reset_device(struct cts_platform_data *pdata) +{ + /* !!!can not be modified */ + /* !!!can not be modified */ + /* !!!can not be modified */ + cts_plat_set_reset(pdata, 1); + mdelay(1); + cts_plat_set_reset(pdata, 0); + mdelay(5);/* 1ms */ + cts_plat_set_reset(pdata, 1); + /* under normal mode, delay over 50ms */ +#ifdef CONFIG_CTS_I2C_HOST + mdelay(120); +#else + mdelay(70); +#endif + return 0; +} +#endif /* CFG_CTS_HAS_RESET_PIN */ + +#ifdef CFG_CTS_MANUAL_CS +int cts_plat_set_cs(struct cts_platform_data *pdata, int val) +{ +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + if (val) + pinctrl_select_state(pdata->pinctrl1, pdata->spi_cs_high); + else + pinctrl_select_state(pdata->pinctrl1, pdata->spi_cs_low); +#else + if (val) + gpio_set_value(pdata->cs_gpio, 1); + else + gpio_set_value(pdata->cs_gpio, 0); +#endif + return 0; +} +#endif /* CFG_CTS_MANUAL_CS */ + + +#ifdef CONFIG_CTS_I2C_HOST +size_t cts_plat_get_max_i2c_xfer_size(struct cts_platform_data *pdata) +{ +#ifdef TPD_SUPPORT_I2C_DMA + if (pdata->i2c_dma_available) { + return CFG_CTS_MAX_I2C_XFER_SIZE; + } else { + return CFG_CTS_MAX_I2C_FIFO_XFER_SIZE; + } +#else + return CFG_CTS_MAX_I2C_XFER_SIZE; +#endif +} + +u8 *cts_plat_get_i2c_xfer_buf(struct cts_platform_data *pdata, size_t xfer_size) +{ +#ifdef TPD_SUPPORT_I2C_DMA + if (pdata->i2c_dma_available && xfer_size > CFG_CTS_MAX_I2C_FIFO_XFER_SIZE) { + return pdata->i2c_dma_buff_va; + } else +#endif /* TPD_SUPPORT_I2C_DMA */ + return pdata->i2c_fifo_buf; +} + +int cts_plat_i2c_write(struct cts_platform_data *pdata, u8 i2c_addr, + const void *src, size_t len, int retry, int delay) +{ + int ret = 0, retries = 0; + +#ifdef TPD_SUPPORT_I2C_DMA + struct i2c_msg msg = { + .addr = i2c_addr, + .flags = !I2C_M_RD, + .len = len, + .timing = 300, + }; + + if (pdata->i2c_dma_available && len > CFG_CTS_MAX_I2C_FIFO_XFER_SIZE) { + msg.ext_flag = (pdata->i2c_client->ext_flag | I2C_ENEXT_FLAG | I2C_DMA_FLAG); + msg.buf = (u8 *)pdata->i2c_dma_buff_pa; + memcpy(pdata->i2c_dma_buff_va, src, len); + } else { + msg.buf = (u8 *)src; + } + msg.len = len; +#else /* TPD_SUPPORT_I2C_DMA */ + struct i2c_msg msg = { + .flags = !I2C_M_RD, + .addr = i2c_addr, + .buf = (u8 *) src, + .len = len, + }; +#endif /* TPD_SUPPORT_I2C_DMA */ + + do { + ret = i2c_transfer(pdata->i2c_client->adapter, &msg, 1); + if (ret != 1) { + if (ret >= 0) { + ret = -EIO; + } + + if (delay) { + mdelay(delay); + } + continue; + } else { + return 0; + } + } while (++retries < retry); + + return ret; +} + +int cts_plat_i2c_read(struct cts_platform_data *pdata, u8 i2c_addr, + const u8 *wbuf, size_t wlen, void *rbuf, size_t rlen, + int retry, int delay) +{ + int num_msg, ret = 0, retries = 0; + +#ifdef TPD_SUPPORT_I2C_DMA + struct i2c_msg msgs[2] = { + { + .addr = i2c_addr, + .flags = !I2C_M_RD, + .len = wlen, + .buf = (u8 *)wbuf, + .timing = 300, + }, + { + .addr = i2c_addr, + .flags = I2C_M_RD, + .len = rlen, + .timing = 300, + }, + }; + + if (pdata->i2c_dma_available && rlen > CFG_CTS_MAX_I2C_FIFO_XFER_SIZE) { + msgs[1].ext_flag = (pdata->i2c_client->ext_flag | I2C_ENEXT_FLAG | I2C_DMA_FLAG); + msgs[1].buf = (u8 *)pdata->i2c_dma_buff_pa; + } else { + msgs[1].buf = (u8 *)rbuf; + } +#else /* TPD_SUPPORT_I2C_DMA */ + struct i2c_msg msgs[2] = { + { + .addr = i2c_addr, + .flags = !I2C_M_RD, + .buf = (u8 *) wbuf, + .len = wlen }, + { + .addr = i2c_addr, + .flags = I2C_M_RD, + .buf = (u8 *) rbuf, + .len = rlen } + }; +#endif /* TPD_SUPPORT_I2C_DMA */ + + if (wbuf == NULL || wlen == 0) + num_msg = 1; + else + num_msg = 2; + + do { + ret = i2c_transfer(pdata->i2c_client->adapter, + msgs + ARRAY_SIZE(msgs) - num_msg, num_msg); + + if (ret != num_msg) { + if (ret >= 0) + ret = -EIO; + + if (delay) + mdelay(delay); + continue; + } else { +#ifdef TPD_SUPPORT_I2C_DMA + if (pdata->i2c_dma_available && rlen > CFG_CTS_MAX_I2C_FIFO_XFER_SIZE) { + memcpy(rbuf, pdata->i2c_dma_buff_va, rlen); + } +#endif /* TPD_SUPPORT_I2C_DMA */ + + return 0; + } + } while (++retries < retry); + + return ret; +} + +#else /*CONFIG_CTS_I2C_HOST*/ + +#ifdef CFG_MTK_LEGEND_PLATFORM +struct mt_chip_conf cts_spi_conf_mt65xx = { + .setuptime = 15, + .holdtime = 15, + .high_time = 21, //for mt6582, 104000khz/(4+4) = 130000khz + .low_time = 21, + .cs_idletime = 20, + .ulthgh_thrsh = 0, + + .cpol = 0, + .cpha = 0, + + .rx_mlsb = 1, + .tx_mlsb = 1, + + .tx_endian = 0, + .rx_endian = 0, + + .com_mod = FIFO_TRANSFER, + .pause = 1, + .finish_intr = 1, + .deassert = 0, + .ulthigh = 0, + .tckdly = 0, +}; + +typedef enum { + SPEED_500KHZ = 500, + SPEED_1MHZ = 1000, + SPEED_2MHZ = 2000, + SPEED_3MHZ = 3000, + SPEED_4MHZ = 4000, + SPEED_6MHZ = 6000, + SPEED_8MHZ = 8000, + SPEED_KEEP, + SPEED_UNSUPPORTED +} SPI_SPEED; + +static int cts_plat_spi_set_mode(struct spi_device *spi, SPI_SPEED speed, int flag) +{ + struct mt_chip_conf *mcc = &cts_spi_conf_mt65xx; + int ret; + + if (flag == 0) { + mcc->com_mod = FIFO_TRANSFER; + } else { + mcc->com_mod = DMA_TRANSFER; + } + + switch (speed) { + case SPEED_500KHZ: + mcc->high_time = 120; + mcc->low_time = 120; + break; + case SPEED_1MHZ: + mcc->high_time = 60; + mcc->low_time = 60; + break; + case SPEED_2MHZ: + mcc->high_time = 30; + mcc->low_time = 30; + break; + case SPEED_3MHZ: + mcc->high_time = 20; + mcc->low_time = 20; + break; + case SPEED_4MHZ: + mcc->high_time = 15; + mcc->low_time = 15; + break; + case SPEED_6MHZ: + mcc->high_time = 10; + mcc->low_time = 10; + break; + case SPEED_8MHZ: + mcc->high_time = 8; + mcc->low_time = 8; + break; + case SPEED_KEEP: + case SPEED_UNSUPPORTED: + break; + } + + ret = spi_setup(spi); + if (ret) { + cts_err("Spi setup failed %d(%s)", ret, cts_strerror(ret)); + } + return ret; +} +#endif /* CFG_MTK_LEGEND_PLATFORM */ + +int cts_plat_spi_setup(struct cts_platform_data *pdata) +{ + int ret; + + pdata->spi_client->chip_select = 0; + pdata->spi_client->mode = SPI_MODE_0; + pdata->spi_client->bits_per_word = 8; + + cts_info(" - chip_select :%d", pdata->spi_client->chip_select); + cts_info(" - spi_mode :%d", pdata->spi_client->mode); + cts_info(" - bits_per_word:%d", pdata->spi_client->bits_per_word); + +#ifdef CFG_MTK_LEGEND_PLATFORM + pdata->spi_client->controller_data = (void *)&cts_spi_conf_mt65xx; + ret = spi_setup(pdata->spi_client); + cts_plat_spi_set_mode(pdata->spi_client, pdata->spi_speed, 0); +#else /* CFG_MTK_LEGEND_PLATFORM */ + ret = spi_setup(pdata->spi_client); +#endif /* CFG_MTK_LEGEND_PLATFORM */ + if (ret) { + cts_err("spi_setup err!"); + } + return ret; +} + +int cts_spi_send_recv(struct cts_platform_data *pdata, size_t len, + u8 *tx_buffer, u8 *rx_buffer) +{ + struct chipone_ts_data *cts_data; + struct spi_message msg; + struct spi_transfer cmd = { + .delay_usecs = 0, + .speed_hz = pdata->spi_speed * 1000u, + .tx_buf = tx_buffer, + .rx_buf = rx_buffer, + .len = len, + /* .tx_dma = 0, */ + /* .rx_dma = 0, */ + }; + int ret = 0; + + cts_data = container_of(pdata->cts_dev, struct chipone_ts_data, cts_dev); + +#ifdef CFG_CTS_MANUAL_CS + cts_plat_set_cs(pdata, 0); +#endif + spi_message_init(&msg); + spi_message_add_tail(&cmd, &msg); + ret = spi_sync(cts_data->spi_client, &msg); + if (ret) + cts_err("spi sync failed %d", ret); + +#ifdef CFG_CTS_MANUAL_CS + udelay(100); + cts_plat_set_cs(pdata, 1); +#endif + return ret; +} + +size_t cts_plat_get_max_spi_xfer_size(struct cts_platform_data *pdata) +{ + return CFG_CTS_MAX_SPI_XFER_SIZE; +} + +u8 *cts_plat_get_spi_xfer_buf(struct cts_platform_data *pdata, size_t xfer_size) +{ + return pdata->spi_cache_buf; +} + +int cts_plat_spi_write(struct cts_platform_data *pdata, u8 dev_addr, + const void *src, size_t len, int retry, int delay) +{ + int ret = 0, retries = 0; + u16 crc16_calc; + size_t data_len; + + if (len > CFG_CTS_MAX_SPI_XFER_SIZE) { + cts_err("write too much data:wlen=%zu", len); + return -EIO; + } + + if (pdata->cts_dev->rtdata.program_mode) { + pdata->spi_tx_buf[0] = dev_addr; + memcpy(&pdata->spi_tx_buf[1], src, len); + do { + ret = cts_spi_send_recv(pdata, len + 1, pdata->spi_tx_buf, + pdata->spi_rx_buf); + if (ret) { + cts_err("SPI write failed %d", ret); + if (delay) + mdelay(delay); + } else + return 0; + } while (++retries < retry); + } else { + data_len = len - 2; + pdata->spi_tx_buf[0] = dev_addr; + pdata->spi_tx_buf[1] = *((u8 *) src + 1); + pdata->spi_tx_buf[2] = *((u8 *) src); + put_unaligned_le16(data_len, &pdata->spi_tx_buf[3]); + crc16_calc = (u16) cts_crc32(pdata->spi_tx_buf, 5); + put_unaligned_le16(crc16_calc, &pdata->spi_tx_buf[5]); + memcpy(&pdata->spi_tx_buf[7], (char *)src + 2, data_len); + crc16_calc = (u16) cts_crc32((char *)src + 2, data_len); + put_unaligned_le16(crc16_calc, &pdata->spi_tx_buf[7 + data_len]); + do { + ret = cts_spi_send_recv(pdata, len + 7, pdata->spi_tx_buf, + pdata->spi_rx_buf); + udelay(10 * data_len); + if (ret) { + cts_err("SPI write failed %d", ret); + if (delay) + mdelay(delay); + } else + return 0; + } while (++retries < retry); + } + return ret; +} + +int cts_plat_spi_read(struct cts_platform_data *pdata, u8 dev_addr, + const u8 *wbuf, size_t wlen, void *rbuf, size_t rlen, + int retry, int delay) +{ + int ret = 0, retries = 0; + u16 crc16_calc, crc16_recv; + + if (wlen > CFG_CTS_MAX_SPI_XFER_SIZE + || rlen > CFG_CTS_MAX_SPI_XFER_SIZE) { + cts_err("write/read too much data:wlen=%zd, rlen=%zd", wlen, rlen); + return -EIO; + } + + if (pdata->cts_dev->rtdata.program_mode) { + pdata->spi_tx_buf[0] = dev_addr | 0x01; + memcpy(&pdata->spi_tx_buf[1], wbuf, wlen); + do { + ret = cts_spi_send_recv(pdata, rlen + 5, pdata->spi_tx_buf, + pdata->spi_rx_buf); + if (ret) { + cts_err("SPI read failed %d", ret); + if (delay) + mdelay(delay); + continue; + } + memcpy(rbuf, pdata->spi_rx_buf + 5, rlen); + return 0; + } while (++retries < retry); + } else { + do { + if (wlen != 0) { + pdata->spi_tx_buf[0] = dev_addr | 0x01; + pdata->spi_tx_buf[1] = wbuf[1]; + pdata->spi_tx_buf[2] = wbuf[0]; + put_unaligned_le16(rlen, &pdata->spi_tx_buf[3]); + crc16_calc = (u16) cts_crc32(pdata->spi_tx_buf, 5); + put_unaligned_le16(crc16_calc, &pdata->spi_tx_buf[5]); + ret = cts_spi_send_recv(pdata, 7, pdata->spi_tx_buf, + pdata->spi_rx_buf); + if (ret) { + cts_err("SPI read failed %d", ret); + if (delay) + mdelay(delay); + continue; + } + } + memset(pdata->spi_tx_buf, 0, 7); + pdata->spi_tx_buf[0] = dev_addr | 0x01; + udelay(100); + ret = cts_spi_send_recv(pdata, rlen + 2, + pdata->spi_tx_buf, pdata->spi_rx_buf); + if (ret) { + cts_err("SPI read failed %d", ret); + if (delay) + mdelay(delay); + continue; + } + memcpy(rbuf, pdata->spi_rx_buf, rlen); + crc16_calc = (u16) cts_crc32(pdata->spi_rx_buf, rlen); + crc16_recv = get_unaligned_le16(&pdata->spi_rx_buf[rlen]); + if (crc16_recv != crc16_calc) { + cts_err("SPI RX CRC error: rx_crc %04x != %04x", + crc16_recv, crc16_calc); + continue; + } + return 0; + } while (++retries < retry); + } + if (retries >= retry) + cts_err("SPI read too much retry"); + + return -EIO; +} + +int cts_plat_spi_read_delay_idle(struct cts_platform_data *pdata, u8 dev_addr, + const u8 *wbuf, size_t wlen, void *rbuf, + size_t rlen, int retry, int delay, int idle) +{ + int ret = 0, retries = 0; + u16 crc; + + if (wlen > CFG_CTS_MAX_SPI_XFER_SIZE + || rlen > CFG_CTS_MAX_SPI_XFER_SIZE) { + cts_err("write/read too much data:wlen=%zu, rlen=%zu", wlen, rlen); + return -E2BIG; + } + + if (pdata->cts_dev->rtdata.program_mode) { + pdata->spi_tx_buf[0] = dev_addr | 0x01; + memcpy(&pdata->spi_tx_buf[1], wbuf, wlen); + do { + ret = cts_spi_send_recv(pdata, rlen + 5, pdata->spi_tx_buf, + pdata->spi_rx_buf); + if (ret) { + cts_err("SPI read failed %d", ret); + if (delay) + mdelay(delay); + continue; + } + memcpy(rbuf, pdata->spi_rx_buf + 5, rlen); + return 0; + } while (++retries < retry); + } else { + do { + if (wlen != 0) { + pdata->spi_tx_buf[0] = dev_addr | 0x01; + pdata->spi_tx_buf[1] = wbuf[1]; + pdata->spi_tx_buf[2] = wbuf[0]; + put_unaligned_le16(rlen, &pdata->spi_tx_buf[3]); + crc = (u16) cts_crc32(pdata->spi_tx_buf, 5); + put_unaligned_le16(crc, &pdata->spi_tx_buf[5]); + ret = cts_spi_send_recv(pdata, 7, pdata->spi_tx_buf, + pdata->spi_rx_buf); + if (ret) { + cts_err("SPI read failed %d", ret); + if (delay) + mdelay(delay); + continue; + } + } + memset(pdata->spi_tx_buf, 0, 7); + pdata->spi_tx_buf[0] = dev_addr | 0x01; + udelay(idle); + ret = cts_spi_send_recv(pdata, rlen + 2, + pdata->spi_tx_buf, pdata->spi_rx_buf); + if (ret) { + if (delay) + mdelay(delay); + continue; + } + memcpy(rbuf, pdata->spi_rx_buf, rlen); + crc = (u16) cts_crc32(pdata->spi_rx_buf, rlen); + if (get_unaligned_le16(&pdata->spi_rx_buf[rlen]) != crc) + continue; + return 0; + } while (++retries < retry); + } + if (retries >= retry) + cts_err("cts_plat_spi_read error"); + + return -EIO; +} +#endif /*CONFIG_CTS_I2C_HOST*/ + +int cts_plat_is_normal_mode(struct cts_platform_data *pdata) +{ + struct chipone_ts_data *cts_data; + u16 fwid; + int ret; + + cts_set_normal_addr(pdata->cts_dev); + cts_data = container_of(pdata->cts_dev, struct chipone_ts_data, cts_dev); + ret = cts_tcs_get_fw_id(pdata->cts_dev, &fwid); + if (ret || !cts_is_fwid_valid(fwid)) + return false; + + return true; +} + +static void cts_plat_handle_irq(struct cts_platform_data *pdata) +{ + int ret; + + cts_dbg("Handle IRQ"); + + cts_lock_device(pdata->cts_dev); + ret = cts_irq_handler(pdata->cts_dev); + if (ret) + cts_err("Device handle IRQ failed %d", ret); + cts_unlock_device(pdata->cts_dev); +} + +static irqreturn_t cts_plat_irq_handler(int irq, void *dev_id) +{ + struct cts_platform_data *pdata; +#ifndef CONFIG_GENERIC_HARDIRQS + struct chipone_ts_data *cts_data; +#endif + + cts_dbg("IRQ handler"); + + pdata = (struct cts_platform_data *)dev_id; + if (pdata == NULL) { + cts_err("IRQ handler with NULL dev_id"); + return IRQ_NONE; + } +#ifdef CONFIG_GENERIC_HARDIRQS + cts_plat_handle_irq(pdata); +#else + cts_data = container_of(pdata->cts_dev, struct chipone_ts_data, cts_dev); + + if (queue_work(cts_data->workqueue, &pdata->ts_irq_work)) { + cts_dbg("IRQ queue work"); + cts_plat_disable_irq(pdata); + } else + cts_warn("IRQ handler queue work failed as already on the queue"); +#endif /* CONFIG_GENERIC_HARDIRQS */ + + return IRQ_HANDLED; +} + +#ifndef CONFIG_GENERIC_HARDIRQS +static void cts_plat_touch_dev_irq_work(struct work_struct *work) +{ + struct cts_platform_data *pdata = + container_of(work, struct cts_platform_data, ts_irq_work); + + cts_dbg("IRQ work"); + + cts_plat_handle_irq(pdata); + + cts_plat_enable_irq(pdata); +} +#endif /* CONFIG_GENERIC_HARDIRQS */ + + +#ifdef CFG_CTS_FORCE_UP +static void cts_plat_touch_timeout_work(struct work_struct *work) +{ + struct cts_platform_data *pdata = container_of(work, + struct cts_platform_data, touch_timeout.work); + + cts_warn("Touch event timeout work"); + + cts_plat_release_all_touch(pdata); +} +#endif + + +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +#ifdef CONFIG_CTS_OF +static int cts_plat_parse_dt(struct cts_platform_data *pdata, + struct device_node *dev_node) +{ + int ret = 0; + + cts_info("Parse device tree"); + + pdata->int_gpio = of_get_named_gpio(dev_node, CFG_CTS_OF_INT_GPIO_NAME, 0); + if (!gpio_is_valid(pdata->int_gpio)) { + cts_err("Parse INT GPIO from dt failed %d", pdata->int_gpio); + pdata->int_gpio = -1; + } + cts_info(" %-12s: %d", "int gpio", pdata->int_gpio); + + pdata->irq = gpio_to_irq(pdata->int_gpio); + if (pdata->irq < 0) { + cts_err("Parse irq failed %d", ret); + return pdata->irq; + } + cts_info(" %-12s: %d", "irq num", pdata->irq); + +#ifdef CFG_CTS_HAS_RESET_PIN + pdata->rst_gpio = of_get_named_gpio(dev_node, CFG_CTS_OF_RST_GPIO_NAME, 0); + if (!gpio_is_valid(pdata->rst_gpio)) { + cts_err("Parse RST GPIO from dt failed %d", pdata->rst_gpio); + pdata->rst_gpio = -1; + } + cts_info(" %-12s: %d", "rst gpio", pdata->rst_gpio); +#endif /* CFG_CTS_HAS_RESET_PIN */ + +#ifdef CFG_CTS_MANUAL_CS + pdata->cs_gpio = of_get_named_gpio(dev_node, CFG_CTS_OF_CS_GPIO_NAME, 0); + if (!gpio_is_valid(pdata->cs_gpio)) { + cts_err("Parse CS GPIO from dt failed %d", pdata->cs_gpio); + pdata->cs_gpio = -1; + } + cts_info(" %-12s: %d", "cs gpio", pdata->cs_gpio); +#endif + + ret = of_property_read_u32(dev_node, CFG_CTS_OF_X_RESOLUTION_NAME, + &pdata->res_x); + if (ret) + cts_warn("Parse X resolution from dt failed %d", ret); + + cts_info(" %-12s: %d", "X resolution", pdata->res_x); + + ret = of_property_read_u32(dev_node, CFG_CTS_OF_Y_RESOLUTION_NAME, + &pdata->res_y); + if (ret) + cts_warn("Parse Y resolution from dt failed %d", ret); + + cts_info(" %-12s: %d", "Y resolution", pdata->res_y); + + if (of_property_read_u32(dev_node, "chipone,def-build-id", &pdata->build_id)) { + pdata->build_id = 0; + cts_info("chipone,build_id undefined."); + } else + cts_info("chipone,build_id=0x%04X", pdata->build_id); + + if (of_property_read_u32(dev_node, "chipone,def-config-id", &pdata->config_id)) { + pdata->config_id = 0; + cts_info("chipone,config_id undefined."); + } else + cts_info("chipone,config_id=0x%04X", pdata->config_id); + +#ifdef CFG_CTS_FW_UPDATE_SYS + ret = of_property_read_string(dev_node, CFG_CTS_OF_PANEL_SUPPLIER, + &pdata->panel_supplier); + if (ret) { + pdata->panel_supplier = NULL; + cts_warn("read panel supplier failed, ret=%d", ret); + } else + cts_info("panel supplier=%s", (char *)pdata->panel_supplier); +#endif + + return 0; +} +#endif /* CONFIG_CTS_OF */ + +#ifdef CONFIG_CTS_I2C_HOST +int cts_init_platform_data(struct cts_platform_data *pdata, + struct i2c_client *i2c_client) +#else +int cts_init_platform_data(struct cts_platform_data *pdata, + struct spi_device *spi) +#endif +{ + struct input_dev *input_dev; + struct input_dev *pen_dev; + int ret = 0; + + cts_info("cts_init_platform_data Init"); + +#ifdef CONFIG_CTS_OF + { + struct device *dev; + +#ifdef CONFIG_CTS_I2C_HOST + dev = &i2c_client->dev; +#else + dev = &spi->dev; +#endif /* CONFIG_CTS_I2C_HOST */ + ret = cts_plat_parse_dt(pdata, dev->of_node); + if (ret) { + cts_err("Parse dt failed %d", ret); + return ret; + } + } +#endif /* CONFIG_CTS_OF */ + +#ifdef CONFIG_CTS_I2C_HOST + pdata->i2c_client = i2c_client; + pdata->i2c_client->irq = pdata->irq; +#else + pdata->spi_client = spi; + pdata->spi_client->irq = pdata->irq; +#endif /* CONFIG_CTS_I2C_HOST */ + + mutex_init(&pdata->dev_lock); + spin_lock_init(&pdata->irq_lock); + + input_dev = input_allocate_device(); + if (input_dev == NULL) { + cts_err("Failed to allocate input device."); + return -ENOMEM; + } + + /** - Init input device */ + input_dev->name = CFG_CTS_DEVICE_NAME; + input_dev->name = CFG_CTS_DEVICE_NAME; +#ifdef CONFIG_CTS_I2C_HOST + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &pdata->i2c_client->dev; +#else + input_dev->id.bustype = BUS_SPI; + input_dev->dev.parent = &pdata->spi_client->dev; +#endif /* CONFIG_CTS_I2C_HOST */ + input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); +#ifdef CFG_CTS_PALM_DETECT + set_bit(CFG_CTS_PALM_EVENT, input_dev->keybit); +#endif + +#ifdef CFG_CTS_SWAP_XY + input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, pdata->res_y, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, pdata->res_x, 0, 0); +#else /* CFG_CTS_SWAP_XY */ + input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, pdata->res_x, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, pdata->res_y, 0, 0); +#endif /* CFG_CTS_SWAP_XY */ + + input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, + CFG_CTS_MAX_TOUCH_NUM * 2, 0, 0); + + input_set_capability(input_dev, EV_KEY, BTN_TOUCH); + +#ifdef CONFIG_CTS_SLOTPROTOCOL + input_mt_init_slots(input_dev, CFG_CTS_MAX_TOUCH_NUM, 0); +#endif /* CONFIG_CTS_SLOTPROTOCOL */ + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + __set_bit(EV_ABS, input_dev->evbit); + input_set_drvdata(input_dev, pdata); + ret = input_register_device(input_dev); + if (ret) { + cts_err("Failed to register input device"); + return ret; + } + pdata->ts_input_dev = input_dev; + + + pen_dev = input_allocate_device(); + if (pen_dev == NULL) { + cts_err("Failed to allocate pen device."); + return -ENOMEM; + } + +#ifdef CONFIG_CTS_I2C_HOST + pen_dev->id.bustype = BUS_I2C; + pen_dev->dev.parent = &pdata->i2c_client->dev; +#else + pen_dev->id.bustype = BUS_SPI; + pen_dev->dev.parent = &pdata->spi_client->dev; +#endif /* CONFIG_CTS_I2C_HOST */ + pen_dev->name = "chipone-tddi,pen"; + pen_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + __set_bit(ABS_X, pen_dev->absbit); + __set_bit(ABS_Y, pen_dev->absbit); + __set_bit(BTN_STYLUS, pen_dev->keybit); + __set_bit(BTN_STYLUS2, pen_dev->keybit); + __set_bit(BTN_TOUCH, pen_dev->keybit); + __set_bit(BTN_TOOL_PEN, pen_dev->keybit); + __set_bit(INPUT_PROP_DIRECT, pen_dev->propbit); + input_set_abs_params(pen_dev, ABS_X, 0, pdata->res_x, 0, 0); + input_set_abs_params(pen_dev, ABS_Y, 0, pdata->res_y, 0, 0); + input_set_abs_params(pen_dev, ABS_PRESSURE, 0, 4096, 0, 0); + input_set_abs_params(pen_dev, ABS_TILT_X, -9000, 9000, 0, 0); + input_set_abs_params(pen_dev, ABS_TILT_Y, -9000, 9000, 0, 0); + input_set_abs_params(pen_dev, ABS_Z, 0, 36000, 0, 0); + + ret = input_register_device(pen_dev); + if (ret) { + cts_err("Input pen device registration failed"); + input_free_device(pen_dev); + pen_dev = NULL; + return ret; + } + pdata->pen_input_dev = pen_dev; + +#if !defined(CONFIG_GENERIC_HARDIRQS) + INIT_WORK(&pdata->ts_irq_work, cts_plat_touch_dev_irq_work); +#endif /* CONFIG_GENERIC_HARDIRQS */ + +#ifdef CONFIG_CTS_VIRTUALKEY + { + u8 vkey_keymap[CFG_CTS_NUM_VKEY] = CFG_CTS_VKEY_KEYCODES; + + memcpy(pdata->vkey_keycodes, vkey_keymap, sizeof(vkey_keymap)); + pdata->vkey_num = CFG_CTS_NUM_VKEY; + } +#endif /* CONFIG_CTS_VIRTUALKEY */ + +#ifdef CFG_CTS_GESTURE + { + u8 gesture_keymap[CFG_CTS_NUM_GESTURE][2] = CFG_CTS_GESTURE_KEYMAP; + + memcpy(pdata->gesture_keymap, gesture_keymap, sizeof(gesture_keymap)); + pdata->gesture_num = CFG_CTS_NUM_GESTURE; + } +#endif /* CFG_CTS_GESTURE */ + +#ifdef CFG_CTS_FORCE_UP + INIT_DELAYED_WORK(&pdata->touch_timeout, cts_plat_touch_timeout_work); +#endif + +#ifndef CONFIG_CTS_I2C_HOST + pdata->spi_speed = CFG_CTS_SPI_SPEED_KHZ; + cts_plat_spi_setup(pdata); +#endif + return 0; +} + +int cts_deinit_platform_data(struct cts_platform_data *pdata) +{ + cts_info("De-Init platform_data"); + input_unregister_device(pdata->ts_input_dev); + return 0; +} + +int cts_plat_request_resource(struct cts_platform_data *pdata) +{ + int ret; + + cts_info("Request resource"); + + ret = gpio_request_one(pdata->int_gpio, GPIOF_IN, + CFG_CTS_DEVICE_NAME "-int"); + if (ret) { + cts_err("Request INT gpio (%d) failed %d", pdata->int_gpio, ret); + goto err_out; + } +#ifdef CFG_CTS_HAS_RESET_PIN + ret = gpio_request_one(pdata->rst_gpio, GPIOF_OUT_INIT_HIGH, + CFG_CTS_DEVICE_NAME "-rst"); + if (ret) { + cts_err("Request RST gpio (%d) failed %d", pdata->rst_gpio, ret); + goto err_free_int; + } +#endif /* CFG_CTS_HAS_RESET_PIN */ + +#ifdef CFG_CTS_MANUAL_CS + ret = gpio_request_one(pdata->cs_gpio, GPIOF_OUT_INIT_HIGH, + CFG_CTS_DEVICE_NAME "-cs"); + if (ret) { + cts_err("Request CS gpio (%d) failed %d", pdata->cs_gpio, ret); + goto err_request_cs_gpio; + } +#endif + + return 0; + +#ifdef CFG_CTS_MANUAL_CS +err_request_cs_gpio: +#endif + +#ifdef CONFIG_CTS_REGULATOR +err_free_rst: +#endif /* CONFIG_CTS_REGULATOR */ +#ifdef CFG_CTS_HAS_RESET_PIN + gpio_free(pdata->rst_gpio); +err_free_int: +#endif /* CFG_CTS_HAS_RESET_PIN */ + gpio_free(pdata->int_gpio); +err_out: + return ret; +} + +void cts_plat_free_resource(struct cts_platform_data *pdata) +{ + cts_info("Free resource"); + + if (gpio_is_valid(pdata->int_gpio)) + gpio_free(pdata->int_gpio); + +#ifdef CFG_CTS_HAS_RESET_PIN + if (gpio_is_valid(pdata->rst_gpio)) + gpio_free(pdata->rst_gpio); + +#endif /* CFG_CTS_HAS_RESET_PIN */ +#ifdef CFG_CTS_MANUAL_CS + if (gpio_is_valid(pdata->cs_gpio)) + gpio_free(pdata->cs_gpio); + +#endif +} + +#else /*CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED*/ + +#ifndef CONFIG_CTS_I2C_HOST +static int cts_plat_init_dts(struct cts_platform_data *pdata, struct device *device) +{ +#ifdef CFG_CTS_MANUAL_CS + struct device_node *node; + + pdata->pinctrl1 = devm_pinctrl_get(device); + node = device->of_node; + if (node) { + pdata->spi_cs_low = pinctrl_lookup_state(pdata->pinctrl1, "spi_cs_low"); + if (IS_ERR(pdata->spi_cs_low)) { + cts_err("Cannot find pinctrl spi cs high!\n"); + return -ENOENT; + } + pdata->spi_cs_high = pinctrl_lookup_state(pdata->pinctrl1, "spi_cs_high"); + if (IS_ERR(pdata->spi_cs_high)) { + return -ENOENT; + } + return 0; + } + return -ENOENT; +#else + return 0; +#endif +} +#endif /* CONFIG_CTS_I2C_HOST */ + +#ifdef CONFIG_CTS_I2C_HOST +int cts_init_platform_data(struct cts_platform_data *pdata, + struct i2c_client *i2c_client) +#else +int cts_init_platform_data(struct cts_platform_data *pdata, + struct spi_device *spi) +#endif +{ + struct device_node *node = NULL; + u32 ints[2] = { 0, 0 }; + + cts_info("cts_init_platform_data Init"); + +#ifdef CONFIG_CTS_OF + { + struct device *dev; + +#ifdef CONFIG_CTS_I2C_HOST + dev = &i2c_client->dev; +#else + dev = &spi->dev; +#endif /* CONFIG_CTS_I2C_HOST */ + } +#endif /* CONFIG_CTS_OF */ + +#ifdef CONFIG_CTS_I2C_HOST + pdata->i2c_client = i2c_client; +#else + pdata->spi_client = spi; +#endif /* CONFIG_CTS_I2C_HOST */ + + pdata->ts_input_dev = tpd->dev; + + spin_lock_init(&pdata->irq_lock); + mutex_init(&pdata->dev_lock); + +#if !defined(CONFIG_GENERIC_HARDIRQS) + INIT_WORK(&pdata->ts_irq_work, cts_plat_touch_dev_irq_work); +#endif /* CONFIG_GENERIC_HARDIRQS */ + + if ((node = of_find_matching_node(node, touch_of_match)) == NULL) { + cts_err("Find touch eint node failed"); + return -ENODATA; + } + if (of_property_read_u32_array(node, "debounce", ints, ARRAY_SIZE(ints)) == 0) { + gpio_set_debounce(ints[0], ints[1]); + } else { + cts_info("Debounce time not found"); + } + pdata->irq = irq_of_parse_and_map(node, 0); + if (pdata->irq == 0) { + cts_err("Parse irq in dts failed"); + return -ENODEV; + } + +#ifdef CONFIG_CTS_VIRTUALKEY + pdata->vkey_num = tpd_dts_data.tpd_keycnt; +#endif /* CONFIG_CTS_VIRTUALKEY */ + +#ifdef CFG_CTS_GESTURE + { + u8 gesture_keymap[CFG_CTS_NUM_GESTURE][2] = CFG_CTS_GESTURE_KEYMAP; + memcpy(pdata->gesture_keymap, gesture_keymap, sizeof(gesture_keymap)); + pdata->gesture_num = CFG_CTS_NUM_GESTURE; + } +#endif /* CFG_CTS_GESTURE */ + +#ifdef TPD_SUPPORT_I2C_DMA + tpd->dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + pdata->i2c_dma_buff_va = (u8 *)dma_alloc_coherent(&tpd->dev->dev, + CFG_CTS_MAX_I2C_XFER_SIZE, &pdata->i2c_dma_buff_pa, GFP_KERNEL); + if (pdata->i2c_dma_buff_va == NULL) { + cts_err("Allocate I2C DMA Buffer failed!"); + //return -ENOMEM; + } else { + pdata->i2c_dma_available = true; + } +#endif /* TPD_SUPPORT_I2C_DMA */ + +#ifdef CFG_CTS_FORCE_UP + INIT_DELAYED_WORK(&pdata->touch_timeout, cts_plat_touch_timeout_work); +#endif + +#ifndef CONFIG_CTS_I2C_HOST + cts_plat_init_dts(pdata, &spi->dev); + pdata->spi_speed = CFG_CTS_SPI_SPEED_KHZ; + cts_plat_spi_setup(pdata); +#endif + return 0; +} + +int cts_plat_request_resource(struct cts_platform_data *pdata) +{ + cts_info("Request resource"); + + tpd_gpio_as_int(tpd_int_gpio_index); + tpd_gpio_output(tpd_rst_gpio_index, 1); + + return 0; +} + +void cts_plat_free_resource(struct cts_platform_data *pdata) +{ + cts_info("Free resource"); + + /** + * Note: + * If resource request without managed, should free all resource + * requested in cts_plat_request_resource(). + */ +#ifdef TPD_SUPPORT_I2C_DMA + if (pdata->i2c_dma_buff_va) { + dma_free_coherent(&tpd->dev->dev, CFG_CTS_MAX_I2C_XFER_SIZE, + pdata->i2c_dma_buff_va, pdata->i2c_dma_buff_pa); + pdata->i2c_dma_buff_va = NULL; + pdata->i2c_dma_buff_pa = 0; + } +#endif /* TPD_SUPPORT_I2C_DMA */ +} +#endif /*CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED*/ + + +int cts_plat_request_irq(struct cts_platform_data *pdata) +{ + int ret; + + cts_info("Request IRQ"); + +#ifdef CONFIG_GENERIC_HARDIRQS + ret = request_threaded_irq(pdata->irq, NULL, cts_plat_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, CFG_CTS_DRIVER_NAME, pdata); +#else /* CONFIG_GENERIC_HARDIRQS */ + ret = request_irq(pdata->irq, cts_plat_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, CFG_CTS_DRIVER_NAME, pdata); +#endif /* CONFIG_GENERIC_HARDIRQS */ + if (ret) { + cts_err("Request IRQ failed %d", ret); + return ret; + } + + cts_plat_disable_irq(pdata); + + return 0; +} + +void cts_plat_free_irq(struct cts_platform_data *pdata) +{ + free_irq(pdata->irq, pdata); +} + +int cts_plat_enable_irq(struct cts_platform_data *pdata) +{ + unsigned long irqflags; + + cts_dbg("Enable IRQ"); + + if (pdata->irq > 0) { + spin_lock_irqsave(&pdata->irq_lock, irqflags); + if (pdata->irq_is_disable) {/* && !cts_is_device_suspended(pdata->chip)) */ + cts_dbg("Real enable IRQ"); + enable_irq(pdata->irq); + pdata->irq_is_disable = false; + } + spin_unlock_irqrestore(&pdata->irq_lock, irqflags); + + return 0; + } + + return -ENODEV; +} + +int cts_plat_disable_irq(struct cts_platform_data *pdata) +{ + unsigned long irqflags; + + cts_dbg("Disable IRQ"); + + if (pdata->irq > 0) { + spin_lock_irqsave(&pdata->irq_lock, irqflags); + if (!pdata->irq_is_disable) { + cts_dbg("Real disable IRQ"); + disable_irq_nosync(pdata->irq); + pdata->irq_is_disable = true; + } + spin_unlock_irqrestore(&pdata->irq_lock, irqflags); + + return 0; + } + + return -ENODEV; +} + +int cts_plat_get_int_pin(struct cts_platform_data *pdata) +{ +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + return gpio_get_value(pdata->int_gpio); +#else + cts_err("MTK platform can not get INT pin value"); + return -ENOTSUPP; +#endif +} + +int cts_plat_init_touch_device(struct cts_platform_data *pdata) +{ + cts_info("Init touch device"); + +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + return input_mt_init_slots(pdata->ts_input_dev, + tpd_dts_data.touch_max_num, INPUT_MT_DIRECT); +#endif + return 0; +} + +void cts_plat_deinit_touch_device(struct cts_platform_data *pdata) +{ + cts_info("De-init touch device"); + +#ifndef CONFIG_GENERIC_HARDIRQS + if (work_pending(&pdata->ts_irq_work)) { + cancel_work_sync(&pdata->ts_irq_work); + } +#endif /* CONFIG_GENERIC_HARDIRQS */ +} + +#ifdef CFG_CTS_PALM_DETECT +void cts_report_palm_event(struct cts_platform_data *pdata) +{ + input_report_key(pdata->ts_input_dev, CFG_CTS_PALM_EVENT, 1); + input_sync(pdata->ts_input_dev); + msleep(100); + input_report_key(pdata->ts_input_dev, CFG_CTS_PALM_EVENT, 0); + input_sync(pdata->ts_input_dev); +} +#endif + +int cts_plat_process_touch_msg(struct cts_platform_data *pdata, + struct cts_device_touch_msg *msgs, int num) +{ + struct chipone_ts_data *cts_data; + struct input_dev *input_dev = pdata->ts_input_dev; + int i; + int contact = 0; +#ifdef CONFIG_CTS_SLOTPROTOCOL + static unsigned char finger_last[CFG_CTS_MAX_TOUCH_NUM] = { 0 }; + unsigned char finger_current[CFG_CTS_MAX_TOUCH_NUM] = { 0 }; +#endif + + cts_dbg("Process touch %d msgs", num); + + cts_data = container_of(pdata->cts_dev, struct chipone_ts_data, cts_dev); + + if (num == 0 || num > CFG_CTS_MAX_TOUCH_NUM) + return 0; + + for (i = 0; i < num; i++) { + u16 x, y; + + x = (msgs[i].xl) | (msgs[i].xh << 8); + y = (msgs[i].yl) | (msgs[i].yh << 8); + + cts_dbg(" Process touch msg[%d]: id[%u] ev=%u x=%u y=%u p=%u", + i, msgs[i].id, msgs[i].event, x, y, msgs[i].pressure); + if (msgs[i].event == CTS_DEVICE_TOUCH_EVENT_DOWN + || msgs[i].event == CTS_DEVICE_TOUCH_EVENT_MOVE + || msgs[i].event == CTS_DEVICE_TOUCH_EVENT_STAY) { + if (msgs[i].id < CFG_CTS_MAX_TOUCH_NUM) + finger_current[msgs[i].id] = 1; + } +#ifdef CONFIG_CTS_SLOTPROTOCOL + /* input_mt_slot(input_dev, msgs[i].id); */ + switch (msgs[i].event) { + case CTS_DEVICE_TOUCH_EVENT_DOWN: +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + TPD_DEBUG_SET_TIME; + TPD_EM_PRINT(x, y, x, y, msgs[i].id, 1); + tpd_history_x = x; + tpd_history_y = y; +#ifdef CONFIG_MTK_BOOT + if (tpd_dts_data.use_tpd_button) { + if (FACTORY_BOOT == get_boot_mode() || + RECOVERY_BOOT == get_boot_mode()) + tpd_button(x, y, 1); + } +#endif /* CONFIG_MTK_BOOT */ +#endif /* CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED */ + case CTS_DEVICE_TOUCH_EVENT_MOVE: + case CTS_DEVICE_TOUCH_EVENT_STAY: + contact++; + input_mt_slot(input_dev, msgs[i].id); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, true); + input_report_abs(input_dev, ABS_MT_POSITION_X,x); + input_report_abs(input_dev, ABS_MT_POSITION_Y,y); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, msgs[i].pressure); + input_report_abs(input_dev, ABS_MT_PRESSURE, msgs[i].pressure); + break; + + case CTS_DEVICE_TOUCH_EVENT_UP: +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + TPD_DEBUG_SET_TIME; + TPD_EM_PRINT(tpd_history_x, tpd_history_y, tpd_history_x, tpd_history_y, msgs[i].id, 0); + tpd_history_x = 0; + tpd_history_y = 0; +#ifdef CONFIG_MTK_BOOT + if (tpd_dts_data.use_tpd_button) { + if (FACTORY_BOOT == get_boot_mode() || + RECOVERY_BOOT == get_boot_mode()) + tpd_button(0, 0, 0); + } +#endif /* CONFIG_MTK_BOOT */ + //input_report_key(input_dev, BTN_TOUCH, 0); + //input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false); +#endif /* CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED */ + break; + + default: + cts_warn("Process touch msg with unknwon event %u id %u", + msgs[i].event, msgs[i].id); + break; + } +#else /* CONFIG_CTS_SLOTPROTOCOL */ + /** + * If the driver reports one of BTN_TOUCH or ABS_PRESSURE + * in addition to the ABS_MT events, the last SYN_MT_REPORT event + * may be omitted. Otherwise, the last SYN_REPORT will be dropped + * by the input core, resulting in no zero-contact event + * reaching userland. + */ + switch (msgs[i].event) { + case CTS_DEVICE_TOUCH_EVENT_DOWN: + case CTS_DEVICE_TOUCH_EVENT_MOVE: + case CTS_DEVICE_TOUCH_EVENT_STAY: + contact++; + input_report_abs(input_dev, ABS_MT_PRESSURE, msgs[i].pressure); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, msgs[i].pressure); + input_report_key(input_dev, BTN_TOUCH, 1); + input_report_abs(input_dev, ABS_MT_POSITION_X, x); + input_report_abs(input_dev, ABS_MT_POSITION_Y, y); + input_mt_sync(input_dev); + break; + + case CTS_DEVICE_TOUCH_EVENT_UP: + break; + default: + cts_warn("Process touch msg with unknwon event %u id %u", + msgs[i].event, msgs[i].id); + break; + } +#endif /* CONFIG_CTS_SLOTPROTOCOL */ + } + +#ifdef CONFIG_CTS_SLOTPROTOCOL + for (i = 0; i < CFG_CTS_MAX_TOUCH_NUM; i++) { + if (finger_last[i] != 0 && finger_current[i] == 0) { + input_mt_slot(input_dev, i); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false); + } + finger_last[i] = finger_current[i]; + } + input_report_key(input_dev, BTN_TOUCH, contact > 0); +#else /* CONFIG_CTS_SLOTPROTOCOL */ + if (contact == 0) { + input_report_key(input_dev, BTN_TOUCH, 0); + input_mt_sync(input_dev); + } +#endif /* CONFIG_CTS_SLOTPROTOCOL */ + input_sync(input_dev); + +#ifdef CFG_CTS_FORCE_UP + if (contact) { + if (delayed_work_pending(&pdata->touch_timeout)) { + mod_delayed_work(cts_data->workqueue, + &pdata->touch_timeout, msecs_to_jiffies(100)); + } else { + queue_delayed_work(cts_data->workqueue, + &pdata->touch_timeout, msecs_to_jiffies(100)); + } + } else { + cancel_delayed_work_sync(&pdata->touch_timeout); + } +#endif + +#ifdef CFG_CTS_HEARTBEAT_MECHANISM + if (contact) { + if (delayed_work_pending(&cts_data->heart_work)) { + mod_delayed_work(cts_data->heart_workqueue, + &cts_data->heart_work, msecs_to_jiffies(2000)); + } else { + queue_delayed_work(cts_data->heart_workqueue, + &cts_data->heart_work, msecs_to_jiffies(2000)); + } + } +#endif + + return 0; +} + +int cts_plat_process_stylus_msg(struct cts_platform_data *pdata, + struct cts_device_stylus_msg *msgs, int num) +{ + struct chipone_ts_data *cts_data; + struct input_dev *pen_dev = pdata->pen_input_dev; + int i; + int contact = 0; + + cts_dbg("Process stylus %d msgs", num); + + cts_data = container_of(pdata->cts_dev, struct chipone_ts_data, cts_dev); + + if (num == 0 || num > CFG_CTS_MAX_STYLUS_NUM) + return 0; + + for (i = 0; i < num; i++) { + u16 x, y, p; + + x = (msgs[i].tip_xl) | (msgs[i].tip_xh << 8); + y = (msgs[i].tip_yl) | (msgs[i].tip_yh << 8); + p = msgs[i].pressure_l | (msgs[i].pressure_h << 8); + + cts_dbg(" Process stylus msg[%d]: id[%u] ev=%u x=%u y=%u p=%u" + " tx=%d ty=%d btn0=%d btn1=%d btn2=%d", + i, msgs[i].id, msgs[i].event, x, y, p, msgs[i].tiltx, msgs[i].tilty, + msgs[i].btn0, msgs[i].btn1, msgs[i].btn2); + + /* Report stylus button */ + input_report_key(pen_dev, BTN_STYLUS, msgs[i].btn0); + input_report_key(pen_dev, BTN_STYLUS2, msgs[i].btn1); + + switch (msgs[i].event) { + case CTS_DEVICE_TOUCH_EVENT_DOWN: + case CTS_DEVICE_TOUCH_EVENT_MOVE: + case CTS_DEVICE_TOUCH_EVENT_STAY: + contact++; + input_report_abs(pen_dev, ABS_X, x); + input_report_abs(pen_dev, ABS_Y, y); + input_report_abs(pen_dev, ABS_PRESSURE, p); + input_report_abs(pen_dev, ABS_TILT_X, msgs[i].tiltx); + input_report_abs(pen_dev, ABS_TILT_Y, msgs[i].tilty); + break; + + case CTS_DEVICE_TOUCH_EVENT_UP: + break; + default: + cts_warn("Process stylus msg with unknwon event %u id %u", + msgs[i].event, msgs[i].id); + break; + } + } + + input_report_key(pen_dev, BTN_TOUCH, contact > 0); + input_report_key(pen_dev, BTN_TOOL_PEN, contact > 0); + input_sync(pen_dev); + +#ifdef CFG_CTS_FORCE_UP + if (contact) { + if (delayed_work_pending(&pdata->touch_timeout)) { + mod_delayed_work(cts_data->workqueue, + &pdata->touch_timeout, msecs_to_jiffies(100)); + } else { + queue_delayed_work(cts_data->workqueue, + &pdata->touch_timeout, msecs_to_jiffies(100)); + } + } else { + cancel_delayed_work_sync(&pdata->touch_timeout); + } +#endif + +#ifdef CFG_CTS_HEARTBEAT_MECHANISM + if (contact) { + if (delayed_work_pending(&cts_data->heart_work)) { + mod_delayed_work(cts_data->heart_workqueue, + &cts_data->heart_work, msecs_to_jiffies(2000)); + } else { + queue_delayed_work(cts_data->heart_workqueue, + &cts_data->heart_work, msecs_to_jiffies(2000)); + } + } +#endif + + return 0; +} + +#ifdef CFG_CTS_FINGER_STYLUS_SUPPORTED +int cts_plat_process_touch_stylus(struct cts_platform_data *pdata, + struct cts_device_touch_info *touch_info) +{ + struct chipone_ts_data *cts_data; + struct input_dev *input_dev = pdata->ts_input_dev; + struct input_dev *pen_dev = pdata->pen_input_dev; + struct cts_device_touch_msg *msgs = touch_info->msgs; + int touch_num = touch_info->num_msg; + struct cts_device_stylus_msg *smsgs = touch_info->smsgs; + int stylus_num = touch_info->stylus_num; + int i; + int contact = 0; +#ifdef CONFIG_CTS_SLOTPROTOCOL + static unsigned char finger_last[CFG_CTS_MAX_TOUCH_NUM] = { 0 }; + unsigned char finger_current[CFG_CTS_MAX_TOUCH_NUM] = { 0 }; +#endif + + cts_dbg("Process touch %d msgs, stylus %d msgs", touch_num, stylus_num); + + cts_data = container_of(pdata->cts_dev, struct chipone_ts_data, cts_dev); + + if (touch_num == 0 || touch_num > CFG_CTS_MAX_TOUCH_NUM) { + goto process_stylus; + } + + for (i = 0; i < touch_num; i++) { + u16 x, y; + + x = (msgs[i].xl) | (msgs[i].xh << 8); + y = (msgs[i].yl) | (msgs[i].yh << 8); + + cts_dbg(" Process touch msg[%d]: id[%u] ev=%u x=%u y=%u p=%u", + i, msgs[i].id, msgs[i].event, x, y, msgs[i].pressure); + if (msgs[i].event == CTS_DEVICE_TOUCH_EVENT_DOWN + || msgs[i].event == CTS_DEVICE_TOUCH_EVENT_MOVE + || msgs[i].event == CTS_DEVICE_TOUCH_EVENT_STAY) { + if (msgs[i].id < CFG_CTS_MAX_TOUCH_NUM) + finger_current[msgs[i].id] = 1; + } +#ifdef CONFIG_CTS_SLOTPROTOCOL + /* input_mt_slot(input_dev, msgs[i].id); */ + switch (msgs[i].event) { + case CTS_DEVICE_TOUCH_EVENT_DOWN: +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + TPD_DEBUG_SET_TIME; + TPD_EM_PRINT(x, y, x, y, msgs[i].id, 1); + tpd_history_x = x; + tpd_history_y = y; +#ifdef CONFIG_MTK_BOOT + if (tpd_dts_data.use_tpd_button) { + if (FACTORY_BOOT == get_boot_mode() || + RECOVERY_BOOT == get_boot_mode()) + tpd_button(x, y, 1); + } +#endif /* CONFIG_MTK_BOOT */ +#endif /* CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED */ + case CTS_DEVICE_TOUCH_EVENT_MOVE: + case CTS_DEVICE_TOUCH_EVENT_STAY: + contact++; + input_mt_slot(input_dev, msgs[i].id); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, true); + input_report_abs(input_dev, ABS_MT_POSITION_X, x); + input_report_abs(input_dev, ABS_MT_POSITION_Y, y); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, msgs[i].pressure); + input_report_abs(input_dev, ABS_MT_PRESSURE, msgs[i].pressure); + break; + + case CTS_DEVICE_TOUCH_EVENT_UP: +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + TPD_DEBUG_SET_TIME; + TPD_EM_PRINT(tpd_history_x, tpd_history_y, tpd_history_x, tpd_history_y, msgs[i].id, 0); + tpd_history_x = 0; + tpd_history_y = 0; +#ifdef CONFIG_MTK_BOOT + if (tpd_dts_data.use_tpd_button) { + if (FACTORY_BOOT == get_boot_mode() || + RECOVERY_BOOT == get_boot_mode()) + tpd_button(0, 0, 0); + } +#endif /* CONFIG_MTK_BOOT */ +#endif /* CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED */ + break; + default: + cts_warn("Process touch msg with unknwon event %u id %u", + msgs[i].event, msgs[i].id); + break; + } +#else /* CONFIG_CTS_SLOTPROTOCOL */ + /** + * If the driver reports one of BTN_TOUCH or ABS_PRESSURE + * in addition to the ABS_MT events, the last SYN_MT_REPORT event + * may be omitted. Otherwise, the last SYN_REPORT will be dropped + * by the input core, resulting in no zero-contact event + * reaching userland. + */ + switch (msgs[i].event) { + case CTS_DEVICE_TOUCH_EVENT_DOWN: + case CTS_DEVICE_TOUCH_EVENT_MOVE: + case CTS_DEVICE_TOUCH_EVENT_STAY: + contact++; + input_report_abs(input_dev, ABS_MT_PRESSURE, msgs[i].pressure); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, msgs[i].pressure); + input_report_key(input_dev, BTN_TOUCH, 1); + input_report_abs(input_dev, ABS_MT_POSITION_X, x); + input_report_abs(input_dev, ABS_MT_POSITION_Y, y); + input_mt_sync(input_dev); + break; + + case CTS_DEVICE_TOUCH_EVENT_UP: + break; + default: + cts_warn("Process touch msg with unknwon event %u id %u", + msgs[i].event, msgs[i].id); + break; + } +#endif /* CONFIG_CTS_SLOTPROTOCOL */ + } + +#ifdef CONFIG_CTS_SLOTPROTOCOL + for (i = 0; i < CFG_CTS_MAX_TOUCH_NUM; i++) { + if (finger_last[i] != 0 && finger_current[i] == 0) { + input_mt_slot(input_dev, i); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false); + } + finger_last[i] = finger_current[i]; + } + input_report_key(input_dev, BTN_TOUCH, contact > 0); +#else + if (contact == 0) { + input_report_key(input_dev, BTN_TOUCH, 0); + input_mt_sync(input_dev); + } +#endif + input_sync(input_dev); + +process_stylus: + if (stylus_num == 0 || stylus_num > CFG_CTS_MAX_STYLUS_NUM) { + return 0; + } + + for (i = 0; i < stylus_num; i++) { + u16 x, y, p; + + x = (smsgs[i].tip_xl) | (smsgs[i].tip_xh << 8); + y = (smsgs[i].tip_yl) | (smsgs[i].tip_yh << 8); + p = smsgs[i].pressure_l | (smsgs[i].pressure_h << 8); + + cts_dbg(" Process stylus msg[%d]: id[%u] ev=%u x=%u y=%u p=%u" + " tx=%d ty=%d btn0=%d btn1=%d btn2=%d", + i, smsgs[i].id, smsgs[i].event, x, y, p, smsgs[i].tiltx, smsgs[i].tilty, + smsgs[i].btn0, smsgs[i].btn1, smsgs[i].btn2); + + /* Report stylus button */ + input_report_key(pen_dev, BTN_STYLUS, smsgs[i].btn0); + input_report_key(pen_dev, BTN_STYLUS2, smsgs[i].btn1); + + switch (smsgs[i].event) { + case CTS_DEVICE_TOUCH_EVENT_DOWN: + case CTS_DEVICE_TOUCH_EVENT_MOVE: + case CTS_DEVICE_TOUCH_EVENT_STAY: + contact++; + input_report_abs(pen_dev, ABS_X, x); + input_report_abs(pen_dev, ABS_Y, y); + input_report_abs(pen_dev, ABS_PRESSURE, p); + input_report_abs(pen_dev, ABS_TILT_X, smsgs[i].tiltx); + input_report_abs(pen_dev, ABS_TILT_Y, smsgs[i].tilty); + break; + + case CTS_DEVICE_TOUCH_EVENT_UP: + break; + default: + cts_warn("Process stylus msg with unknwon event %u id %u", + smsgs[i].event, smsgs[i].id); + break; + } + } + + input_report_key(pen_dev, BTN_TOUCH, contact > 0); + input_report_key(pen_dev, BTN_TOOL_PEN, contact > 0); + input_sync(pen_dev); + + +#ifdef CFG_CTS_FORCE_UP + if (contact) { + if (delayed_work_pending(&pdata->touch_timeout)) { + mod_delayed_work(cts_data->workqueue, + &pdata->touch_timeout, msecs_to_jiffies(100)); + } else { + queue_delayed_work(cts_data->workqueue, + &pdata->touch_timeout, msecs_to_jiffies(100)); + } + } else { + cancel_delayed_work_sync(&pdata->touch_timeout); + } +#endif + +#ifdef CFG_CTS_HEARTBEAT_MECHANISM + if (contact) { + if (delayed_work_pending(&cts_data->heart_work)) { + mod_delayed_work(cts_data->heart_workqueue, + &cts_data->heart_work, msecs_to_jiffies(2000)); + } else { + queue_delayed_work(cts_data->heart_workqueue, + &cts_data->heart_work, msecs_to_jiffies(2000)); + } + } +#endif + + return 0; +} +#endif + +int cts_plat_release_all_touch(struct cts_platform_data *pdata) +{ + struct input_dev *input_dev = pdata->ts_input_dev; + struct input_dev *pen_dev = pdata->pen_input_dev; + +#if defined(CONFIG_CTS_SLOTPROTOCOL) + int id; +#endif /* CONFIG_CTS_SLOTPROTOCOL */ + + cts_info("Release all touch"); + +#ifdef CONFIG_CTS_SLOTPROTOCOL + for (id = 0; id < CFG_CTS_MAX_TOUCH_NUM; id++) { + input_mt_slot(input_dev, id); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false); + } + input_report_key(input_dev, BTN_TOUCH, 0); +#else + input_report_key(input_dev, BTN_TOUCH, 0); + input_mt_sync(input_dev); +#endif /* CONFIG_CTS_SLOTPROTOCOL */ + input_sync(input_dev); + + input_report_key(pen_dev, BTN_TOUCH, 0); + input_report_key(pen_dev, BTN_TOOL_PEN, 0); + input_sync(pen_dev); + + return 0; +} + +#ifdef CONFIG_CTS_VIRTUALKEY +int cts_plat_init_vkey_device(struct cts_platform_data *pdata) +{ + int i; + + cts_info("Init VKey"); + pdata->vkey_state = 0; + +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + for (i = 0; i < pdata->vkey_num; i++) { + input_set_capability(pdata->ts_input_dev, + EV_KEY, pdata->vkey_keycodes[i]); + } +#else + if (tpd_dts_data.use_tpd_button) { + cts_info("Init vkey"); + + pdata->vkey_state = 0; + tpd_button_setting(tpd_dts_data.tpd_key_num, tpd_dts_data.tpd_key_local, + tpd_dts_data.tpd_key_dim_local); + } +#endif + return 0; +} + +void cts_plat_deinit_vkey_device(struct cts_platform_data *pdata) +{ + cts_info("De-init VKey"); + + pdata->vkey_state = 0; +} + +int cts_plat_process_vkey(struct cts_platform_data *pdata, u8 vkey_state) +{ + u8 event; + int i; + + event = pdata->vkey_state ^ vkey_state; + + cts_dbg("Process vkey state=0x%02x, event=0x%02x", vkey_state, event); + +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + for (i = 0; i < pdata->vkey_num; i++) { + input_report_key(pdata->ts_input_dev, pdata->vkey_keycodes[i], + vkey_state & BIT(i) ? 1 : 0); + } +#else + for (i = 0; i < pdata->vkey_num; i++) { + if (event & BIT(i)) { + tpd_button(x, y, vkey_state & BIT(i)); + + /* MTK fobidon more than one key pressed in the same time */ + break; + } + } +#endif + pdata->vkey_state = vkey_state; + + return 0; +} + +int cts_plat_release_all_vkey(struct cts_platform_data *pdata) +{ + int i; + + cts_info("Release all vkeys"); + +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + for (i = 0; i < pdata->vkey_num; i++) { + if (pdata->vkey_state & BIT(i)) { + input_report_key(pdata->ts_input_dev, pdata->vkey_keycodes[i], 0); + } + } +#else + for (i = 0; i < pdata->vkey_num; i++) { + if (pdata->vkey_state & BIT(i)) { + tpd_button(x, y, 0); + } + } +#endif + pdata->vkey_state = 0; + + return 0; +} +#endif /* CONFIG_CTS_VIRTUALKEY */ + +#ifdef CFG_CTS_GESTURE +int cts_plat_enable_irq_wake(struct cts_platform_data *pdata) +{ + int ret; + + cts_info("Enable IRQ wake"); + + if (pdata->irq > 0) { + ret = enable_irq_wake(pdata->irq); + if (ret < 0) { + cts_err("Enable irq wake failed"); + return -EINVAL; + } + pdata->irq_wake_enabled = true; + return 0; + } + + cts_warn("Enable irq wake while irq invalid %d", pdata->irq); + return -ENODEV; +} + +int cts_plat_disable_irq_wake(struct cts_platform_data *pdata) +{ + int ret; + + cts_info("Disable IRQ wake"); + + if (pdata->irq > 0) { + ret = disable_irq_wake(pdata->irq); + if (ret < 0) { + cts_warn("Disable irq wake while already disabled"); + return -EINVAL; + } + pdata->irq_wake_enabled = false; + return 0; + } + + cts_warn("Disable irq wake while irq invalid %d", pdata->irq); + return -ENODEV; +} + +int cts_plat_init_gesture(struct cts_platform_data *pdata) +{ + int i; + + cts_info("Init gesture"); + + /* TODO: If system will issure enable/disable command, comment following line. */ + /* cts_enable_gesture_wakeup(pdata->cts_dev); */ + + for (i = 0; i < pdata->gesture_num; i++) { + input_set_capability(pdata->ts_input_dev, EV_KEY, + pdata->gesture_keymap[i][1]); + } + + return 0; +} + +void cts_plat_deinit_gesture(struct cts_platform_data *pdata) +{ + cts_info("De-init gesture"); +} + +int cts_plat_process_gesture_info(struct cts_platform_data *pdata, + struct cts_device_gesture_info *gesture_info) +{ + int i; + + cts_info("Process gesture, id=0x%02x", gesture_info->gesture_id); + +#if defined(CFG_CTS_GESTURE_REPORT_KEY) + for (i = 0; i < CFG_CTS_NUM_GESTURE; i++) { + if (gesture_info->gesture_id == pdata->gesture_keymap[i][0]) { + if (gesture_info->gesture_id == GESTURE_D_TAP) { + if (!pdata->cts_dev->rtdata.gesture_d_tap_enabled) { + cts_info("Ingore double tap"); + return 0; + } + } + + cts_info("Report key[%u]", pdata->gesture_keymap[i][1]); + input_report_key(pdata->ts_input_dev, pdata->gesture_keymap[i][1], 1); + input_sync(pdata->ts_input_dev); + + input_report_key(pdata->ts_input_dev, pdata->gesture_keymap[i][1], 0); + input_sync(pdata->ts_input_dev); + + return 0; + } + } +#endif /* CFG_CTS_GESTURE_REPORT_KEY */ + + cts_warn("Process unrecognized gesture id=%u", gesture_info->gesture_id); + + return -EINVAL; +} + +#endif /* CFG_CTS_GESTURE */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_platform.h b/drivers/input/touchscreen/chipone_tddi/cts_platform.h new file mode 100644 index 000000000000..777acf3b75be --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_platform.h @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CTS_PLATFORM_H +#define CTS_PLATFORM_H + +#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* #include */ +#include +#include + + +#ifdef CONFIG_OF +#include +#include +#include +#endif /* CONFIG_OF */ + +#include +#include + +#include +#include + +#include "cts_config.h" +#include "cts_core.h" + +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +#ifdef TPD_SUPPORT_I2C_DMA +#include +#endif /* TPD_SUPPORT_I2C_DMA */ + +#ifdef CONFIG_MTK_BOOT +#include "mtk_boot_common.h" +#endif /* CONFIG_MTK_BOOT */ + +#include "tpd.h" +#include "tpd_debug.h" +#include "upmu_common.h" +#endif + +extern bool cts_show_debug_log; + +#ifndef LOG_TAG +#define LOG_TAG "" +#endif /* LOG_TAG */ + +enum cts_driver_log_level { + CTS_DRIVER_LOG_ERROR, + CTS_DRIVER_LOG_WARN, + CTS_DRIVER_LOG_INFO, + CTS_DRIVER_LOG_DEBUG, +}; + +extern int cts_start_driver_log_redirect(const char *filepath, bool append_to_file, + char *log_buffer, int log_buf_size, int log_level); +extern void cts_stop_driver_log_redirect(void); +extern int cts_get_driver_log_redirect_size(void); +extern void cts_log(int level, const char *fmt, ...); + +#define cts_err(fmt, ...) \ + cts_log(CTS_DRIVER_LOG_ERROR, "CTS-" LOG_TAG " " fmt"\n", ##__VA_ARGS__) +#define cts_warn(fmt, ...) \ + cts_log(CTS_DRIVER_LOG_WARN, "CTS-" LOG_TAG " " fmt"\n", ##__VA_ARGS__) +#define cts_info(fmt, ...) \ + cts_log(CTS_DRIVER_LOG_INFO, "CTS-" LOG_TAG " " fmt"\n", ##__VA_ARGS__) +#define cts_dbg(fmt, ...) \ + cts_log(CTS_DRIVER_LOG_DEBUG, "CTS-" LOG_TAG " " fmt"\n", ##__VA_ARGS__) + + +struct cts_device; +struct cts_device_touch_msg; +struct cts_device_gesture_info; + +struct cts_platform_data { + int irq; + +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + int int_gpio; +#ifdef CFG_CTS_HAS_RESET_PIN + int rst_gpio; +#endif + +#ifdef CFG_CTS_MANUAL_CS + int cs_gpio; +#endif +#endif + + u32 res_x; + u32 res_y; + +#ifdef CONFIG_CTS_VIRTUALKEY + u8 vkey_num; + u8 vkey_state; + u8 vkey_keycodes[CFG_CTS_MAX_VKEY_NUM]; +#endif + +#ifdef CFG_CTS_FW_UPDATE_SYS + const char *panel_supplier; +#endif + u32 build_id; + u32 config_id; + + struct cts_device *cts_dev; + + struct input_dev *ts_input_dev; + struct input_dev *pen_input_dev; + +#ifndef CONFIG_GENERIC_HARDIRQS + struct work_struct ts_irq_work; +#endif + + struct mutex dev_lock; + struct spinlock irq_lock; + bool irq_is_disable; + +#ifdef CFG_CTS_GESTURE + u8 gesture_num; + u8 gesture_keymap[CFG_CTS_NUM_GESTURE][2]; + bool irq_wake_enabled; +#endif + +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +#ifdef TPD_SUPPORT_I2C_DMA + u8 *i2c_dma_buff_va; + dma_addr_t i2c_dma_buff_pa; +#endif /* TPD_SUPPORT_I2C_DMA */ +#else +#ifdef CONFIG_CTS_PM_FB_NOTIFIER + struct notifier_block fb_notifier; +#endif +#endif +#ifdef CFG_CTS_FORCE_UP + struct delayed_work touch_timeout; +#endif + +#ifdef CONFIG_CTS_I2C_HOST + struct i2c_client *i2c_client; + u8 i2c_fifo_buf[CFG_CTS_MAX_I2C_XFER_SIZE]; + u8 i2c_rbuf[ALIGN(CFG_CTS_MAX_I2C_READ_SIZE, 4)]; +#else + struct spi_device *spi_client; + u8 spi_cache_buf[ALIGN(CFG_CTS_MAX_SPI_XFER_SIZE + 10, 4)]; + u8 spi_rx_buf[ALIGN(CFG_CTS_MAX_SPI_XFER_SIZE + 10, 4)]; + u8 spi_tx_buf[ALIGN(CFG_CTS_MAX_SPI_XFER_SIZE + 10, 4)]; + u32 spi_speed; + +#ifdef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +#ifdef CFG_CTS_MANUAL_CS + struct pinctrl *pinctrl1; + struct pinctrl_state *spi_cs_low, *spi_cs_high; +#endif +#endif/* CONFIG_CTS_I2C_HOST */ + +#ifdef TPD_SUPPORT_I2C_DMA + bool i2c_dma_available; +#endif /* TPD_SUPPORT_I2C_DMA */ +#endif +}; + +#ifdef CONFIG_CTS_I2C_HOST +extern size_t cts_plat_get_max_i2c_xfer_size(struct cts_platform_data *pdata); +extern u8 *cts_plat_get_i2c_xfer_buf(struct cts_platform_data *pdata, + size_t xfer_size); +extern int cts_plat_i2c_write(struct cts_platform_data *pdata, u8 i2c_addr, + const void *src, size_t len, int retry, int delay); +extern int cts_plat_i2c_read(struct cts_platform_data *pdata, u8 i2c_addr, + const u8 *wbuf, size_t wlen, void *rbuf, + size_t rlen, int retry, int delay); +#else /* CONFIG_CTS_I2C_HOST */ +extern size_t cts_plat_get_max_spi_xfer_size(struct cts_platform_data *pdata); +extern u8 *cts_plat_get_spi_xfer_buf(struct cts_platform_data *pdata, + size_t xfer_size); +extern int cts_plat_spi_write(struct cts_platform_data *pdata, u8 i2c_addr, + const void *src, size_t len, int retry, int delay); +extern int cts_plat_spi_read(struct cts_platform_data *pdata, u8 i2c_addr, + const u8 *wbuf, size_t wlen, void *rbuf, + size_t rlen, int retry, int delay); +extern int cts_plat_spi_read_delay_idle(struct cts_platform_data *pdata, + u8 dev_addr, const u8 *wbuf, size_t wlen, void *rbuf, size_t rlen, + int retry, int delay, int idle); +extern int cts_spi_send_recv(struct cts_platform_data *pdata, size_t len, + u8 *tx_buffer, u8 *rx_buffer); +#endif /* CONFIG_CTS_I2C_HOST */ + +#ifdef CONFIG_CTS_I2C_HOST +extern int cts_init_platform_data(struct cts_platform_data *pdata, + struct i2c_client *i2c_client); +#else +extern int cts_init_platform_data(struct cts_platform_data *pdata, + struct spi_device *spi); +#endif + +extern int cts_plat_is_normal_mode(struct cts_platform_data *pdata); +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +extern int cts_deinit_platform_data(struct cts_platform_data *pdata); +#endif +extern int cts_plat_request_resource(struct cts_platform_data *pdata); +extern void cts_plat_free_resource(struct cts_platform_data *pdata); + +extern int cts_plat_request_irq(struct cts_platform_data *pdata); +extern void cts_plat_free_irq(struct cts_platform_data *pdata); +extern int cts_plat_enable_irq(struct cts_platform_data *pdata); +extern int cts_plat_disable_irq(struct cts_platform_data *pdata); + +#ifdef CFG_CTS_HAS_RESET_PIN +extern int cts_plat_reset_device(struct cts_platform_data *pdata); +#else /* CFG_CTS_HAS_RESET_PIN */ +static inline int cts_plat_reset_device(struct cts_platform_data *pdata) +{ + return 0; +} +#endif /* CFG_CTS_HAS_RESET_PIN */ + +extern int cts_plat_init_touch_device(struct cts_platform_data *pdata); +extern void cts_plat_deinit_touch_device(struct cts_platform_data *pdata); +#ifdef CFG_CTS_PALM_DETECT +void cts_report_palm_event(struct cts_platform_data *pdata); +#endif +extern int cts_plat_process_touch_msg(struct cts_platform_data *pdata, + struct cts_device_touch_msg *msgs, int num); +extern int cts_plat_process_stylus_msg(struct cts_platform_data *pdata, + struct cts_device_stylus_msg *msgs, int num); +#ifdef CFG_CTS_FINGER_STYLUS_SUPPORTED +extern int cts_plat_process_touch_stylus(struct cts_platform_data *pdata, + struct cts_device_touch_info *touch_info); +#endif +extern int cts_plat_release_all_touch(struct cts_platform_data *pdata); + +#ifdef CONFIG_CTS_VIRTUALKEY +extern int cts_plat_init_vkey_device(struct cts_platform_data *pdata); +extern void cts_plat_deinit_vkey_device(struct cts_platform_data *pdata); +extern int cts_plat_process_vkey(struct cts_platform_data *pdata, + u8 vkey_state); +extern int cts_plat_release_all_vkey(struct cts_platform_data *pdata); +#else /* CONFIG_CTS_VIRTUALKEY */ +static inline int cts_plat_init_vkey_device(struct cts_platform_data *pdata) +{ + return 0; +} + +static inline void cts_plat_deinit_vkey_device(struct cts_platform_data *pdata) +{ +} + +static inline int cts_plat_process_vkey(struct cts_platform_data *pdata, + u8 vkey_state) +{ + return 0; +} + +static inline int cts_plat_release_all_vkey(struct cts_platform_data *pdata) +{ + return 0; +} +#endif /* CONFIG_CTS_VIRTUALKEY */ + +#ifdef CFG_CTS_GESTURE +extern int cts_plat_enable_irq_wake(struct cts_platform_data *pdata); +extern int cts_plat_disable_irq_wake(struct cts_platform_data *pdata); + +extern int cts_plat_init_gesture(struct cts_platform_data *pdata); +extern void cts_plat_deinit_gesture(struct cts_platform_data *pdata); +extern int cts_plat_process_gesture_info(struct cts_platform_data *pdata, + struct cts_device_gesture_info *gesture_info); +#else /* CFG_CTS_GESTURE */ +static inline int cts_plat_init_gesture(struct cts_platform_data *pdata) +{ + return 0; +} + +static inline void cts_plat_deinit_gesture(struct cts_platform_data *pdata) +{ +} +#endif /* CFG_CTS_GESTURE */ + +extern int cts_plat_set_reset(struct cts_platform_data *pdata, int val); +extern int cts_plat_get_int_pin(struct cts_platform_data *pdata); + +#ifdef CFG_CTS_MANUAL_CS +extern int cts_plat_set_cs(struct cts_platform_data *pdata, int val); +#endif +#endif /* CTS_PLATFORM_H */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_sfctrl.h b/drivers/input/touchscreen/chipone_tddi/cts_sfctrl.h new file mode 100644 index 000000000000..769522e60934 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_sfctrl.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CTS_SFCTRL_H +#define CTS_SFCTRL_H + +#include "cts_core.h" +#include "cts_spi_flash.h" + +struct cts_device; + +struct cts_sfctrl_ops { + int (*rdid)(struct cts_device *cts_dev, u32 *id); + int (*se)(struct cts_device *cts_dev, u32 sector_addr); + int (*be)(struct cts_device *cts_dev, u32 sector_addr); + int (*read)(struct cts_device *cts_dev, + u32 flash_addr, void *dst, size_t len); + int (*read_to_sram)(struct cts_device *cts_dev, + u32 flash_addr, u32 sram_addr, size_t len); + int (*program)(struct cts_device *cts_dev, + u32 flash_addr, const void *src, size_t len); + int (*program_from_sram)(struct cts_device *cts_dev, + u32 flash_addr, u32 sram_addr, size_t len); + int (*calc_sram_crc)(struct cts_device *cts_dev, + u32 sram_addr, size_t len, u32 *crc); + int (*calc_flash_crc)(struct cts_device *cts_dev, + u32 flash_addr, size_t len, u32 *crc); +}; + +struct cts_sfctrl { + u32 reg_base; + u32 xchg_sram_base; + size_t xchg_sram_size; + const struct cts_sfctrl_ops *ops; +}; + +extern const struct cts_sfctrl_ops cts_sfctrlv2_ops; + +#endif /* CTS_SFCTRL_H */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_sfctrlv2.c b/drivers/input/touchscreen/chipone_tddi/cts_sfctrlv2.c new file mode 100644 index 000000000000..3125b7b568dd --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_sfctrlv2.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define LOG_TAG "SFCtrl" + +#include "cts_config.h" +#include "cts_platform.h" +#include "cts_core.h" +#include "cts_sfctrl.h" + +#define rSFCTRLv2_CMD_SEL (0x0000) +#define rSFCTRLv2_FLASH_ADDR (0x0004) +#define rSFCTRLv2_SRAM_ADDR (0x0008) +#define rSFCTRLv2_DATA_LENGTH (0x000C) +#define rSFCTRLv2_START_DEXC (0x0010) +#define rSFCTRLv2_RELEASE_FLASH (0x0014) +#define rSFCTRLv2_HW_STATE (0x0018) +#define rSFCTRLv2_CRC_RESULT (0x001C) +#define rSFCTRLv2_SRAM_CRC_START (0x0020) +#define rSFCTRLv2_FLASH_CRC_START (0x0022) +#define rSFCTRLv2_SF_BUSY (0x0024) + +/** Constants for register @ref rSFCTRLv2_CMD_SEL */ +enum sfctrlv2_cmd { + SFCTRLv2_CMD_FAST_READ = 0x01u, + SFCTRLv2_CMD_SE = 0x02u, + SFCTRLv2_CMD_BE = 0x03u, + SFCTRLv2_CMD_PP = 0x04u, + SFCTRLv2_CMD_RDSR = 0x05u, + SFCTRLv2_CMD_RDID = 0x06u +}; + +/** SPI flash controller v2 operation flags. */ +enum sfctrlv2_opflags { + SFCTRLv2_OPFLAG_READ = BIT(0), + SFCTRLv2_OPFLAG_SET_FLASH_ADDR = BIT(1), + SFCTRLv2_OPFLAG_SRAM_DATA_XCHG = BIT(2), + SFCTRLv2_OPFLAG_SET_DATA_LENGTH = BIT(3), + SFCTRLv2_OPFLAG_WAIT_WIP_CLR = BIT(4), +}; + +#define SFCTRLv2_CMD_RDID_FLAGS \ + (SFCTRLv2_OPFLAG_READ | \ + SFCTRLv2_OPFLAG_SRAM_DATA_XCHG) + +#define SFCTRLv2_CMD_RDSR_FLAGS \ + (SFCTRLv2_OPFLAG_READ | \ + SFCTRLv2_OPFLAG_SRAM_DATA_XCHG) + +#define SFCTRLv2_CMD_SE_FLAGS \ + (SFCTRLv2_OPFLAG_SET_FLASH_ADDR | \ + SFCTRLv2_OPFLAG_WAIT_WIP_CLR) + +#define SFCTRLv2_CMD_BE_FLAGS \ + (SFCTRLv2_OPFLAG_SET_FLASH_ADDR | \ + SFCTRLv2_OPFLAG_WAIT_WIP_CLR) + +#define SFCTRLv2_CMD_PP_FLAGS \ + (SFCTRLv2_OPFLAG_SET_FLASH_ADDR | \ + SFCTRLv2_OPFLAG_SRAM_DATA_XCHG | \ + SFCTRLv2_OPFLAG_SET_DATA_LENGTH | \ + SFCTRLv2_OPFLAG_WAIT_WIP_CLR) + +#define SFCTRLv2_CMD_FAST_READ_FLAGS \ + (SFCTRLv2_OPFLAG_READ | \ + SFCTRLv2_OPFLAG_SET_FLASH_ADDR | \ + SFCTRLv2_OPFLAG_SRAM_DATA_XCHG | \ + SFCTRLv2_OPFLAG_SET_DATA_LENGTH) + +#define sfctrl_reg_addr(cts_dev, offset) \ + ((cts_dev)->hwdata->sfctrl->reg_base + offset) + +#define DEFINE_SFCTRL_REG_ACCESS_FUNC(access_type, data_type) \ + static inline int sfctrl_reg_ ## access_type(struct cts_device *cts_dev, \ + u32 reg, data_type data) { \ + return cts_hw_reg_ ## access_type(cts_dev, sfctrl_reg_addr(cts_dev, reg), data); \ + } + +DEFINE_SFCTRL_REG_ACCESS_FUNC(writeb_relaxed, u8) +DEFINE_SFCTRL_REG_ACCESS_FUNC(writew_relaxed, u16) +DEFINE_SFCTRL_REG_ACCESS_FUNC(writel_relaxed, u32) +DEFINE_SFCTRL_REG_ACCESS_FUNC(readb_relaxed, u8 *) +DEFINE_SFCTRL_REG_ACCESS_FUNC(readw_relaxed, u16 *) +DEFINE_SFCTRL_REG_ACCESS_FUNC(readl_relaxed, u32 *) + +#define sfctrl_write_reg_check_ret(access_type, reg, val) \ + do { \ + int ret; \ + cts_dbg("Write " #reg " to 0x%x", val); \ + ret = sfctrl_reg_ ## access_type(cts_dev, reg, val); \ + if (ret) { \ + cts_err("Write " #reg " failed %d", ret); \ + return ret; \ + } \ + } while (0) +#define sfctrl_read_reg_check_ret(access_type, reg, val) \ + do { \ + int ret; \ + cts_dbg("Read " #reg ""); \ + ret = sfctrl_reg_ ## access_type(cts_dev, reg, val); \ + if (ret) { \ + cts_err("Read " #reg " failed %d", ret); \ + return ret; \ + } \ + } while (0) +static int wait_sfctrl_xfer_comp(struct cts_device *cts_dev) +{ + int retries = 0; + u8 status; + + do { + sfctrl_read_reg_check_ret(readb_relaxed, rSFCTRLv2_SF_BUSY, &status); + if (status == 0) + return 0; + mdelay(1); + } while (status && retries++ < 1000); + + return -ETIMEDOUT; +} + +static int sfctrlv2_rdsr(struct cts_device *cts_dev, u8 *status) +{ +#define RDSR_XCHG_SRAM_ADDR (cts_dev->hwdata->sfctrl->xchg_sram_base + cts_dev->hwdata->sfctrl->xchg_sram_size - 1) + + int ret; + + sfctrl_write_reg_check_ret(writeb_relaxed, rSFCTRLv2_CMD_SEL, + SFCTRLv2_CMD_RDSR); + sfctrl_write_reg_check_ret(writel_relaxed, rSFCTRLv2_SRAM_ADDR, + RDSR_XCHG_SRAM_ADDR); + sfctrl_write_reg_check_ret(writeb_relaxed, rSFCTRLv2_START_DEXC, 1); + + ret = wait_sfctrl_xfer_comp(cts_dev); + if (ret != 0) { + cts_err("Wait sfctrl ready failed %d", ret); + return ret; + } + + ret = cts_sram_readb(cts_dev, RDSR_XCHG_SRAM_ADDR, status); + if (ret) { + cts_err("Read exchange sram failed %d", ret); + return ret; + } +#undef RDSR_XCHG_SRAM_ADDR + + return 0; +} + +static int wait_flash_wip_clear(struct cts_device *cts_dev) +{ +#define FLASH_SR_WIP BIT(0) /*!< Write in progress */ + + int ret, retries = 0; + u8 status; + + do { + ret = sfctrlv2_rdsr(cts_dev, &status); + if (ret) { + cts_err("Read flash status register failed %d", ret); + return ret; + } + + if (status & FLASH_SR_WIP) + mdelay(1); + else + return 0; + } while (status & FLASH_SR_WIP && retries++ < 1000); + + return -ETIMEDOUT; +#undef FLASH_SR_WIP +} + +static int sfctrlv2_transfer(struct cts_device *cts_dev, + u8 cmd, void *data, u32 flash_addr, u32 sram_addr, + size_t size, u32 flags) +{ + int ret; + + sfctrl_write_reg_check_ret(writeb_relaxed, rSFCTRLv2_CMD_SEL, cmd); + + if (flags & SFCTRLv2_OPFLAG_SRAM_DATA_XCHG) { + sfctrl_write_reg_check_ret(writel_relaxed, rSFCTRLv2_SRAM_ADDR, + sram_addr); + +/** - Write data to exchange SRAM if operation is write and + * data != NULL(data not in SRAM) + */ + if ((!(flags & SFCTRLv2_OPFLAG_READ)) && data) { + ret = cts_sram_writesb(cts_dev, sram_addr, data, size); + if (ret) { + cts_err("Write data to exchange sram 0x%06x size %zu failed %d", + sram_addr, size, ret); + return ret; + } + } + } + + if (flags & SFCTRLv2_OPFLAG_SET_FLASH_ADDR) { + sfctrl_write_reg_check_ret(writel_relaxed, rSFCTRLv2_FLASH_ADDR, + flash_addr); + } + if (flags & SFCTRLv2_OPFLAG_SET_DATA_LENGTH) { + sfctrl_write_reg_check_ret(writel_relaxed, + rSFCTRLv2_DATA_LENGTH, (u32) size); + } + + sfctrl_write_reg_check_ret(writeb_relaxed, rSFCTRLv2_START_DEXC, 1); + + ret = wait_sfctrl_xfer_comp(cts_dev); + if (ret != 0) { + cts_err("Wait sfctrl ready failed %d", ret); + return ret; + } + + ret = wait_flash_wip_clear(cts_dev); + if (flags & SFCTRLv2_OPFLAG_WAIT_WIP_CLR && ret != 0) { + cts_err("Wait WIP clear failed %d", ret); + return ret; + } + + if ((flags & SFCTRLv2_OPFLAG_READ) && data) { + ret = cts_sram_readsb(cts_dev, sram_addr, data, size); + if (ret) { + cts_err("Read data from exchange sram 0x%06x size %zu failed %d", + sram_addr, size, ret); + return ret; + } + } + + return 0; +} + +static int sfctrlv2_rdid(struct cts_device *cts_dev, u32 *id) +{ + int ret; + u8 buf[4]; + + ret = sfctrlv2_transfer(cts_dev, SFCTRLv2_CMD_RDID, buf, + 0, cts_dev->hwdata->sfctrl->xchg_sram_base, 3, + SFCTRLv2_CMD_RDID_FLAGS); + *id = ret ? 0 : get_unaligned_be24(buf); + + return ret; +} + +static int sfctrlv2_se(struct cts_device *cts_dev, u32 sector_addr) +{ + return sfctrlv2_transfer(cts_dev, SFCTRLv2_CMD_SE, NULL, + sector_addr, 0, 0, SFCTRLv2_CMD_SE_FLAGS); +} + +static int sfctrlv2_be(struct cts_device *cts_dev, u32 block_addr) +{ + return sfctrlv2_transfer(cts_dev, SFCTRLv2_CMD_BE, NULL, + block_addr, 0, 0, SFCTRLv2_CMD_BE_FLAGS); +} + +static int sfctrlv2_pp(struct cts_device *cts_dev, + u32 flash_addr, const void *src, size_t size) +{ + return sfctrlv2_transfer(cts_dev, SFCTRLv2_CMD_PP, (void *)src, + flash_addr, cts_dev->hwdata->sfctrl->xchg_sram_base, size, + SFCTRLv2_CMD_PP_FLAGS); +} + +static int sfctrlv2_program_flash_from_sram(struct cts_device *cts_dev, + u32 flash_addr, u32 sram_addr, size_t len) +{ + return sfctrlv2_transfer(cts_dev, SFCTRLv2_CMD_PP, NULL, + flash_addr, sram_addr, len, SFCTRLv2_CMD_PP_FLAGS); +} + +static int sfctrlv2_read(struct cts_device *cts_dev, + u32 flash_addr, void *dst, size_t size) +{ + return sfctrlv2_transfer(cts_dev, SFCTRLv2_CMD_FAST_READ, dst, + flash_addr, cts_dev->hwdata->sfctrl->xchg_sram_base, size, + SFCTRLv2_CMD_FAST_READ_FLAGS); +} + +static int sfctrlv2_read_flash_to_sram(struct cts_device *cts_dev, + u32 flash_addr, u32 sram_addr, size_t size) +{ + return sfctrlv2_transfer(cts_dev, SFCTRLv2_CMD_FAST_READ, NULL, + flash_addr, sram_addr, size, SFCTRLv2_CMD_FAST_READ_FLAGS); +} + +static int sfctrlv2_calc_sram_crc(struct cts_device *cts_dev, + u32 sram_addr, size_t size, u32 *crc) +{ + int ret; + + sfctrl_write_reg_check_ret(writel_relaxed, rSFCTRLv2_SRAM_ADDR, sram_addr); + mdelay(1); + sfctrl_write_reg_check_ret(writel_relaxed, rSFCTRLv2_DATA_LENGTH, (u32) size); + mdelay(1); + sfctrl_write_reg_check_ret(writeb_relaxed, rSFCTRLv2_SRAM_CRC_START, 1); + mdelay(10); + + ret = wait_sfctrl_xfer_comp(cts_dev); + if (ret != 0) { + cts_err("Wait sfctrl ready failed %d", ret); + return ret; + } + + sfctrl_read_reg_check_ret(readl_relaxed, rSFCTRLv2_CRC_RESULT, crc); + + return 0; +} + +static int sfctrlv2_calc_flash_crc(struct cts_device *cts_dev, + u32 flash_addr, size_t size, u32 *crc) +{ + int ret; + + cts_info("Calc crc from flash 0x%06x size %zu", flash_addr, size); + + sfctrl_write_reg_check_ret(writel_relaxed, rSFCTRLv2_FLASH_ADDR, flash_addr); + sfctrl_write_reg_check_ret(writel_relaxed, rSFCTRLv2_DATA_LENGTH, (u32) size); + sfctrl_write_reg_check_ret(writeb_relaxed, rSFCTRLv2_FLASH_CRC_START, 1); + + ret = wait_sfctrl_xfer_comp(cts_dev); + if (ret != 0) { + cts_err("Wait sfctrl ready failed %d", ret); + return ret; + } + + sfctrl_read_reg_check_ret(readl_relaxed, rSFCTRLv2_CRC_RESULT, crc); + + return 0; +} + +const struct cts_sfctrl_ops cts_sfctrlv2_ops = { + .rdid = sfctrlv2_rdid, + .se = sfctrlv2_se, + .be = sfctrlv2_be, + .read = sfctrlv2_read, + .read_to_sram = sfctrlv2_read_flash_to_sram, + .program = sfctrlv2_pp, + .program_from_sram = sfctrlv2_program_flash_from_sram, + .calc_sram_crc = sfctrlv2_calc_sram_crc, + .calc_flash_crc = sfctrlv2_calc_flash_crc, +}; diff --git a/drivers/input/touchscreen/chipone_tddi/cts_spi_flash.c b/drivers/input/touchscreen/chipone_tddi/cts_spi_flash.c new file mode 100644 index 000000000000..8c279367238d --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_spi_flash.c @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define LOG_TAG "Flash" + +#include "cts_config.h" +#include "cts_platform.h" +#include "cts_core.h" +#include "cts_sfctrl.h" +#include "cts_spi_flash.h" +#include "cts_strerror.h" + +/* NOTE: double check command sets and memory organization when you add + * more flash chips. This current list focusses on newer chips, which + * have been converging on command sets which including JEDEC ID. + */ +static const struct cts_flash cts_flashes[] = { + /* Winbond */ + { "Winbond,W25Q10EW", + 0xEF6011, 256, 0x1000, 0x8000, 0x20000}, + { "Winbond,W25Q20EW", + 0xEF6012, 256, 0x1000, 0x8000, 0x40000}, + { "Winbond,W25Q40EW", + 0xEF6013, 256, 0x1000, 0x8000, 0x80000}, + { "Winbond,W25Q20BW", + 0xEF5012, 256, 0x1000, 0x8000, 0x40000}, + { "Winbond,W25Q40BW", + 0xEF5013, 256, 0x1000, 0x8000, 0x80000}, + + /* Giga device */ + { "Giga,GD25LQ10B", + 0xC86011, 256, 0x1000, 0x8000, 0x20000}, + { "Giga,GD25LD20CEIGR", + 0xC86012, 256, 0x1000, 0x8000, 0x40000}, + { "Giga,GD25LD40CEIGR", + 0xC86013, 256, 0x1000, 0x8000, 0x80000}, + + /* Macronix */ + { "Macronix,MX25U1001E", + 0xC22531, 32, 0x1000, 0x10000, 0x20000}, + { "Macronix,MX25R2035F", + 0xC22812, 256, 0x1000, 0x8000, 0x40000}, + { "Macronix,MX25R4035F", + 0xC22813, 256, 0x1000, 0x8000, 0x80000}, + + /* Puya-Semi */ + { "Puya-Semi,P25Q20LUVHIR", + 0x856012, 256, 0x1000, 0x8000, 0x40000}, + { "Puya-Semi,P25Q40LUXHIR", + 0x856013, 256, 0x1000, 0x8000, 0x80000}, + { "Puya-Semi,P25Q11L", + 0x854011, 256, 0x1000, 0x8000, 0x20000}, + { "Puya-Semi,P25Q21L", + 0x854012, 256, 0x1000, 0x8000, 0x40000}, + { "Puya-Semi,P25T22L", + 0x854412, 256, 0x1000, 0x8000, 0x40000}, + + + /* Boya */ + { "Boya,BY25D10", + 0x684011, 256, 0x1000, 0x8000, 0x20000}, + + /* EON */ + { "EoN,EN25S20A", + 0x1C3812, 256, 0x1000, 0x8000, 0x40000}, + + /* XTXTECH */ + { "XTX,XT25Q01", + 0x0B6011, 256, 0x1000, 0, 0x20000}, + { "XTX,XT25Q02", + 0x0B6012, 256, 0x1000, 0, 0x40000}, + + /* Xinxin-Semi */ + { "Xinxin-Semi,XM25QU20B", + 0x205012, 256, 0x1000, 0x8000, 0x40000}, + { "Xinxin-Semi,XM25QU40B", + 0x205013, 256, 0x1000, 0x8000, 0x80000}, + + { "KangYong,XK25Q20T", + 0xEB6012, 256, 0x1000, 0x8000, 0x40000}, + + /*ZBIT*/ + { "ZBIT,ZB25LD40B", + 0x5E1013, 256, 0x1000, 0x8000, 0x80000}, +}; + +static const struct cts_flash *find_flash_by_jedec_id(u32 jedec_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cts_flashes); i++) { + if (cts_flashes[i].jedec_id == jedec_id) { + return &cts_flashes[i]; + } + } + + return NULL; +} + +static int probe_flash(struct cts_device *cts_dev) +{ + int ret; + + cts_info("Probe flash"); + + if (cts_dev->hwdata->sfctrl->ops->rdid != NULL) { + u32 id; + + cts_info("Read JEDEC ID"); + + ret = cts_dev->hwdata->sfctrl->ops->rdid(cts_dev, &id); + if (ret) { + cts_err("Read JEDEC ID failed %d(%s)", + ret, cts_strerror(ret)); + return ret; + } + + cts_dev->flash = find_flash_by_jedec_id(id); + if (cts_dev->flash == NULL) { + cts_err("Unknown JEDEC ID: %06x", id); + return -ENODEV; + } + + cts_info("Flash type: '%s'", cts_dev->flash->name); + return 0; + } else { + cts_err("Probe flash with sfctrl->ops->rdid == NULL"); + return -ENOTSUPP; + } +} + +/** Make sure sector addr is sector aligned && < flash total size */ +static int erase_sector_retry(struct cts_device *cts_dev, + u32 sector_addr, int retry) +{ + int ret, retries; + + cts_info(" Erase sector 0x%06x", sector_addr); + + retries = 0; + do { + retries++; + ret = cts_dev->hwdata->sfctrl->ops->se(cts_dev, sector_addr); + if (ret) { + cts_err("Erase sector 0x%06x failed %d(%s) retries %d", + sector_addr, ret, cts_strerror(ret), retries); + continue; + } + } while (retries < retry); + + return ret; +} + +/** Make sure sector addr is sector aligned && < flash total size */ +static inline int erase_sector(struct cts_device *cts_dev, + u32 sector_addr) +{ + return erase_sector_retry(cts_dev, sector_addr, CTS_FLASH_ERASE_DEFAULT_RETRY); +} + +/** Make sure block addr is block aligned && < flash total size */ +static int erase_block_retry(struct cts_device *cts_dev, + u32 block_addr, int retry) +{ + int ret, retries; + + cts_info(" Erase block 0x%06x", block_addr); + + retries = 0; + do { + retries++; + + ret = cts_dev->hwdata->sfctrl->ops->be(cts_dev, block_addr); + if (ret) { + cts_err("Erase block 0x%06x failed %d(%s) retries %d", + block_addr, ret, cts_strerror(ret), retries); + continue; + } + } while (retries < retry); + + return ret; +} + +/** Make sure block addr is block aligned && < flash total size */ +static inline int erase_block(struct cts_device *cts_dev, + u32 block_addr) +{ + return erase_block_retry(cts_dev, block_addr, + CTS_FLASH_ERASE_DEFAULT_RETRY); +} + +int cts_prepare_flash_operation(struct cts_device *cts_dev) +{ + int ret; + u8 diva = 0; + bool program_mode = cts_is_device_program_mode(cts_dev); + u32 hwid; + bool enabled = cts_is_device_enabled(cts_dev); + + cts_info("Prepare for flash operation"); + + if (!program_mode) { + ret = cts_enter_program_mode(cts_dev); + if (ret) { + cts_err("Enter program mode failed %d(%s)", + ret, cts_strerror(ret)); + goto err_start_device; + } + } + + ret = cts_hw_reg_readb_retry(cts_dev, CTS_DEV_HW_REG_CLK_DIV_CFG, &diva, 5, 0); + + if (ret) { + cts_warn("Read DIVA failed %d(%s)", ret, cts_strerror(ret)); + } else { + cts_dbg("Device DIVA = %d", diva); + } + + if (ret == 0 && diva == 0x0C) { + cts_info("DIVA is ready already"); + } else { + int retries; + + /* Set HCLK to 10MHz */ + hwid = cts_dev->hwdata->hwid; + if (hwid == CTS_DEV_HWID_ICNL9951) { + + ret = cts_hw_reg_writeb_retry(cts_dev, CTS_DEV_HW_REG_CLK_DIV_CFG, 0x0C, 5, 0); + if (ret) { + cts_err("Write DIVA failed %d(%s)", + ret, cts_strerror(ret)); + goto err_enter_normal_mode; + } + } + + /* Reset SFCTL */ + ret = cts_hw_reg_writeb_retry(cts_dev, CTS_DEV_HW_REG_RESET_CONFIG, 0xFB, 5, 0); + if (ret) { + cts_err("Reset sfctl failed %d(%s)", + ret, cts_strerror(ret)); + goto err_enter_normal_mode; + } + + retries = 0; + do { + u8 state; + + ret = cts_hw_reg_readb_relaxed(cts_dev, CTS_DEV_HW_REG_HW_STATUS, &state); + if (ret == 0 && (state & 0x40) != 0) + goto init_flash; + + mdelay(2); + } while (++retries < 1000); + + if (ret) { + cts_warn("Wait SFCTRL ready failed %d(%s)", ret, cts_strerror(ret)); + } + // Go through and try + } + +init_flash: + if (cts_dev->flash == NULL) { + cts_info("Flash is not initialized, try to probe..."); + if ((ret = probe_flash(cts_dev)) != 0) { + cts_dev->rtdata.has_flash = false; + cts_warn("Probe flash failed %d(%s)", + ret, cts_strerror(ret)); + return 0; + } + } + cts_dev->rtdata.has_flash = true; + return 0; + +err_enter_normal_mode: + if (!program_mode) { + int r = cts_enter_normal_mode(cts_dev); + if (r) { + cts_err("Enter normal mode failed %d(%s)", + r, cts_strerror(r)); + } + } +err_start_device: + if (enabled) { + int r = cts_start_device(cts_dev); + if (r) { + cts_err("Start device failed %d(%s)", + r, cts_strerror(r)); + } + } + + return ret; +} + +int cts_post_flash_operation(struct cts_device *cts_dev) +{ + u32 hwid; + cts_info("Post flash operation"); + + hwid = cts_dev->hwdata->hwid; + if (hwid == CTS_DEV_HWID_ICNL9951) { + return cts_hw_reg_writeb_retry(cts_dev, CTS_DEV_HW_REG_CLK_DIV_CFG, 4, 5, 0); + } + return 0; +} + +int cts_read_flash_retry(struct cts_device *cts_dev, + u32 flash_addr, void *dst, size_t size, int retry) +{ + const struct cts_sfctrl *sfctrl; + const struct cts_flash *flash; + int ret; + + cts_info("Read from 0x%06x size %zu", flash_addr, size); + + sfctrl = cts_dev->hwdata->sfctrl; + flash = cts_dev->flash; + + if (flash == NULL || + sfctrl == NULL || + sfctrl->ops == NULL || + sfctrl->ops->read == NULL) { + cts_err("Read not supported"); + return -ENOTSUPP; + } + + if (flash_addr > flash->total_size) { + cts_err("Read from 0x%06x > flash size 0x%06zx", + flash_addr, flash->total_size); + return -EINVAL; + } + size = min(size, flash->total_size - flash_addr); + + cts_info("Read actually from 0x%06x size %zu", flash_addr, size); + + while (size) { + size_t l; + + l = min(sfctrl->xchg_sram_size, size); + + ret = sfctrl->ops->read(cts_dev, flash_addr, dst, l); + if(ret < 0) { + cts_err("Read from 0x%06x size %zu failed %d(%s)", + flash_addr, size, ret, cts_strerror(ret)); + return ret; + } + + dst += l; + size -= l; + flash_addr += l; + } + + return 0; +} + +int cts_program_flash(struct cts_device *cts_dev, + u32 flash_addr, const void *src, size_t size) +{ + const struct cts_sfctrl *sfctrl; + const struct cts_flash *flash; + int ret; + + cts_info("Program to 0x%06x size %zu", flash_addr, size); + + sfctrl = cts_dev->hwdata->sfctrl; + flash = cts_dev->flash; + + if (flash == NULL || + sfctrl == NULL || + sfctrl->ops == NULL || + sfctrl->ops->program == NULL) { + cts_err("Program not supported"); + return -ENOTSUPP; + } + + if (flash_addr >= flash->total_size) { + cts_err("Program from 0x%06x >= flash size 0x%06zx", + flash_addr, flash->total_size); + return -EINVAL; + } + size = min(size, flash->total_size - flash_addr); + + cts_info("Program actually to 0x%06x size %zu", flash_addr, size); + + while (size) { + size_t l, offset; + + l = min(flash->page_size, size); + offset = flash_addr & (flash->page_size - 1); + + if (offset) { + l = min(flash->page_size - offset, l); + } + + ret = sfctrl->ops->program(cts_dev, flash_addr, src, l); + if(ret) { + cts_err("Program to 0x%06x size %zu failed %d(%s)", + flash_addr, l, ret, cts_strerror(ret)); + return ret; + } + + src += l; + size -= l; + flash_addr += l; + } + + return 0; +} + +int cts_program_flash_from_sram(struct cts_device *cts_dev, + u32 flash_addr, u32 sram_addr, size_t size) +{ + const struct cts_sfctrl *sfctrl; + const struct cts_flash *flash; + int ret; + + cts_info("Program to 0x%06x from sram 0x%06x size %zu", + flash_addr, sram_addr, size); + + sfctrl = cts_dev->hwdata->sfctrl; + flash = cts_dev->flash; + + if (flash == NULL || + sfctrl == NULL || + sfctrl->ops == NULL || + sfctrl->ops->program_from_sram == NULL) { + cts_err("Program from sram not supported"); + return -ENOTSUPP; + } + + if (flash_addr >= flash->total_size) { + cts_err("Program from 0x%06x >= flash size 0x%06zx", + flash_addr, flash->total_size); + return -EINVAL; + } + size = min(size, flash->total_size - flash_addr); + + cts_info("Program actually to 0x%06x from sram 0x%06x size %zu", + flash_addr, sram_addr, size); + + while (size) { + size_t l, offset; + + l = min(flash->page_size, size); + offset = flash_addr & (flash->page_size - 1); + + if (offset) { + l = min(flash->page_size - offset, l); + } + + ret = sfctrl->ops->program_from_sram(cts_dev, + flash_addr, sram_addr, l); + if(ret) { + cts_err("Program to 0x%06x from sram 0x%06x size %zu " + "failed %d(%s)", + flash_addr, sram_addr, l, ret, cts_strerror(ret)); + return ret; + } + + size -= l; + flash_addr += l; + sram_addr += l; + } + + return 0; +} + +int cts_erase_flash(struct cts_device *cts_dev, u32 addr, size_t size) +{ + const struct cts_sfctrl *sfctrl; + const struct cts_flash *flash; + int ret; + + cts_info("Erase from 0x%06x size %zu", addr, size); + + sfctrl = cts_dev->hwdata->sfctrl; + flash = cts_dev->flash; + + if (flash == NULL || + sfctrl == NULL || sfctrl->ops == NULL || + sfctrl->ops->se == NULL || sfctrl->ops->be == NULL || + flash == NULL) { + cts_err("Oops"); + return -EINVAL; + } + + /* Addr and size MUST sector aligned */ + addr = rounddown(addr, flash->sector_size); + size = roundup(size, flash->sector_size); + + if (addr > flash->total_size) { + cts_err("Erase from 0x%06x > flash size 0x%06zx", + addr, flash->total_size); + return -EINVAL; + } + size = min(size, flash->total_size - addr); + + cts_info("Erase actually from 0x%06x size %zu", addr, size); + + if (flash->block_size) { + while (addr != ALIGN(addr, flash->block_size) && + size >= flash->sector_size) { + ret = erase_sector(cts_dev, addr); + if (ret) { + cts_err("Erase sector 0x%06x size 0x%04zx failed %d(%s)", + addr, flash->sector_size, ret, cts_strerror(ret)); + return ret; + } + addr += flash->sector_size; + size -= flash->sector_size; + } + + while (size >= flash->block_size) { + ret = erase_block(cts_dev, addr); + if (ret) { + cts_err("Erase block 0x%06x size 0x%04zx failed %d(%s)", + addr, flash->block_size, ret, cts_strerror(ret)); + return ret; + } + addr += flash->block_size; + size -= flash->block_size; + } + } + + while (size >= flash->sector_size) { + ret = erase_sector(cts_dev, addr); + if (ret) { + cts_err("Erase sector 0x%06x size 0x%04zx failed %d(%s)", + addr, flash->sector_size, ret, cts_strerror(ret)); + return ret; + } + addr += flash->sector_size; + size -= flash->sector_size; + } + + return 0; +} + diff --git a/drivers/input/touchscreen/chipone_tddi/cts_spi_flash.h b/drivers/input/touchscreen/chipone_tddi/cts_spi_flash.h new file mode 100644 index 000000000000..18db4386b6b7 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_spi_flash.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CTS_SPI_FLASH_H +#define CTS_SPI_FLASH_H + +struct cts_flash { + const char *name; + u32 jedec_id; /* Device ID by command 0x9F */ + size_t page_size; /* Page size by command 0x02 */ + size_t sector_size; /* Sector size by command 0x20 */ + +/* Block size by command 0x52, + * if 0 means block erase NOT supported + */ + size_t block_size; + size_t total_size; + +}; + +#define CTS_FLASH_READ_DEFAULT_RETRY (3) +#define CTS_FLASH_ERASE_DEFAULT_RETRY (3) + +struct cts_device; + +extern int cts_prepare_flash_operation(struct cts_device *cts_dev); +extern int cts_post_flash_operation(struct cts_device *cts_dev); + +extern int cts_read_flash_retry(struct cts_device *cts_dev, + u32 flash_addr, void *dst, size_t size, int retry); +static inline int cts_read_flash(struct cts_device *cts_dev, + u32 flash_addr, void *dst, size_t size) +{ + return cts_read_flash_retry(cts_dev, flash_addr, dst, size, + CTS_FLASH_READ_DEFAULT_RETRY); +} + +extern int cts_erase_flash(struct cts_device *cts_dev, u32 addr, + size_t size); + +extern int cts_program_flash(struct cts_device *cts_dev, + u32 flash_addr, const void *src, size_t size); +extern int cts_program_flash_from_sram(struct cts_device *cts_dev, + u32 flash_addr, u32 sram_addr, size_t size); + +#endif /* CTS_SPI_FLASH_H */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_strerror.c b/drivers/input/touchscreen/chipone_tddi/cts_strerror.c new file mode 100644 index 000000000000..fe5eac2f40d9 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_strerror.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "cts_strerror.h" + +#define ERROR_TEXT_ENTRY(errno, err_desc_str) \ + [(errno)] = #errno ", " err_desc_str + +static char *_error_text[] = { + [0] = "Success", + ERROR_TEXT_ENTRY(EPERM, "Operation not permitted"), + ERROR_TEXT_ENTRY(ENOENT, "No such file or directory"), + ERROR_TEXT_ENTRY(ESRCH, "No such process"), + ERROR_TEXT_ENTRY(EINTR, "Interrupted system call"), + ERROR_TEXT_ENTRY(EIO, "Input/output error"), + ERROR_TEXT_ENTRY(ENXIO, "No such device or address"), + ERROR_TEXT_ENTRY(E2BIG, "Argument list too long"), + ERROR_TEXT_ENTRY(ENOEXEC, "Exec format error"), + ERROR_TEXT_ENTRY(EBADF, "Bad file descriptor"), + ERROR_TEXT_ENTRY(ECHILD, "No child processes"), + ERROR_TEXT_ENTRY(EAGAIN, "Resource temporarily unavailable"), + ERROR_TEXT_ENTRY(ENOMEM, "Cannot allocate memory"), + ERROR_TEXT_ENTRY(EACCES, "Permission denied"), + ERROR_TEXT_ENTRY(EFAULT, "Bad address"), + ERROR_TEXT_ENTRY(ENOTBLK, "Block device required"), + ERROR_TEXT_ENTRY(EBUSY, "Device or resource busy"), + ERROR_TEXT_ENTRY(EEXIST, "File exists"), + ERROR_TEXT_ENTRY(EXDEV, "Invalid cross-device link"), + ERROR_TEXT_ENTRY(ENODEV, "No such device"), + ERROR_TEXT_ENTRY(ENOTDIR, "Not a directory"), + ERROR_TEXT_ENTRY(EISDIR, "Is a directory"), + ERROR_TEXT_ENTRY(EINVAL, "Invalid argument"), + ERROR_TEXT_ENTRY(EMFILE, "Too many open files"), + ERROR_TEXT_ENTRY(ENFILE, "Too many open files in system"), + ERROR_TEXT_ENTRY(ENOTTY, "Inappropriate ioctl for device"), + ERROR_TEXT_ENTRY(ETXTBSY, "Text file busy"), + ERROR_TEXT_ENTRY(EFBIG, "File too large"), + ERROR_TEXT_ENTRY(ENOSPC, "No space left on device"), + ERROR_TEXT_ENTRY(ESPIPE, "Illegal seek"), + ERROR_TEXT_ENTRY(EROFS, "Read-only file system"), + ERROR_TEXT_ENTRY(EMLINK, "Too many links"), + ERROR_TEXT_ENTRY(EPIPE, "Broken pipe"), + ERROR_TEXT_ENTRY(EDOM, "Numerical argument out of domain"), + ERROR_TEXT_ENTRY(ERANGE, "Numerical result out of range"), + ERROR_TEXT_ENTRY(EDEADLK, "Resource deadlock avoided"), + ERROR_TEXT_ENTRY(ENAMETOOLONG, "File name too long"), + ERROR_TEXT_ENTRY(ENOLCK, "No locks available"), + ERROR_TEXT_ENTRY(ENOSYS, "Function not implemented"), + ERROR_TEXT_ENTRY(ENOTEMPTY, "Directory not empty"), + ERROR_TEXT_ENTRY(ELOOP, "Too many levels of symbolic links"), + ERROR_TEXT_ENTRY(EWOULDBLOCK, "Operation would block"), + ERROR_TEXT_ENTRY(ENOMSG, "No message of desired type"), + ERROR_TEXT_ENTRY(EIDRM, "Identifier removed"), + ERROR_TEXT_ENTRY(ECHRNG, "Channel number out of range"), + ERROR_TEXT_ENTRY(EL2NSYNC, "Level 2 not synchronized"), + ERROR_TEXT_ENTRY(EL3HLT, "Level 3 halted"), + ERROR_TEXT_ENTRY(EL3RST, "Level 3 reset"), + ERROR_TEXT_ENTRY(ELNRNG, "Link number out of range"), + ERROR_TEXT_ENTRY(EUNATCH, "Protocol driver not attached"), + ERROR_TEXT_ENTRY(ENOCSI, "No CSI structure available"), + ERROR_TEXT_ENTRY(EL2HLT, "Level 2 halted"), + ERROR_TEXT_ENTRY(EBADE, "Invalid exchange"), + ERROR_TEXT_ENTRY(EBADR, "Invalid request descriptor"), + ERROR_TEXT_ENTRY(EXFULL, "Exchange full"), + ERROR_TEXT_ENTRY(ENOANO, "No anode"), + ERROR_TEXT_ENTRY(EBADRQC, "Invalid request code"), + ERROR_TEXT_ENTRY(EBADSLT, "Invalid slot"), + ERROR_TEXT_ENTRY(EDEADLOCK, "File locking deadlock error"), + ERROR_TEXT_ENTRY(EBFONT, "Bad font file format"), + ERROR_TEXT_ENTRY(ENOSTR, "Device not a stream"), + ERROR_TEXT_ENTRY(ENODATA, "No data available"), + ERROR_TEXT_ENTRY(ETIME, "Timer expired"), + ERROR_TEXT_ENTRY(ENOSR, "Out of streams resources"), + ERROR_TEXT_ENTRY(ENONET, "Machine is not on the network"), + ERROR_TEXT_ENTRY(ENOPKG, "Package not installed"), + ERROR_TEXT_ENTRY(EREMOTE, "Object is remote"), + ERROR_TEXT_ENTRY(ENOLINK, "Link has been severed"), + ERROR_TEXT_ENTRY(EADV, "Advertise error"), + ERROR_TEXT_ENTRY(ESRMNT, "Srmount error"), + ERROR_TEXT_ENTRY(ECOMM, "Communication error on send"), + ERROR_TEXT_ENTRY(EPROTO, "Protocol error"), + ERROR_TEXT_ENTRY(EMULTIHOP, "Multihop attempted"), + ERROR_TEXT_ENTRY(EDOTDOT, "RFS specific error"), + ERROR_TEXT_ENTRY(EBADMSG, "Bad message"), + ERROR_TEXT_ENTRY(EOVERFLOW, "Value too large for defined data type"), + ERROR_TEXT_ENTRY(ENOTUNIQ, "Name not unique on network"), + ERROR_TEXT_ENTRY(EBADFD, "File descriptor in bad state"), + ERROR_TEXT_ENTRY(EREMCHG, "Remote address changed"), + ERROR_TEXT_ENTRY(ELIBACC, "Can not access a needed shared library"), + ERROR_TEXT_ENTRY(ELIBBAD, "Accessing a corrupted shared library"), + ERROR_TEXT_ENTRY(ELIBSCN, ".lib section in a.out corrupted"), + ERROR_TEXT_ENTRY(ELIBMAX, "Attempting to link in too many shared libraries"), + ERROR_TEXT_ENTRY(ELIBEXEC, "Cannot exec a shared library directly"), + ERROR_TEXT_ENTRY(EILSEQ, "Invalid or incomplete multibyte or wide character"), + ERROR_TEXT_ENTRY(ERESTART, "Interrupted system call should be restarted"), + ERROR_TEXT_ENTRY(ESTRPIPE, "Streams pipe error"), + ERROR_TEXT_ENTRY(EUSERS, "Too many users"), + ERROR_TEXT_ENTRY(ENOTSOCK, "Socket operation on non-socket"), + ERROR_TEXT_ENTRY(EDESTADDRREQ, "Destination address required"), + ERROR_TEXT_ENTRY(EMSGSIZE, "Message too long"), + ERROR_TEXT_ENTRY(EPROTOTYPE, "Protocol wrong type for socket"), + ERROR_TEXT_ENTRY(ENOPROTOOPT, "Protocol not available"), + ERROR_TEXT_ENTRY(EPROTONOSUPPORT, "Protocol not supported"), + ERROR_TEXT_ENTRY(ESOCKTNOSUPPORT, "Socket type not supported"), + ERROR_TEXT_ENTRY(EOPNOTSUPP, "Operation not supported"), + ERROR_TEXT_ENTRY(EPFNOSUPPORT, "Protocol family not supported"), + ERROR_TEXT_ENTRY(EAFNOSUPPORT, "Address family not supported by protocol"), + ERROR_TEXT_ENTRY(EADDRINUSE, "Address already in use"), + ERROR_TEXT_ENTRY(EADDRNOTAVAIL, "Cannot assign requested address"), + ERROR_TEXT_ENTRY(ENETDOWN, "Network is down"), + ERROR_TEXT_ENTRY(ENETUNREACH, "Network is unreachable"), + ERROR_TEXT_ENTRY(ENETRESET, "Network dropped connection on reset"), + ERROR_TEXT_ENTRY(ECONNABORTED, "Software caused connection abort"), + ERROR_TEXT_ENTRY(ECONNRESET, "Connection reset by peer"), + ERROR_TEXT_ENTRY(ENOBUFS, "No buffer space available"), + ERROR_TEXT_ENTRY(EISCONN, "Transport endpoint is already connected"), + ERROR_TEXT_ENTRY(ENOTCONN, "Transport endpoint is not connected"), + ERROR_TEXT_ENTRY(ESHUTDOWN, "Cannot send after transport endpoint shutdown"), + ERROR_TEXT_ENTRY(ETOOMANYREFS, "Too many references: cannot splice"), + ERROR_TEXT_ENTRY(ETIMEDOUT, "Connection timed out"), + ERROR_TEXT_ENTRY(ECONNREFUSED, "Connection refused"), + ERROR_TEXT_ENTRY(EHOSTDOWN, "Host is down"), + ERROR_TEXT_ENTRY(EHOSTUNREACH, "No route to host"), + ERROR_TEXT_ENTRY(EALREADY, "Operation already in progress"), + ERROR_TEXT_ENTRY(EINPROGRESS, "Operation now in progress"), + ERROR_TEXT_ENTRY(ESTALE, "Stale file handle"), + ERROR_TEXT_ENTRY(EUCLEAN, "Structure needs cleaning"), + ERROR_TEXT_ENTRY(ENOTNAM, "Not a XENIX named type file"), + ERROR_TEXT_ENTRY(ENAVAIL, "No XENIX semaphores available"), + ERROR_TEXT_ENTRY(EISNAM, "Is a named type file"), + ERROR_TEXT_ENTRY(EREMOTEIO, "Remote I/O error"), + ERROR_TEXT_ENTRY(EDQUOT, "Disk quota exceeded"), + ERROR_TEXT_ENTRY(ENOMEDIUM, "No medium found"), + ERROR_TEXT_ENTRY(EMEDIUMTYPE, "Wrong medium type"), + ERROR_TEXT_ENTRY(ECANCELED, "Operation canceled"), + ERROR_TEXT_ENTRY(ENOKEY, "Required key not available"), + ERROR_TEXT_ENTRY(EKEYEXPIRED, "Key has expired"), + ERROR_TEXT_ENTRY(EKEYREVOKED, "Key has been revoked"), + ERROR_TEXT_ENTRY(EKEYREJECTED, "Key was rejected by service"), + ERROR_TEXT_ENTRY(EOWNERDEAD, "Owner died"), + ERROR_TEXT_ENTRY(ENOTRECOVERABLE, "State not recoverable"), + ERROR_TEXT_ENTRY(ERFKILL, "Operation not possible due to RF-kill"), + ERROR_TEXT_ENTRY(EHWPOISON, "Memory page has hardware error"), + ERROR_TEXT_ENTRY(ERESTARTSYS, "Restart system"), + ERROR_TEXT_ENTRY(ERESTARTNOINTR, "Restart monitor"), + ERROR_TEXT_ENTRY(ERESTARTNOHAND, "Restart if no handler"), + ERROR_TEXT_ENTRY(ENOIOCTLCMD, "No ioctl command"), + ERROR_TEXT_ENTRY(ERESTART_RESTARTBLOCK, "Restart by calling sys_restart_syscall"), + ERROR_TEXT_ENTRY(EPROBE_DEFER, "Driver requests probe retry"), + ERROR_TEXT_ENTRY(EOPENSTALE, "Open found a stale dentry"), + ERROR_TEXT_ENTRY(EBADHANDLE, "Illegal NFS file handle"), + ERROR_TEXT_ENTRY(ENOTSYNC, "Update synchronization mismatch"), + ERROR_TEXT_ENTRY(EBADCOOKIE, "Cookie is stale"), + ERROR_TEXT_ENTRY(ENOTSUPP, "Operation is not supported"), + ERROR_TEXT_ENTRY(ETOOSMALL, "Buffer or request is too small"), + ERROR_TEXT_ENTRY(ESERVERFAULT, "An untranslatable error occurred"), + ERROR_TEXT_ENTRY(EBADTYPE, "Type not supported by server"), + ERROR_TEXT_ENTRY(EJUKEBOX, "Request initiated, but will not complete before timeout"), + ERROR_TEXT_ENTRY(EIOCBQUEUED, "IOCB queued, will get completion event"), +}; + +#define ERROR_TEXT_ARRAY_SIZE (sizeof(_error_text) / sizeof(_error_text[0])) +char *cts_strerror(int errno) +{ + if (errno > 0) { + return ""; + } else if (-errno < (int) ERROR_TEXT_ARRAY_SIZE) { + char *s = _error_text[-errno]; + if (s) { + return s; + } + } + return ""; +} diff --git a/drivers/input/touchscreen/chipone_tddi/cts_strerror.h b/drivers/input/touchscreen/chipone_tddi/cts_strerror.h new file mode 100644 index 000000000000..dfdcf640c182 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_strerror.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CTS_STRERROR_H +#define CTS_STRERROR_H + +#define CTS_ERR_FMT_STR "%d(%s)" +#define CTS_ERR_ARG(errno) cts_strerror(errno) +char *cts_strerror(int errno); + +#endif + diff --git a/drivers/input/touchscreen/chipone_tddi/cts_sysfs.c b/drivers/input/touchscreen/chipone_tddi/cts_sysfs.c new file mode 100644 index 000000000000..c25fc5f1edf2 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_sysfs.c @@ -0,0 +1,2904 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define LOG_TAG "Sysfs" + +#include "cts_config.h" +#include "cts_platform.h" +#include "cts_core.h" +#include "cts_tcs.h" +#include "cts_test.h" +#include "cts_sfctrl.h" +#include "cts_spi_flash.h" +#include "cts_firmware.h" +#include "cts_strerror.h" +#include "cts_sysfs.h" + +#ifdef CONFIG_CTS_SYSFS + +#define SPLIT_LINE_STR \ + "-----------------------------------------------"\ + "------------------------------------------------\n" +#define ROW_NUM_FORMAT_STR "%2d | " +#define COL_NUM_FORMAT_STR "%4u " +#define DATA_FORMAT_STR "%5d" + +#define RAWDATA_BUFFER_SIZE(cts_dev) \ + (cts_dev->fwdata.rows * cts_dev->fwdata.cols * 2) + +#define MAX_ARG_NUM (100) +#define MAX_ARG_LENGTH (1024) +#define HW_STUB_ADDR (0XF000) + +static char cmdline_param[MAX_ARG_LENGTH + 1]; +int cts_argc; +char *cts_argv[MAX_ARG_NUM]; + +int cts_parse_arg(const char *buf, size_t count) +{ + char *p; + size_t size; + + size = min((size_t)MAX_ARG_LENGTH, count); + memcpy(cmdline_param, buf, size); + cmdline_param[size] = '\0'; + + cts_argc = 0; + p = strim(cmdline_param); + if (p == NULL || p[0] == '\0') + return 0; + + while (p && p[0] != '\0' && cts_argc < MAX_ARG_NUM) + cts_argv[cts_argc++] = strsep(&p, " ,"); + + return cts_argc; +} + + +#ifdef CFG_CTS_FW_UPDATE_SYS +static ssize_t cts_panel_supplier_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *data = dev_get_drvdata(dev); + + if (data->pdata && data->pdata->panel_supplier) { + return scnprintf(buf, PAGE_SIZE, "%s\n", + data->pdata->panel_supplier); + } + return 0; +} + +static ssize_t buildid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "0000-%04x\n", + cts_data->cts_dev.fwdata.version); +} + +static ssize_t forcereflash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + unsigned int input; + + if (kstrtouint(buf, 10, &input) != 0) + return -EINVAL; + + cts_data->force_reflash = (input == 0) ? false : true; + + cts_info("%s force_reflash=%d, count=%zu", __func__, + (cts_data->force_reflash ? 1 : 0), count); + return count; +} + +static ssize_t flashprog_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", + cts_is_firmware_updating(&cts_data->cts_dev) ? 1 : 0); +} + +static bool is_reflash_filename_valid(const struct chipone_ts_data *cts_data, + const char *filename) +{ + char prefix[CFG_CTS_FW_FILE_NAME_MAX_LEN]; + + if (cts_data->pdata->panel_supplier != NULL) { + snprintf(prefix, sizeof(prefix), "%s-%s-%s-", + CFG_CTS_FW_FILE_NAME_VENDOR, + cts_data->pdata->panel_supplier, + cts_data->cts_dev.hwdata->name); + } else { + /* panel supplier not set, just check vendor. */ + snprintf(prefix, sizeof(prefix), "%s", + CFG_CTS_FW_FILE_NAME_VENDOR); + } + + cts_info("%s: prefix=%s", __func__, prefix); + if (strncmp(filename, prefix, strlen(prefix))) { + cts_err("%s: invalid FW file.", __func__); + return false; + } + + return true; +} + +static ssize_t doreflash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + int ret; + + cts_info("doreflash FW filename: %s len: %zu", buf, count); + + if (count > CFG_CTS_FW_FILE_NAME_MAX_LEN) { + cts_err("doreflash FW filename is too long %zu > %d", + count, CFG_CTS_FW_FILE_NAME_MAX_LEN); + return -EINVAL; + } + + if (cts_is_device_suspended(&cts_data->cts_dev)) { + cts_err("In suspend state, try again later"); + return -EAGAIN; + } + + if (cts_is_firmware_updating(&cts_data->cts_dev)) { + cts_err("In FW flashing state, try again later"); + return -EAGAIN; + } + + if (!cts_data->force_reflash) { + /* Check filename if force_reflash is false */ + if (!is_reflash_filename_valid(cts_data, buf)) { + cts_err("Invalid firmware filename '%*.s'", (int)count, buf); + return -EINVAL; + } + } + + strncpy(cts_data->cts_dev.config_fw_name, buf, count); + + /* If use echo xxx > doreflash, 0x0A will append to the string, + * if use echo -n xxx > doreflash, nothing will append. + */ + if (cts_data->cts_dev.config_fw_name[count - 1] == '\n') + cts_data->cts_dev.config_fw_name[count - 1] = '\0'; + else + cts_data->cts_dev.config_fw_name[count] = '\0'; + + cts_stop_device(&cts_data->cts_dev); + + cts_lock_device(&cts_data->cts_dev); + ret = cts_update_firmware_from_file(&cts_data->cts_dev, + cts_data->cts_dev.config_fw_name); + cts_unlock_device(&cts_data->cts_dev); + + if (ret) + cts_err("Update firmware from file '%s' failed %d", + cts_data->cts_dev.config_fw_name, ret); + + cts_start_device(&cts_data->cts_dev); + + cts_data->force_reflash = false; + + cts_info("%s: end", __func__); + + return ret ? ret : count; +} + +static ssize_t cts_poweron_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + bool val; + + /* TBD: check if cts is power to ready for flash. + * set "1" if power on ready. + */ + val = cts_is_device_suspended(&cts_data->cts_dev); + return scnprintf(buf, PAGE_SIZE, "%d\n", val == false); +} + +static ssize_t cts_productinfo_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + /* set chip IC type to productinfo */ + return scnprintf(buf, PAGE_SIZE, "%s\n", + cts_data->cts_dev.hwdata->name); +} + +/* add sys entries for FW update */ +/* static DEVICE_ATTR(drv_irq, S_IRUGO | S_IWUSR, drv_irq_show, drv_irq_store); */ +/* static DEVICE_ATTR(reset, S_IWUSR | S_IWGRP, NULL, reset_store); */ +static DEVICE_ATTR(panel_supplier, 0444, cts_panel_supplier_show, NULL); +static DEVICE_ATTR(buildid, S_IRUGO, buildid_show, NULL); +static DEVICE_ATTR(forcereflash, S_IWUSR | S_IWGRP, NULL, forcereflash_store); +static DEVICE_ATTR(flashprog, S_IRUGO, flashprog_show, NULL); +static DEVICE_ATTR(doreflash, S_IWUSR | S_IWGRP, NULL, doreflash_store); +static DEVICE_ATTR(poweron, S_IRUGO, cts_poweron_show, NULL); +static DEVICE_ATTR(productinfo, S_IRUGO, cts_productinfo_show, NULL); + +static struct attribute *cts_dev_fw_up_atts[] = { + /** + * &dev_attr_drv_irq.attr, + * &dev_attr_reset.attr, + */ + &dev_attr_buildid.attr, + &dev_attr_forcereflash.attr, + &dev_attr_flashprog.attr, + &dev_attr_doreflash.attr, + &dev_attr_poweron.attr, + &dev_attr_productinfo.attr, + &dev_attr_panel_supplier.attr, + NULL +}; + +static const struct attribute_group cts_dev_fw_up_attr_group = { + .attrs = cts_dev_fw_up_atts, +}; +#endif + + +static ssize_t write_tcs_register_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u16 addr; + int i, ret; + u8 *data = NULL; + + cts_parse_arg(buf, count); + + cts_info("Write firmware register '%.*s'", (int)count, buf); + + if (cts_argc < 2) { + cts_err("Too few args %d", cts_argc); + return -EFAULT; + } + + ret = kstrtou16(cts_argv[0], 0, &addr); + if (ret) { + cts_err("Invalid address %s", cts_argv[0]); + return -EINVAL; + } + + data = (u8 *) kmalloc(cts_argc - 1, GFP_KERNEL); + if (data == NULL) { + cts_err("Allocate buffer for write data failed\n"); + return -ENOMEM; + } + + for (i = 1; i < cts_argc; i++) { + ret = kstrtou8(cts_argv[i], 0, data + i - 1); + if (ret) { + cts_err("Invalid value %s", cts_argv[i]); + goto free_data; + } + } + + ret = cts_tcs_write(cts_dev, addr, data, cts_argc - 1); + if (ret) { + cts_err("Write tcs register addr: 0x%04x size: %d failed", + addr, cts_argc - 1); + goto free_data; + } + +free_data: + kfree(data); + + return (ret < 0 ? ret : count); +} +static DEVICE_ATTR(write_tcs_reg, S_IWUSR, NULL, write_tcs_register_store); + +static ssize_t read_tcs_register_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#define PRINT_ROW_SIZE (16) + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u16 addr, size, i, remaining; + u8 *data = NULL; + ssize_t count = 0; + int ret; + + cts_info("Read tcs register "); + + if (cts_argc != 2) { + return snprintf(buf, PAGE_SIZE, + "Invalid num args %d\n" + " 1. echo (0x)addr size > read_reg\n" + " 2. cat read_reg\n", cts_argc); + } + + ret = kstrtou16(cts_argv[0], 0, &addr); + if (ret) { + return snprintf(buf, PAGE_SIZE, "Invalid address: %s\n", cts_argv[0]); + } + ret = kstrtou16(cts_argv[1], 0, &size); + if (ret) + return snprintf(buf, PAGE_SIZE, "Invalid size: %s\n", cts_argv[1]); + + data = (u8 *) kmalloc(size, GFP_KERNEL); + if (data == NULL) { + return snprintf(buf, PAGE_SIZE, "Allocate buffer for read data failed\n"); + } + + cts_info("Read tcs register from 0x%04x size %u", addr, size); + cts_lock_device(cts_dev); + ret = cts_tcs_read(cts_dev, addr, data, (size_t)size); + cts_unlock_device(cts_dev); + if (ret) { + count = snprintf(buf, PAGE_SIZE, + "Read tcs register from 0x%04x size %u failed %d\n", addr, size, ret); + goto err_free_data; + } + + remaining = size; + for (i = 0; i < size && count <= PAGE_SIZE; i += PRINT_ROW_SIZE) { + size_t linelen = min((size_t)remaining, (size_t)PRINT_ROW_SIZE); + + remaining -= PRINT_ROW_SIZE; + + count += snprintf(buf + count, PAGE_SIZE - count, "%04x: ", addr); + + /* Lower version kernel return void */ + hex_dump_to_buffer(data + i, linelen, PRINT_ROW_SIZE, 1, + buf + count, PAGE_SIZE - count, true); + count += strlen(buf + count); + + if (count < PAGE_SIZE) { + buf[count++] = '\n'; + addr += PRINT_ROW_SIZE; + } else { + break; + } + } + +err_free_data: + kfree(data); + + return count; +#undef PRINT_ROW_SIZE +} + +static ssize_t read_tcs_register_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + cts_parse_arg(buf, count); + + return (cts_argc == 0 ? 0 : count); +} + +static DEVICE_ATTR(read_tcs_reg, S_IWUSR | S_IRUSR, + read_tcs_register_show, read_tcs_register_store); + +static ssize_t read_hw_reg_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#define PRINT_ROW_SIZE (16) + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u32 addr, size, i, remaining; + u8 *data = NULL; + ssize_t count = 0; + int ret; + + cts_info("Read hw register"); + + if (cts_argc != 2) { + return snprintf(buf, PAGE_SIZE, + "Invalid num args %d\n" + " 1. echo addr size > read_hw_reg\n" + " 2. cat read_hw_reg\n", cts_argc); + } + + ret = kstrtou32(cts_argv[0], 0, &addr); + if (ret) { + return snprintf(buf, PAGE_SIZE, "Invalid address: %s\n", cts_argv[0]); + } + ret = kstrtou32(cts_argv[1], 0, &size); + if (ret) + return snprintf(buf, PAGE_SIZE, "Invalid size: %s\n", cts_argv[1]); + + data = (u8 *) kmalloc(size, GFP_KERNEL); + if (data == NULL) + return snprintf(buf, PAGE_SIZE, "Allocate buffer for read data failed\n"); + + cts_info("Read hw register from 0x%04x size %u", addr, size); + cts_lock_device(cts_dev); + + if (cts_dev->rtdata.program_mode) { + for (i = 0; i < size; i++) { + ret = cts_dev_readb(cts_dev, addr + i, data + i, 3, 10); + if (ret) { + count = snprintf(buf, PAGE_SIZE, "Write hw register error\n"); + goto err_free_data; + } + } + } else { + ret = cts_tcs_read_hw_reg(cts_dev, addr, data, size); + if (ret < 0) { + count = snprintf(buf, PAGE_SIZE, "Read hw register error\n"); + goto err_free_data; + } + } + + remaining = size; + for (i = 0; i < size && count <= PAGE_SIZE; i += PRINT_ROW_SIZE) { + size_t linelen = min((size_t)remaining, (size_t)PRINT_ROW_SIZE); + + remaining -= PRINT_ROW_SIZE; + + count += snprintf(buf + count, PAGE_SIZE - count, "%04x: ", addr); + + /* Lower version kernel return void */ + hex_dump_to_buffer(data + i, linelen, PRINT_ROW_SIZE, 1, + buf + count, PAGE_SIZE - count, true); + count += strlen(buf + count); + + if (count < PAGE_SIZE) { + buf[count++] = '\n'; + addr += PRINT_ROW_SIZE; + } else { + break; + } + } + +err_free_data: + cts_unlock_device(cts_dev); + kfree(data); + + return count; +#undef PRINT_ROW_SIZE +} + +/* echo addr size > read_reg */ +static ssize_t read_hw_reg_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + cts_parse_arg(buf, count); + + return (cts_argc == 0 ? 0 : count); +} + +static DEVICE_ATTR(read_hw_reg, S_IRUSR | S_IWUSR, read_hw_reg_show, + read_hw_reg_store); + +static ssize_t write_hw_reg_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u32 addr; + int i, ret; + u8 *data = NULL; + + cts_parse_arg(buf, count); + + cts_info("Write hw register"); + + if (cts_argc < 2) { + cts_err("Too few args %d", cts_argc); + return -EFAULT; + } + + ret = kstrtou32(cts_argv[0], 0, &addr); + if (ret) { + cts_err("Invalid address %s", cts_argv[0]); + return -EINVAL; + } + + data = (u8 *) kmalloc(cts_argc - 1, GFP_KERNEL); + if (data == NULL) { + cts_err("Allocate buffer for write data failed\n"); + return -ENOMEM; + } + + for (i = 1; i < cts_argc; i++) { + ret = kstrtou8(cts_argv[i], 0, data + i - 1); + if (ret) { + cts_err("Invalid value %s", cts_argv[i]); + goto free_data; + } + } + + cts_lock_device(cts_dev); + + if (cts_dev->rtdata.program_mode) { + for (i = 0; i < cts_argc - 1; i++) { + ret = cts_dev_writeb(cts_dev, addr + i, data[i], 3, 10); + if (ret) { + cts_err("Write hw register error"); + break; + } + } + } else { + ret = cts_tcs_write_hw_reg(cts_dev, addr, data, cts_argc - 1); + if (ret < 0) + cts_err("Write hw register error"); + } + + cts_unlock_device(cts_dev); +free_data: + kfree(data); + + return (ret < 0 ? ret : count); +} + +static DEVICE_ATTR(write_hw_reg, S_IWUSR, NULL, write_hw_reg_store); + +static ssize_t curr_firmware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "Current firmware version: %04x\n", + cts_data->cts_dev.fwdata.version); +} + +static DEVICE_ATTR(curr_version, S_IRUGO, curr_firmware_version_show, NULL); + +static ssize_t curr_ddi_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "Current ddi version: %02x\n", + cts_data->cts_dev.fwdata.ddi_version); +} + +static DEVICE_ATTR(curr_ddi_version, S_IRUGO, curr_ddi_version_show, NULL); + +static ssize_t rows_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "Num rows: %u\n", + cts_data->cts_dev.fwdata.rows); +} + +static DEVICE_ATTR(rows, S_IRUGO, rows_show, NULL); + +static ssize_t cols_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "Num cols: %u\n", + cts_data->cts_dev.fwdata.cols); +} + +static DEVICE_ATTR(cols, S_IRUGO, cols_show, NULL); + +static ssize_t res_x_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "X Resolution: %u\n", + cts_data->cts_dev.fwdata.res_x); +} + +static DEVICE_ATTR(res_x, S_IRUGO, res_x_show, NULL); + +static ssize_t res_y_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "Y Resolution: %u\n", + cts_data->cts_dev.fwdata.res_y); +} + +static DEVICE_ATTR(res_y, S_IRUGO, res_y_show, NULL); + +static ssize_t esd_protection_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + int ret; + u8 esd_protection; + + cts_lock_device(cts_dev); + ret = cts_tcs_get_esd_protection(cts_dev, &esd_protection); + cts_unlock_device(cts_dev); + if (ret) + return snprintf(buf, PAGE_SIZE, + "Read firmware ESD protection register failed %d\n", ret); + + return snprintf(buf, PAGE_SIZE, "ESD protection: %u\n", esd_protection); +} + +static DEVICE_ATTR(esd_protection, S_IRUGO, esd_protection_show, NULL); + +static ssize_t monitor_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + int ret; + u8 value; + + cts_lock_device(cts_dev); + ret = cts_tcs_is_mnt_enabled(cts_dev, &value); + cts_unlock_device(cts_dev); + if (ret) + return snprintf(buf, PAGE_SIZE, + "Read firmware monitor enable register failed %d\n", ret); + + return snprintf(buf, PAGE_SIZE, "Monitor mode: %s\n", + value & BIT(0) ? "Enable" : "Disable"); +} + +static ssize_t monitor_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + int ret; + u8 value, enable = 0; + + if (buf[0] == 'Y' || buf[0] == 'y' || buf[0] == '1') + enable = 1; + + cts_info("Write firmware monitor mode to '%c', %s", + buf[0], enable ? "Enable" : "Disable"); + + cts_lock_device(cts_dev); + ret = cts_fw_reg_readb(&cts_data->cts_dev, 0x8000 + 344, &value); + cts_unlock_device(cts_dev); + if (ret) { + cts_err("Write firmware monitor enable register failed %d", ret); + return -EIO; + } + + if ((value & BIT(0)) && enable) + cts_info("Monitor mode already enabled"); + else if ((value & BIT(0)) == 0 && enable == 0) + cts_info("Monitor mode already disabled"); + else { + if (enable) + value |= BIT(0); + else + value &= ~BIT(0); + + cts_lock_device(cts_dev); + ret = cts_fw_reg_writeb(&cts_data->cts_dev, 0x8000 + 344, value); + cts_unlock_device(cts_dev); + if (ret) { + cts_err("Write firmware monitor enable register failed %d", ret); + return -EIO; + } + } + + return count; +} + +static DEVICE_ATTR(monitor_mode, S_IRUGO, monitor_mode_show, + monitor_mode_store); + +static ssize_t auto_compensate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + int ret; + u8 value; + + cts_lock_device(cts_dev); + ret = cts_tcs_is_cneg_enabled(&cts_data->cts_dev, &value); + cts_unlock_device(cts_dev); + if (ret) + return snprintf(buf, PAGE_SIZE, + "Read auto compensate enable register failed %d\n", ret); + + return snprintf(buf, PAGE_SIZE, "Auto compensate: %s\n", + value ? "Enable" : "Disable"); +} + +static DEVICE_ATTR(auto_compensate, S_IRUGO, auto_compensate_show, NULL); + +#ifdef CFG_CTS_FIRMWARE_IN_FS +/* echo filepath [flash/sram] > update_firmware_from_file */ +static ssize_t update_firmware_from_file_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + bool to_flash = true; + int ret; + + cts_parse_arg(buf, count); + + if (cts_argc > 2) { + cts_err("Invalid num args %d\n" + " echo filepath [flash/sram] > update_from_file\n", + cts_argc); + return -EFAULT; + } else if (cts_argc > 1) { + if (strncasecmp(cts_argv[1], "flash", 5) == 0) + to_flash = true; + else if (strncasecmp(cts_argv[1], "sram", 4) == 0) + to_flash = false; + else { + cts_err("Invalid location '%s', must be 'flash' or 'sram'", cts_argv[1]); + return -EINVAL; + } + } + + cts_info("Update firmware from file '%s'", cts_argv[0]); + + ret = cts_request_newer_firmware_from_fs(cts_dev, cts_argv[0], 0); + if (ret) { + cts_err("Request firmware from file '%s' failed", cts_argv[0]); + return -ENOENT; + } + + cts_reset_device(cts_dev); + + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + goto err_release_firmware; + } + + cts_lock_device(cts_dev); + ret = cts_update_firmware(cts_dev); + cts_unlock_device(cts_dev); + if (ret) { + cts_err("Update firmware failed %d", ret); + goto err_release_firmware; + } + + ret = cts_start_device(cts_dev); + if (ret) { + cts_err("Start device failed %d", ret); + goto err_release_firmware; + } + + ret = count; + +err_release_firmware: + return ret; +} + +static DEVICE_ATTR(update_from_file, S_IWUSR, NULL, + update_firmware_from_file_store); +#endif /* CFG_CTS_FIRMWARE_IN_FS */ + +static ssize_t updating_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "Updating: %s\n", + cts_data->cts_dev.rtdata.updating ? "Y" : "N"); +} + +static DEVICE_ATTR(updating, S_IRUGO, updating_show, NULL); + +static struct attribute *cts_dev_firmware_atts[] = { + &dev_attr_curr_version.attr, + &dev_attr_curr_ddi_version.attr, + &dev_attr_rows.attr, + &dev_attr_cols.attr, + &dev_attr_res_x.attr, + &dev_attr_res_y.attr, + &dev_attr_esd_protection.attr, + &dev_attr_monitor_mode.attr, + &dev_attr_auto_compensate.attr, +#ifdef CFG_CTS_FIRMWARE_IN_FS + &dev_attr_update_from_file.attr, +#endif /* CFG_CTS_FIRMWARE_IN_FS */ + &dev_attr_updating.attr, + NULL +}; + +static const struct attribute_group cts_dev_firmware_attr_group = { + .name = "cts_firmware", + .attrs = cts_dev_firmware_atts, +}; + +static ssize_t flash_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + const struct cts_flash *flash; + + if (cts_dev->flash == NULL) { + bool program_mode; + bool enabled; + int ret; + + program_mode = cts_is_device_program_mode(cts_dev); + enabled = cts_is_device_enabled(cts_dev); + + ret = cts_prepare_flash_operation(cts_dev); + if (ret) + return snprintf(buf, PAGE_SIZE, + "Prepare flash operation failed %d", ret); + + cts_post_flash_operation(cts_dev); + + if (!program_mode) { + ret = cts_enter_normal_mode(cts_dev); + if (ret) + return snprintf(buf, PAGE_SIZE, + "Enter normal mode failed %d", ret); + } + + if (enabled) { + ret = cts_start_device(cts_dev); + if (ret) + return snprintf(buf, PAGE_SIZE, + "Start device failed %d", ret); + } + + if (cts_dev->flash == NULL) + return snprintf(buf, PAGE_SIZE, "Flash not found\n"); + } + + flash = cts_dev->flash; + return snprintf(buf, PAGE_SIZE, + "%s:\n" + " JEDEC ID : %06X\n" + " Page size : 0x%zx\n" + " Sector size: 0x%zx\n" + " Block size : 0x%zx\n" + " Total size : 0x%zx\n", + flash->name, flash->jedec_id, flash->page_size, + flash->sector_size, flash->block_size, + flash->total_size); +} + +static DEVICE_ATTR(info, S_IRUGO, flash_info_show, NULL); + +static ssize_t read_flash_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u32 flash_addr, size, i, remaining; + u8 *data = NULL; + ssize_t count = 0; + int ret; + bool program_mode; + bool enabled; +#ifndef CFG_CTS_FOR_GKI + loff_t pos = 0; +#endif + + if (cts_argc != 2 && cts_argc != 3) + return snprintf(buf, PAGE_SIZE, "Invalid num args %d\n", cts_argc); + + ret = kstrtou32(cts_argv[0], 0, &flash_addr); + if (ret) + return snprintf(buf, PAGE_SIZE, "Invalid flash addr: %s\n", cts_argv[0]); + ret = kstrtou32(cts_argv[1], 0, &size); + if (ret) + return snprintf(buf, PAGE_SIZE, "Invalid size: %s\n", cts_argv[1]); + + data = (u8 *) kmalloc(size, GFP_KERNEL); + if (data == NULL) + return snprintf(buf, PAGE_SIZE, "Allocate buffer for read data failed\n"); + + cts_info("Read flash from 0x%06x size %u%s%s", flash_addr, size, + cts_argc == 3 ? " to file " : "", cts_argc == 3 ? cts_argv[2] : ""); + + cts_lock_device(cts_dev); + program_mode = cts_is_device_program_mode(cts_dev); + enabled = cts_is_device_enabled(cts_dev); + + ret = cts_prepare_flash_operation(cts_dev); + if (ret) { + count += snprintf(buf, PAGE_SIZE, "Prepare flash operation failed %d", ret); + goto err_free_data; + } + + ret = cts_read_flash(cts_dev, flash_addr, data, size); + if (ret) { + count = snprintf(buf, PAGE_SIZE, "Read flash data failed %d\n", ret); + goto err_post_flash_operation; + } + + if (cts_argc == 3) { +#ifdef CFG_CTS_FOR_GKI + cts_info("%s(): some functions are forbiddon with GKI Version!", __func__); +#else + struct file *file; + + cts_info("Write flash data to file '%s'", cts_argv[2]); + + file = filp_open(cts_argv[2], O_RDWR | O_CREAT | O_TRUNC, 0666); + if (IS_ERR(file)) { + count += snprintf(buf, PAGE_SIZE, "Open file '%s' failed %ld", + cts_argv[2], PTR_ERR(file)); + goto err_post_flash_operation; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) + ret = kernel_write(file, data, size, &pos); +#else + ret = kernel_write(file, data, size, pos); +#endif + if (ret != size) + count += snprintf(buf, PAGE_SIZE, + "Write flash data to file '%s' failed %d", cts_argv[2], ret); + + ret = filp_close(file, NULL); + if (ret) + count += snprintf(buf, PAGE_SIZE, + "Close file '%s' failed %d", cts_argv[2], ret); +#endif + } else { +#define PRINT_ROW_SIZE (16) + remaining = size; + for (i = 0; i < size && count <= PAGE_SIZE; i += PRINT_ROW_SIZE) { + size_t linelen = min((size_t)remaining, (size_t)PRINT_ROW_SIZE); + + remaining -= PRINT_ROW_SIZE; + + count += snprintf(buf + count, PAGE_SIZE - count - 1, + "%04x-%04x: ", flash_addr >> 16, flash_addr & 0xFFFF); + /* Lower version kernel return void */ + hex_dump_to_buffer(data + i, linelen, PRINT_ROW_SIZE, 1, + buf + count, PAGE_SIZE - count - 1, true); + count += strlen(buf + count); + buf[count++] = '\n'; + flash_addr += linelen; +#undef PRINT_ROW_SIZE + } + } + +err_post_flash_operation: + cts_post_flash_operation(cts_dev); + + if (!program_mode) { + int r = cts_enter_normal_mode(cts_dev); + + if (r) + count += snprintf(buf, PAGE_SIZE, "Enter normal mode failed %d", r); + } + + if (enabled) { + int r = cts_start_device(cts_dev); + + if (r) { + cts_unlock_device(cts_dev); + return snprintf(buf, PAGE_SIZE, "Start device failed %d", r); + } + } +err_free_data: + cts_unlock_device(cts_dev); + kfree(data); + + return (ret < 0 ? ret : count); +} + +/* echo start_addr size [filepath] > read */ +static ssize_t read_flash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + cts_parse_arg(buf, count); + + return count; +} + +static DEVICE_ATTR(read, S_IWUSR | S_IRUGO, read_flash_show, read_flash_store); + +/* echo addr size > erase */ +static ssize_t erase_flash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u32 flash_addr, size; + int ret; + bool program_mode; + bool enabled; + + cts_parse_arg(buf, count); + + if (cts_argc != 2) { + cts_err("Invalid num args %d", cts_argc); + return -EFAULT; + } + + ret = kstrtou32(cts_argv[0], 0, &flash_addr); + if (ret) { + cts_err("Invalid flash addr: %s", cts_argv[0]); + return -EINVAL; + } + ret = kstrtou32(cts_argv[1], 0, &size); + if (ret) { + cts_err("Invalid size: %s", cts_argv[1]); + return -EINVAL; + } + + cts_info("Erase flash from 0x%06x size %u", flash_addr, size); + + cts_lock_device(cts_dev); + program_mode = cts_is_device_program_mode(cts_dev); + enabled = cts_is_device_enabled(cts_dev); + + ret = cts_prepare_flash_operation(cts_dev); + if (ret) { + cts_err("Prepare flash operation failed %d", ret); + cts_unlock_device(cts_dev); + return ret; + } + + ret = cts_erase_flash(cts_dev, flash_addr, size); + if (ret) { + cts_err("Erase flash from 0x%06x size %u failed %d", + flash_addr, size, ret); + goto err_post_flash_operation; + } + +err_post_flash_operation: + cts_post_flash_operation(cts_dev); + + if (!program_mode) { + int r = cts_enter_normal_mode(cts_dev); + + if (r) + cts_err("Enter normal mode failed %d", r); + } + + if (enabled) { + int r = cts_start_device(cts_dev); + + if (r) + cts_err("Start device failed %d", r); + } + cts_unlock_device(cts_dev); + + return (ret < 0 ? ret : count); +} + +static DEVICE_ATTR(erase, S_IWUSR, NULL, erase_flash_store); + +static struct attribute *cts_dev_flash_attrs[] = { + &dev_attr_info.attr, + &dev_attr_read.attr, + &dev_attr_erase.attr, + + NULL +}; + +static const struct attribute_group cts_dev_flash_attr_group = { + .name = "flash", + .attrs = cts_dev_flash_attrs, +}; + +static ssize_t open_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + struct cts_test_param test_param = { + .test_item = CTS_TEST_OPEN, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = "/sdcard/open-test-data.txt", + .num_invalid_node = 0, + .invalid_nodes = NULL, + }; + int min = 0; + int ret; + ktime_t start_time, end_time, delta_time; + + if (cts_argc != 1) { + return scnprintf(buf, PAGE_SIZE, "Invalid num args %d\n", cts_argc); + } + + ret = kstrtoint(cts_argv[0], 0, &min); + if (ret) { + return scnprintf(buf, PAGE_SIZE, "Invalid min thres: %s\n", cts_argv[0]); + } + + cts_info("Open test, threshold = %u", min); + + test_param.min = &min; + + start_time = ktime_get(); + + ret = cts_test_open(cts_dev, &test_param); + + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + + if (ret > 0) { + return scnprintf(buf, PAGE_SIZE, + "Open test has %d nodes FAIL, min: %u, ELAPSED TIME: %lldms\n", + ret, min, ktime_to_ms(delta_time)); + } else if (ret < 0) { + return scnprintf(buf, PAGE_SIZE, + "Open test FAIL %d(%s), ELAPSED TIME: %lldms\n", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + return scnprintf(buf, PAGE_SIZE, + "Open test PASS, ELAPSED TIME: %lldms\n", + ktime_to_ms(delta_time)); + } + +} + +/* echo threshod > open_test */ +static ssize_t open_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + cts_parse_arg(buf, count); + + return count; +} + +static DEVICE_ATTR(open_test, S_IWUSR | S_IRUGO, open_test_show, + open_test_store); + +static ssize_t short_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + struct cts_test_param test_param = { + .test_item = CTS_TEST_SHORT, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = "/sdcard/short-test-data.txt", + .num_invalid_node = 0, + .invalid_nodes = NULL, + }; + int min = 0; + int ret; + ktime_t start_time, end_time, delta_time; + + if (cts_argc != 1) { + return scnprintf(buf, PAGE_SIZE, "Invalid num args %d\n", cts_argc); + } + + ret = kstrtoint(cts_argv[0], 0, &min); + if (ret) { + return scnprintf(buf, PAGE_SIZE, "Invalid min thres: %s\n", cts_argv[0]); + } + + cts_info("Short test, threshold = %u", min); + + test_param.min = &min; + + start_time = ktime_get(); + + ret = cts_test_short(cts_dev, &test_param); + + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + + if (ret > 0) { + return scnprintf(buf, PAGE_SIZE, + "Short test has %d nodes FAIL, min: %u, ELAPSED TIME: %lldms\n", + ret, min, ktime_to_ms(delta_time)); + } else if (ret < 0) { + return scnprintf(buf, PAGE_SIZE, + "Short test FAIL %d(%s), ELAPSED TIME: %lldms\n", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + return scnprintf(buf, PAGE_SIZE, + "Short test PASS, ELAPSED TIME: %lldms\n", + ktime_to_ms(delta_time)); + } +} + +/* echo threshod > short_test */ +static ssize_t short_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + cts_parse_arg(buf, count); + + return count; +} + +static DEVICE_ATTR(short_test, S_IWUSR | S_IRUGO, + short_test_show, short_test_store); + +static ssize_t testing_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "Testting: %s\n", + cts_data->cts_dev.rtdata.testing ? "Y" : "N"); +} + +static DEVICE_ATTR(testing, S_IRUGO, testing_show, NULL); + +#ifdef CFG_CTS_HAS_RESET_PIN +static ssize_t reset_pin_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#ifdef CFG_CTS_HAS_RESET_PIN + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + struct cts_test_param test_param = { + .test_item = CTS_TEST_RESET_PIN, + .flags = 0, + }; + int ret; + ktime_t start_time, end_time, delta_time; + + start_time = ktime_get(); + + ret = cts_test_reset_pin(cts_dev, &test_param); + + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Reset-Pin test FAIL %d(%s), ELAPSED TIME: %lldms\n", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + return scnprintf(buf, PAGE_SIZE, + "Reset-Pin test PASS, ELAPSED TIME: %lldms\n", + ktime_to_ms(delta_time)); + } +#else /* CFG_CTS_HAS_RESET_PIN */ + return scnprintf(buf, PAGE_SIZE, + "Reset-Pin test NOT supported(CFG_CTS_HAS_RESET_PIN not defined)\n"); +#endif +} +static DEVICE_ATTR(reset_pin_test, S_IRUGO, reset_pin_test_show, NULL); +#endif + +static ssize_t int_pin_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + struct cts_test_param test_param = { + .test_item = CTS_TEST_INT_PIN, + .flags = 0, + }; + int ret; + ktime_t start_time, end_time, delta_time; + + start_time = ktime_get(); + + ret = cts_test_int_pin(cts_dev, &test_param); + + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Int-Pin test FAIL %d(%s), ELAPSED TIME: %lldms\n", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + return scnprintf(buf, PAGE_SIZE, + "Int-Pin test PASS, ELAPSED TIME: %lldms\n", + ktime_to_ms(delta_time)); + } +} +static DEVICE_ATTR(int_pin_test, S_IRUGO, int_pin_test_show, NULL); + +static ssize_t compensate_cap_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + struct cts_test_param test_param = { + .test_item = CTS_TEST_COMPENSATE_CAP, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = + "/sdcard/comp-cap-test-data.txt", + .num_invalid_node = 0, + .invalid_nodes = NULL, + }; + int min = 0, max = 0; + int ret; + ktime_t start_time, end_time, delta_time; + + if (cts_argc != 2) { + return scnprintf(buf, PAGE_SIZE, "Invalid num args\n" + "USAGE:\n" + " 1. echo min max > compensate_cap_test\n" + " 2. cat compensate_cap_test\n"); + } + + ret = kstrtoint(cts_argv[0], 0, &min); + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Invalid min thres: %s\n", cts_argv[0]); + } + + ret = kstrtoint(cts_argv[1], 0, &max); + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Invalid max thres: %s\n", cts_argv[1]); + } + + cts_info("Compensate cap test, min: %u, max: %u", min, max); + + test_param.min = &min; + test_param.max = &max; + + start_time = ktime_get(); + + ret = cts_test_compensate_cap(cts_dev, &test_param); + + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + + if (ret > 0) { + return scnprintf(buf, PAGE_SIZE, + "Compensate cap test has %d nodes FAIL, " + "threshold[%u, %u], ELAPSED TIME: %lldms\n", + ret, min, max, ktime_to_ms(delta_time)); + } else if (ret < 0) { + return scnprintf(buf, PAGE_SIZE, + "Compensate cap test FAIL %d(%s), ELAPSED TIME: %lldms\n", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + return scnprintf(buf, PAGE_SIZE, + "Compensate cap test PASS, ELAPSED TIME: %lldms\n", + ktime_to_ms(delta_time)); + } +} + +/* echo threshod > short_test */ +static ssize_t compensate_cap_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + cts_parse_arg(buf, count); + + return count; +} + +static DEVICE_ATTR(compensate_cap_test, S_IWUSR | S_IRUGO, + compensate_cap_test_show, compensate_cap_test_store); + +static ssize_t rawdata_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + struct cts_rawdata_test_priv_param priv_param = { + .frames = 16, + //.work_mode = 0, + }; + struct cts_test_param test_param = { + .test_item = CTS_TEST_RAWDATA, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = "/sdcard/rawdata-test-data.txt", + .num_invalid_node = 0, + .invalid_nodes = NULL, + .priv_param = &priv_param, + .priv_param_size = sizeof(priv_param), + }; + + int min, max; + int ret; + ktime_t start_time, end_time, delta_time; + + if (cts_argc < 2 || cts_argc > 3) { + return scnprintf(buf, PAGE_SIZE, "Invalid num args\n" + "USAGE:\n" + " 1. echo min max [frames] > rawdata_test\n" + " 2. cat rawdata_test\n"); + } + + ret = kstrtoint(cts_argv[0], 0, &min); + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Invalid min thres: %s\n", cts_argv[0]); + } + + ret = kstrtoint(cts_argv[1], 0, &max); + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Invalid max thres: %s\n", cts_argv[1]); + } + + if (cts_argc > 2) { + ret = kstrtou32(cts_argv[2], 0, &priv_param.frames); + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Invalid frames: %s\n", cts_argv[2]); + } + } + cts_info("Rawdata test, frames: %u min: %d, max: %d", + priv_param.frames, min, max); + + test_param.min = &min; + test_param.max = &max; + + start_time = ktime_get(); + + ret = cts_test_rawdata(cts_dev, &test_param); + + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + + if (ret > 0) { + return scnprintf(buf, PAGE_SIZE, + "Rawdata test has %d nodes FAIL, threshold[%u, %u], " + "ELAPSED TIME: %lldms\n", + ret, min, max, ktime_to_ms(delta_time)); + } else if (ret < 0) { + return scnprintf(buf, PAGE_SIZE, + "Rawdata test FAIL %d(%s), ELAPSED TIME: %lldms\n", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + return scnprintf(buf, PAGE_SIZE, + "Rawdata test PASS, ELAPSED TIME: %lldms\n", + ktime_to_ms(delta_time)); + } +} + +/* echo threshod > short_test */ +static ssize_t rawdata_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + cts_parse_arg(buf, count); + + return count; +} + +static DEVICE_ATTR(rawdata_test, S_IWUSR | S_IRUGO, + rawdata_test_show, rawdata_test_store); + +static ssize_t noise_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + struct cts_noise_test_priv_param priv_param = { + .frames = 50, + //.work_mode = 0, + }; + struct cts_test_param test_param = { + .test_item = CTS_TEST_NOISE, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = + "/sdcard/noise-test-data.txt", + .num_invalid_node = 0, + .invalid_nodes = NULL, + .priv_param = &priv_param, + .priv_param_size = sizeof(priv_param), + }; + + int max; + int ret; + ktime_t start_time, end_time, delta_time; + + if (cts_argc < 1 || cts_argc > 2) { + return scnprintf(buf, PAGE_SIZE, "Invalid num args\n" + "USAGE:\n" + " 1. echo threshold [frames] > noise_test\n" + " 2. cat noise_test\n"); + } + + ret = kstrtoint(cts_argv[0], 0, &max); + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Invalid max thres: %s\n", cts_argv[0]); + } + + if (cts_argc > 1) { + ret = kstrtou32(cts_argv[1], 0, &priv_param.frames); + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Invalid frames: %s\n", cts_argv[1]); + } + } + cts_info("Noise test, frames: %u threshold: %d", + priv_param.frames, max); + + test_param.max = &max; + + start_time = ktime_get(); + + ret = cts_test_noise(cts_dev, &test_param); + + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + + if (ret > 0) { + return scnprintf(buf, PAGE_SIZE, + "Noise test has %d nodes FAIL, max: %u, ELAPSED TIME: %lldms\n", + ret, max, ktime_to_ms(delta_time)); + } else if (ret < 0) { + return scnprintf(buf, PAGE_SIZE, + "Noise test FAIL %d(%s), ELAPSED TIME: %lldms\n", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + return scnprintf(buf, PAGE_SIZE, + "Noise test PASS, ELAPSED TIME: %lldms\n", + ktime_to_ms(delta_time)); + } +} + +static ssize_t noise_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + cts_parse_arg(buf, count); + + return count; +} +static DEVICE_ATTR(noise_test, S_IWUSR | S_IRUGO, + noise_test_show, noise_test_store); + + +static ssize_t stylus_rawdata_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + struct cts_rawdata_test_priv_param priv_param = { + .frames = 16, + //.work_mode = 0, + }; + struct cts_test_param test_param = { + .test_item = CTS_TEST_RAWDATA, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MIN | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = "/sdcard/stylus-rawdata-test-data.txt", + .num_invalid_node = 0, + .invalid_nodes = NULL, + .priv_param = &priv_param, + .priv_param_size = sizeof(priv_param), + }; + + int min, max; + int ret; + ktime_t start_time, end_time, delta_time; + + if (cts_argc < 2 || cts_argc > 3) { + return scnprintf(buf, PAGE_SIZE, "Invalid num args\n" + "USAGE:\n" + " 1. echo min max [frames] > stylus_rawdata_test\n" + " 2. cat stylus_rawdata_test\n"); + } + + ret = kstrtoint(cts_argv[0], 0, &min); + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Invalid min thres: %s\n", cts_argv[0]); + } + + ret = kstrtoint(cts_argv[1], 0, &max); + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Invalid max thres: %s\n", cts_argv[1]); + } + + if (cts_argc > 2) { + ret = kstrtou32(cts_argv[2], 0, &priv_param.frames); + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Invalid frames: %s\n", cts_argv[2]); + } + } + cts_info("stylus rawdata test, frames: %u min: %d, max: %d", + priv_param.frames, min, max); + + test_param.min = &min; + test_param.max = &max; + + start_time = ktime_get(); + + ret = cts_test_stylus_rawdata(cts_dev, &test_param); + + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + + if (ret > 0) { + return scnprintf(buf, PAGE_SIZE, + "stylus rawdata test has %d nodes FAIL, threshold[%u, %u], " + "ELAPSED TIME: %lldms\n", + ret, min, max, ktime_to_ms(delta_time)); + } else if (ret < 0) { + return scnprintf(buf, PAGE_SIZE, + "stylus rawdata test FAIL %d(%s), ELAPSED TIME: %lldms\n", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + return scnprintf(buf, PAGE_SIZE, + "stylus rawdata test PASS, ELAPSED TIME: %lldms\n", + ktime_to_ms(delta_time)); + } +} + +/* echo threshod > short_test */ +static ssize_t stylus_rawdata_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + cts_parse_arg(buf, count); + + return count; +} + +static DEVICE_ATTR(stylus_rawdata_test, S_IWUSR | S_IRUGO, + stylus_rawdata_test_show, stylus_rawdata_test_store); + + +static ssize_t stylus_noise_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + struct cts_noise_test_priv_param priv_param = { + .frames = 50, + //.work_mode = 0, + }; + struct cts_test_param test_param = { + .test_item = CTS_TEST_NOISE, + .flags = CTS_TEST_FLAG_VALIDATE_DATA | + CTS_TEST_FLAG_VALIDATE_MAX | + CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE | + CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE, + .test_data_filepath = + "/sdcard/stylus-noise-test-data.txt", + .num_invalid_node = 0, + .invalid_nodes = NULL, + .priv_param = &priv_param, + .priv_param_size = sizeof(priv_param), + }; + + int max; + int ret; + ktime_t start_time, end_time, delta_time; + + if (cts_argc < 1 || cts_argc > 2) { + return scnprintf(buf, PAGE_SIZE, "Invalid num args\n" + "USAGE:\n" + " 1. echo threshold [frames] > stylus_noise_test\n" + " 2. cat stylus_noise_test\n"); + } + + ret = kstrtoint(cts_argv[0], 0, &max); + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Invalid max thres: %s\n", cts_argv[0]); + } + + if (cts_argc > 1) { + ret = kstrtou32(cts_argv[1], 0, &priv_param.frames); + if (ret) { + return scnprintf(buf, PAGE_SIZE, + "Invalid frames: %s\n", cts_argv[1]); + } + } + cts_info("stylus noise test, frames: %u threshold: %d", + priv_param.frames, max); + + test_param.max = &max; + + start_time = ktime_get(); + + ret = cts_test_stylus_noise(cts_dev, &test_param); + + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + + if (ret > 0) { + return scnprintf(buf, PAGE_SIZE, + "stylus noise test has %d nodes FAIL, max: %u, ELAPSED TIME: %lldms\n", + ret, max, ktime_to_ms(delta_time)); + } else if (ret < 0) { + return scnprintf(buf, PAGE_SIZE, + "stylus noise test FAIL %d(%s), ELAPSED TIME: %lldms\n", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + return scnprintf(buf, PAGE_SIZE, + "stylus noise test PASS, ELAPSED TIME: %lldms\n", + ktime_to_ms(delta_time)); + } +} + +static ssize_t stylus_noise_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + cts_parse_arg(buf, count); + + return count; +} +static DEVICE_ATTR(stylus_noise_test, S_IWUSR | S_IRUGO, + stylus_noise_test_show, stylus_noise_test_store); + + +static struct attribute *cts_dev_test_atts[] = { + &dev_attr_open_test.attr, + &dev_attr_short_test.attr, + &dev_attr_testing.attr, +#ifdef CFG_CTS_HAS_RESET_PIN + &dev_attr_reset_pin_test.attr, +#endif + &dev_attr_int_pin_test.attr, + &dev_attr_compensate_cap_test.attr, + &dev_attr_rawdata_test.attr, + &dev_attr_noise_test.attr, + &dev_attr_stylus_rawdata_test.attr, + &dev_attr_stylus_noise_test.attr, + NULL +}; + +static const struct attribute_group cts_dev_test_attr_group = { + .name = "test", + .attrs = cts_dev_test_atts, +}; + +static ssize_t ic_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "IC Type : %s\n", + cts_data->cts_dev.hwdata->name); +} + +static DEVICE_ATTR(ic_type, S_IRUGO, ic_type_show, NULL); + +static ssize_t program_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "Program mode: %s\n", + cts_data->cts_dev.rtdata.program_mode ? "Y" : "N"); +} + +static ssize_t program_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + int ret; + + cts_parse_arg(buf, count); + + if (cts_argc != 1) { + cts_err("Invalid num args %d", cts_argc); + return -EFAULT; + } + + if (*cts_argv[0] == '1' || tolower(*cts_argv[0]) == 'y') { + ret = cts_enter_program_mode(&cts_data->cts_dev); + if (ret) { + cts_err("Enter program mode failed %d", ret); + return ret; + } + } else if (*cts_argv[0] == '0' || tolower(*cts_argv[0]) == 'n') { + ret = cts_enter_normal_mode(&cts_data->cts_dev); + if (ret) { + cts_err("Exit program mode failed %d", ret); + return ret; + } + } else + cts_err("Invalid args"); + + return count; +} + +static DEVICE_ATTR(program_mode, S_IWUSR | S_IRUGO, + program_mode_show, program_mode_store); + +#ifdef CFG_CTS_HAS_RESET_PIN +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +static ssize_t reset_pin_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + cts_info("Read RESET-PIN"); + + return snprintf(buf, PAGE_SIZE, + "Reset pin: %d, status: %d\n", + cts_data->pdata->rst_gpio, + gpio_get_value(cts_data->pdata->rst_gpio)); +} +#endif + +static ssize_t reset_pin_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + + cts_info("Write RESET-PIN"); + cts_info("Chip staus maybe changed"); + + cts_plat_set_reset(cts_dev->pdata, (buf[0] == '1') ? 1 : 0); + return count; +} + +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +static DEVICE_ATTR(reset_pin, S_IRUSR | S_IWUSR, reset_pin_show, + reset_pin_store); +#else +static DEVICE_ATTR(reset_pin, S_IWUSR, NULL, reset_pin_store); +#endif +#endif + +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED +static ssize_t irq_pin_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + cts_info("Read IRQ-PIN"); + + return snprintf(buf, PAGE_SIZE, + "IRQ pin: %d, status: %d\n", + cts_data->pdata->int_gpio, + gpio_get_value(cts_data->pdata->int_gpio)); +} + +static DEVICE_ATTR(irq_pin, S_IRUGO, irq_pin_show, NULL); +#endif + +static ssize_t irq_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct irq_desc *desc; + + cts_info("Read IRQ-INFO"); + + desc = irq_to_desc(cts_data->pdata->irq); + if (desc == NULL) { + return snprintf(buf, PAGE_SIZE, + "IRQ: %d descriptor not found\n", + cts_data->pdata->irq); + } + + return scnprintf(buf, PAGE_SIZE, + "IRQ num: %d, depth: %u, " + "count: %u, unhandled: %u, last unhandled eslape: %lu, irq flags: 0x%x, int_mode: %s\n", + cts_data->pdata->irq, desc->depth, + desc->irq_count, desc->irqs_unhandled, + desc->last_unhandled, desc->action->flags, + (desc->action->flags & IRQF_TRIGGER_MASK ) == IRQF_TRIGGER_RISING ? "IRQF_TRIGGER_RISING" : + (desc->action->flags & IRQF_TRIGGER_MASK ) == IRQF_TRIGGER_FALLING ? "IRQF_TRIGGER_FALLING" : + (desc->action->flags & IRQF_TRIGGER_MASK ) == IRQF_TRIGGER_HIGH ? "IRQF_TRIGGER_HIGH" : + (desc->action->flags & IRQF_TRIGGER_MASK ) == IRQF_TRIGGER_LOW ? "IRQF_TRIGGER_LOW " : "IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING"); +} + +static DEVICE_ATTR(irq_info, S_IRUGO, irq_info_show, NULL); + +#ifndef CONFIG_CTS_I2C_HOST +static ssize_t debug_spi_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + + return snprintf(buf, PAGE_SIZE, "spi_speed=%d\n", + cts_dev->pdata->spi_speed); +} + +static ssize_t debug_spi_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + u16 s = 0; + int ret = 0; + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + + cts_parse_arg(buf, count); + + if (cts_argc != 1) { + cts_err("Invalid num args %d", cts_argc); + return -EFAULT; + } + + ret = kstrtou16(cts_argv[0], 0, &s); + if (ret) { + cts_err("Invalid spi speed: %s", cts_argv[0]); + return -EINVAL; + } + + cts_dev->pdata->spi_speed = s; + + return count; +} + +static DEVICE_ATTR(debug_spi, S_IRUSR | S_IWUSR, debug_spi_show, + debug_spi_store); +#endif + +#ifdef CFG_CTS_GESTURE +static ssize_t gesture_en_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + + return sprintf(buf, "Gesture wakup is %s\n", + cts_is_gesture_wakeup_enabled(cts_dev) ? "enable" : "disable"); +} + +static ssize_t gesture_en_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u8 enable = 0; + + if (buf[0] == 'Y' || buf[0] == 'y' || buf[0] == '1') + enable = 1; + + if (enable) + cts_enable_gesture_wakeup(cts_dev); + else + cts_disable_gesture_wakeup(cts_dev); + + return count; +} + +static DEVICE_ATTR(gesture_en, S_IRUSR | S_IWUSR, gesture_en_show, + gesture_en_store); +#endif + +static ssize_t int_data_types_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%#04x\n", + cts_data->cts_dev.fwdata.int_data_types); +} + +static ssize_t int_data_types_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + u16 type = 0; + int ret = 0; + + cts_parse_arg(buf, count); + + if (cts_argc != 1) { + cts_err("Invalid num args %d", cts_argc); + return -EFAULT; + } + + ret = kstrtou16(cts_argv[0], 0, &type); + if (ret) { + cts_err("Invalid int data types: %s", cts_argv[0]); + return -EINVAL; + } + + cts_lock_device(&cts_data->cts_dev); + ret = cts_set_int_data_types(&cts_data->cts_dev, type); + cts_unlock_device(&cts_data->cts_dev); + if (ret) + return -EIO; + return count; +} + +static DEVICE_ATTR(int_data_types, S_IWUSR | S_IRUGO, + int_data_types_show, int_data_types_store); + +static ssize_t int_data_method_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", + cts_data->cts_dev.fwdata.int_data_method); +} + +static ssize_t int_data_method_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + u8 method = 0; + int ret = 0; + + cts_parse_arg(buf, count); + + if (cts_argc != 1) { + cts_err("Invalid num args %d", cts_argc); + return -EFAULT; + } + + ret = kstrtou8(cts_argv[0], 0, &method); + if (ret) { + cts_err("Invalid int data method: %s", cts_argv[0]); + return -EINVAL; + } else if (method >= INT_DATA_METHOD_CNT) { + cts_err("Invalid int data method: %s", cts_argv[0]); + return -EINVAL; + } + + cts_lock_device(&cts_data->cts_dev); + ret = cts_set_int_data_method(&cts_data->cts_dev, method); + cts_unlock_device(&cts_data->cts_dev); + if (ret) + return -EIO; + return count; +} + +static DEVICE_ATTR(int_data_method, S_IWUSR | S_IRUGO, + int_data_method_show, int_data_method_store); + + +static ssize_t cts_charger_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u8 val; + int ret; + + cts_info("Read sysfs '/%s'", attr->attr.name); + + cts_lock_device(cts_dev); + ret = cts_tcs_get_charger_plug(cts_dev, &val); + cts_unlock_device(cts_dev); + + if (ret) { + cts_err("Get charger state failed %d(%s)", ret, cts_strerror(ret)); + return -1; + } + switch (val) { + case 0: return scnprintf(buf, PAGE_SIZE, "Detached\n"); + case 1: return scnprintf(buf, PAGE_SIZE, "Attached\n"); + default: return scnprintf(buf, PAGE_SIZE, "Read error\n"); + } +} + +static ssize_t cts_charger_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + int state; + int ret; + + cts_info("Write sysfs '/%s' size %zu", attr->attr.name, count); + + switch (buf[0]) { + case '0': state = 0; break; + case '1': state = 1; break; + default: + cts_err("Invalid arg for state"); + return -EINVAL; + } + + cts_info("state = %d", state); + + cts_lock_device(cts_dev); + ret = cts_tcs_set_charger_plug(cts_dev, state); + cts_unlock_device(cts_dev); + + if (ret) { + cts_err("Set charger state failed %d(%s)", ret, cts_strerror(ret)); + return -EIO; + } + + return count; +} +static DEVICE_ATTR(charger_state, S_IRUGO | S_IWUSR, + cts_charger_state_show, cts_charger_state_store); + +static ssize_t cts_earjack_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u8 val; + int ret; + + cts_info("Read sysfs '/%s'", attr->attr.name); + + cts_lock_device(cts_dev); + ret = cts_tcs_get_earjack_plug(cts_dev, &val); + cts_unlock_device(cts_dev); + + if (ret) { + cts_err("Get earjack state failed %d(%s)", + ret, cts_strerror(ret)); + return -1; + } + switch (val) { + case 0: return scnprintf(buf, PAGE_SIZE, "Detached\n"); + case 1: return scnprintf(buf, PAGE_SIZE, "Attached\n"); + default: return scnprintf(buf, PAGE_SIZE, "Read error\n"); + } +} + +static ssize_t cts_earjack_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + int state; + int ret; + + cts_info("Write sysfs '/%s' size %zu", attr->attr.name, count); + + switch (buf[0]) { + case '0': state = 0; break; + case '1': state = 1; break; + default: + cts_err("Invalid arg for state"); + return -EINVAL; + } + + cts_info("state = %d", state); + + cts_lock_device(cts_dev); + ret = cts_tcs_set_earjack_plug(cts_dev, state); + cts_unlock_device(cts_dev); + + if (ret) { + cts_err("Set earjack state failed %d(%s)", ret, cts_strerror(ret)); + return -EIO; + } + + return count; +} +static DEVICE_ATTR(earjack_state, S_IRUGO | S_IWUSR, + cts_earjack_state_show, cts_earjack_state_store); + +static ssize_t cts_edge_restain_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u8 direction; + int ret; + + cts_info("Read sysfs '/%s'", attr->attr.name); + + cts_lock_device(cts_dev); + ret = cts_tcs_get_panel_direction(cts_dev, &direction); + cts_unlock_device(cts_dev); + if (ret) { + cts_err("get panel direction failed!"); + return scnprintf(buf, PAGE_SIZE, "Read error\n"); + } + + return scnprintf(buf, PAGE_SIZE, "direction: 0x%02x\n", direction); +} + +static ssize_t cts_edge_restain_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u8 direction; + int ret; + + cts_info("Write sysfs '/%s' size %zu", attr->attr.name, count); + + switch (buf[0]) { + case '0': direction = 0; break;//normal + case '1': direction = 1; break;//notch left + case '2': direction = 2; break;//notch right + default: + cts_err("Invalid arg for mode"); + return -EINVAL; + } + cts_info("direction = %d", direction); + + cts_lock_device(cts_dev); + ret = cts_tcs_set_panel_direction(cts_dev, direction); + cts_unlock_device(cts_dev); + + if (ret) { + cts_err("Set edge restain failed"); + return -EIO; + } + + return count; +} +static DEVICE_ATTR(edge_restain, S_IWUSR | S_IRUGO, + cts_edge_restain_show, cts_edge_restain_store); + +static ssize_t cts_game_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u8 enabled; + int ret; + + cts_info("Read sysfs '/%s'", attr->attr.name); + + cts_lock_device(cts_dev); + ret = cts_tcs_get_game_mode(cts_dev, &enabled); + cts_unlock_device(cts_dev); + if (ret) { + cts_err("get game mode failed!"); + return scnprintf(buf, PAGE_SIZE, "Read error\n"); + } + + return scnprintf(buf, PAGE_SIZE, "game mode: 0x%02x\n", enabled); +} + +static ssize_t cts_game_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + int enable = -1; + int ret; + + cts_info("Write sysfs '/%s' size %zu", attr->attr.name, count); + + if (count >= 1) { + if (buf[0] == 'Y' || buf[0] == 'y' || buf[0] == '1') { + enable = 1; + } else if (buf[0] == 'N' || buf[0] == 'n' || buf[0] == '0') { + enable = 0; + } + } + if (enable == -1) { + cts_err("Invalid arg for game mode enable"); + return -EINVAL; + } + + cts_lock_device(cts_dev); + ret = cts_tcs_set_game_mode(cts_dev, enable); + cts_unlock_device(cts_dev); + if (ret) { + cts_err("Set game mode failed"); + return -EIO; + } + + return count; +} +static DEVICE_ATTR(game_mode, S_IWUSR | S_IRUGO, + cts_game_mode_show, cts_game_mode_store); + +#ifdef CONFIG_CTS_TP_PROXIMITY +static ssize_t cts_proximity_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + struct cts_firmware_status *status = (struct cts_firmware_status *) + &cts_dev->rtdata.firmware_status; + + cts_info("Read sysfs '/%s'", attr->attr.name); + + return scnprintf(buf, PAGE_SIZE, "proximity mode: %d\n", + status->proximity); +} + +static ssize_t cts_proximity_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + int enable = -1; + int ret; + + cts_info("Write sysfs '/%s' size %zu", attr->attr.name, count); + + if (count >= 1) { + if (buf[0] == 'Y' || buf[0] == 'y' || buf[0] == '1') { + enable = 1; + } else if (buf[0] == 'N' || buf[0] == 'n' || buf[0] == '0') { + enable = 0; + } + } + if (enable == -1) { + cts_err("Invalid arg for proximity mode enable"); + return -EINVAL; + } + + cts_lock_device(cts_dev); + ret = cts_tcs_set_proximity_mode(cts_dev, enable); + cts_unlock_device(cts_dev); + if (ret) { + cts_err("Set proximity mode failed"); + return -EIO; + } + + return count; +} +static DEVICE_ATTR(proximity_mode, S_IWUSR | S_IRUGO, + cts_proximity_mode_show, cts_proximity_mode_store); +#endif + + +static ssize_t cts_tcs_cmd_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + + return snprintf(buf, PAGE_SIZE, "chipone_cmds.bin size:%zu\n", + cts_dev->rtdata.tcscmd_len * sizeof(u16)); +} + +static ssize_t cts_tcs_cmd_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + const struct firmware *tcscmds; + char *update = "update"; + int ret; + + cts_info("Write sysfs '/%s' size %zu", attr->attr.name, count); + + ret = memcmp(update, buf, count - 1); + if (ret) { + cts_err("Not update tcs cmd buf, %s", buf); + return count; + } + + ret = request_firmware(&tcscmds, "chipone_cmds.bin", dev); + if (ret) { + cts_err("Could not load firmware from chipone_cmds.bin: %d", ret); + return ret; + } + + if (tcscmds->size > PAGE_SIZE) { + cts_err("tcscmds length is over one PAGE_SIZE"); + return count; + } + + memcpy((u8 *)cts_dev->rtdata.tcscmd, tcscmds->data, tcscmds->size); + cts_dev->rtdata.tcscmd_len = tcscmds->size / sizeof(u16); + + release_firmware(tcscmds); + cts_info("update tcscmds successfully"); + + return count; +} +static DEVICE_ATTR(tcs_cmd, S_IWUSR | S_IRUSR, + cts_tcs_cmd_show, cts_tcs_cmd_store); + +static u16 cts_find_tcs_cmd(struct cts_device *cts_dev, u8 class_id, u8 cmd_id) +{ + struct cts_tcs_cmd *tcmd; + u8 cmd_buf[2]; + int i; + + for (i = 0; i < cts_dev->rtdata.tcscmd_len; i++) { + put_unaligned_le16(cts_dev->rtdata.tcscmd[i], cmd_buf); + tcmd = (struct cts_tcs_cmd *)cmd_buf; + if (class_id == tcmd->class_id && cmd_id == tcmd->cmd_id) { + cts_info("Found tcs cmd:0x%04x: { %d, %d, %d, %d, %d}", + cts_dev->rtdata.tcscmd[i], tcmd->base_flag, + tcmd->class_id, tcmd->cmd_id, + tcmd->is_read, tcmd->is_write); + return cts_dev->rtdata.tcscmd[i]; + } + } + + return 0; +} +static ssize_t cts_read_tcs_cmd_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + int count = 0; + u8 *read_buf; + int i; + int ret; + + read_buf = kzalloc(cts_dev->rtdata.curr_len, GFP_KERNEL); + if (read_buf == NULL) { + return snprintf(buf, PAGE_SIZE, "kzalloc read_buf failed\n"); + } + + if (!(cts_dev->rtdata.curr_cmd & BIT(14))) { + kfree(read_buf); + return snprintf(buf, PAGE_SIZE, "This cmd %04x is not readable!\n", + cts_dev->rtdata.curr_cmd); + } + + cts_lock_device(cts_dev); + ret = cts_tcs_read(cts_dev, cts_dev->rtdata.curr_cmd, read_buf, + cts_dev->rtdata.curr_len); + cts_unlock_device(cts_dev); + if (ret) { + cts_err("read tcscmd: 0x%04x, failed", cts_dev->rtdata.curr_cmd); + kfree(read_buf); + return ret; + } + + count += snprintf(buf + count, PAGE_SIZE - count, "read_tcs(0x%02x): ", + cts_dev->rtdata.curr_cmd); + for (i = 0; i < cts_dev->rtdata.curr_len; i++) { + count += snprintf(buf + count, PAGE_SIZE - count, "%02x ", read_buf[i]); + } + count += snprintf(buf + count, PAGE_SIZE - count, "\n"); + + kfree(read_buf); + return count; +} +static ssize_t cts_read_tcs_cmd_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u16 cmd; + u8 class_id, cmd_id, read_len; + int ret; + + cts_info("Write sysfs '/%s' size %zu", attr->attr.name, count); + cts_parse_arg(buf, count); + + if (cts_argc != 3) { + cts_err("usage: echo class_id cmd_id read_len > read_tcs"); + return count; + } + + ret = kstrtou8(cts_argv[0], 0, &class_id); + if (ret) { + cts_err("kstrtou8 class_id %d failed", class_id); + return ret; + } + + ret = kstrtou8(cts_argv[1], 0, &cmd_id); + if (ret) { + cts_err("kstrtou8 cmd_id %d failed", cmd_id); + return ret; + } + + ret = kstrtou8(cts_argv[2], 0, &read_len); + if (ret) { + cts_err("kstrtou8 read_len %d failed", read_len); + return ret; + } + + cmd = cts_find_tcs_cmd(cts_dev, class_id, cmd_id); + if (cmd == 0) { + cts_err("Not found classid %d, cmdid %d", class_id, cmd_id); + return count; + } + + cts_dev->rtdata.curr_cmd = cmd; + cts_dev->rtdata.curr_len = read_len; + cts_info("tcs cmd:%02x", cmd); + + return count; +} +static DEVICE_ATTR(read_tcs_cmd, S_IWUSR | S_IRUSR, + cts_read_tcs_cmd_show, cts_read_tcs_cmd_store); + + +static ssize_t cts_write_tcs_cmd_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + struct cts_device *cts_dev = &cts_data->cts_dev; + u8 class_id, cmd_id; + u16 cmd; + u8 wbuf[32]; + int wlen; + int i; + int ret; + + cts_info("Write sysfs '/%s' size %zu", attr->attr.name, count); + cts_parse_arg(buf, count); + + if (cts_argc < 3) { + cts_err("usage: echo class_id cmd_id x1 x2 .. > write_tcs"); + return count; + } + + ret = kstrtou8(cts_argv[0], 0, &class_id); + if (ret) { + cts_err("kstrtou8 class_id %d failed", ret); + return ret; + } + + ret = kstrtou8(cts_argv[1], 0, &cmd_id); + if (ret) { + cts_err("kstrtou8 cmd_id %d failed", ret); + return ret; + } + + wlen = cts_argc - 2; + if (wlen > sizeof(wbuf)) { + cts_warn("write size is limit %zu bytes.", sizeof(wbuf)); + wlen = sizeof(wbuf); + } + + for (i = 0; i < wlen; i++) { + ret = kstrtou8(cts_argv[i + 2], 0, wbuf + i); + if (ret) { + cts_err("kstrtou8 wbuf %d failed", i); + return ret; + } + } + + cmd = cts_find_tcs_cmd(cts_dev, class_id, cmd_id); + if (cmd == 0) { + cts_err("Not found classid %d, cmdid %d", class_id, cmd_id); + return count; + } + + cts_lock_device(cts_dev); + ret = cts_tcs_write(cts_dev, cmd, wbuf, wlen); + cts_unlock_device(cts_dev); + if (ret) { + cts_err("write tcscmd: 0x%04x, failed", cmd); + return ret; + } + + return count; +} +static DEVICE_ATTR(write_tcs_cmd, S_IWUSR, NULL, cts_write_tcs_cmd_store); + +static struct attribute *cts_dev_misc_atts[] = { + &dev_attr_ic_type.attr, + &dev_attr_program_mode.attr, +#ifdef CFG_CTS_HAS_RESET_PIN + &dev_attr_reset_pin.attr, +#endif +#ifndef CFG_CTS_PLATFORM_MTK_TPD_SUPPORTED + &dev_attr_irq_pin.attr, +#endif + &dev_attr_irq_info.attr, + &dev_attr_read_tcs_reg.attr, + &dev_attr_write_tcs_reg.attr, + &dev_attr_read_hw_reg.attr, + &dev_attr_write_hw_reg.attr, +#ifndef CONFIG_CTS_I2C_HOST + &dev_attr_debug_spi.attr, +#endif +#ifdef CFG_CTS_GESTURE + &dev_attr_gesture_en.attr, +#endif /* CFG_CTS_GESTURE */ + &dev_attr_int_data_types.attr, + &dev_attr_int_data_method.attr, + &dev_attr_charger_state.attr, + &dev_attr_earjack_state.attr, + &dev_attr_edge_restain.attr, + &dev_attr_game_mode.attr, +#ifdef CONFIG_CTS_TP_PROXIMITY + &dev_attr_proximity_mode.attr, +#endif + &dev_attr_tcs_cmd.attr, + &dev_attr_read_tcs_cmd.attr, + &dev_attr_write_tcs_cmd.attr, + NULL +}; + +static const struct attribute_group cts_dev_misc_attr_group = { + .name = "misc", + .attrs = cts_dev_misc_atts, +}; + +static const struct attribute_group *cts_dev_attr_groups[] = { + &cts_dev_firmware_attr_group, +#ifdef CFG_CTS_FW_UPDATE_SYS + &cts_dev_fw_up_attr_group, +#endif + &cts_dev_flash_attr_group, + &cts_dev_test_attr_group, + &cts_dev_misc_attr_group, + NULL +}; + +#ifdef CFG_CTS_PLATFORM_SPRD_SUPPORTED +static ssize_t cts_suspend_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cts_device *cts_dev = &g_cts_data->cts_dev; + return sprintf(buf, "%s\n", cts_dev->rtdata.suspended ? "true" : "false"); +} +static ssize_t cts_suspend_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + if (count >= 1) { + if (buf[0] == '1') { + cts_info("ts_suspend_store suspend."); + cts_suspend(g_cts_data); + } else if (buf[0] == '0') { + cts_info("ts_suspend_store cts_resume."); + cts_resume(g_cts_data); + } else { + cts_err("usage: echo 0/1 > ts_suspend"); + } + } + + return count; +} +static DEVICE_ATTR(ts_suspend, S_IWUSR | S_IRUGO, + cts_suspend_show, cts_suspend_store); + +static struct attribute *cts_dev_ts_suspend_atts[] = { + &dev_attr_ts_suspend.attr, + NULL +}; + +static struct attribute_group ts_suspend_attr_group = { + .attrs = cts_dev_ts_suspend_atts, +}; + +static int cts_suspend_sysfs_init(struct device *dev) +{ + int ret = 0; + cts_info("sprocomm ts_suspend_store suspend. ret=%d",ret); + + ret = sysfs_create_group(&dev->kobj, &ts_suspend_attr_group); + if (ret) { + cts_info("Create sysfs ts_suspend group failed!"); + return -ENOMEM; + } else { + cts_info("Create sysfs ts_suspend group succesfully!"); + } + + ret = sysfs_create_link(NULL, &dev->kobj, "touchscreen"); + if (ret < 0) { + dev_err(dev, "Failed to create touchscreen link!"); + return -ENOMEM; + } + return ret; +} + +static void cts_suspend_sysfs_deinit(struct device *dev) +{ + sysfs_remove_link(NULL, "touchscreen"); + sysfs_remove_group(&dev->kobj, &ts_suspend_attr_group); +} +#endif + +#include +#include + +#ifndef CFG_CTS_FOR_GKI +/* Attribute: path (RO) */ +static ssize_t path_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *data = dev_get_drvdata(dev); + ssize_t blen; + const char *path; + + if (!data) { + cts_err("Read 'path' with chipone_ts_data NULL"); + return (ssize_t) 0; + } +#ifdef CONFIG_CTS_I2C_HOST + path = kobject_get_path(&data->i2c_client->dev.kobj, GFP_KERNEL); +#else + path = kobject_get_path(&data->spi_client->dev.kobj, GFP_KERNEL); +#endif + blen = scnprintf(buf, PAGE_SIZE, "%s", path ? path : "na"); + kfree(path); + return blen; +} +#endif + +/* Attribute: vendor (RO) */ +static ssize_t vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "chipone"); +} + +/* Attribute: vendor (RO) */ +static ssize_t ic_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chipone_ts_data *ts = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%s%s\n%s%04x\n%s%04x\n", + "Product ID: ", ts->cts_dev.hwdata->name, + "Build ID: ", ts->cts_dev.fwdata.version + ? ts->cts_dev.fwdata.version + : ts->pdata->build_id, + "Config ID: ", ts->cts_dev.fwdata.ddi_version + ? ts->cts_dev.fwdata.ddi_version + : ts->pdata->config_id); +} + +static struct device_attribute touchscreen_attributes[] = { +#ifndef CFG_CTS_FOR_GKI + __ATTR_RO(path), +#endif + __ATTR_RO(vendor), + __ATTR_RO(ic_ver), + __ATTR_NULL +}; + +#define TSDEV_MINOR_BASE 128 +#define TSDEV_MINOR_MAX 32 + +/******************************************************* + *Description: + * Chipone touchscreen FW function class. file node + * initial function. + * + * return: + * Executive outcomes. 0---succeed. -1---failed. + *******************************************************/ +static int cts_fw_class_init(void *_data, bool create) +{ + struct chipone_ts_data *data = _data; + struct device_attribute *attrs = touchscreen_attributes; + int i, error = 0; + static struct class *touchscreen_class; + static struct device *ts_class_dev; + dev_t devno; + + cts_info("%s touchscreen class files", create ? "Add" : "Remove"); + + if (create) { + if (data->cts_dev.hwdata->name != NULL) + error = alloc_chrdev_region(&devno, 0, 1, data->cts_dev.hwdata->name); + else + error = alloc_chrdev_region(&devno, 0, 1, CFG_CTS_CHIP_NAME); + + if (error) { + cts_info("Alloc input devno failed %d", error); + return error; + } + + cts_info("Create class 'touchscreen'"); + touchscreen_class = class_create(THIS_MODULE, "touchscreen"); + if (IS_ERR(touchscreen_class)) { + cts_err("Create class 'touchscreen' failed %ld", + PTR_ERR(touchscreen_class)); + error = PTR_ERR(touchscreen_class); + touchscreen_class = NULL; + return error; + } + + if (data->cts_dev.hwdata->name != NULL) { + ts_class_dev = device_create(touchscreen_class, NULL, + devno, data, "%s", data->cts_dev.hwdata->name); + cts_info("Create device for IC: %s", data->cts_dev.hwdata->name); + } else { + ts_class_dev = device_create(touchscreen_class, NULL, + devno, data, "%s", CFG_CTS_CHIP_NAME); + cts_info("Create device '" CFG_CTS_CHIP_NAME "'"); + } + if (IS_ERR(ts_class_dev)) { + cts_err("Create device '" CFG_CTS_CHIP_NAME + "'failed %ld", PTR_ERR(ts_class_dev)); + error = PTR_ERR(ts_class_dev); + ts_class_dev = NULL; + return error; + } + + cts_info("Create attr files"); + for (i = 0; attrs[i].attr.name != NULL; ++i) { + cts_info(" Create attr file '%s'", attrs[i].attr.name); + error = device_create_file(ts_class_dev, &attrs[i]); + if (error) { + cts_err("Create attr file '%s' failed %d", + attrs[i].attr.name, error); + break; + } + } + + if (error) + goto device_destroy; + else + cts_info("Create /sys/class/touchscreen/ Succeeded"); + } else { + if (!touchscreen_class || !ts_class_dev) + return -ENODEV; + + for (i = 0; attrs[i].attr.name != NULL; ++i) { + cts_info("Remove device file '%s'", attrs[i].attr.name); + device_remove_file(ts_class_dev, &attrs[i]); + } + device_unregister(ts_class_dev); + class_unregister(touchscreen_class); + } + + return 0; + +device_destroy: + for (--i; i >= 0; --i) + device_remove_file(ts_class_dev, &attrs[i]); + device_destroy(touchscreen_class, devno); + ts_class_dev = NULL; + class_unregister(touchscreen_class); + cts_err("Creating touchscreen class failed %d", error); + + return -ENODEV; +} + +int cts_sysfs_add_device(struct device *dev) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + int ret = 0, i; + + cts_info("Add device attr groups"); + + /*Low version kernel NOT support sysfs_create_groups() */ + for (i = 0; cts_dev_attr_groups[i]; i++) { + ret = sysfs_create_group(&dev->kobj, cts_dev_attr_groups[i]); + if (ret) { + while (--i >= 0) + sysfs_remove_group(&dev->kobj, cts_dev_attr_groups[i]); + break; + } + } + + if (ret) { + cts_err("Add device attr failed %d", ret); + return ret; + } + + ret = sysfs_create_link(NULL, &dev->kobj, "chipone-tddi"); + if (ret) + cts_err("Create sysfs link error:%d", ret); + + ret = cts_fw_class_init(cts_data, true); + if (ret) { + cts_err("Create touchscreen class failed. ret=%d", ret); + return ret; + } + +#ifdef CFG_CTS_PLATFORM_SPRD_SUPPORTED + ret = cts_suspend_sysfs_init(dev); + if (ret) { + cts_info("create suspend sysfs node failed"); + return ret; + } +#endif + + return 0; +} + +void cts_sysfs_remove_device(struct device *dev) +{ + struct chipone_ts_data *cts_data = dev_get_drvdata(dev); + int i; + + cts_info("Remove device attr groups"); + + sysfs_remove_link(NULL, "chipone-tddi"); + /*Low version kernel NOT support sysfs_remove_groups() */ + for (i = 0; cts_dev_attr_groups[i]; i++) + sysfs_remove_group(&dev->kobj, cts_dev_attr_groups[i]); + + cts_fw_class_init(cts_data, false); + +#ifdef CFG_CTS_PLATFORM_SPRD_SUPPORTED + cts_suspend_sysfs_deinit(dev); +#endif + +} + +#undef SPLIT_LINE_STR +#undef ROW_NUM_FORMAT_STR +#undef COL_NUM_FORMAT_STR +#undef DATA_FORMAT_STR + +#endif /* CONFIG_CTS_SYSFS */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_sysfs.h b/drivers/input/touchscreen/chipone_tddi/cts_sysfs.h new file mode 100644 index 000000000000..b0224e317908 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_sysfs.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CTS_SYSFS_H +#define CTS_SYSFS_H + +#include "cts_config.h" + +struct device; + +#ifdef CONFIG_CTS_SYSFS +extern int cts_argc; +extern char *cts_argv[]; +extern int cts_sysfs_add_device(struct device *dev); +extern void cts_sysfs_remove_device(struct device *dev); +extern int cts_parse_arg(const char *buf, size_t count); +#else /* CONFIG_CTS_SYSFS */ +static inline int cts_sysfs_add_device(struct device *dev) +{ + return -ENOTSUPP; +} + +static inline void cts_sysfs_remove_device(struct device *dev) +{ +} +#endif /* CONFIG_CTS_SYSFS */ + +#endif /* CTS_SYSFS_H */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_tcs.c b/drivers/input/touchscreen/chipone_tddi/cts_tcs.c new file mode 100644 index 000000000000..73cdad598a58 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_tcs.c @@ -0,0 +1,1901 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#define LOG_TAG "TCS" + +#include "cts_config.h" +#include "cts_firmware.h" +#include "cts_platform.h" +#include "cts_tcs.h" + +#define TCS_WR_ADDR (0xF0) +#define TCS_RD_ADDR (0xF1) + +#define TCS_READ_BIT (14) +#define TCS_WRITE_BIT (13) +#define TCS_HEADER_SIZE sizeof(STRUCT_TCS_TX_HEAD) +#define TCS_TAIL_SIZE sizeof(STRUCT_TCS_RX_TAIL) + +/* raw touch info without data */ +#define TOUCH_INFO_SIZE sizeof(struct cts_device_touch_info) +#define INT_DATA_VALID_INDEX (174) +#define INT_DATA_HEADER_SIZE (180) + +#define CTS_I2C_MAX_TRANS_SIZE (48) + +#pragma pack(1) +#ifdef CONFIG_CTS_I2C_HOST +typedef struct { + u16 cmd; + u16 datlen; + u8 check_l; + u8 check_h; +} STRUCT_TCS_TX_HEAD; + +typedef struct { + u8 ecode; + u16 cmd; + u8 check_l; + u8 check_h; +} STRUCT_TCS_RX_TAIL; +#else /* CONFIG_CTS_I2C_HOST */ +typedef struct { + u8 addr; + u16 cmd; + u16 datlen; + u16 crc16; +} STRUCT_TCS_TX_HEAD; +typedef struct { + u8 ecode; + u16 cmd; + u16 crc16; +} STRUCT_TCS_RX_TAIL; +#endif /* CONFIG_CTS_I2C_HOST */ +#pragma pack() + +#define CMD_INFO_CHIP_HW_ID_RO (0x4002) +#define CMD_INFO_CHIP_FW_ID_RO (0x4003) +#define CMD_INFO_FW_VER_RO (0x4005) +#define CMD_INFO_TOUCH_XY_INFO_RO (0x4007) +#define CMD_INFO_IC_INFO_RO (0x4009) +#define CMD_INFO_PAD_PANEL_INFO_RO (0x400a) +#define CMD_INFO_MODULE_ID_RO (0x4011) +#define CMD_TP_DATA_OFFSET_AND_TYPE_CFG_RW (0x6101) +#define CMD_TP_DATA_READ_START_RO (0x4102) +#define CMD_TP_DATA_COORDINATES_RO (0x4103) +#define CMD_TP_DATA_RAW_RO (0x4104) +#define CMD_TP_DATA_DIFF_RO (0x4105) +#define CMD_TP_DATA_BASE_RO (0x4106) +#define CMD_TP_DATA_CNEG_RO (0x410a) + +#define CMD_TP_DATA_WR_REG_RAM_SEQUENCE_WO (0x2114) +#define CMD_TP_DATA_WR_REG_RAM_BATCH_WO (0x2115) +#define CMD_TP_DATA_WR_DDI_REG_SEQUENCE_WO (0x2116) +#define CMD_TP_STD_CMD_SET_KRANG_STOP (0x224a) +#define CMD_GET_DATA_BY_POLLING_RO (0x4123) +#define CMD_TP_DATA_STATUS_RO (0x4125) +#define CMD_SYS_STS_READ_RO (0x4200) +#define CMD_SYS_STS_WORK_MODE_RW (0x6201) +#define CMD_SYS_STS_DAT_RDY_FLAG_RW (0x6203) +#define CMD_SYS_STS_PWR_STATE_RW (0x6204) +#define CMD_SYS_STS_CHARGER_PLUGIN_RW (0x6205) +#define CMD_SYS_STS_DDI_CODE_VER_RO (0x4206) +#define CMD_SYS_STS_DAT_TRANS_IN_NORMAL_RW (0x6207) +#define CMD_SYS_STS_VSTIM_LVL_RW (0x6208) +#define CMD_SYS_STS_CNEG_RDY_FLAG_RW (0x6211) +#define CMD_SYS_STS_EP_PLUGIN_RW (0x6213) +#define CMD_SYS_STS_RESET_WO (0x2216) +#define CMD_SYS_STS_INT_TEST_EN_RW (0x6217) +#define CMD_SYS_STS_SET_INT_PIN_RW (0x6218) +#define CMD_SYS_STS_CNEG_RD_EN_RW (0x6219) +#define CMD_SYS_STS_HI_SENSE_EN_RW (0x621a) +#define CMD_SYS_STS_INT_MODE_RW (0x6223) +#define CMD_SYS_STS_INT_KEEP_TIME_RW (0x6224) +#define CMD_SYS_STS_CURRENT_WORKMODE_RO (0x4233) +#define CMD_SYS_STS_DATA_CAPTURE_SUPPORT_RO (0x423f) +#define CMD_SYS_STS_DATA_CAPTURE_EN_RW (0x6240) +#define CMD_SYS_STS_DATA_CAPTURE_FUNC_MAP_RW (0x6241) +#define CMD_SYS_STS_PANEL_DIRECTION_RW (0x6242) +#define CMD_SYS_STS_KRANG_WORK_MODE_RW (0x6243) +#define CMD_SYS_STS_KRANG_CURRENT_WORKMODE_RO (0x4244) +#define CMD_SYS_STS_GAME_MODE_RW (0x624e) +#define CMD_SYS_STS_POCKET_MODE_EN_RW (0x6250) +#define CMD_SYS_STS_PRODUCTION_TEST_EN_RW (0x6252) +#define CMD_SYS_STS_KRANG_WORK_STS_RO (0x4259) + +#define CMD_GSTR_WAKEUP_EN_RW (0x6301) +#define CMD_GSTR_DAT_RDY_FLAG_GSTR_RW (0x631e) +#define CMD_GSTR_ENTER_MAP_RW (0x6328) +#define CMD_MNT_EN_RW (0x6401) +#define CMD_MNT_FORCE_EXIT_MNT_WO (0x2403) +#define CMD_DDI_ESD_EN_RW (0x6501) +#define CMD_DDI_ESD_OPTIONS_RW (0x6502) +#define CMD_CNEG_EN_RW (0x6601) +#define CMD_CNEG_OPTIONS_RW (0x6602) +#define CMD_COORD_FLIP_X_EN_RW (0x6702) +#define CMD_COORD_FLIP_Y_EN_RW (0x6703) +#define CMD_COORD_SWAP_AXES_EN_RW (0x6704) +#define CMD_PARA_PROXI_EN_RW (0x692a) +#define CMD_PARA_KUNCKLE_RW (0x694b) +#define CMD_OPENSHORT_EN_RW (0x6b01) +#define CMD_OPENSHORT_MODE_SEL_RW (0x6b02) +#define CMD_OPENSHORT_SHORT_SEL_RW (0x6b03) +#define CMD_OPENSHORT_SHORT_DISP_ON_EN_RW (0x6b04) + +#if 0 +static u8 str[1024 * 4]; + +void dump_spi(const char *prefix, u8 *data, size_t datalen) +{ + + int offset = 0; + int i; + + offset += snprintf(str + offset, sizeof(str) - offset, "%s", prefix); + for (i = 0; i < datalen; i++) { + offset += snprintf(str + offset, sizeof(str) - offset, " %02x", data[i]); + } + cts_err("%s", str); +} +#endif + +#ifdef CONFIG_CTS_I2C_HOST +static int cts_tcs_tail_check(u8 *buf, u16 cmd, int len) +{ + STRUCT_TCS_RX_TAIL *tail; + u8 check_l = 0; + u8 check_h = 0x01; + int check_len = len - TCS_TAIL_SIZE; + int i; + + tail = (STRUCT_TCS_RX_TAIL *)(buf + check_len); + if (tail->ecode != 0) { + cts_err("error code:0x%02x", tail->ecode); + return -EIO; + } + + if (tail->cmd != cmd) { + cts_err("cmd error: recv %04x != %04x send", tail->cmd, cmd); + return -EIO; + } + + for (i = 0; i < check_len + 3; i++) { + check_l += buf[i]; + } + check_l = ~check_l; + if (tail->check_h != check_h || tail->check_l != check_l) { + cts_err("crc error: recv %02x%02x != %02x%02x calc", + tail->check_h, tail->check_l, check_h, check_l); + return -EIO; + } + + return 0; +} + +static int cts_tcs_i2c_trans(struct cts_platform_data *pdata, u8 i2c_addr, + const u8 *wbuf, size_t wlen, void *rbuf, size_t rlen, + int retry, int delay) +{ + int i, retries = 0; + int ret; + + struct i2c_msg msgs[2] = { + { + .addr = i2c_addr, + .flags = 0, + .buf = (u8 *) wbuf, + .len = wlen, + }, + { + .addr = i2c_addr, + .flags = I2C_M_RD, + .buf = (u8 *) rbuf, + .len = rlen, + } + }; + + for (i = 0; i < 2; i++) { + for (retries = 0; retries < retry; retries++) { + ret = i2c_transfer(pdata->i2c_client->adapter, &msgs[i], 1); + if (ret == 1) { + ret = 0; + + if (delay > 0) + mdelay(delay); + else + udelay(300); + + break; + } else { + mdelay(10); + } + } + + if (retries >= retry) { + return -EIO; + } + } + + ret = cts_tcs_tail_check(rbuf, get_unaligned_le16(wbuf), rlen); + + return ret; +} + +static int cts_tcs_i2c_trans_1_time(struct cts_platform_data *pdata, u8 i2c_addr, + const u8 *wbuf, size_t wlen, void *rbuf, size_t rlen, + int retry, int delay) +{ + int retries = 0; + int ret; + + struct i2c_msg msgs[2] = { + { + .addr = i2c_addr, + .flags = 0, + .buf = (u8 *) wbuf, + .len = wlen, + }, + { + .addr = i2c_addr, + .flags = I2C_M_RD, + .buf = (u8 *) rbuf, + .len = rlen, + } + }; + + do { + ret = i2c_transfer(pdata->i2c_client->adapter, msgs, 2); + + if (ret != 2) { + if (ret >= 0) { + ret = -EIO; + } + + udelay(delay); + + continue; + } else { + return 0; + } + } while (++retries < retry); + + ret = cts_tcs_tail_check(rbuf, get_unaligned_le16(wbuf), rlen); + + return ret; +} + +static int cts_tcs_i2c_read_pack(u8 *tx, u16 cmd, u16 rdatalen) +{ + STRUCT_TCS_TX_HEAD *txhdr = (STRUCT_TCS_TX_HEAD *) tx; + u16 is_read; + int packlen = 0; + + is_read = cmd & BIT(TCS_READ_BIT); + if (0 == is_read) { + return packlen; + } + txhdr->cmd = (cmd & ~BIT(TCS_WRITE_BIT)); + txhdr->datlen = rdatalen; + txhdr->check_l = ~((txhdr->cmd & 0xff) + + ((txhdr->cmd >> 8) & 0xff) + + (rdatalen & 0xff) + + ((rdatalen >> 8) & 0xff)); + txhdr->check_h = 1; + packlen = TCS_HEADER_SIZE; + + return packlen; +} + +static int cts_tcs_i2c_write_pack(u8 *tx, u16 cmd, u8 *wdata, u16 wlen) +{ + STRUCT_TCS_TX_HEAD *txhdr = (STRUCT_TCS_TX_HEAD *) tx; + int packlen = 0; + u8 check_l = 0; + u16 is_write; + int i; + + is_write = cmd & BIT(TCS_WRITE_BIT); + if (0 == is_write) { + return packlen; + } + txhdr->cmd = (cmd & ~BIT(TCS_READ_BIT)); + txhdr->datlen = wlen; + txhdr->check_l = ~((txhdr->cmd & 0xff) + + ((txhdr->cmd >> 8) & 0xff) + + (wlen & 0xff) + + ((wlen >> 8) & 0xff)); + txhdr->check_h = 1; + packlen += TCS_HEADER_SIZE; + + if (wlen > 0) { + memcpy(tx + TCS_HEADER_SIZE, wdata, wlen); + for (i = 0; i < wlen; i++) { + check_l += wdata[i]; + } + *(tx + TCS_HEADER_SIZE + wlen) = ~check_l; + *(tx + TCS_HEADER_SIZE + wlen + 1) = 1; + packlen += wlen + 2; + } + + return packlen; +} + +static int cts_tcs_i2c_read(struct cts_device *cts_dev, u16 cmd, + u8 *buf, size_t len) +{ + int txlen; + int size; + int ret; + + size = len + TCS_TAIL_SIZE; + + txlen = cts_tcs_i2c_read_pack(cts_dev->pdata->i2c_fifo_buf, cmd, len); + + ret = cts_tcs_i2c_trans(cts_dev->pdata, CTS_DEV_NORMAL_MODE_I2CADDR, + cts_dev->pdata->i2c_fifo_buf, txlen, cts_dev->pdata->i2c_rbuf, size, + 3, 0); + + if (ret == 0) { + memcpy(buf, cts_dev->pdata->i2c_rbuf, len); + } + + return ret; +} + +static int cts_tcs_i2c_write(struct cts_device *cts_dev, + u16 cmd, u8 *wbuf, size_t wlen) +{ + int txlen = cts_tcs_i2c_write_pack(cts_dev->pdata->i2c_fifo_buf, cmd, wbuf, wlen); + return cts_tcs_i2c_trans(cts_dev->pdata, CTS_DEV_NORMAL_MODE_I2CADDR, + cts_dev->pdata->i2c_fifo_buf, txlen, cts_dev->pdata->i2c_rbuf, TCS_TAIL_SIZE, + 3, 0); +} + +static int cts_tcs_i2c_set_krang_stop(struct cts_device *cts_dev, + u16 cmd, u8 *wbuf, size_t wlen) +{ + int txlen = cts_tcs_i2c_write_pack(cts_dev->pdata->i2c_fifo_buf, cmd, wbuf, wlen); + return cts_tcs_i2c_trans(cts_dev->pdata, CTS_DEV_NORMAL_MODE_I2CADDR, + cts_dev->pdata->i2c_fifo_buf, txlen, cts_dev->pdata->i2c_rbuf, TCS_TAIL_SIZE, + 3, 50); +} + +static int cts_tcs_i2c_read_touch(struct cts_device *cts_dev, + u16 cmd, u8 *buf, size_t len) +{ + int txlen; + int rxlen_without_tail = len - TCS_TAIL_SIZE; + int ret; + + txlen = cts_tcs_i2c_read_pack(cts_dev->pdata->i2c_fifo_buf, + cmd, len - TCS_TAIL_SIZE); + ret = cts_tcs_i2c_trans_1_time(cts_dev->pdata, CTS_DEV_NORMAL_MODE_I2CADDR, + cts_dev->pdata->i2c_fifo_buf, txlen, cts_dev->pdata->i2c_rbuf, len, 3, 10); + + if (ret == 0) { + memcpy(buf, cts_dev->pdata->i2c_rbuf, rxlen_without_tail); + } + + return ret; +} +#else +static int cts_tcs_spi_xtrans(struct cts_device *cts_dev, u8 *tx, + size_t txlen, u8 *rx, size_t rxlen, int delay) +{ + struct chipone_ts_data *cts_data = container_of(cts_dev, + struct chipone_ts_data, cts_dev); + struct spi_transfer xfer[2]; + struct spi_message msg; + u16 crc16_recv, crc16_calc; + u16 cmd_recv, cmd_send; + int ret; + + memset(&xfer[0], 0, sizeof(struct spi_transfer)); + xfer[0].delay_usecs = 0; + xfer[0].speed_hz = cts_dev->pdata->spi_speed * 1000u; + xfer[0].tx_buf = tx; + xfer[0].rx_buf = NULL; + xfer[0].len = txlen; + + spi_message_init(&msg); + spi_message_add_tail(&xfer[0], &msg); +#ifdef CFG_CTS_MANUAL_CS + cts_plat_set_cs(cts_dev->pdata, 0); +#endif + ret = spi_sync(cts_data->spi_client, &msg); +#ifdef CFG_CTS_MANUAL_CS + cts_plat_set_cs(cts_dev->pdata, 1); +#endif + if (ret < 0) { + cts_err("spi_sync xfer[0] failed: %d", ret); + return ret; + } + + if (delay > 0) + mdelay(delay); + else + udelay(500); + + memset(&xfer[1], 0, sizeof(struct spi_transfer)); + xfer[1].delay_usecs = 0; + xfer[1].speed_hz = cts_dev->pdata->spi_speed * 1000u; + xfer[1].tx_buf = NULL; + xfer[1].rx_buf = rx; + xfer[1].len = rxlen; + + spi_message_init(&msg); + spi_message_add_tail(&xfer[1], &msg); +#ifdef CFG_CTS_MANUAL_CS + cts_plat_set_cs(cts_dev->pdata, 0); +#endif + ret = spi_sync(cts_data->spi_client, &msg); +#ifdef CFG_CTS_MANUAL_CS + cts_plat_set_cs(cts_dev->pdata, 1); +#endif + if (ret < 0) { + cts_err("spi_sync xfer[1] failed: %d", ret); + return ret; + } + + cmd_recv = get_unaligned_le16(rx +rxlen - 4); + cmd_send = get_unaligned_le16(tx + 1); + if (cmd_recv != cmd_send) { + cts_dbg("cmd check error, send %04x != %04x recv", cmd_send, cmd_recv); + // return -EIO; + } + + crc16_recv = get_unaligned_le16(rx + rxlen - 2); + crc16_calc = cts_crc16(rx, rxlen - 2); + if (crc16_recv != crc16_calc) { + cts_err("crc error: recv %04x != %04x calc", crc16_recv, crc16_calc); + return -EIO; + } + udelay(100); + + return 0; +} + +static int cts_tcs_spi_xtrans_1_cs(struct cts_device *cts_dev, u8 *tx, + size_t txlen, u8 *rx, size_t rxlen) +{ + struct chipone_ts_data *cts_data = container_of(cts_dev, + struct chipone_ts_data, cts_dev); + struct spi_transfer xfer[1]; + struct spi_message msg; + u16 crc16_recv, crc16_calc; + u16 cmd_recv, cmd_send; + int ret; + + memset(&xfer[0], 0, sizeof(struct spi_transfer)); + xfer[0].delay_usecs = 0; + xfer[0].speed_hz = cts_dev->pdata->spi_speed * 1000u; + xfer[0].tx_buf = tx; + xfer[0].rx_buf = rx; + xfer[0].len = txlen > rxlen ? txlen : rxlen; + + spi_message_init(&msg); + spi_message_add_tail(&xfer[0], &msg); +#ifdef CFG_CTS_MANUAL_CS + cts_plat_set_cs(cts_dev->pdata, 0); +#endif + ret = spi_sync(cts_data->spi_client, &msg); +#ifdef CFG_CTS_MANUAL_CS + cts_plat_set_cs(cts_dev->pdata, 1); +#endif + if (ret < 0) { + cts_err("spi_sync xfer[0] failed: %d", ret); + return ret; + } + + cmd_recv = get_unaligned_le16(rx + rxlen - 4); + cmd_send = get_unaligned_le16(tx + 1); + if (cmd_recv != cmd_send) { + cts_dbg("cmd check error, send %04x != %04x recv", cmd_send, cmd_recv); + // return -EIO; + } + + crc16_recv = get_unaligned_le16(rx + rxlen - 2); + crc16_calc = cts_crc16(rx, rxlen - 2); + if (crc16_recv != crc16_calc) { + cts_err("1cs crc error: recv %04x != %04x calc", crc16_recv, crc16_calc); + return -EIO; + } + + return 0; +} + +static int cts_tcs_spi_read_pack(u8 *tx, u16 cmd, u16 rdatalen) +{ + STRUCT_TCS_TX_HEAD *txhdr = (STRUCT_TCS_TX_HEAD *) tx; + u16 is_read; + int packlen = 0; + u16 crc16; + + is_read = cmd & BIT(TCS_READ_BIT); + if (0 == is_read) { + return packlen; + } + txhdr->addr = TCS_RD_ADDR; + txhdr->cmd = (cmd & ~BIT(TCS_WRITE_BIT)); + txhdr->datlen = rdatalen; + crc16 = cts_crc16((const u8 *)txhdr, offsetof(STRUCT_TCS_TX_HEAD, crc16)); + txhdr->crc16 = crc16; + packlen += TCS_HEADER_SIZE; + + return packlen; +} + +static int cts_tcs_spi_write_pack(u8 *tx, u16 cmd, u8 *wdata, u16 wdatalen) +{ + STRUCT_TCS_TX_HEAD *txhdr = (STRUCT_TCS_TX_HEAD *) tx; + u16 is_write; + int packlen = 0; + u16 crc16; + + is_write = cmd & BIT(TCS_WRITE_BIT); + if (0 == is_write) { + return packlen; + } + txhdr->addr = TCS_WR_ADDR; + txhdr->cmd = (cmd & ~BIT(TCS_READ_BIT)); + txhdr->datlen = wdatalen; + crc16 = cts_crc16((const u8 *)txhdr, offsetof(STRUCT_TCS_TX_HEAD, crc16)); + txhdr->crc16 = crc16; + packlen += TCS_HEADER_SIZE; + + if (wdatalen > 0) { + memcpy(tx + TCS_HEADER_SIZE, wdata, wdatalen); + crc16 = cts_crc16(wdata, wdatalen); + *(tx + TCS_HEADER_SIZE + wdatalen) = ((crc16 >> 0) & 0xFF); + *(tx + TCS_HEADER_SIZE + wdatalen + 1) = ((crc16 >> 8) & 0xFF); + packlen += wdatalen + sizeof(crc16); + } + + return packlen; +} + +static int cts_tcs_spi_read(struct cts_device *cts_dev, + u16 cmd, u8 *rdata, size_t rdatalen) +{ + int txlen; + int ret; + + txlen = cts_tcs_spi_read_pack(cts_dev->pdata->spi_tx_buf, cmd, rdatalen); + if (0 == txlen) { + cts_err("spi read pack len: %d", txlen); + return -EINVAL; + } + ret = cts_tcs_spi_xtrans(cts_dev, cts_dev->pdata->spi_tx_buf, txlen, + cts_dev->pdata->spi_rx_buf, rdatalen + TCS_TAIL_SIZE, 0); + if (ret) { + return ret; + } + + memcpy(rdata, cts_dev->pdata->spi_rx_buf, rdatalen); + + return ret; +} + +static int cts_tcs_spi_read_1_cs(struct cts_device *cts_dev, + u16 cmd, u8 *rdata, size_t rdatalen) +{ + int txlen; + int ret; + + txlen = cts_tcs_spi_read_pack(cts_dev->pdata->spi_tx_buf, cmd, rdatalen); + if (0 == txlen) { + cts_err("spi read pack len: %d", txlen); + return -EINVAL; + } + ret = cts_tcs_spi_xtrans_1_cs(cts_dev, cts_dev->pdata->spi_tx_buf, txlen, + cts_dev->pdata->spi_rx_buf, rdatalen); + if (ret) { + return ret; + } + + memcpy(rdata, cts_dev->pdata->spi_rx_buf, rdatalen); + + return ret; +} + +static int cts_tcs_spi_write(struct cts_device *cts_dev, + u16 cmd, u8 *data, size_t wlen) +{ + int txlen; + int ret; + + txlen = cts_tcs_spi_write_pack(cts_dev->pdata->spi_tx_buf, cmd, data, wlen); + if (0 == txlen) { + cts_err("spi write pack len: %d", txlen); + return -EINVAL; + } + ret = cts_tcs_spi_xtrans(cts_dev, cts_dev->pdata->spi_tx_buf, txlen, + cts_dev->pdata->spi_rx_buf, TCS_TAIL_SIZE, 0); + return ret; +} + +static int cts_tcs_spi_set_krang_stop(struct cts_device *cts_dev, + u16 cmd, u8 *data, size_t wlen) +{ + int txlen; + int ret; + + txlen = cts_tcs_spi_write_pack(cts_dev->pdata->spi_tx_buf, cmd, data, wlen); + if (0 == txlen) { + cts_err("spi write pack len: %d", txlen); + return -EINVAL; + } + ret = cts_tcs_spi_xtrans(cts_dev, cts_dev->pdata->spi_tx_buf, txlen, + cts_dev->pdata->spi_rx_buf, TCS_TAIL_SIZE, 50); + return ret; +} +#endif + +int cts_tcs_tool_xtrans(struct cts_device *cts_dev, u8 *tx, size_t txlen, + u8 *rx, size_t rxlen) +{ +#ifdef CONFIG_CTS_I2C_HOST + int ret; + memcpy(cts_dev->pdata->i2c_fifo_buf, tx, txlen); + + ret = cts_tcs_i2c_trans(cts_dev->pdata, CTS_DEV_NORMAL_MODE_I2CADDR, + cts_dev->pdata->i2c_fifo_buf, txlen, cts_dev->pdata->i2c_rbuf, rxlen, + 3, 1); + if (ret == 0) + memcpy(rx, cts_dev->pdata->i2c_rbuf, rxlen); + + return ret; +#else + return cts_tcs_spi_xtrans(cts_dev, tx, txlen, rx, rxlen, 1); +#endif +} + +int cts_tcs_read(struct cts_device *cts_dev, + u16 cmd, u8 *buf, size_t len) +{ +#ifdef CONFIG_CTS_I2C_HOST + return cts_tcs_i2c_read(cts_dev, cmd, buf, len); +#else + return cts_tcs_spi_read(cts_dev, cmd, buf, len); +#endif +} +int cts_tcs_write(struct cts_device *cts_dev, + u16 cmd, u8 *buf, size_t len) +{ +#ifdef CONFIG_CTS_I2C_HOST + return cts_tcs_i2c_write(cts_dev, cmd, buf, len); +#else + return cts_tcs_spi_write(cts_dev, cmd, buf, len); +#endif +} + +static int cts_tcs_set_krang_stop(struct cts_device *cts_dev) +{ + uint8_t stop = 1; + int ret; + +#ifdef CONFIG_CTS_I2C_HOST + ret = cts_tcs_i2c_set_krang_stop(cts_dev, CMD_TP_STD_CMD_SET_KRANG_STOP, + &stop, sizeof(stop)); +#else + ret = cts_tcs_spi_set_krang_stop(cts_dev, CMD_TP_STD_CMD_SET_KRANG_STOP, + &stop, sizeof(stop)); +#endif + if (ret < 0) { + cts_err("Set krang stop failed!"); + } + + return ret; +} + +int cts_tcs_get_hwid_info(struct cts_device *cts_dev, u8 *info) +{ + u8 buf[12] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_INFO_CHIP_HW_ID_RO, buf, sizeof(buf)); + if (ret == 0) { + memcpy(info, buf, sizeof(buf)); + } + return ret; +} + +int cts_tcs_get_fw_ver(struct cts_device *cts_dev, u16 *fwver) +{ + u8 buf[4] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_INFO_FW_VER_RO, buf, sizeof(buf)); + if (ret == 0) { + *fwver = buf[0] | (buf[1] << 8); + } + return ret; +} + +int cts_tcs_get_lib_ver(struct cts_device *cts_dev, u16 *libver) +{ + u8 buf[4] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_INFO_FW_VER_RO, buf, sizeof(buf)); + if (ret == 0) { + *libver = buf[2] | (buf[3] << 8); + } + return ret; +} + +int cts_tcs_get_fw_id(struct cts_device *cts_dev, u16 *fwid) +{ + u8 buf[4] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_INFO_CHIP_FW_ID_RO, buf, sizeof(buf)); + if (ret == 0) { + *fwid = buf[0] | (buf[1] << 8); + } + + return ret; +} + +int cts_tcs_get_ddi_ver(struct cts_device *cts_dev, u8 *ddiver) +{ + u8 buf[1] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_DDI_CODE_VER_RO, buf, sizeof(buf)); + if (ret == 0) { + *ddiver = buf[0]; + } + return ret; +} + +int cts_tcs_get_res_x(struct cts_device *cts_dev, u16 *res_x) +{ + u8 buf[10] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_INFO_TOUCH_XY_INFO_RO, buf, sizeof(buf)); + if (ret == 0) { + *res_x = buf[0] | (buf[1] << 8); + } + return ret; +} + +int cts_tcs_get_res_y(struct cts_device *cts_dev, u16 *res_y) +{ + u8 buf[10] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_INFO_TOUCH_XY_INFO_RO, buf, sizeof(buf)); + if (ret == 0) { + *res_y = buf[2] | (buf[3] << 8); + } + return ret; +} + +int cts_tcs_get_rows(struct cts_device *cts_dev, u8 *rows) +{ + u8 buf[10] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_INFO_TOUCH_XY_INFO_RO, buf, sizeof(buf)); + if (ret == 0) { + *rows = buf[5]; + } + return ret; +} + +int cts_tcs_get_cols(struct cts_device *cts_dev, u8 *cols) +{ + u8 buf[10] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_INFO_TOUCH_XY_INFO_RO, buf, sizeof(buf)); + if (ret == 0) { + *cols = buf[4]; + } + return ret; +} + +int cts_tcs_get_rx_tx_info(struct cts_device *cts_dev, u8 *info) +{ + u8 buf[8] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_INFO_IC_INFO_RO, buf, sizeof(buf)); + if (ret == 0) { + memcpy(info, buf, sizeof(buf)); + } + return ret; +} + +int cts_tcs_get_panel_rx_tx_info(struct cts_device *cts_dev, u8 *info) +{ + u8 buf[15] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_INFO_PAD_PANEL_INFO_RO, buf, sizeof(buf)); + if (ret == 0) { + memcpy(info, buf, sizeof(buf)); + } + return ret; +} + +int cts_tcs_get_flip_x(struct cts_device *cts_dev, bool *flip_x) +{ + u8 buf[1] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_COORD_FLIP_X_EN_RW, buf, sizeof(buf)); + if (ret == 0) { + *flip_x = !!buf[0]; + } + return ret; +} + +int cts_tcs_get_flip_y(struct cts_device *cts_dev, bool *flip_y) +{ + u8 buf[1] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_COORD_FLIP_Y_EN_RW, buf, sizeof(buf)); + if (ret == 0) { + *flip_y = !!buf[0]; + } + return ret; +} + +int cts_tcs_get_swap_axes(struct cts_device *cts_dev, bool *swap_axes) +{ + u8 buf[1] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_COORD_SWAP_AXES_EN_RW, buf, sizeof(buf)); + if (ret == 0) { + *swap_axes = !!buf[0]; + } + return ret; +} + +int cts_tcs_get_int_mode(struct cts_device *cts_dev, u8 *int_mode) +{ + u8 buf[1] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_INT_MODE_RW, buf, sizeof(buf)); + if (ret == 0) { + *int_mode = buf[0]; + } + return ret; +} + +int cts_tcs_get_int_keep_time(struct cts_device *cts_dev, + u16 *int_keep_time) +{ + u8 buf[2] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_INT_KEEP_TIME_RW, buf, sizeof(buf)); + if (ret == 0) { + *int_keep_time = (buf[0] | (buf[1] << 8)); + } + return ret; + +} + +int cts_tcs_get_rawdata_target(struct cts_device *cts_dev, + u16 *rawdata_target) +{ + u8 buf[2] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_CNEG_OPTIONS_RW, buf, sizeof(buf)); + if (ret == 0) { + *rawdata_target = (buf[0] | (buf[1] << 8)); + } + return ret; + +} + +int cts_tcs_get_esd_method(struct cts_device *cts_dev, u8 *esd_method) +{ + u8 buf[1] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_DDI_ESD_OPTIONS_RW, buf, sizeof(buf)); + if (ret == 0) { + *esd_method = buf[0]; + } + return ret; +} + +int cts_tcs_get_esd_protection(struct cts_device *cts_dev, + u8 *esd_protection) +{ + u8 buf[4] = { 0 }; + int ret; + + buf[0] = 0x01; + buf[1] = 0x56; + buf[2] = 0x81; + buf[3] = 0x00; + + ret = cts_tcs_write(cts_dev, CMD_TP_DATA_OFFSET_AND_TYPE_CFG_RW, + buf, sizeof(buf)); + if (ret != 0) + return ret; + + ret = cts_tcs_read(cts_dev, CMD_TP_DATA_OFFSET_AND_TYPE_CFG_RW, + esd_protection, sizeof(u8)); + + return ret; +} + +static int cts_tcs_get_data_ready_flag(struct cts_device *cts_dev, u8 *ready) +{ + u8 buf[1] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_DAT_RDY_FLAG_RW, buf, sizeof(buf)); + if (ret == 0) { + *ready = buf[0]; + } + return ret; +} + +int cts_tcs_clr_gstr_ready_flag(struct cts_device *cts_dev) +{ + u8 ready = 0; + + return cts_tcs_write(cts_dev, CMD_GSTR_DAT_RDY_FLAG_GSTR_RW, + &ready, sizeof(ready)); +} + +static int cts_tcs_clr_data_ready_flag(struct cts_device *cts_dev) +{ + u8 ready = 0; + + return cts_tcs_write(cts_dev, CMD_SYS_STS_DAT_RDY_FLAG_RW, + &ready, sizeof(ready)); +} + +int cts_tcs_read_hw_reg(struct cts_device *cts_dev, u32 addr, + u8 *regbuf, size_t size) +{ + u8 buf[4] = { 0 }; + int ret; + + buf[0] = 1; + buf[1] = ((addr >> 0) & 0xFF); + buf[2] = ((addr >> 8) & 0xFF); + buf[3] = ((addr >> 16) & 0xFF); + + ret = cts_tcs_write(cts_dev, CMD_TP_DATA_OFFSET_AND_TYPE_CFG_RW, + buf, sizeof(buf)); + if (ret != 0) + return ret; + + ret = cts_tcs_read(cts_dev, CMD_TP_DATA_READ_START_RO, regbuf, size); + + return ret; +} + +int cts_tcs_write_hw_reg(struct cts_device *cts_dev, u32 addr, + u8 *regbuf, size_t size) +{ + u8 *buf; + int ret; + + buf = kmalloc(size + 6, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = ((size >> 0) & 0xFF); + buf[1] = ((size >> 8) & 0xFF); + buf[2] = ((addr >> 0) & 0xFF); + buf[3] = ((addr >> 8) & 0xFF); + buf[4] = ((addr >> 16) & 0xFF); + buf[5] = 0x00; + memcpy(buf + 6, regbuf, size); + + ret = cts_tcs_write(cts_dev, CMD_TP_DATA_WR_REG_RAM_SEQUENCE_WO, + buf, size + 6); + if (ret != 0) { + kfree(buf); + return ret; + } + + kfree(buf); + + return ret; +} + +int cts_tcs_read_ddi_reg(struct cts_device *cts_dev, u32 addr, + u8 *regbuf, size_t size) +{ + u8 buf[2] = { 0 }; + int ret; + + buf[0] = 2; + buf[1] = addr; + + ret = cts_tcs_write(cts_dev, CMD_TP_DATA_OFFSET_AND_TYPE_CFG_RW, + buf, sizeof(buf)); + if (ret != 0) + return ret; + + ret = cts_tcs_read(cts_dev, CMD_TP_DATA_READ_START_RO, regbuf, size); + if (ret != 0) + return ret; + + return 0; +} + +int cts_tcs_write_ddi_reg(struct cts_device *cts_dev, u32 addr, + u8 *regbuf, size_t size) +{ + u8 *buf; + int ret; + + buf = kmalloc(size + 6, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = ((size >> 0) & 0xFF); + buf[1] = ((size >> 8) & 0xFF); + buf[2] = addr; + buf[3] = 0; + buf[4] = 0; + buf[5] = 0; + memcpy(buf + 6, regbuf, size); + + ret = cts_tcs_write(cts_dev, CMD_TP_DATA_WR_DDI_REG_SEQUENCE_WO, + buf, size + 6); + if (ret != 0) { + kfree(buf); + return ret; + } + + kfree(buf); + + return ret; + +} + +int cts_tcs_read_reg(struct cts_device *cts_dev, u32 addr, + u8 *regbuf, size_t size) +{ + return cts_tcs_read(cts_dev, CMD_SYS_STS_READ_RO, regbuf, size); +} + +int cts_tcs_write_reg(struct cts_device *cts_dev, u32 addr, + u8 *regbuf, size_t size) +{ + u8 buf[4] = { 0 }; + int ret; + + buf[0] = 0x01; + buf[1] = ((addr >> 0) & 0xFF); + buf[2] = ((addr >> 8) & 0xFF); + buf[3] = ((addr >> 16) & 0xFF); + + ret = cts_tcs_write(cts_dev, CMD_TP_DATA_OFFSET_AND_TYPE_CFG_RW, + buf, sizeof(buf)); + if (ret != 0) + return ret; + + ret = cts_tcs_write(cts_dev, CMD_TP_DATA_OFFSET_AND_TYPE_CFG_RW, + regbuf, sizeof(buf)); + if (ret != 0) + return ret; + + return ret; +} + +int cts_tcs_calc_int_data_size(struct cts_device *cts_dev) +{ +#define INT_DATA_TYPE_U8_SIZ \ + (cts_dev->hwdata->num_row * cts_dev->hwdata->num_col * sizeof(u8)) +#define INT_DATA_TYPE_U16_SIZ \ + (cts_dev->hwdata->num_row * cts_dev->hwdata->num_col * sizeof(u16)) + + u16 data_types = cts_dev->fwdata.int_data_types; + bool is_stylus = !!(cts_dev->fwdata.int_data_types & BIT(14)); + u8 data_method = cts_dev->fwdata.int_data_method; + + u8 pen_freq_num = cts_dev->fwdata.pen_freq_num; + u8 cascade_num = cts_dev->fwdata.cascade_num; + u8 pc_cols = cts_dev->fwdata.pc_cols; + u8 pc_rows = cts_dev->fwdata.pc_rows; + u8 pr_cols = cts_dev->fwdata.pr_cols; + u8 pr_rows = cts_dev->fwdata.pr_rows; + + cts_dev->fwdata.int_data_size = (TOUCH_INFO_SIZE + TCS_TAIL_SIZE); + + if (data_method == INT_DATA_METHOD_NONE) { + return 0; + } else if (data_method == INT_DATA_METHOD_DEBUG) { + cts_dev->fwdata.int_data_size += INT_DATA_TYPE_U16_SIZ; + return 0; + } + + if (data_types != INT_DATA_TYPE_NONE) { + cts_dev->fwdata.int_data_size = INT_DATA_HEADER_SIZE; + if (is_stylus) { + cts_dev->fwdata.int_data_size += (pr_rows * pr_cols + pc_rows * pc_cols) + * pen_freq_num * sizeof(u16) * cascade_num; + } else { + if ((data_types & INT_DATA_TYPE_CNEGDATA)) { + cts_dev->fwdata.int_data_size += INT_DATA_TYPE_U8_SIZ; + } else { + cts_dev->fwdata.int_data_size += INT_DATA_TYPE_U16_SIZ; + } + } + + cts_dev->fwdata.int_data_size += TCS_TAIL_SIZE; + } + + cts_info("data_method:%d, data_type:%04x", data_method, data_types); + cts_info("data_size:%d", cts_dev->fwdata.int_data_size); + return 0; +} + +int cts_tcs_polling_data(struct cts_device *cts_dev, + u8 *buf, size_t size) +{ + int retries = 100; + u8 ready = 0; + int ret; + + size_t data_size = cts_dev->fwdata.int_data_size; + + do { + ret = cts_tcs_get_data_ready_flag(cts_dev, &ready); + if (!ret && ready) + break; + mdelay(10); + } while (!ready && --retries); + cts_info("get data rdy, retries left %d", retries); + + if (!ready) { + cts_err("time out wait for data rdy"); + return -EIO; + } + + retries = 3; + do { +#ifdef CONFIG_CTS_I2C_HOST + ret = cts_tcs_i2c_read_touch(cts_dev, CMD_GET_DATA_BY_POLLING_RO, + cts_dev->int_data, data_size); +#else + ret = cts_tcs_spi_read_1_cs(cts_dev, CMD_GET_DATA_BY_POLLING_RO, + cts_dev->int_data, data_size); +#endif + mdelay(1); + } while (ret && --retries); + + if (cts_tcs_clr_data_ready_flag(cts_dev)) + cts_err("Clear data ready flag failed"); + + return ret; +} + +static void cts_rotate_data(struct cts_device *cts_dev, + u8 *dst, u8 *src, enum int_data_type type) +{ + int rows = cts_dev->fwdata.rows; + int cols = cts_dev->fwdata.cols; + u16 *u16dst = (u16 *)dst; + u16 *u16src = (u16 *)src; + int i, j; + + if ((type & 0x3f) ==INT_DATA_TYPE_CNEGDATA) { + for (i = 0; i < cols; i++) { + for (j = 0; j < rows; j++) { + *dst++ = src[j * cols + i]; + } + } + } else { + for (i = 0; i < cols; i++) { + for (j = 0; j < rows; j++) { + *u16dst++ = u16src[j * cols + i]; + } + } + } +} + +static int tool_polling_data(struct cts_device *cts_dev, u8 *buf, + enum int_data_type type) +{ + u8 old_int_data_method; + u16 old_int_data_types; + int retries = 5; + int ret; + + old_int_data_types = cts_dev->fwdata.int_data_types; + old_int_data_method = cts_dev->fwdata.int_data_method; + + cts_set_int_data_types(cts_dev, type); + cts_set_int_data_method(cts_dev, INT_DATA_METHOD_POLLING); + + while (retries--) { + ret = cts_tcs_polling_data(cts_dev, buf, 0); + if (!ret) { + memcpy(buf, cts_dev->int_data, cts_dev->fwdata.int_data_size); + break; + } + } + + cts_set_int_data_types(cts_dev, old_int_data_types); + cts_set_int_data_method(cts_dev, old_int_data_method); + + return ret; +} + +static int polling_data(struct cts_device *cts_dev, u8 *buf, size_t size, + enum int_data_type type) +{ + u8 old_int_data_method; + u16 old_int_data_types; + int offset = INT_DATA_HEADER_SIZE; + int retries = 5; + int ret; + + old_int_data_types = cts_dev->fwdata.int_data_types; + old_int_data_method = cts_dev->fwdata.int_data_method; + + cts_set_int_data_types(cts_dev, type); + cts_set_int_data_method(cts_dev, INT_DATA_METHOD_POLLING); + + while (retries--) { + ret = cts_tcs_polling_data(cts_dev, buf, size); + if (!ret) { + if (cts_dev->hwdata->hwid == CTS_DEV_HWID_ICNL9951R) { + cts_rotate_data(cts_dev, buf, cts_dev->int_data + offset, type); + } else { + memcpy(buf, cts_dev->int_data + offset, size); + } + break; + } + } + + cts_set_int_data_types(cts_dev, old_int_data_types); + cts_set_int_data_method(cts_dev, old_int_data_method); + + return ret; +} + + +int cts_polling_test_data(struct cts_device *cts_dev, + u8 *buf, size_t size, enum int_data_type type) +{ + int offset = INT_DATA_HEADER_SIZE; + int retries = 5; + int ret; + + while (retries--) { + ret = cts_tcs_polling_data(cts_dev, buf, size); + if (!ret) { + if (cts_dev->int_data[INT_DATA_VALID_INDEX]) { + memcpy(buf, cts_dev->int_data + offset, size); + break; + } + } + } + + return ret; +} + +int cts_tcs_tool_get_rawdata(struct cts_device *cts_dev, u8 *buf, u16 data_source) +{ + return tool_polling_data(cts_dev, buf, data_source | INT_DATA_TYPE_RAWDATA); +} + +int cts_tcs_tool_get_manual_diff(struct cts_device *cts_dev, u8 *buf, u16 data_source) +{ + return tool_polling_data(cts_dev, buf, data_source | INT_DATA_TYPE_MANUAL_DIFF); +} + +int cts_tcs_tool_get_real_diff(struct cts_device *cts_dev, u8 *buf, u16 data_source) +{ + return tool_polling_data(cts_dev, buf, data_source | INT_DATA_TYPE_REAL_DIFF); +} + +int cts_tcs_tool_get_basedata(struct cts_device *cts_dev, u8 *buf, u16 data_source) +{ + return tool_polling_data(cts_dev, buf, data_source | INT_DATA_TYPE_BASEDATA); +} + + +int cts_tcs_top_get_rawdata(struct cts_device *cts_dev, u8 *buf, size_t size, + u16 data_source) +{ + return polling_data(cts_dev, buf, size, data_source | INT_DATA_TYPE_RAWDATA); +} + +int cts_tcs_top_get_manual_diff(struct cts_device *cts_dev, u8 *buf, size_t size, + u16 data_source) +{ + return polling_data(cts_dev, buf, size, data_source | INT_DATA_TYPE_MANUAL_DIFF); +} + +int cts_tcs_top_get_real_diff(struct cts_device *cts_dev, u8 *buf, size_t size, + u16 data_source) +{ + return polling_data(cts_dev, buf, size, data_source | INT_DATA_TYPE_REAL_DIFF); +} + +int cts_tcs_top_get_noise_diff(struct cts_device *cts_dev, u8 *buf, size_t size, + u16 data_source) +{ + return polling_data(cts_dev, buf, size, data_source | INT_DATA_TYPE_NOISE_DIFF); +} + +int cts_tcs_top_get_basedata(struct cts_device *cts_dev, u8 *buf, size_t size, + u16 data_source) +{ + return polling_data(cts_dev, buf, size, data_source | INT_DATA_TYPE_BASEDATA); +} + +int cts_tcs_top_get_cnegdata(struct cts_device *cts_dev, u8 *buf, size_t size, + u16 data_source) +{ + return polling_data(cts_dev, buf, size, data_source | INT_DATA_TYPE_CNEGDATA); +} + +int cts_tcs_reset_device(struct cts_device *cts_dev) +{ +#ifdef CONFIG_CTS_ICTYPE_ICNL9922 + u8 buf[2] = { 0x01, 0xfe }; + int ret; + + cts_info("ICNL9922 use software reset"); + /* normal */ + cts_info("tp reset in normal mode"); + ret = cts_tcs_write(cts_dev, CMD_SYS_STS_RESET_WO, + buf, sizeof(buf)); + if (!ret) { + mdelay(40); + return 0; + } + /* program */ + cts_info("tp reset in program mode"); + ret = cts_hw_reg_writeb(cts_dev, CTS_DEV_HW_REG_RESET_CONFIG, 0xfd); + if (!ret) { + mdelay(40); + return 0; + } + return ret; +#else + return cts_plat_reset_device(cts_dev->pdata); +#endif +} + +int cts_tcs_set_int_test(struct cts_device *cts_dev, u8 enable) +{ + return cts_tcs_write(cts_dev, CMD_SYS_STS_INT_TEST_EN_RW, &enable, + sizeof(enable)); +} + +int cts_tcs_set_int_pin(struct cts_device *cts_dev, u8 high) +{ + return cts_tcs_write(cts_dev, CMD_SYS_STS_SET_INT_PIN_RW, &high, + sizeof(high)); +} + +int cts_tcs_get_module_id(struct cts_device *cts_dev, u32 *modId) +{ + u8 buf[4] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_INFO_MODULE_ID_RO, buf, sizeof(buf)); + if (ret == 0) { + *modId = *(u32 *)buf; + } + return ret; +} + + +int cts_tcs_get_gestureinfo(struct cts_device *cts_dev, + struct cts_device_gesture_info *gesture_info) +{ + size_t size = sizeof(*gesture_info) + TCS_TAIL_SIZE; + int ret; + +#ifdef CONFIG_CTS_I2C_HOST + ret = cts_tcs_i2c_read_touch(cts_dev, CMD_TP_DATA_COORDINATES_RO, + cts_dev->int_data, size); +#else + ret = cts_tcs_spi_read_1_cs(cts_dev, CMD_TP_DATA_COORDINATES_RO, + cts_dev->int_data, size); +#endif + if (cts_tcs_clr_gstr_ready_flag(cts_dev)) { + cts_err("Clear gesture ready flag failed"); + } + if (ret < 0) { + cts_err("Get gesture info failed: ret=%d", ret); + return ret; + } + + memcpy(gesture_info, cts_dev->int_data, sizeof(*gesture_info)); + + return ret; +} + +int cts_tcs_get_touchinfo(struct cts_device *cts_dev, + struct cts_device_touch_info *touch_info) +{ + size_t size = cts_dev->fwdata.int_data_size; + int ret; + + memset(touch_info, 0, sizeof(*touch_info)); + +#ifdef CONFIG_CTS_I2C_HOST + ret = cts_tcs_i2c_read_touch(cts_dev, CMD_TP_DATA_COORDINATES_RO, + cts_dev->int_data, size); + if (unlikely(ret != 0)) { + cts_err("cts_tcs_i2c_read_touch failed"); + return ret; + } +#else + ret = cts_tcs_spi_read_1_cs(cts_dev, CMD_TP_DATA_COORDINATES_RO, + cts_dev->int_data, size); + if (unlikely(ret != 0)) { + cts_err("tcs_spi_read_1_cs failed"); + return ret; + } +#endif + memcpy(touch_info, cts_dev->int_data, sizeof(*touch_info)); + +#ifdef CFG_CTS_HEARTBEAT_MECHANISM + if (unlikely((touch_info->debug_msg.reset_flag & 0xFFFFFF) != 0xFFFFFF + || (touch_info->debug_msg.reset_flag & 0xFFFF) != 0xFFFF)) { + cts_err("reset flag:0x%08x error", touch_info->debug_msg.reset_flag); + cts_show_touch_debug_msg(&touch_info->debug_msg); + } +#endif + + return ret; +} + +int cts_tcs_get_touch_status(struct cts_device *cts_dev) +{ + return cts_tcs_read(cts_dev, CMD_TP_DATA_STATUS_RO, + cts_dev->int_data, TOUCH_INFO_SIZE); +} + +int cts_tcs_get_workmode(struct cts_device *cts_dev, u8 *workmode) +{ + u8 buf = 0; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_CURRENT_WORKMODE_RO, + &buf, sizeof(buf)); + if (ret == 0) { + *workmode = buf; + } + + return ret; +} + +int cts_tcs_set_workmode(struct cts_device *cts_dev, u8 workmode) +{ + return cts_tcs_write(cts_dev, CMD_SYS_STS_WORK_MODE_RW, + &workmode, sizeof(workmode)); +} + +int cts_tcs_set_openshort_mode(struct cts_device *cts_dev, u8 mode) +{ + return cts_tcs_write(cts_dev, CMD_OPENSHORT_MODE_SEL_RW, &mode, + sizeof(mode)); +} + +int cts_tcs_get_curr_mode(struct cts_device *cts_dev, u8 *currmode) +{ + u8 buf = 0; + int ret; + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_CURRENT_WORKMODE_RO, + &buf, sizeof(buf)); + if (ret == 0) { + *currmode = buf; + } + + return ret; +} + +int cts_tcs_get_krang_current_workmode(struct cts_device *cts_dev, u8 *workmode) +{ + u8 buf = 0; + int ret = 0; + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_KRANG_CURRENT_WORKMODE_RO, + &buf, sizeof(buf)); + if (ret == 0) { + *workmode = buf; + } + + return ret; +} + +int cts_tcs_set_krang_workmode(struct cts_device *cts_dev, u8 workmode) +{ + return cts_tcs_write(cts_dev, CMD_SYS_STS_KRANG_WORK_MODE_RW, &workmode, + sizeof(workmode)); +} + +/*Tab A9 code for AX6739A-953 by suyurui at 20230609 end*/ +int cts_tcs_set_tx_vol(struct cts_device *cts_dev, u8 txvol) +{ + return cts_tcs_write(cts_dev, CMD_SYS_STS_VSTIM_LVL_RW, &txvol, + sizeof(txvol)); +} + +int cts_tcs_set_short_test_type(struct cts_device *cts_dev, + u8 short_type) +{ + return cts_tcs_write(cts_dev, CMD_OPENSHORT_SHORT_SEL_RW, + &short_type, sizeof(short_type)); +} + +int cts_tcs_is_openshort_enabled(struct cts_device *cts_dev, u8 *enabled) +{ + u8 buf = 0; + int ret; + + ret = cts_tcs_write(cts_dev, CMD_OPENSHORT_EN_RW, &buf, sizeof(buf)); + if (ret == 0) { + *enabled = buf; + } + + return ret; +} + +int cts_tcs_set_openshort_enable(struct cts_device *cts_dev, u8 enable) +{ + return cts_tcs_write(cts_dev, CMD_OPENSHORT_EN_RW, &enable, sizeof(enable)); +} + +int cts_tcs_set_esd_enable(struct cts_device *cts_dev, u8 enable) +{ + return cts_tcs_write(cts_dev, CMD_DDI_ESD_EN_RW, &enable, sizeof(enable)); +} + +int cts_tcs_is_cneg_enabled(struct cts_device *cts_dev, u8 *enabled) +{ + return cts_tcs_read(cts_dev, CMD_CNEG_EN_RW, enabled, sizeof(*enabled)); +} + +int cts_tcs_is_mnt_enabled(struct cts_device *cts_dev, u8 *enabled) +{ + return cts_tcs_read(cts_dev, CMD_MNT_EN_RW, enabled, sizeof(*enabled)); +} + +int cts_tcs_set_cneg_enable(struct cts_device *cts_dev, u8 enable) +{ + return cts_tcs_write(cts_dev, CMD_CNEG_EN_RW, &enable, sizeof(enable)); +} + +int cts_tcs_set_mnt_enable(struct cts_device *cts_dev, u8 enable) +{ + return cts_tcs_write(cts_dev, CMD_MNT_EN_RW, &enable, sizeof(enable)); +} + +int cts_tcs_is_display_on(struct cts_device *cts_dev, u8 *display_on) +{ + u8 buf = 0; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_OPENSHORT_SHORT_DISP_ON_EN_RW, + &buf, sizeof(buf)); + if (ret == 0) { + *display_on = buf; + } + + return ret; +} + +int cts_tcs_set_pwr_mode(struct cts_device *cts_dev, u8 pwr_mode) +{ + return cts_tcs_write(cts_dev, CMD_SYS_STS_PWR_STATE_RW, + &pwr_mode, sizeof(pwr_mode)); +} + +int cts_tcs_set_display_on(struct cts_device *cts_dev, u8 display_on) +{ + return cts_tcs_write(cts_dev, CMD_OPENSHORT_SHORT_DISP_ON_EN_RW, + &display_on, sizeof(display_on)); +} + + +int cts_tcs_set_charger_plug(struct cts_device *cts_dev, u8 set) +{ + struct cts_firmware_status *status = (struct cts_firmware_status *) + &cts_dev->rtdata.firmware_status; + int ret; + + cts_info("Set charger enable:%d", set); + status->charger = set; + + ret = cts_tcs_write(cts_dev, CMD_SYS_STS_CHARGER_PLUGIN_RW, &set, 1); + if (ret < 0) { + cts_info("Set charger failed!"); + } + + return ret; +} + +int cts_tcs_get_charger_plug(struct cts_device *cts_dev, u8 *isset) +{ + u8 buf = 0; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_CHARGER_PLUGIN_RW,&buf, sizeof(buf)); + if (ret == 0) { + *isset = buf; + } + + return ret; +} + +int cts_tcs_set_earjack_plug(struct cts_device *cts_dev, u8 set) +{ + struct cts_firmware_status *status = (struct cts_firmware_status *) + &cts_dev->rtdata.firmware_status; + int ret; + + cts_info("Set earjack enable:%d", set); + + status->earjack = set; + + ret = cts_tcs_write(cts_dev, CMD_SYS_STS_EP_PLUGIN_RW, &set, 1); + if (ret) { + cts_info("Set earjack failed!"); + } + + return ret; +} + +int cts_tcs_get_earjack_plug(struct cts_device *cts_dev, u8 *isset) +{ + u8 buf = 0; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_EP_PLUGIN_RW, &buf, sizeof(buf)); + if (ret == 0) { + *isset = buf; + } + + return ret; +} + +int cts_tcs_set_panel_direction(struct cts_device *cts_dev, u8 direction) +{ + return cts_tcs_write(cts_dev, CMD_SYS_STS_PANEL_DIRECTION_RW, + &direction, sizeof(direction)); +} + +int cts_tcs_get_panel_direction(struct cts_device *cts_dev, u8 *direction) +{ + u8 buf = 0; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_PANEL_DIRECTION_RW, + &buf, sizeof(buf)); + if (ret == 0) { + *direction = buf; + } + + return ret; +} + +int cts_tcs_set_game_mode(struct cts_device *cts_dev, u8 enable) +{ + struct cts_firmware_status *status = (struct cts_firmware_status *) + &cts_dev->rtdata.firmware_status; + int ret; + + cts_info("Set game enable:%d", enable); + + status->game = enable; + + ret = cts_tcs_write(cts_dev, CMD_SYS_STS_GAME_MODE_RW, + &enable, sizeof(enable)); + if (ret) { + cts_err("Set game failed!"); + } + + return ret; +} + +int cts_tcs_get_game_mode(struct cts_device *cts_dev, u8 *enabled) +{ + u8 buf[1] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_GAME_MODE_RW, buf, sizeof(buf)); + if (ret == 0) { + *enabled = buf[0]; + } + + return ret; +} + +int cts_tcs_get_has_int_data(struct cts_device *cts_dev, bool *has) +{ + u8 buf[1] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_DATA_CAPTURE_SUPPORT_RO, + buf, sizeof(buf)); + if (ret == 0) { + *has = !!buf[0]; + } + return ret; +} + +int cts_tcs_get_int_data_types(struct cts_device *cts_dev, u16 *type) +{ + u8 buf[2] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_DATA_CAPTURE_FUNC_MAP_RW, + buf, sizeof(buf)); + if (ret == 0) { + *type = buf[0] | (buf[1] << 8); + } + return ret; +} + +int cts_tcs_set_int_data_types(struct cts_device *cts_dev, u16 type) +{ + return cts_tcs_write(cts_dev, CMD_SYS_STS_DATA_CAPTURE_FUNC_MAP_RW, + (u8 *) &type, sizeof(type)); +} + +int cts_tcs_get_int_data_method(struct cts_device *cts_dev, u8 *method) +{ + u8 buf[1] = { 0 }; + int ret; + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_DATA_CAPTURE_EN_RW, + buf, sizeof(buf)); + if (ret == 0) { + *method = buf[0]; + } + return ret; +} + +int cts_tcs_set_int_data_method(struct cts_device *cts_dev, u8 method) +{ + return cts_tcs_write(cts_dev, CMD_SYS_STS_DATA_CAPTURE_EN_RW, + &method, sizeof(method)); +} + +int cts_tcs_set_proximity_mode(struct cts_device *cts_dev, u8 enable) +{ + struct cts_firmware_status *status = (struct cts_firmware_status *) + &cts_dev->rtdata.firmware_status; + int ret; + + cts_info("Set proximity enable:%d", enable); + status->proximity = enable; + + ret = cts_tcs_write(cts_dev, CMD_PARA_PROXI_EN_RW, &enable, 1); + if (ret != 0) { + cts_err("Set proximity failed!"); + } + + return ret; +} + +static int cts_tcs_set_knuckle_mode(struct cts_device *cts_dev, u8 enable) +{ + struct cts_firmware_status *status = (struct cts_firmware_status *) + &cts_dev->rtdata.firmware_status; + int ret; + + cts_info("Set knuckle enable:%d", enable); + status->knuckle = enable; + + ret = cts_tcs_write(cts_dev, CMD_PARA_KUNCKLE_RW, &enable, 1); + if (ret != 0) { + cts_err("Set knuckle failed!"); + } + + return ret; +} + +int cts_tcs_set_glove_mode(struct cts_device *cts_dev, u8 enable) +{ + struct cts_firmware_status *status = (struct cts_firmware_status *) + &cts_dev->rtdata.firmware_status; + int ret; + + cts_info("Set glove enable:%d", enable); + status->glove = enable; + + ret = cts_tcs_write(cts_dev, CMD_SYS_STS_HI_SENSE_EN_RW, &enable, 1); + if (ret != 0) { + cts_err("Set glove failed!"); + } + + return ret; +} + +static int cts_tcs_set_pocket_enable(struct cts_device *cts_dev, u8 enable) +{ + struct cts_firmware_status *status = (struct cts_firmware_status *) + &cts_dev->rtdata.firmware_status; + int ret; + + cts_info("Set pocket enable:%d", enable); + status->pocket = enable; + + ret = cts_tcs_write(cts_dev, CMD_SYS_STS_POCKET_MODE_EN_RW, &enable, 1); + if (ret != 0) { + cts_err("Set pocket failed!"); + } + + return ret; +} + +void cts_tcs_reinit_fw_status(struct cts_device *cts_dev) +{ + struct cts_firmware_status *status = (struct cts_firmware_status *) + &cts_dev->rtdata.firmware_status; + cts_tcs_set_charger_plug(cts_dev, status->charger); + cts_tcs_set_proximity_mode(cts_dev, status->proximity); + cts_tcs_set_earjack_plug(cts_dev, status->earjack); + cts_tcs_set_knuckle_mode(cts_dev, status->knuckle); + cts_tcs_set_glove_mode(cts_dev, status->glove); + cts_tcs_set_pocket_enable(cts_dev, status->pocket); + cts_tcs_set_game_mode(cts_dev, status->game); +} + + +int cts_tcs_set_product_en(struct cts_device *cts_dev, u8 enable) +{ + int ret; + ret = cts_tcs_write(cts_dev, CMD_SYS_STS_PRODUCTION_TEST_EN_RW, + &enable, sizeof(enable)); + if (ret) { + cts_err("Set product_en failed!"); + } + + return ret; +} + +int cts_tcs_wait_krang_stop(struct cts_device *cts_dev) +{ + uint8_t status = 1; + int i; + int ret; + + for (i = 0; i < 3; i++) { + ret = cts_tcs_set_krang_stop(cts_dev); + if (ret < 0) { + cts_err("Set krang stop failed!"); + mdelay(1); + continue; + } + + ret = cts_tcs_read(cts_dev, CMD_SYS_STS_KRANG_WORK_STS_RO, &status, sizeof(status)); + if (ret < 0) { + cts_err("Read krang stop status failed!"); + mdelay(1); + continue; + } + + if (0 == status) { + cts_info("krang flag was already stopped!"); + return 0; + } else { + cts_err("Read krang stop status:%d != 0", status); + mdelay(1); + continue; + } + } + + return -EBUSY; +} diff --git a/drivers/input/touchscreen/chipone_tddi/cts_tcs.h b/drivers/input/touchscreen/chipone_tddi/cts_tcs.h new file mode 100644 index 000000000000..c540a97101f7 --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_tcs.h @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef _CTS_TCS_H_ +#define _CTS_TCS_H_ + +int cts_tcs_read(struct cts_device *cts_dev, + u16 cmd, u8 *buf, size_t len); +int cts_tcs_write(struct cts_device *cts_dev, + u16 cmd, u8 *buf, size_t len); + +int cts_tcs_get_hwid_info(struct cts_device *cts_dev, u8 *info); +int cts_tcs_get_fw_ver(struct cts_device *cts_dev, u16 *fwver); +int cts_tcs_get_lib_ver(struct cts_device *cts_dev, u16 *libver); +int cts_tcs_get_ddi_ver(struct cts_device *cts_dev, u8 *ddiver); +int cts_tcs_get_res_x(struct cts_device *cts_dev, u16 *res_x); +int cts_tcs_get_res_y(struct cts_device *cts_dev, u16 *res_y); +int cts_tcs_get_rows(struct cts_device *cts_dev, u8 *rows); +int cts_tcs_get_cols(struct cts_device *cts_dev, u8 *cols); +int cts_tcs_get_rx_tx_info(struct cts_device *cts_dev, u8 *info); +int cts_tcs_get_panel_rx_tx_info(struct cts_device *cts_dev, u8 *info); +int cts_tcs_get_flip_x(struct cts_device *cts_dev, bool *flip_x); +int cts_tcs_get_flip_y(struct cts_device *cts_dev, bool *flip_y); +int cts_tcs_get_swap_axes(struct cts_device *cts_dev, bool *swap_axes); +int cts_tcs_clr_gstr_ready_flag(struct cts_device *cts_dev); + +int cts_tcs_get_int_mode(struct cts_device *cts_dev, u8 *int_mode); +int cts_tcs_get_int_keep_time(struct cts_device *cts_dev, + u16 *int_keep_time); +int cts_tcs_get_rawdata_target(struct cts_device *cts_dev, + u16 *rawdata_target); +int cts_tcs_get_esd_method(struct cts_device *cts_dev, u8 *esd_method); + +int cts_tcs_get_touchinfo(struct cts_device *cts_dev, + struct cts_device_touch_info *touch_info); +int cts_tcs_get_gestureinfo(struct cts_device *cts_dev, + struct cts_device_gesture_info *gesture_info); +int cts_tcs_get_touch_status(struct cts_device *cts_dev); +int cts_tcs_get_esd_protection(struct cts_device *cts_dev, + u8 *esd_protection); + +int cts_tcs_read_hw_reg(struct cts_device *cts_dev, u32 addr, + u8 *buf, size_t size); +int cts_tcs_write_hw_reg(struct cts_device *cts_dev, u32 addr, + u8 *buf, size_t size); +int cts_tcs_read_ddi_reg(struct cts_device *cts_dev, u32 addr, + u8 *regbuf, size_t size); +int cts_tcs_write_ddi_reg(struct cts_device *cts_dev, u32 addr, + u8 *regbuf, size_t size); +int cts_tcs_read_reg(struct cts_device *cts_dev, u32 addr, + u8 *buf, size_t size); +int cts_tcs_write_reg(struct cts_device *cts_dev, u32 addr, + u8 *buf, size_t size); + +int cts_tcs_get_fw_id(struct cts_device *cts_dev, u16 *fwid); +int cts_tcs_get_workmode(struct cts_device *cts_dev, u8 *workmode); +int cts_tcs_set_workmode(struct cts_device *cts_dev, u8 workmode); +int cts_tcs_set_openshort_mode(struct cts_device *cts_dev, u8 mode); +int cts_tcs_get_curr_mode(struct cts_device *cts_dev, u8 *currmode); +int cts_tcs_get_krang_current_workmode(struct cts_device *cts_dev, u8 *workmode); +int cts_tcs_set_krang_workmode(struct cts_device *cts_dev, u8 workmode); + +int cts_tcs_set_tx_vol(struct cts_device *cts_dev, u8 txvol); + +int cts_tcs_set_short_test_type(struct cts_device *cts_dev, u8 short_type); +int cts_tcs_set_openshort_enable(struct cts_device *cts_dev, u8 enable); +int cts_tcs_is_openshort_enabled(struct cts_device *cts_dev, u8 *enabled); + +int cts_tcs_set_esd_enable(struct cts_device *cts_dev, u8 enable); +int cts_tcs_set_cneg_enable(struct cts_device *cts_dev, u8 enable); +int cts_tcs_set_mnt_enable(struct cts_device *cts_dev, u8 enable); +int cts_tcs_is_display_on(struct cts_device *cts_dev, u8 *display_on); +int cts_tcs_set_display_on(struct cts_device *cts_dev, u8 display_on); +int cts_tcs_is_cneg_enabled(struct cts_device *cts_dev, u8 *enabled); +int cts_tcs_is_mnt_enabled(struct cts_device *cts_dev, u8 *enabled); +int cts_tcs_set_pwr_mode(struct cts_device *cts_dev, u8 pwr_mode); +int cts_tcs_get_has_int_data(struct cts_device *cts_dev, bool *has); +int cts_tcs_get_int_data_types(struct cts_device *cts_dev, u16 *type); +int cts_tcs_set_int_data_types(struct cts_device *cts_dev, u16 type); +int cts_tcs_get_int_data_method(struct cts_device *cts_dev, u8 *method); +int cts_tcs_set_int_data_method(struct cts_device *cts_dev, u8 method); +int cts_tcs_calc_int_data_size(struct cts_device *cts_dev); + +int cts_tcs_polling_data(struct cts_device *cts_dev,u8 *buf, size_t size); +int cts_polling_test_data(struct cts_device *cts_dev, + u8 *buf, size_t size, enum int_data_type type); + +int cts_tcs_tool_get_rawdata(struct cts_device *cts_dev, u8 *buf, u16 data_source); +int cts_tcs_tool_get_manual_diff(struct cts_device *cts_dev, u8 *buf, u16 data_source); +int cts_tcs_tool_get_real_diff(struct cts_device *cts_dev, u8 *buf, u16 data_source); +int cts_tcs_tool_get_basedata(struct cts_device *cts_dev, u8 *buf, u16 data_source); + +int cts_tcs_top_get_rawdata(struct cts_device *cts_dev, u8 *buf, size_t size, + u16 data_source); +int cts_tcs_top_get_manual_diff(struct cts_device *cts_dev, u8 *buf, + size_t size, u16 data_source); +int cts_tcs_top_get_real_diff(struct cts_device *cts_dev, u8 *buf, size_t size, + u16 data_source); +int cts_tcs_top_get_noise_diff(struct cts_device *cts_dev, u8 *buf,size_t size, + u16 data_source); +int cts_tcs_top_get_basedata(struct cts_device *cts_dev, u8 *buf, size_t size, + u16 data_source); +int cts_tcs_top_get_cnegdata(struct cts_device *cts_dev, u8 *buf, size_t size, + u16 data_source); +int cts_tcs_reset_device(struct cts_device *cts_dev); +int cts_tcs_set_int_test(struct cts_device *cts_dev, u8 enable); +int cts_tcs_set_int_pin(struct cts_device *cts_dev, u8 high); +int cts_tcs_get_module_id(struct cts_device *cts_dev, u32 *modId); + +int cts_tcs_tool_xtrans(struct cts_device *cts_dev, u8 *tx, size_t txlen, + u8 *rx, size_t rxlen); + +int cts_tcs_set_charger_plug(struct cts_device *cts_dev, u8 set); +int cts_tcs_get_charger_plug(struct cts_device *cts_dev, u8 *isset); +int cts_tcs_set_earjack_plug(struct cts_device *cts_dev, u8 set); +int cts_tcs_get_earjack_plug(struct cts_device *cts_dev, u8 *isset); +int cts_tcs_set_panel_direction(struct cts_device *cts_dev, u8 direction); +int cts_tcs_get_panel_direction(struct cts_device *cts_dev, u8 *direction); +int cts_tcs_set_game_mode(struct cts_device *cts_dev, u8 enable); +int cts_tcs_get_game_mode(struct cts_device *cts_dev, u8 *enabled); +int cts_tcs_set_proximity_mode(struct cts_device *cts_dev, u8 enable); +void cts_tcs_reinit_fw_status(struct cts_device *cts_dev); + +int cts_tcs_set_product_en(struct cts_device *cts_dev, u8 enable); +int cts_tcs_wait_krang_stop(struct cts_device *cts_dev); + +extern int cts_tcs_set_glove_mode(struct cts_device *cts_dev, u8 enable); +#endif /* _CTS_TCS_H_ */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_test.c b/drivers/input/touchscreen/chipone_tddi/cts_test.c new file mode 100644 index 000000000000..8f6f3e9935dc --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_test.c @@ -0,0 +1,2927 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define LOG_TAG "Test" + +#include "cts_config.h" +#include "cts_platform.h" +#include "cts_core.h" +#include "cts_strerror.h" +#include "cts_test.h" +#include "cts_tcs.h" + +#ifdef CTS_CONFIG_MKDIR_FOR_CTS_TEST +/* for ksys_mkdir/sys_mkdir */ +#include +#include +#endif /* CTS_CONFIG_MKDIR_FOR_CTS_TEST */ + +const char *cts_test_item_str(int test_item) +{ +#define case_test_item(item) \ + case CTS_TEST_ ## item: return #item "-TEST" + + switch (test_item) { + case_test_item(RESET_PIN); + case_test_item(INT_PIN); + case_test_item(RAWDATA); + case_test_item(NOISE); + case_test_item(OPEN); + case_test_item(SHORT); + case_test_item(COMPENSATE_CAP); + + default: + return "INVALID"; + } +#undef case_test_item +} + +#define CTS_TEST_SHORT (0x01) +#define CTS_TEST_OPEN (0x02) + +#define CTS_SHORT_TEST_UNDEFINED (0x00) +#define CTS_SHORT_TEST_BETWEEN_COLS (0x01) +#define CTS_SHORT_TEST_BETWEEN_ROWS (0x02) +#define CTS_SHORT_TEST_BETWEEN_GND (0x03) + +#define SHORT_COLS_TEST_LOOP (1) +#define SHORT_ROWS_TEST_LOOP (3) + + +#define RAWDATA_BUFFER_SIZE(cts_dev) \ + (cts_dev->hwdata->num_row * cts_dev->hwdata->num_col * 2) + +struct cts_fw_short_test_param { + u8 type; + u32 col_pattern[2]; + u32 row_pattern[2]; +}; + +int cts_write_file(struct file *filp, const void *data, size_t size) +{ +#ifdef CFG_CTS_FOR_GKI + cts_info("%s(): kernel_write is forbiddon with GKI Version!", __func__); + return -EPERM; +#else + loff_t pos; + ssize_t ret; + + pos = filp->f_pos; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) + ret = kernel_write(filp, data, size, &pos); +#else + ret = kernel_write(filp, data, size, pos); +#endif + + if (ret >= 0) { + filp->f_pos += ret; + } + + return ret; +#endif +} + +#ifdef CTS_CONFIG_MKDIR_FOR_CTS_TEST +/* copied from fs/namei.c */ +long do_mkdirat(int dfd, const char __user *pathname, umode_t mode) +{ + struct dentry *dentry; + struct path path; + int error; + unsigned int lookup_flags = LOOKUP_DIRECTORY; + +retry: + dentry = user_path_create(dfd, pathname, &path, lookup_flags); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + if (!IS_POSIXACL(path.dentry->d_inode)) + mode &= ~current_umask(); + error = security_path_mkdir(&path, dentry, mode); + if (!error) + error = vfs_mkdir(path.dentry->d_inode, dentry, mode); + done_path_create(&path, dentry); + if (retry_estale(error, lookup_flags)) { + lookup_flags |= LOOKUP_REVAL; + goto retry; + } + return error; +} + +/* Make directory for filepath + * If filepath = "/A/B/C/D.file", it will make dir /A/B/C recursive + * like userspace mkdir -p + */ +int cts_mkdir_for_file(const char *filepath, umode_t mode) +{ +#ifdef CFG_CTS_FOR_GKI + cts_info("%s(): some functions are forbiddon with GKI Version!", __func__); + return -EPERM; +#else + char *dirname = NULL; + int dirname_len; + char *s; + int ret; + mm_segment_t fs; + + if (filepath == NULL) { + cts_err("Create dir for file with filepath = NULL"); + return -EINVAL; + } + + if (filepath[0] == '\0' || filepath[0] != '/') { + cts_err("Create dir for file with invalid filepath[0]: %c", filepath[0]); + return -EINVAL; + } + + dirname_len = strrchr(filepath, '/') - filepath; + if (dirname_len == 0) { + cts_warn("Create dir for file '%s' in root dir", filepath); + return 0; + } + + dirname = kstrndup(filepath, dirname_len, GFP_KERNEL); + if (dirname == NULL) { + cts_err("Create dir alloc mem for dirname failed"); + return -ENOMEM; + } + + cts_info("Create dir '%s' for file '%s'", dirname, filepath); + + fs = get_fs(); + set_fs(KERNEL_DS); + + s = dirname + 1; /* Skip leading '/' */ + while (1) { + char c = '\0'; + + /* Bypass leading non-'/'s and then subsequent '/'s */ + while (*s) { + if (*s == '/') { + do { + ++s; + } while (*s == '/'); + c = *s; /* Save current char */ + *s = '\0'; /* and replace it with nul */ + break; + } + ++s; + } + + cts_dbg(" - Create dir '%s'", dirname); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) + ret = ksys_mkdir(dirname, 0777); +#else + ret = sys_mkdir(dirname, 0777); +#endif + if (ret < 0 && ret != -EEXIST) { + cts_info("Create dir '%s' failed %d(%s)", dirname, ret, + cts_strerror(ret)); + /* Remove any inserted nul from the path */ + *s = c; + break; + } + /* Reset ret to 0 if return -EEXIST */ + ret = 0; + + if (c) { + /* Remove any inserted nul from the path */ + *s = c; + } else { + break; + } + } + + set_fs(fs); + + if (dirname) { + kfree(dirname); + } + + return ret; +#endif +} +#endif /* CTS_CONFIG_MKDIR_FOR_CTS_TEST */ + +static struct file *cts_test_data_filp; +int cts_start_dump_test_data_to_file(const char *filepath, bool append_to_file) +{ + int ret; + +#define START_BANNER \ + ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" + + cts_info("Start dump test data to file '%s'", filepath); + +#ifdef CTS_CONFIG_MKDIR_FOR_CTS_TEST + ret = cts_mkdir_for_file(filepath, 0777); + if (ret) { + cts_err("Create dir for test data file failed %d", ret); + return ret; + } +#endif /* CTS_CONFIG_MKDIR_FOR_CTS_TEST */ + +#ifdef CFG_CTS_FOR_GKI + cts_info("%s(): filp_open is forbiddon with GKI Version!", __func__); + ret = -EPERM; + return ret; +#else + cts_test_data_filp = filp_open(filepath, + O_WRONLY | O_CREAT | (append_to_file ? O_APPEND : O_TRUNC), + S_IRUGO | S_IWUGO); + if (IS_ERR(cts_test_data_filp)) { + ret = PTR_ERR(cts_test_data_filp); + cts_test_data_filp = NULL; + cts_err("Open file '%p' for test data failed %d", cts_test_data_filp, ret); + return ret; + } + + cts_write_file(cts_test_data_filp, START_BANNER, strlen(START_BANNER)); + + return 0; +#endif +#undef START_BANNER +} + +void cts_stop_dump_test_data_to_file(void) +{ +#define END_BANNER \ + "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n" +#ifndef CFG_CTS_FOR_GKI + int r; +#endif + + cts_info("Stop dump test data to file"); + + if (cts_test_data_filp) { + cts_write_file(cts_test_data_filp, END_BANNER, strlen(END_BANNER)); +#ifndef CFG_CTS_FOR_GKI + r = filp_close(cts_test_data_filp, NULL); + if (r) { + cts_err("Close test data file failed %d", r); + } +#endif + cts_test_data_filp = NULL; + } else { + cts_warn("Stop dump tsdata to file with filp = NULL"); + } +#undef END_BANNER +} + +static void cts_dump_tsdata(struct cts_device *cts_dev, + const char *desc, const u16 *data, bool to_console) +{ +#define SPLIT_LINE_STR \ + "--------------------------------------------------------"\ + "--------------------------------------------------------" +#define ROW_NUM_FORMAT_STR "%2d | " +#define COL_NUM_FORMAT_STR "%-5u " +#define DATA_FORMAT_STR "%-5u " + + int r, c; + u32 max, min, sum, average; + int max_r, max_c, min_r, min_c; + char line_buf[512]; + int count = 0; + + max = min = data[0]; + sum = 0; + max_r = max_c = min_r = min_c = 0; + for (r = 0; r < cts_dev->fwdata.cols; r++) { + for (c = 0; c < cts_dev->fwdata.rows; c++) { + u16 val = data[r * cts_dev->fwdata.rows + c]; + + sum += val; + if (val > max) { + max = val; + max_r = c; + max_c = r; + } else if (val < min) { + min = val; + min_r = c; + min_c = r; + } + } + } + average = sum / (cts_dev->fwdata.rows * cts_dev->fwdata.cols); + + count = 0; + count += scnprintf(line_buf + count, sizeof(line_buf) - count, + " %s test data MIN: [%u][%u]=%u, MAX: [%u][%u]=%u, AVG=%u", + desc, min_r, min_c, min, max_r, max_c, max, average); + if (to_console) { + cts_info(SPLIT_LINE_STR); + cts_info("%s", line_buf); + cts_info(SPLIT_LINE_STR); + } + if (cts_test_data_filp) { + cts_write_file(cts_test_data_filp, SPLIT_LINE_STR, strlen(SPLIT_LINE_STR)); + cts_write_file(cts_test_data_filp, "\n", 1); + cts_write_file(cts_test_data_filp, line_buf, count); + cts_write_file(cts_test_data_filp, "\n", 1); + cts_write_file(cts_test_data_filp, SPLIT_LINE_STR, strlen(SPLIT_LINE_STR)); + cts_write_file(cts_test_data_filp, "\n", 1); + } + + count = 0; + count += scnprintf(line_buf + count, sizeof(line_buf) - count, " | "); + for (c = 0; c < cts_dev->fwdata.rows; c++) { + count += scnprintf(line_buf + count, sizeof(line_buf) - count, + COL_NUM_FORMAT_STR, c); + } + if (to_console) { + cts_info("%s", line_buf); + cts_info(SPLIT_LINE_STR); + } + if (cts_test_data_filp) { + cts_write_file(cts_test_data_filp, line_buf, count); + cts_write_file(cts_test_data_filp, "\n", 1); + cts_write_file(cts_test_data_filp, SPLIT_LINE_STR, strlen(SPLIT_LINE_STR)); + cts_write_file(cts_test_data_filp, "\n", 1); + } + + for (r = 0; r < cts_dev->fwdata.cols; r++) { + count = 0; + count += scnprintf(line_buf + count, sizeof(line_buf) - count, + ROW_NUM_FORMAT_STR, r); + for (c = 0; c < cts_dev->fwdata.rows; c++) { + count += scnprintf(line_buf + count, sizeof(line_buf) - count, + DATA_FORMAT_STR, data[r * cts_dev->fwdata.rows + c]); + } + if (to_console) { + cts_info("%s", line_buf); + } + if (cts_test_data_filp) { + cts_write_file(cts_test_data_filp, line_buf, count); + cts_write_file(cts_test_data_filp, "\n", 1); + } + } + if (to_console) { + cts_info(SPLIT_LINE_STR); + } + if (cts_test_data_filp) { + cts_write_file(cts_test_data_filp, SPLIT_LINE_STR, strlen(SPLIT_LINE_STR)); + cts_write_file(cts_test_data_filp, "\n", 1); + } +#undef SPLIT_LINE_STR +#undef ROW_NUM_FORMAT_STR +#undef COL_NUM_FORMAT_STR +#undef DATA_FORMAT_STR +} + + +static void cts_dump_stylus_data(struct cts_device *cts_dev, + const char *desc, const u16 *data, u8 px_rows, u8 px_cols, bool to_console) +{ +#define SPLIT_LINE_STR \ + "--------------------------------------------------------"\ + "--------------------------------------------------------" +#define ROW_NUM_FORMAT_STR "%2d | " +#define COL_NUM_FORMAT_STR "%-5u " +#define DATA_FORMAT_STR "%-5u " + + int r, c; + u32 max, min, sum, average; + int max_r, max_c, min_r, min_c; + char line_buf[512]; + int count = 0; + + max = min = data[0]; + sum = 0; + max_r = max_c = min_r = min_c = 0; + for (r = 0; r < px_rows; r++) { + for (c = 0; c < px_cols; c++) { + u16 val = data[r * px_cols + c]; + + sum += val; + if (val > max) { + max = val; + max_r = c; + max_c = r; + } else if (val < min) { + min = val; + min_r = c; + min_c = r; + } + } + } + average = sum / (px_rows * px_cols); + + count = 0; + count += scnprintf(line_buf + count, sizeof(line_buf) - count, + " %s test data MIN: [%u][%u]=%u, MAX: [%u][%u]=%u, AVG=%u", + desc, min_r, min_c, min, max_r, max_c, max, average); + if (to_console) { + cts_info(SPLIT_LINE_STR); + cts_info("%s", line_buf); + cts_info(SPLIT_LINE_STR); + } + if (cts_test_data_filp) { + cts_write_file(cts_test_data_filp, SPLIT_LINE_STR, strlen(SPLIT_LINE_STR)); + cts_write_file(cts_test_data_filp, "\n", 1); + cts_write_file(cts_test_data_filp, line_buf, count); + cts_write_file(cts_test_data_filp, "\n", 1); + cts_write_file(cts_test_data_filp, SPLIT_LINE_STR, strlen(SPLIT_LINE_STR)); + cts_write_file(cts_test_data_filp, "\n", 1); + } + + count = 0; + count += scnprintf(line_buf + count, sizeof(line_buf) - count, " | "); + for (c = 0; c < px_cols; c++) { + count += scnprintf(line_buf + count, sizeof(line_buf) - count, + COL_NUM_FORMAT_STR, c); + } + if (to_console) { + cts_info("%s", line_buf); + cts_info(SPLIT_LINE_STR); + } + if (cts_test_data_filp) { + cts_write_file(cts_test_data_filp, line_buf, count); + cts_write_file(cts_test_data_filp, "\n", 1); + cts_write_file(cts_test_data_filp, SPLIT_LINE_STR, strlen(SPLIT_LINE_STR)); + cts_write_file(cts_test_data_filp, "\n", 1); + } + + for (r = 0; r < px_rows; r++) { + count = 0; + count += scnprintf(line_buf + count, sizeof(line_buf) - count, + ROW_NUM_FORMAT_STR, r); + for (c = 0; c < px_cols; c++) { + count += scnprintf(line_buf + count, sizeof(line_buf) - count, + DATA_FORMAT_STR, data[r * px_cols + c]); + } + if (to_console) { + cts_info("%s", line_buf); + } + if (cts_test_data_filp) { + cts_write_file(cts_test_data_filp, line_buf, count); + cts_write_file(cts_test_data_filp, "\n", 1); + } + } + if (to_console) { + cts_info(SPLIT_LINE_STR); + } + if (cts_test_data_filp) { + cts_write_file(cts_test_data_filp, SPLIT_LINE_STR, strlen(SPLIT_LINE_STR)); + cts_write_file(cts_test_data_filp, "\n", 1); + } +#undef SPLIT_LINE_STR +#undef ROW_NUM_FORMAT_STR +#undef COL_NUM_FORMAT_STR +#undef DATA_FORMAT_STR +} + + +static bool is_invalid_node(u32 *invalid_nodes, u32 num_invalid_nodes, + u16 row, u16 col) +{ + int i; + + for (i = 0; i < num_invalid_nodes; i++) { + if (MAKE_INVALID_NODE(row, col) == invalid_nodes[i]) { + return true; + } + } + + return false; +} + +/* Return number of failed nodes */ +static int validate_tsdata(struct cts_device *cts_dev, const char *desc, u16 *data, + u32 *invalid_nodes, u32 num_invalid_nodes, bool per_node, int *min, int *max) +{ +#define SPLIT_LINE_STR \ + "------------------------------" + + int r, c; + int failed_cnt = 0; + + cts_info("%s validate data: %s, num invalid node: %u, thresh[0]=[%d, %d]", + desc, per_node ? "Per-Node" : "Uniform-Threshold", + num_invalid_nodes, min ? min[0] : INT_MIN, max ? max[0] : INT_MAX); + + for (r = 0; r < cts_dev->fwdata.rows; r++) { + for (c = 0; c < cts_dev->fwdata.cols; c++) { + int offset = r * cts_dev->fwdata.cols + c; + + if (num_invalid_nodes && + is_invalid_node(invalid_nodes, num_invalid_nodes, r, c)) { + continue; + } + + if ((min != NULL && data[offset] < min[per_node ? offset : 0]) || + (max != NULL && data[offset] > max[per_node ? offset : 0])) { + if (failed_cnt == 0) { + cts_info(SPLIT_LINE_STR); + cts_info("%s failed nodes:", desc); + } + failed_cnt++; + + cts_info(" %3d: [%-2d][%-2d] = %u", failed_cnt, r, c, data[offset]); + } + } + } + + if (failed_cnt) { + cts_info(SPLIT_LINE_STR); + cts_info("%s test %d node total failed", desc, failed_cnt); + } + + return failed_cnt; + +#undef SPLIT_LINE_STR +} + +/* Return number of failed nodes */ +static int validate_stylus_pc_data(struct cts_device *cts_dev, const char *desc, u16 *data, + u32 *invalid_nodes, u32 num_invalid_nodes, bool per_node, int *min, int *max) +{ +#define SPLIT_LINE_STR \ + "------------------------------" + u8 pc_rows_used = cts_dev->fwdata.pc_rows_used; + u8 pc_cols_used = cts_dev->fwdata.pc_cols_used; + u8 pen_freq_num = cts_dev->fwdata.pen_freq_num; + + int r, c; + int failed_cnt = 0; + + cts_info("%s validate data: %s, num invalid node: %u, thresh[0]=[%d, %d]", + desc, per_node ? "Per-Node" : "Uniform-Threshold", + num_invalid_nodes, min ? min[0] : INT_MIN, max ? max[0] : INT_MAX); + + for (r = 0; r < pc_rows_used; r++) { + for (c = 0; c < pc_cols_used * pen_freq_num; c++) { + int offset = r * pc_cols_used * pen_freq_num + c; + if (num_invalid_nodes && + is_invalid_node(invalid_nodes, num_invalid_nodes, r, c)) { + continue; + } + + if ((min != NULL && data[offset] < min[per_node ? offset : 0]) || + (max != NULL && data[offset] > max[per_node ? offset : 0])) { + if (failed_cnt == 0) { + cts_info(SPLIT_LINE_STR); + cts_info("%s failed nodes:", desc); + } + failed_cnt++; + + cts_info(" %3d: [%-2d][%-2d] = %u", failed_cnt, r, c, data[offset]); + } + } + } + + if (failed_cnt) { + cts_info(SPLIT_LINE_STR); + cts_info("%s test %d node total failed", desc, failed_cnt); + } + + return failed_cnt; + +#undef SPLIT_LINE_STR +} + + +/* Return number of failed nodes */ +static int validate_stylus_pr_data(struct cts_device *cts_dev, const char *desc, u16 *data, + u32 *invalid_nodes, u32 num_invalid_nodes, bool per_node, int *min, int *max) +{ +#define SPLIT_LINE_STR \ + "------------------------------" + u8 pr_rows_used = cts_dev->fwdata.pr_rows_used; + u8 pr_cols_used = cts_dev->fwdata.pr_cols_used; + u8 pen_freq_num = cts_dev->fwdata.pen_freq_num; + + int r, c; + int failed_cnt = 0; + + cts_info("%s validate data: %s, num invalid node: %u, thresh[0]=[%d, %d]", + desc, per_node ? "Per-Node" : "Uniform-Threshold", + num_invalid_nodes, min ? min[0] : INT_MIN, max ? max[0] : INT_MAX); + + for (r = 0; r < pr_rows_used * pen_freq_num; r++) { + for (c = 0; c < pr_cols_used; c++) { + int offset = r * pr_cols_used + c; + if (num_invalid_nodes && + is_invalid_node(invalid_nodes, num_invalid_nodes, r, c)) { + continue; + } + + if ((min != NULL && data[offset] < min[per_node ? offset : 0]) || + (max != NULL && data[offset] > max[per_node ? offset : 0])) { + if (failed_cnt == 0) { + cts_info(SPLIT_LINE_STR); + cts_info("%s failed nodes:", desc); + } + failed_cnt++; + + cts_info(" %3d: [%-2d][%-2d] = %u", failed_cnt, r, c, data[offset]); + } + } + } + + if (failed_cnt) { + cts_info(SPLIT_LINE_STR); + cts_info("%s test %d node total failed", desc, failed_cnt); + } + + return failed_cnt; + +#undef SPLIT_LINE_STR +} + +#ifdef CFG_CTS_HAS_RESET_PIN +int cts_test_reset_pin(struct cts_device *cts_dev, struct cts_test_param *param) +{ + ktime_t start_time, end_time, delta_time; + int ret; + + if (cts_dev == NULL || param == NULL) { + cts_err("Reset-pin test with invalid param: cts_dev: %p test param: %p", + cts_dev, param); + return -EINVAL; + } + + cts_info("Reset-Pin test, flags: 0x%08x, drive log file: '%s' buf size: %d", + param->flags, param->driver_log_filepath, param->driver_log_buf_size); + + start_time = ktime_get(); + + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + goto show_test_result; + } + + cts_lock_device(cts_dev); + + cts_plat_set_reset(cts_dev->pdata, 0); + + mdelay(50); + + if (cts_plat_is_normal_mode(cts_dev->pdata)) { + ret = -EIO; + cts_err("Device is alive while reset is low"); + } + cts_plat_set_reset(cts_dev->pdata, 1); + mdelay(120); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + } + + if (!cts_plat_is_normal_mode(cts_dev->pdata)) { + ret = -EIO; + cts_err("Device is offline while reset is high"); + } + + cts_unlock_device(cts_dev); + cts_start_device(cts_dev); + + if (!cts_dev->rtdata.program_mode) { + cts_set_normal_addr(cts_dev); + } + +show_test_result: + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + if (ret) { + cts_info("Reset-Pin test FAIL %d(%s), ELAPSED TIME: %lldms", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + cts_info("Reset-Pin test PASS, ELAPSED TIME: %lldms", + ktime_to_ms(delta_time)); + } + + return ret; +} +#endif + +int cts_test_int_pin(struct cts_device *cts_dev, struct cts_test_param *param) +{ + ktime_t start_time, end_time, delta_time; + int ret; + + if (cts_dev == NULL || param == NULL) { + cts_err("Int-pin test with invalid param: cts_dev: %p test param: %p", + cts_dev, param); + return -EINVAL; + } + + cts_info("Int-Pin test, flags: 0x%08x, drive log file: '%s' buf size: %d", + param->flags, param->driver_log_filepath, param->driver_log_buf_size); + + start_time = ktime_get(); + + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + goto show_test_result; + } + + cts_lock_device(cts_dev); + + ret = cts_tcs_set_int_test(cts_dev, 1); + if (ret) { + cts_err("Enable Int Test failed"); + goto unlock_device; + } + + ret = cts_tcs_set_int_pin(cts_dev, 1); + if (ret) { + cts_err("Enable Int Test High failed"); + goto exit_int_test; + } + + mdelay(10); + + if (cts_plat_get_int_pin(cts_dev->pdata) == 0) { + cts_err("INT pin state != HIGH"); + ret = -EFAULT; + goto exit_int_test; + } + + ret = cts_tcs_set_int_pin(cts_dev, 0); + if (ret) { + cts_err("Enable Int Test LOW failed"); + goto exit_int_test; + } + + mdelay(10); + + if (cts_plat_get_int_pin(cts_dev->pdata) != 0) { + cts_err("INT pin state != LOW"); + ret = -EFAULT; + goto exit_int_test; + } + +exit_int_test: + { + int r = cts_tcs_set_int_test(cts_dev, 0); + if (r) { + cts_err("Disable Int Test failed %d", r); + } + } + mdelay(10); + +unlock_device: + cts_unlock_device(cts_dev); + cts_start_device(cts_dev); + +show_test_result: + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + if (ret) { + cts_info("Int-Pin test FAIL %d(%s), ELAPSED TIME: %lldms", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + cts_info("Int-Pin test PASS, ELAPSED TIME: %lldms", + ktime_to_ms(delta_time)); + } + + return ret; +} + +int cts_test_rawdata(struct cts_device *cts_dev, struct cts_test_param *param) +{ + struct cts_rawdata_test_priv_param *priv_param; + bool driver_validate_data = false; + bool validate_data_per_node = false; + bool stop_test_if_validate_fail = false; + bool dump_test_data_to_user = false; + bool dump_test_data_to_console = false; + bool dump_test_data_to_file = false; + int num_nodes; + int tsdata_frame_size; + int frame; + int fail_frame = 0; + u16 *rawdata = NULL; + ktime_t start_time, end_time, delta_time; + int i; + int ret; + + if (cts_dev == NULL || param == NULL) { + cts_err("Rawdata test with dev or null param"); + return -EINVAL; + } + + if (param->priv_param_size != sizeof(*priv_param) || + param->priv_param == NULL) { + cts_err("Rawdata test with invalid param: priv param: %p size: %d", + param->priv_param, param->priv_param_size); + return -EINVAL; + } + + priv_param = param->priv_param; + if (priv_param->frames <= 0) { + cts_info("Rawdata test with too little frame %u", priv_param->frames); + return -EINVAL; + } + + num_nodes = cts_dev->fwdata.rows * cts_dev->fwdata.cols; + tsdata_frame_size = 2 * cts_dev->hwdata->num_row * cts_dev->hwdata->num_col; + + driver_validate_data = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_DATA); + validate_data_per_node = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_PER_NODE); + dump_test_data_to_user = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE); + dump_test_data_to_console = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE); + dump_test_data_to_file = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE); + stop_test_if_validate_fail = + !!(param->flags & CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED); + + cts_info("Rawdata test, flags: 0x%08x, frames: %d, num invalid node: %u, " + "test data file: '%s' buf size: %d, drive log file: '%s' buf size: %d", + param->flags, priv_param->frames, param->num_invalid_node, + param->test_data_filepath, param->test_data_buf_size, + param->driver_log_filepath, param->driver_log_buf_size); + + start_time = ktime_get(); + + if (dump_test_data_to_user) { + rawdata = (u16 *) param->test_data_buf; + } else { + rawdata = (u16 *) kmalloc(tsdata_frame_size, GFP_KERNEL); + if (rawdata == NULL) { + cts_err("Allocate memory for rawdata failed"); + ret = -ENOMEM; + goto show_test_result; + } + } + + /* Stop device to avoid un-wanted interrrupt */ + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + goto free_mem; + } + + if (dump_test_data_to_file) { + int r = cts_start_dump_test_data_to_file(param->test_data_filepath, + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE_APPEND)); + if (r) { + cts_err("Start dump test data to file failed %d", r); + } + } + + cts_lock_device(cts_dev); + + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + goto unlock; + } + + ret = cts_tcs_set_product_en(cts_dev, 1); + if (ret) { + cts_err("Set product en failed %d", ret); + goto unlock; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Set firmware work mode to WORK_MODE_CONFIG failed %d", ret); + goto unlock; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Wait CTS_KRANG_CFG_MODE failed %d", ret); + goto unlock; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_FIN_DBG_MODE); + if (ret) { + cts_err("Set CTS_KRANG_FIN_DBG_MODE failed %d", ret); + goto unlock; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_FIN_DBG_MODE); + if (ret) { + cts_err("Wait CTS_KRANG_FIN_DBG_MODE failed %d", ret); + goto unlock; + } + + ret = cts_set_int_data_types(cts_dev, INT_DATA_TYPE_RAWDATA); + if (ret) { + cts_err("Set INT_DATA_TYPE_RAWDATA failed %d", ret); + goto unlock; + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_POLLING); + if (ret) { + cts_err("Set INT_DATA_METHOD_POLLING failed %d", ret); + goto unlock; + } + + for (frame = 0; frame < priv_param->frames; frame++) { + bool data_valid = false; + + for (i = 0; i < 3; i++) { + ret = cts_polling_test_data(cts_dev, (u8 *)rawdata, + RAWDATA_BUFFER_SIZE(cts_dev), INT_DATA_TYPE_RAWDATA); + if (ret < 0) { + cts_err("Get raw data failed: %d", ret); + mdelay(30); + } else { + data_valid = true; + break; + } + } + + if (!data_valid) { + ret = -EIO; + break; + } + + if (dump_test_data_to_user) { + *param->test_data_wr_size += 2 * num_nodes; + } + + if (dump_test_data_to_console || dump_test_data_to_file) { + cts_dump_tsdata(cts_dev, "Rawdata", rawdata, dump_test_data_to_console); + } + + if (driver_validate_data) { + ret = validate_tsdata(cts_dev, "Rawdata", rawdata, param->invalid_nodes, + param->num_invalid_node, validate_data_per_node, param->min, param->max); + if (ret) { + cts_err("Rawdata test failed %d", ret); + fail_frame++; + cts_err("Rawdata test has %d nodes failed", ret); + if (stop_test_if_validate_fail) { + break; + } + } + } + + if (dump_test_data_to_user) { + rawdata += num_nodes; + } + } + + if (dump_test_data_to_file) { + cts_stop_dump_test_data_to_file(); + } + +unlock: + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + } + + ret = cts_set_int_data_types(cts_dev, INT_DATA_TYPE_NONE); + if (ret) { + cts_err("Set INT_DATA_TYPE_NONE failed %d", ret); + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_NONE); + if (ret) { + cts_err("Set INT_DATA_METHOD_NONE failed %d", ret); + } + + cts_unlock_device(cts_dev); + + cts_start_device(cts_dev); + +free_mem: + if (!dump_test_data_to_user && rawdata != NULL) { + kfree(rawdata); + } + +show_test_result: + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + + if (ret) { + cts_info("Rawdata test FAIL %d(%s), ELAPSED TIME: %lldms", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else if (fail_frame > 0) { + cts_info("Rawdata test has %d frame(s) FAIL, ELAPSED TIME: %lldms", + fail_frame, ktime_to_ms(delta_time)); + } else if (fail_frame == 0) { + cts_info("Rawdata test PASS, ELAPSED TIME: %lldms", + ktime_to_ms(delta_time)); + } + + return (ret ? ret : (fail_frame ? fail_frame : 0)); +} + +int cts_test_noise(struct cts_device *cts_dev, struct cts_test_param *param) +{ + struct cts_noise_test_priv_param *priv_param; + bool driver_validate_data = false; + bool validate_data_per_node = false; + bool dump_test_data_to_user = false; + bool dump_test_data_to_console = false; + bool dump_test_data_to_file = false; + int num_nodes; + int tsdata_frame_size; + int frame; + u16 *buffer = NULL; + int buf_size = 0; + u16 *curr_rawdata = NULL; + u16 *max_rawdata = NULL; + u16 *min_rawdata = NULL; + u16 *noise = NULL; + bool first_frame = true; + ktime_t start_time, end_time, delta_time; + int i; + int ret; + + if (cts_dev == NULL || param == NULL) { + cts_err("Noise test with dev or null param"); + return -EINVAL; + } + + if (param->priv_param_size != sizeof(*priv_param) || + param->priv_param == NULL) { + cts_err("Noise test with invalid param: priv param: %p size: %d", + param->priv_param, param->priv_param_size); + return -EINVAL; + } + + priv_param = param->priv_param; + if (priv_param->frames < 2) { + cts_err("Noise test with too little frame %u", priv_param->frames); + return -EINVAL; + } + + num_nodes = cts_dev->fwdata.rows * cts_dev->fwdata.cols; + tsdata_frame_size = 2 * cts_dev->hwdata->num_row * cts_dev->hwdata->num_col; + + driver_validate_data = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_DATA); + validate_data_per_node = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_PER_NODE); + dump_test_data_to_user = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE); + dump_test_data_to_console = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE); + dump_test_data_to_file = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE); + + cts_info("Noise test, flags: 0x%08x, frames: %d, num invalid node: %u, " + "test data file: '%s' buf size: %d, drive log file: '%s' buf size: %d", + param->flags, priv_param->frames, param->num_invalid_node, + param->test_data_filepath, param->test_data_buf_size, + param->driver_log_filepath, param->driver_log_buf_size); + + start_time = ktime_get(); + + buf_size = (driver_validate_data ? 4 : 1) * tsdata_frame_size; + buffer = (u16 *) kmalloc(buf_size, GFP_KERNEL); + if (buffer == NULL) { + cts_err("Alloc mem for touch data failed"); + ret = -ENOMEM; + goto show_test_result; + } + + curr_rawdata = buffer; + if (driver_validate_data) { + noise = curr_rawdata + 1 * num_nodes; + min_rawdata = curr_rawdata + 2 * num_nodes; + max_rawdata = curr_rawdata + 3 * num_nodes; + } + + /* Stop device to avoid un-wanted interrrupt */ + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + goto free_mem; + } + + if (dump_test_data_to_file) { + int r = cts_start_dump_test_data_to_file(param->test_data_filepath, + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE_APPEND)); + if (r) { + cts_err("Start dump test data to file failed %d", r); + } + } + + cts_lock_device(cts_dev); + + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + goto unlock; + } + + ret = cts_tcs_set_product_en(cts_dev, 1); + if (ret) { + cts_err("Set product en failed %d", ret); + goto unlock; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Set firmware work mode to WORK_MODE_CONFIG failed %d", ret); + goto unlock; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Wait CTS_KRANG_CFG_MODE failed %d", ret); + goto unlock; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_FIN_DBG_MODE); + if (ret) { + cts_err("Set CTS_KRANG_FIN_DBG_MODE failed %d", ret); + goto unlock; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_FIN_DBG_MODE); + if (ret) { + cts_err("Wait CTS_KRANG_FIN_DBG_MODE failed %d", ret); + goto unlock; + } + + ret = cts_set_int_data_types(cts_dev, INT_DATA_TYPE_RAWDATA); + if (ret) { + cts_err("Set INT_DATA_TYPE_RAWDATA failed %d", ret); + goto unlock; + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_POLLING); + if (ret) { + cts_err("Set INT_DATA_METHOD_POLLING failed %d", ret); + goto unlock; + } + + for (frame = 0; frame < priv_param->frames; frame++) { + for (i = 0; i < 3; i++) { + ret = cts_polling_test_data(cts_dev, (u8 *)curr_rawdata, + RAWDATA_BUFFER_SIZE(cts_dev), INT_DATA_TYPE_RAWDATA); + if (ret < 0) { + cts_err("Get raw data failed: %d", ret); + mdelay(30); + } else { + break; + } + } + + if (i >= 3) { + cts_err("Read rawdata failed"); + ret = -EIO; + goto disable_get_tsdata; + } + + if (dump_test_data_to_console || dump_test_data_to_file) { + cts_dump_tsdata(cts_dev, "Noise-rawdata", curr_rawdata, + dump_test_data_to_console); + } + + if (dump_test_data_to_user) { + memcpy(param->test_data_buf + frame * 2 * num_nodes, + curr_rawdata, 2 * num_nodes); + *param->test_data_wr_size += 2 * num_nodes; + } + + if (driver_validate_data) { + if (unlikely(first_frame)) { + memcpy(min_rawdata, curr_rawdata, 2 * num_nodes); + memcpy(max_rawdata, curr_rawdata, 2 * num_nodes); + first_frame = false; + } else { + for (i = 0; i < num_nodes; i++) { + if (curr_rawdata[i] > max_rawdata[i]) { + max_rawdata[i] = curr_rawdata[i]; + } else if (curr_rawdata[i] < min_rawdata[i]) { + min_rawdata[i] = curr_rawdata[i]; + } + } + } + } + } + + if (driver_validate_data) { + for (i = 0; i < num_nodes; i++) { + noise[i] = max_rawdata[i] - min_rawdata[i]; + } + + if (dump_test_data_to_user) { + memcpy(param->test_data_buf + (priv_param->frames + 0) * 2 * num_nodes, + noise, 2 * num_nodes); + *param->test_data_wr_size += 2 * num_nodes; + memcpy(param->test_data_buf + (priv_param->frames + 1) * 2 * num_nodes, + min_rawdata, 2 * num_nodes); + *param->test_data_wr_size += 2 * num_nodes; + memcpy(param->test_data_buf + (priv_param->frames + 2) * 2 * num_nodes, + max_rawdata, 2 * num_nodes); + *param->test_data_wr_size += 2 * num_nodes; + } + + if (dump_test_data_to_console || dump_test_data_to_file) { + cts_dump_tsdata(cts_dev, "Noise", noise, + dump_test_data_to_console); + cts_dump_tsdata(cts_dev, "Rawdata MIN", min_rawdata, + dump_test_data_to_console); + cts_dump_tsdata(cts_dev, "Rawdata MAX", max_rawdata, + dump_test_data_to_console); + } + + ret = validate_tsdata(cts_dev, "Noise test", noise, param->invalid_nodes, + param->num_invalid_node, validate_data_per_node, param->min, param->max); + } + +disable_get_tsdata: + if (dump_test_data_to_file) { + cts_stop_dump_test_data_to_file(); + } + +unlock: + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + } + + ret = cts_set_int_data_types(cts_dev, INT_DATA_TYPE_NONE); + if (ret) { + cts_err("Set INT_DATA_TYPE_NONE failed %d", ret); + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_NONE); + if (ret) { + cts_err("Set INT_DATA_METHOD_NONE failed %d", ret); + } + + cts_unlock_device(cts_dev); + + cts_start_device(cts_dev); + +free_mem: + if (buffer != NULL) { + kfree(buffer); + } + +show_test_result: + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + if (ret > 0) { + cts_info("Noise test has %d nodes FAIL, ELAPSED TIME: %lldms", + ret, ktime_to_ms(delta_time)); + } else if (ret < 0) { + cts_info("Noise test FAIL %d(%s), ELAPSED TIME: %lldms", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + cts_info("Noise test PASS, ELAPSED TIME: %lldms", + ktime_to_ms(delta_time)); + } + + return ret; +} + +/* Return 0 success + negative value while error occurs + positive value means how many nodes fail */ +int cts_test_open(struct cts_device *cts_dev, struct cts_test_param *param) +{ + bool driver_validate_data = false; + bool validate_data_per_node = false; + bool dump_test_data_to_user = false; + bool dump_test_data_to_console = false; + bool dump_test_data_to_file = false; + int num_nodes; + int tsdata_frame_size; + int ret; + u16 *test_result = NULL; + ktime_t start_time, end_time, delta_time; + u8 old_int_data_method; + u16 old_int_data_types; + + if (cts_dev == NULL || param == NULL) { + cts_err("Open test with invalid param: cts_dev: %p test param: %p", + cts_dev, param); + return -EINVAL; + } + + old_int_data_method = cts_dev->fwdata.int_data_method; + old_int_data_types = cts_dev->fwdata.int_data_types; + + num_nodes = cts_dev->fwdata.rows * cts_dev->fwdata.cols; + tsdata_frame_size = 2 * cts_dev->hwdata->num_row * cts_dev->hwdata->num_col; + + driver_validate_data = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_DATA); + validate_data_per_node = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_PER_NODE); + dump_test_data_to_user = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE); + dump_test_data_to_console = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE); + dump_test_data_to_file = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE); + + cts_info("Open test, flags: 0x%08x, num invalid node: %u, " + "test data file: '%s' buf size: %d, drive log file: '%s' buf size: %d", + param->flags, param->num_invalid_node, + param->test_data_filepath, param->test_data_buf_size, + param->driver_log_filepath, param->driver_log_buf_size); + + start_time = ktime_get(); + + if (dump_test_data_to_user) { + test_result = (u16 *) param->test_data_buf; + } else { + test_result = (u16 *) kmalloc(tsdata_frame_size, GFP_KERNEL); + if (test_result == NULL) { + cts_err("Allocate memory for test result faild"); + ret = -ENOMEM; + goto show_test_result; + } + } + + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + goto free_mem; + } + + cts_lock_device(cts_dev); + + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_product_en(cts_dev, 1); + if (ret) { + cts_err("Set product en failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Set firmware work mode to WORK_MODE_CONFIG failed %d", ret); + goto unlock_device; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Wait CTS_CFG_MODE failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_openshort_mode(cts_dev, CTS_TEST_OPEN); + if (ret) { + cts_err("Set test type to OPEN_TEST failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_OPEN_SHORT_DET_MODE); + if (ret) { + cts_err("Set firmware work mode to WORK_MODE_TEST failed %d", ret); + goto unlock_device; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_OPEN_SHORT_DET_MODE); + if (ret) { + cts_err("wait_to_curr_mode failed %d", ret); + goto unlock_device; + } + + ret = cts_set_int_data_types(cts_dev, INT_DATA_TYPE_RAWDATA); + if (ret) { + cts_err("Set INT_DATA_TYPE_RAWDATA failed %d", ret); + goto unlock_device; + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_POLLING); + if (ret) { + cts_err("Set INT_DATA_METHOD_POLLING failed %d", ret); + goto unlock_device; + } + + ret = cts_polling_test_data(cts_dev, (u8 *)test_result, + RAWDATA_BUFFER_SIZE(cts_dev), INT_DATA_TYPE_RAWDATA); + if (ret) { + cts_err("Read test result failed %d", ret); + goto unlock_device; + } + + if (dump_test_data_to_user) { + *param->test_data_wr_size += 2 * num_nodes; + } + + if (dump_test_data_to_file) { + int r = cts_start_dump_test_data_to_file(param->test_data_filepath, + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE_APPEND)); + if (r) { + cts_err("Start dump test data to file failed %d", r); + } + } + + if (dump_test_data_to_console || dump_test_data_to_file) { + cts_dump_tsdata(cts_dev, "Open-circuit", test_result, + dump_test_data_to_console); + } + + if (dump_test_data_to_file) { + cts_stop_dump_test_data_to_file(); + } + + if (driver_validate_data) { + ret = validate_tsdata(cts_dev, "Open-circuit", test_result, param->invalid_nodes, + param->num_invalid_node, validate_data_per_node, param->min, param->max); + } + +unlock_device: + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + } + + ret = cts_set_int_data_types(cts_dev, INT_DATA_TYPE_NONE); + if (ret) { + cts_err("Set INT_DATA_TYPE_NONE failed %d", ret); + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_NONE); + if (ret) { + cts_err("Set INT_DATA_METHOD_NONE failed %d", ret); + } + + cts_unlock_device(cts_dev); + + cts_start_device(cts_dev); + +free_mem: + if (!dump_test_data_to_user && test_result) { + kfree(test_result); + } + +show_test_result: + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + if (ret > 0) { + cts_info("Open test has %d nodes FAIL, ELAPSED TIME: %lldms", + ret, ktime_to_ms(delta_time)); + } else if (ret < 0) { + cts_info("Open test FAIL %d(%s), ELAPSED TIME: %lldms", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + cts_info("Open test PASS, ELAPSED TIME: %lldms", + ktime_to_ms(delta_time)); + } + + return ret; +} + +/* Return 0 success + negative value while error occurs + positive value means how many nodes fail */ +int cts_test_short(struct cts_device *cts_dev, struct cts_test_param *param) +{ + bool driver_validate_data = false; + bool validate_data_per_node = false; + bool stop_if_failed = false; + bool dump_test_data_to_user = false; + bool dump_test_data_to_console = false; + bool dump_test_data_to_file = false; + int num_nodes; + int tsdata_frame_size; + int loopcnt; + int ret; + u16 *test_result = NULL; + ktime_t start_time, end_time, delta_time; + u8 old_int_data_method; + u16 old_int_data_types; + + if (cts_dev == NULL || param == NULL) { + cts_err("Short test with invalid param: cts_dev: %p test param: %p", + cts_dev, param); + return -EINVAL; + } + + old_int_data_method = cts_dev->fwdata.int_data_method; + old_int_data_types = cts_dev->fwdata.int_data_types; + + num_nodes = cts_dev->fwdata.rows * cts_dev->fwdata.cols; + tsdata_frame_size = 2 * cts_dev->hwdata->num_row * cts_dev->hwdata->num_col; + + driver_validate_data = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_DATA); + validate_data_per_node = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_PER_NODE); + dump_test_data_to_user = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE); + dump_test_data_to_console = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE); + dump_test_data_to_file = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE); + stop_if_failed = + !!(param->flags & CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED); + + cts_info("Short test, flags: 0x%08x, num invalid node: %u, " + "test data file: '%s' buf size: %d, drive log file: '%s' buf size: %d", + param->flags, param->num_invalid_node, + param->test_data_filepath, param->test_data_buf_size, + param->driver_log_filepath, param->driver_log_buf_size); + + start_time = ktime_get(); + + if (dump_test_data_to_user) { + test_result = (u16 *) param->test_data_buf; + } else { + test_result = (u16 *) kmalloc(tsdata_frame_size, GFP_KERNEL); + if (test_result == NULL) { + cts_err("Allocate test result buffer failed"); + ret = -ENOMEM; + goto show_test_result; + } + } + + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + goto err_free_test_result; + } + + cts_lock_device(cts_dev); + + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_product_en(cts_dev, 1); + if (ret) { + cts_err("Set product en failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Set firmware work mode to WORK_MODE_CONFIG failed %d", ret); + goto unlock_device; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Wait CTS_CFG_MODE failed %d", ret); + goto unlock_device; + } + + cts_info("Test short to GND"); + ret = cts_tcs_set_short_test_type(cts_dev, CTS_SHORT_TEST_UNDEFINED); + if (ret) { + cts_err("Set short test type to SHORT_TO_GND failed %d", ret); + goto unlock_device; + } + ret = cts_tcs_set_openshort_mode(cts_dev, CTS_TEST_SHORT); + if (ret) { + cts_err("Set test type to SHORT failed %d", ret); + goto unlock_device; + } + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_OPEN_SHORT_DET_MODE); + if (ret) { + cts_err("Set firmware work mode to WORK_MODE_TEST failed %d", ret); + goto unlock_device; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_OPEN_SHORT_DET_MODE); + if (ret) { + cts_err("wait_to_curr_mode failed %d", ret); + goto unlock_device; + } + + ret = cts_set_int_data_types(cts_dev, INT_DATA_TYPE_RAWDATA); + if (ret) { + cts_err("Set INT_DATA_TYPE_RAWDATA failed %d", ret); + goto unlock_device; + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_POLLING); + if (ret) { + cts_err("Set INT_DATA_METHOD_POLLING failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_short_test_type(cts_dev, CTS_SHORT_TEST_BETWEEN_GND); + if (ret) { + cts_err("Set short test type to SHORT_TO_GND failed %d", ret); + goto unlock_device; + } + + ret = cts_polling_test_data(cts_dev, (u8 *)test_result, + RAWDATA_BUFFER_SIZE(cts_dev), INT_DATA_TYPE_RAWDATA); + if (ret) { + cts_err("Read test result failed %d", ret); + goto unlock_device; + } + if (dump_test_data_to_user) { + *param->test_data_wr_size += 2 * num_nodes; + } + if (dump_test_data_to_file) { + int r = cts_start_dump_test_data_to_file(param->test_data_filepath, + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE_APPEND)); + if (r) { + cts_err("Start dump test data to file failed %d", r); + } + } + if (dump_test_data_to_console || dump_test_data_to_file) { + cts_dump_tsdata(cts_dev, "GND-short", test_result, + dump_test_data_to_console); + } + if (driver_validate_data) { + ret = validate_tsdata(cts_dev, "GND-short", test_result, param->invalid_nodes, + param->num_invalid_node, validate_data_per_node, param->min, param->max); + if (ret) { + cts_err("Short to GND test failed %d", ret); + if (stop_if_failed) { + goto stop_dump_test_data_to_file; + } + } + } + if (dump_test_data_to_user) { + test_result += num_nodes; + } + + /* Short between colums */ + cts_info("Test short between columns"); + ret = cts_tcs_set_short_test_type(cts_dev, CTS_SHORT_TEST_BETWEEN_COLS); + if (ret) { + cts_err("Set short test type to BETWEEN_COLS failed %d", ret); + goto stop_dump_test_data_to_file; + } + + for (loopcnt = 0; loopcnt < SHORT_COLS_TEST_LOOP; loopcnt++) { + ret = cts_polling_test_data(cts_dev, (u8 *)test_result, + RAWDATA_BUFFER_SIZE(cts_dev), INT_DATA_TYPE_RAWDATA); + if (ret) { + cts_err("Read test result failed %d", ret); + goto stop_dump_test_data_to_file; + } + + if (dump_test_data_to_user) { + *param->test_data_wr_size += 2 * num_nodes; + } + + if (dump_test_data_to_console || dump_test_data_to_file) { + cts_dump_tsdata(cts_dev, "Col-short", test_result, + dump_test_data_to_console); + } + + if (driver_validate_data) { + ret = validate_tsdata(cts_dev, "Col-short", test_result, param->invalid_nodes, + param->num_invalid_node, validate_data_per_node, param->min, param->max); + if (ret) { + cts_err("Short between columns test failed %d", ret); + if (stop_if_failed) { + goto stop_dump_test_data_to_file; + } + } + } + if (dump_test_data_to_user) { + test_result += num_nodes; + } + } + + /* Short between rows */ + cts_info("Test short between rows"); + ret = cts_tcs_set_short_test_type(cts_dev, CTS_SHORT_TEST_BETWEEN_ROWS); + if (ret) { + cts_err("Set short test type to BETWEEN_ROWS failed %d", ret); + goto stop_dump_test_data_to_file; + } + for (loopcnt = 0; loopcnt < SHORT_ROWS_TEST_LOOP; loopcnt++) { + ret = cts_polling_test_data(cts_dev, (u8 *)test_result, + RAWDATA_BUFFER_SIZE(cts_dev), INT_DATA_TYPE_RAWDATA); + if (ret) { + cts_err("Read test result failed %d", ret); + goto stop_dump_test_data_to_file; + } + if (dump_test_data_to_user) { + *param->test_data_wr_size += 2 * num_nodes; + } + if (dump_test_data_to_console || dump_test_data_to_file) { + cts_dump_tsdata(cts_dev, "Row-short", test_result, + dump_test_data_to_console); + } + if (driver_validate_data) { + ret = validate_tsdata(cts_dev, "Row-short", test_result, param->invalid_nodes, + param->num_invalid_node, validate_data_per_node, param->min, param->max); + if (ret) { + cts_err("Short between rows test failed %d", ret); + if (stop_if_failed) { + goto stop_dump_test_data_to_file; + } + } + } + if (dump_test_data_to_user) { + test_result += num_nodes; + } + } + +stop_dump_test_data_to_file: + if (dump_test_data_to_file) { + cts_stop_dump_test_data_to_file(); + } + +unlock_device: + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + } + + ret = cts_set_int_data_types(cts_dev, INT_DATA_TYPE_NONE); + if (ret) { + cts_err("Set INT_DATA_TYPE_NONE failed %d", ret); + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_NONE); + if (ret) { + cts_err("Set INT_DATA_METHOD_NONE failed %d", ret); + } + + cts_unlock_device(cts_dev); + + cts_start_device(cts_dev); + +err_free_test_result: + if (!dump_test_data_to_user && test_result) { + kfree(test_result); + } + +show_test_result: + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + if (ret > 0) { + cts_info("Short test has %d nodes FAIL, ELAPSED TIME: %lldms", + ret, ktime_to_ms(delta_time)); + } else if (ret < 0) { + cts_info("Short test FAIL %d(%s), ELAPSED TIME: %lldms", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + cts_info("Short test PASS, ELAPSED TIME: %lldms", + ktime_to_ms(delta_time)); + } + + return ret; +} + +static int validate_comp_cap(struct cts_device *cts_dev, const char *desc, u8 *cap, + u32 *invalid_nodes, u32 num_invalid_nodes, bool per_node, int *min, int *max) +{ +#define SPLIT_LINE_STR \ + "------------------------------" + + int r, c; + int failed_cnt = 0; + + cts_info("Validate %s data: %s, num invalid node: %u, thresh[0]=[%d, %d]", + desc, per_node ? "Per-Node" : "Uniform-Threshold", + num_invalid_nodes, min ? min[0] : INT_MIN, max ? max[0] : INT_MAX); + + for (r = 0; r < cts_dev->fwdata.rows; r++) { + for (c = 0; c < cts_dev->fwdata.cols; c++) { + int offset = r * cts_dev->fwdata.cols + c; + + if (num_invalid_nodes && + is_invalid_node(invalid_nodes, num_invalid_nodes, r, c)) { + continue; + } + + if ((min != NULL && cap[offset] < min[per_node ? offset : 0]) || + (max != NULL && cap[offset] > max[per_node ? offset : 0])) { + if (failed_cnt == 0) { + cts_info(SPLIT_LINE_STR); + cts_info("%s failed nodes:", desc); + } + failed_cnt++; + + cts_info(" %3d: [%-2d][%-2d] = %u", failed_cnt, r, c, cap[offset]); + } + } + } + + if (failed_cnt) { + cts_info(SPLIT_LINE_STR); + cts_info("%s test %d node total failed", desc, failed_cnt); + } + + return failed_cnt; + +#undef SPLIT_LINE_STR +} + +static void cts_dump_comp_cap(struct cts_device *cts_dev, u8 *cap, bool to_console) +{ +#define SPLIT_LINE_STR \ + "-----------------------------------------------------------------------------" +#define ROW_NUM_FORMAT_STR "%2d | " +#define COL_NUM_FORMAT_STR "%3u " +#define DATA_FORMAT_STR "%4d" + + int r, c; + u32 max, min, sum, average; + int max_r, max_c, min_r, min_c; + char line_buf[512]; + int count; + + max = min = cap[0]; + sum = 0; + max_r = max_c = min_r = min_c = 0; + for (r = 0; r < cts_dev->fwdata.cols; r++) { + for (c = 0; c < cts_dev->fwdata.rows; c++) { + u16 val = cap[r * cts_dev->fwdata.rows + c]; + + sum += val; + if (val > max) { + max = val; + max_r = r; + max_c = c; + } else if (val < min) { + min = val; + min_r = r; + min_c = c; + } + } + } + average = sum / (cts_dev->fwdata.rows * cts_dev->fwdata.cols); + + count = 0; + count += scnprintf(line_buf + count, sizeof(line_buf) - count, + " Compensate Cap MIN: [%u][%u]=%u, MAX: [%u][%u]=%u, AVG=%u", + min_r, min_c, min, max_r, max_c, max, average); + if (to_console) { + cts_info(SPLIT_LINE_STR); + cts_info("%s", line_buf); + cts_info(SPLIT_LINE_STR); + } + if (cts_test_data_filp) { + cts_write_file(cts_test_data_filp, SPLIT_LINE_STR, strlen(SPLIT_LINE_STR)); + cts_write_file(cts_test_data_filp, "\n", 1); + cts_write_file(cts_test_data_filp, line_buf, count); + cts_write_file(cts_test_data_filp, "\n", 1); + cts_write_file(cts_test_data_filp, SPLIT_LINE_STR, strlen(SPLIT_LINE_STR)); + cts_write_file(cts_test_data_filp, "\n", 1); + } + + count = 0; + count += scnprintf(line_buf + count, sizeof(line_buf) - count, " "); + for (c = 0; c < cts_dev->fwdata.rows; c++) { + count += scnprintf(line_buf + count, sizeof(line_buf) - count, + COL_NUM_FORMAT_STR, c); + } + if (to_console) { + cts_info("%s", line_buf); + cts_info(SPLIT_LINE_STR); + } + if (cts_test_data_filp) { + cts_write_file(cts_test_data_filp, line_buf, count); + cts_write_file(cts_test_data_filp, "\n", 1); + cts_write_file(cts_test_data_filp, SPLIT_LINE_STR, strlen(SPLIT_LINE_STR)); + cts_write_file(cts_test_data_filp, "\n", 1); + } + + for (r = 0; r < cts_dev->fwdata.cols; r++) { + count = 0; + count += scnprintf(line_buf + count, sizeof(line_buf) - count, + ROW_NUM_FORMAT_STR, r); + for (c = 0; c < cts_dev->fwdata.rows; c++) { + count += scnprintf(line_buf + count, sizeof(line_buf) - count, + DATA_FORMAT_STR, cap[r * cts_dev->fwdata.rows + c]); + } + if (to_console) { + cts_info("%s", line_buf); + } + if (cts_test_data_filp) { + cts_write_file(cts_test_data_filp, line_buf, count); + cts_write_file(cts_test_data_filp, "\n", 1); + } + } + + if (to_console) { + cts_info(SPLIT_LINE_STR); + } + if (cts_test_data_filp) { + cts_write_file(cts_test_data_filp, SPLIT_LINE_STR, strlen(SPLIT_LINE_STR)); + cts_write_file(cts_test_data_filp, "\n", 1); + } +#undef SPLIT_LINE_STR +#undef ROW_NUM_FORMAT_STR +#undef COL_NUM_FORMAT_STR +#undef DATA_FORMAT_STR +} + +int cts_test_compensate_cap(struct cts_device *cts_dev, + struct cts_test_param *param) +{ + bool driver_validate_data = false; + bool validate_data_per_node = false; + bool dump_test_data_to_user = false; + bool dump_test_data_to_console = false; + bool dump_test_data_to_file = false; + int num_nodes; + int tsdata_frame_size; + u8 *cap = NULL; + int ret = 0; + ktime_t start_time, end_time, delta_time; + + if (cts_dev == NULL || param == NULL) { + cts_err("Compensate cap test with invalid param: cts_dev: %p test param: %p", + cts_dev, param); + return -EINVAL; + } + + num_nodes = cts_dev->fwdata.rows * cts_dev->fwdata.cols; + tsdata_frame_size = cts_dev->hwdata->num_row * cts_dev->hwdata->num_col; + + driver_validate_data = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_DATA); + if (driver_validate_data) { + validate_data_per_node = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_PER_NODE); + } + dump_test_data_to_user = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE); + dump_test_data_to_console = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE); + dump_test_data_to_file = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE); + + cts_info("Compensate cap test, flags: 0x%08x num invalid node: %u, " + "test data file: '%s' buf size: %d, drive log file: '%s' buf size: %d", + param->flags, param->num_invalid_node, + param->test_data_filepath, param->test_data_buf_size, + param->driver_log_filepath, param->driver_log_buf_size); + + start_time = ktime_get(); + + if (dump_test_data_to_user) { + cap = (u8 *) param->test_data_buf; + } else { + cap = (u8 *) kzalloc(tsdata_frame_size, GFP_KERNEL); + if (cap == NULL) { + cts_err("Alloc mem for compensate cap failed"); + ret = -ENOMEM; + goto show_test_result; + } + } + + /* Stop device to avoid un-wanted interrrupt */ + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + goto free_mem; + } + + cts_lock_device(cts_dev); + + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_product_en(cts_dev, 1); + if (ret) { + cts_err("Set product en failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Set firmware work mode to WORK_MODE_CONFIG failed %d", ret); + goto unlock_device; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Wait CTS_KRANG_CFG_MODE failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_FIN_DBG_MODE); + if (ret) { + cts_err("Set CTS_KRANG_FIN_DBG_MODE failed %d", ret); + goto unlock_device; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_FIN_DBG_MODE); + if (ret) { + cts_err("Wait CTS_KRANG_FIN_DBG_MODE failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_top_get_cnegdata(cts_dev, cap, num_nodes, 0); + if (ret) { + cts_err("Get compensate cap failed %d", ret); + } + + if (dump_test_data_to_user) { + *param->test_data_wr_size = num_nodes; + } + + if (dump_test_data_to_file) { + int r = cts_start_dump_test_data_to_file(param->test_data_filepath, + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE_APPEND)); + if (r) { + cts_err("Start dump test data to file failed %d", r); + } + } + + if (dump_test_data_to_console || dump_test_data_to_file) { + cts_dump_comp_cap(cts_dev, cap, dump_test_data_to_console); + } + + if (dump_test_data_to_file) { + cts_stop_dump_test_data_to_file(); + } + + if (driver_validate_data) { + ret = validate_comp_cap(cts_dev, "Compensate-Cap", cap, param->invalid_nodes, + param->num_invalid_node, validate_data_per_node, param->min, param->max); + } + +unlock_device: + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + } + + ret = cts_set_int_data_types(cts_dev, INT_DATA_TYPE_NONE); + if (ret) { + cts_err("Set INT_DATA_TYPE_NONE failed %d", ret); + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_NONE); + if (ret) { + cts_err("Set INT_DATA_METHOD_NONE failed %d", ret); + } + + cts_unlock_device(cts_dev); + + cts_start_device(cts_dev); + +free_mem: + if (!dump_test_data_to_user && cap) { + kfree(cap); + } + +show_test_result: + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + if (ret > 0) { + cts_info("Compensate-Cap test has %d nodes FAIL, ELAPSED TIME: %lldms", + ret, ktime_to_ms(delta_time)); + } else if (ret < 0) { + cts_info("Compensate-Cap test FAIL %d(%s), ELAPSED TIME: %lldms", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + cts_info("Compensate-Cap test PASS, ELAPSED TIME: %lldms", + ktime_to_ms(delta_time)); + } + + return ret; +} + +int cts_test_stylus_rawdata(struct cts_device *cts_dev, + struct cts_test_param *param) +{ + struct cts_rawdata_test_priv_param *priv_param; + bool stop_test_if_validate_fail = false; + bool driver_validate_data = false; + bool validate_data_per_node = false; + bool dump_test_data_to_user = false; + bool dump_test_data_to_console = false; + bool dump_test_data_to_file = false; + int num_nodes; + int tsdata_frame_size; + int frame; + u16 *stylus_rawdata = NULL; + u16 data_type; + u8 pen_freq_num = cts_dev->fwdata.pen_freq_num; + u8 cascade_num = cts_dev->fwdata.cascade_num; + u8 pr_rows_used = cts_dev->fwdata.pr_rows_used; + u8 pr_cols_used = cts_dev->fwdata.pr_cols_used; + u8 pc_cols_used = cts_dev->fwdata.pc_cols_used; + u8 pc_rows_used = cts_dev->fwdata.pc_rows_used; + u8 pr_rows = cts_dev->fwdata.pr_rows; + u8 pr_cols = cts_dev->fwdata.pr_cols; + u8 pc_rows = cts_dev->fwdata.pc_rows; + u8 pc_cols = cts_dev->fwdata.pc_cols; + int pr_data_index = pc_cols_used * pc_rows_used * pen_freq_num; + int fail_frame = 0; + int i, ret = 0; + ktime_t start_time, end_time, delta_time; + + if (cts_dev == NULL || param == NULL) { + cts_err("stylus rawdata test with invalid param: cts_dev: %p test param: %p", + cts_dev, param); + return -EINVAL; + } + + if (param->priv_param_size != sizeof(*priv_param) || + param->priv_param == NULL) { + cts_err("Stylus rawdata test with invalid param: priv param: %p size: %d", + param->priv_param, param->priv_param_size); + return -EINVAL; + } + + priv_param = param->priv_param; + if (priv_param->frames <= 0) { + cts_info("Stylus rawdata test with too little frame %u", priv_param->frames); + return -EINVAL; + } + + num_nodes = (pr_rows_used * pr_cols_used + pc_cols_used * pc_rows_used) + * pen_freq_num; + tsdata_frame_size = (pr_rows * pr_cols + pc_rows * pc_cols) + * pen_freq_num * sizeof(u16) * cascade_num; + + driver_validate_data = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_DATA); + if (driver_validate_data) { + validate_data_per_node = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_PER_NODE); + } + dump_test_data_to_user = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE); + dump_test_data_to_console = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE); + dump_test_data_to_file = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE); + stop_test_if_validate_fail = + !!(param->flags & CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED); + + cts_info("Stylus rawdata test, flags: 0x%08x num invalid node: %u, " + "test data file: '%s' buf size: %d, drive log file: '%s' buf size: %d", + param->flags, param->num_invalid_node, + param->test_data_filepath, param->test_data_buf_size, + param->driver_log_filepath, param->driver_log_buf_size); + + start_time = ktime_get(); + + if (dump_test_data_to_user) { + stylus_rawdata = (u16 *) param->test_data_buf; + } else { + stylus_rawdata = (u16 *) kzalloc(tsdata_frame_size, GFP_KERNEL); + if (stylus_rawdata == NULL) { + cts_err("Alloc mem for stylus_rawdata failed"); + ret = -ENOMEM; + goto show_test_result; + } + } + + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + goto free_mem; + } + cts_lock_device(cts_dev); + + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_product_en(cts_dev, 1); + if (ret) { + cts_err("Set product en failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Set firmware work mode to WORK_MODE_CONFIG failed %d", ret); + goto unlock_device; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Wait CTS_CFG_MODE failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_STY_DBG_MODE); + if (ret) { + cts_err("Set krang CTS_KRANG_STY_DBG_MODE failed"); + goto unlock_device; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_STY_DBG_MODE); + if (ret) { + cts_err("Wait krang CTS_KRANG_STY_DBG_MODE failed"); + goto unlock_device; + } + + ret = cts_tcs_set_pwr_mode(cts_dev, CTS_PWR_MODE_ACTIVE); + if (ret) { + cts_err("Set PWR_MODE_ACTIVE failed"); + goto unlock_device; + } + + data_type = INT_DATA_TYPE_RAWDATA | BIT(14); + ret = cts_set_int_data_types(cts_dev, data_type); + if (ret) { + cts_err("Set data_type '0x%04x' failed", data_type); + goto unlock_device; + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_POLLING); + if (ret) { + cts_err("Set data_method polling failed"); + goto unlock_device; + } + + if (dump_test_data_to_file) { + int r = cts_start_dump_test_data_to_file(param->test_data_filepath, + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE_APPEND)); + if (r) { + cts_err("Start dump test data to file failed %d", r); + } + } + + for (frame = 0; frame < priv_param->frames; frame++) { + for (i = 0; i < 3; i++) { + ret = cts_polling_test_data(cts_dev, (u8 *)stylus_rawdata, + tsdata_frame_size, data_type); + if (ret < 0) { + cts_err("Get stylus rawdata failed: %d", ret); + mdelay(30); + } else { + break; + } + } + + if (i >= 3) { + cts_err("Read stylus rawdata failed"); + ret = -EIO; + goto disable_get_tsdata; + } + + if (dump_test_data_to_user) { + *param->test_data_wr_size += 2 * num_nodes; + } + + if (dump_test_data_to_console || dump_test_data_to_file) { + cts_dump_stylus_data(cts_dev, "Stylus-Rawdata-pc", stylus_rawdata, + pc_rows_used, pc_cols_used * pen_freq_num, dump_test_data_to_console); + cts_dump_stylus_data(cts_dev, "Stylus-Rawdata-pr", stylus_rawdata + pr_data_index, + pr_rows_used * pen_freq_num, pr_cols_used, dump_test_data_to_console); + } + + if (driver_validate_data) { + ret = validate_stylus_pc_data(cts_dev, "Stylus-Rawdata-pc", + stylus_rawdata, param->invalid_nodes, + param->num_invalid_node, validate_data_per_node, param->min, param->max); + ret += validate_stylus_pr_data(cts_dev, "Stylus-Rawdata-pr", + stylus_rawdata + pr_data_index, param->invalid_nodes, + param->num_invalid_node, validate_data_per_node, param->min, param->max); + if (ret) { + cts_err("Stylus rawdata test failed %d", ret); + fail_frame++; + cts_err("Stylus rawdata test has %d nodes failed", ret); + if (stop_test_if_validate_fail) { + break; + } + } + } + } + +disable_get_tsdata: + if (dump_test_data_to_file) { + cts_stop_dump_test_data_to_file(); + } + +unlock_device: + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + } + + ret = cts_set_int_data_types(cts_dev, INT_DATA_TYPE_NONE); + if (ret) { + cts_err("Set INT_DATA_TYPE_NONE failed %d", ret); + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_NONE); + if (ret) { + cts_err("Set INT_DATA_METHOD_NONE failed %d", ret); + } + + cts_unlock_device(cts_dev); + + cts_start_device(cts_dev); + +free_mem: + if (!dump_test_data_to_user && stylus_rawdata) { + kfree(stylus_rawdata); + } + +show_test_result: + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + if (ret > 0) { + cts_info("Stylus-rawdata test has %d nodes FAIL, ELAPSED TIME: %lldms", + ret, ktime_to_ms(delta_time)); + } else if (ret < 0) { + cts_info("Stylus-rawdata test FAIL %d(%s), ELAPSED TIME: %lldms", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + cts_info("Stylus-Noise test PASS, ELAPSED TIME: %lldms", + ktime_to_ms(delta_time)); + } + + return ret; +} + + +int cts_test_stylus_noise(struct cts_device *cts_dev, + struct cts_test_param *param) +{ + struct cts_noise_test_priv_param *priv_param; + bool driver_validate_data = false; + bool validate_data_per_node = false; + bool dump_test_data_to_user = false; + bool dump_test_data_to_console = false; + bool dump_test_data_to_file = false; + int num_nodes; + int tsdata_frame_size; + int frame; + bool first_frame = true; + u16 *stylus_rawdata = NULL; + u16 *max_stylus = NULL; + u16 *min_stylus = NULL; + u16 *noise_stylus = NULL; + u16 data_type; + u8 pen_freq_num = cts_dev->fwdata.pen_freq_num; + u8 cascade_num = cts_dev->fwdata.cascade_num; + u8 pr_rows_used = cts_dev->fwdata.pr_rows_used; + u8 pr_cols_used = cts_dev->fwdata.pr_cols_used; + u8 pc_cols_used = cts_dev->fwdata.pc_cols_used; + u8 pc_rows_used = cts_dev->fwdata.pc_rows_used; + u8 pr_rows = cts_dev->fwdata.pr_rows; + u8 pr_cols = cts_dev->fwdata.pr_cols; + u8 pc_rows = cts_dev->fwdata.pc_rows; + u8 pc_cols = cts_dev->fwdata.pc_cols; + int pr_data_index = pc_cols_used * pc_rows_used * pen_freq_num; + int i, ret = 0; + ktime_t start_time, end_time, delta_time; + + if (cts_dev == NULL || param == NULL) { + cts_err("stylus noise test with invalid param: cts_dev: %p test param: %p", + cts_dev, param); + return -EINVAL; + } + + if (param->priv_param_size != sizeof(*priv_param) || + param->priv_param == NULL) { + cts_err("Stylus noise test with invalid param: priv param: %p size: %d", + param->priv_param, param->priv_param_size); + return -EINVAL; + } + + priv_param = param->priv_param; + if (priv_param->frames <= 0) { + cts_info("Stylus noise test with too little frame %u", priv_param->frames); + return -EINVAL; + } + + cts_info("pc_rows=%d", pc_rows); + cts_info("pc_cols=%d", pc_cols); + cts_info("pr_rows=%d", pr_rows); + cts_info("pr_cols=%d", pr_cols); + + cts_info("pc_rows_used=%d", pc_rows_used); + cts_info("pc_cols_used=%d", pc_cols_used); + cts_info("pr_rows_used=%d", pr_rows_used); + cts_info("pr_cols_used=%d", pr_cols_used); + + cts_info("pen_freq_num=%d", pen_freq_num); + cts_info("cascade_num=%d", cascade_num); + + num_nodes = (pr_rows_used * pr_cols_used + pc_cols_used * pc_rows_used) * pen_freq_num; + tsdata_frame_size = (pr_rows * pr_cols + pc_rows * pc_cols) * pen_freq_num * sizeof(u16) * cascade_num; + + driver_validate_data = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_DATA); + if (driver_validate_data) { + validate_data_per_node = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_PER_NODE); + } + dump_test_data_to_user = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE); + dump_test_data_to_console = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE); + dump_test_data_to_file = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE); + + + cts_info("Stylus rawdata test, flags: 0x%08x num invalid node: %u, " + "test data file: '%s' buf size: %d, drive log file: '%s' buf size: %d", + param->flags, param->num_invalid_node, + param->test_data_filepath, param->test_data_buf_size, + param->driver_log_filepath, param->driver_log_buf_size); + + start_time = ktime_get(); + + if (dump_test_data_to_user) { + stylus_rawdata = (u16 *) param->test_data_buf; + } else { + stylus_rawdata = (u16 *) kzalloc(tsdata_frame_size * 4, GFP_KERNEL); + if (stylus_rawdata == NULL) { + cts_err("Alloc mem for stylus_rawdata failed"); + ret = -ENOMEM; + goto show_test_result; + } + } + + max_stylus = stylus_rawdata + 1 * tsdata_frame_size/sizeof(u16); + min_stylus = stylus_rawdata + 2 * tsdata_frame_size/sizeof(u16); + noise_stylus = stylus_rawdata + 3 * tsdata_frame_size/sizeof(u16); + + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + goto free_mem; + } + cts_lock_device(cts_dev); + + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_product_en(cts_dev, 1); + if (ret) { + cts_err("Set product en failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Set firmware work mode to WORK_MODE_CONFIG failed %d", ret); + goto unlock_device; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Wait CTS_CFG_MODE failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_STY_DBG_MODE); + if (ret) { + cts_err("Set krang CTS_KRANG_STY_DBG_MODE failed"); + goto unlock_device; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_STY_DBG_MODE); + if (ret) { + cts_err("Wait krang CTS_KRANG_STY_DBG_MODE failed"); + goto unlock_device; + } + + ret = cts_tcs_set_pwr_mode(cts_dev, CTS_PWR_MODE_ACTIVE); + if (ret) { + cts_err("Set PWR_MODE_ACTIVE failed"); + goto unlock_device; + } + + data_type = INT_DATA_TYPE_RAWDATA | BIT(14); + ret = cts_set_int_data_types(cts_dev, data_type); + if (ret) { + cts_err("Set data_type '0x%04x' failed", data_type); + goto unlock_device; + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_POLLING); + if (ret) { + cts_err("Set data_method polling failed"); + goto unlock_device; + } + + if (dump_test_data_to_file) { + int r = cts_start_dump_test_data_to_file(param->test_data_filepath, + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE_APPEND)); + if (r) { + cts_err("Start dump test data to file failed %d", r); + } + } + + for (frame = 0; frame < priv_param->frames; frame++) { + for (i = 0; i < 3; i++) { + ret = cts_polling_test_data(cts_dev, (u8 *)stylus_rawdata, + tsdata_frame_size, data_type); + if (ret < 0) { + cts_err("Get stylus rawdata failed: %d", ret); + mdelay(30); + } else { + break; + } + } + + if (i >= 3) { + cts_err("Read stylus rawdata failed"); + ret = -EIO; + goto disable_get_tsdata; + } + + if (dump_test_data_to_console || dump_test_data_to_file) { + cts_dump_stylus_data(cts_dev, "Stylus-Rawdata-pc", stylus_rawdata, + pc_rows_used, pc_cols_used * pen_freq_num, dump_test_data_to_console); + cts_dump_stylus_data(cts_dev, "Stylus-Rawdata-pr", stylus_rawdata + pr_data_index, + pr_rows_used * pen_freq_num, pr_cols_used, dump_test_data_to_console); + } + + if (dump_test_data_to_user) { + memcpy(param->test_data_buf + frame * 2 * num_nodes, + stylus_rawdata, 2 * num_nodes); + *param->test_data_wr_size += 2 * num_nodes; + } + + if (driver_validate_data) { + if (unlikely(first_frame)) { + memcpy(min_stylus, stylus_rawdata, 2 * num_nodes); + memcpy(max_stylus, stylus_rawdata, 2 * num_nodes); + first_frame = false; + } else { + for (i = 0; i < num_nodes; i++) { + if (stylus_rawdata[i] > max_stylus[i]) { + max_stylus[i] = stylus_rawdata[i]; + } + if (stylus_rawdata[i] < min_stylus[i]) { + min_stylus[i] = stylus_rawdata[i]; + } + } + } + } + } + + if (driver_validate_data) { + for (i = 0; i < num_nodes; i++) { + noise_stylus[i] = max_stylus[i] - min_stylus[i]; + } + + if (dump_test_data_to_user) { + memcpy(param->test_data_buf + (priv_param->frames + 0) * 2 * num_nodes, + noise_stylus, 2 * num_nodes); + *param->test_data_wr_size += 2 * num_nodes; + memcpy(param->test_data_buf + (priv_param->frames + 1) * 2 * num_nodes, + min_stylus, 2 * num_nodes); + *param->test_data_wr_size += 2 * num_nodes; + memcpy(param->test_data_buf + (priv_param->frames + 2) * 2 * num_nodes, + max_stylus, 2 * num_nodes); + *param->test_data_wr_size += 2 * num_nodes; + } + + if (dump_test_data_to_console || dump_test_data_to_file) { + cts_dump_stylus_data(cts_dev, "Stylus-Noise-pc", noise_stylus, + pc_rows_used, pc_cols_used * pen_freq_num, dump_test_data_to_console); + cts_dump_stylus_data(cts_dev, "Stylus-Noise-pr", noise_stylus + pr_data_index, + pr_rows_used * pen_freq_num, pr_cols_used, dump_test_data_to_console); + + cts_dump_stylus_data(cts_dev, "Stylus-MIN-pc", min_stylus, + pc_rows_used, pc_cols_used * pen_freq_num, dump_test_data_to_console); + cts_dump_stylus_data(cts_dev, "Stylus-MIN-pr", min_stylus + pr_data_index, + pr_rows_used * pen_freq_num, pr_cols_used, dump_test_data_to_console); + + cts_dump_stylus_data(cts_dev, "Stylus-MAX-pc", max_stylus, + pc_rows_used, pc_cols_used * pen_freq_num, dump_test_data_to_console); + cts_dump_stylus_data(cts_dev, "Stylus-MAX-pr", max_stylus + pr_data_index, + pr_rows_used * pen_freq_num, pr_cols_used, dump_test_data_to_console); + } + + ret = validate_stylus_pc_data(cts_dev, "Stylus-Noise-pc", + noise_stylus, param->invalid_nodes, + param->num_invalid_node, validate_data_per_node, param->min, param->max); + ret += validate_stylus_pr_data(cts_dev, "Stylus-Noise-pr", + noise_stylus + pr_data_index, param->invalid_nodes, + param->num_invalid_node, validate_data_per_node, param->min, param->max); + } + +disable_get_tsdata: + if (dump_test_data_to_file) { + cts_stop_dump_test_data_to_file(); + } + +unlock_device: + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + } + + ret = cts_set_int_data_types(cts_dev, INT_DATA_TYPE_NONE); + if (ret) { + cts_err("Set INT_DATA_TYPE_NONE failed %d", ret); + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_NONE); + if (ret) { + cts_err("Set INT_DATA_METHOD_NONE failed %d", ret); + } + + cts_unlock_device(cts_dev); + + cts_start_device(cts_dev); + +free_mem: + if (!dump_test_data_to_user && stylus_rawdata) { + kfree(stylus_rawdata); + } + +show_test_result: + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + if (ret > 0) { + cts_info("Stylus-Noise test has %d nodes FAIL, ELAPSED TIME: %lldms", + ret, ktime_to_ms(delta_time)); + } else if (ret < 0) { + cts_info("Stylus-Noise test FAIL %d(%s), ELAPSED TIME: %lldms", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + cts_info("Stylus-Noise test PASS, ELAPSED TIME: %lldms", + ktime_to_ms(delta_time)); + } + + return ret; +} + +int cts_test_stylus_mnt_rawdata(struct cts_device *cts_dev, + struct cts_test_param *param) +{ + struct cts_rawdata_test_priv_param *priv_param; + bool stop_test_if_validate_fail = false; + bool driver_validate_data = false; + bool validate_data_per_node = false; + bool dump_test_data_to_user = false; + bool dump_test_data_to_console = false; + bool dump_test_data_to_file = false; + int num_nodes; + int tsdata_frame_size; + int frame; + u16 *stylus_rawdata = NULL; + u16 data_type; + u8 pen_freq_num = cts_dev->fwdata.pen_freq_num; + u8 cascade_num = cts_dev->fwdata.cascade_num; + u8 pr_rows_used = cts_dev->fwdata.pr_rows_used; + u8 pr_cols_used = cts_dev->fwdata.pr_cols_used; + u8 pc_cols_used = cts_dev->fwdata.pc_cols_used; + u8 pc_rows_used = cts_dev->fwdata.pc_rows_used; + u8 pr_rows = cts_dev->fwdata.pr_rows; + u8 pr_cols = cts_dev->fwdata.pr_cols; + u8 pc_rows = cts_dev->fwdata.pc_rows; + u8 pc_cols = cts_dev->fwdata.pc_cols; + int fail_frame = 0; + int i, ret = 0; + ktime_t start_time, end_time, delta_time; + + if (cts_dev == NULL || param == NULL) { + cts_err("stylus mnt rawdata test with invalid param: cts_dev: %p test param: %p", + cts_dev, param); + return -EINVAL; + } + + if (param->priv_param_size != sizeof(*priv_param) || + param->priv_param == NULL) { + cts_err("Stylus mnt rawdata test with invalid param: priv param: %p size: %d", + param->priv_param, param->priv_param_size); + return -EINVAL; + } + + priv_param = param->priv_param; + if (priv_param->frames <= 0) { + cts_info("Stylus mnt rawdata test with too little frame %u", priv_param->frames); + return -EINVAL; + } + + num_nodes = (pr_rows_used * pr_cols_used + pc_cols_used * pc_rows_used) * cascade_num; + tsdata_frame_size = (pr_rows * pr_cols + pc_rows * pc_cols) * pen_freq_num * sizeof(u16) * cascade_num; + + driver_validate_data = !!(param->flags & CTS_TEST_FLAG_VALIDATE_DATA); + if (driver_validate_data) { + validate_data_per_node = + !!(param->flags & CTS_TEST_FLAG_VALIDATE_PER_NODE); + } + dump_test_data_to_user = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE); + dump_test_data_to_console = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE); + dump_test_data_to_file = + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE); + stop_test_if_validate_fail = + !!(param->flags & CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED); + + cts_info("Stylus mnt rawdata test, flags: 0x%08x num invalid node: %u, " + "test data file: '%s' buf size: %d, drive log file: '%s' buf size: %d", + param->flags, param->num_invalid_node, + param->test_data_filepath, param->test_data_buf_size, + param->driver_log_filepath, param->driver_log_buf_size); + + start_time = ktime_get(); + + if (dump_test_data_to_user) { + stylus_rawdata = (u16 *) param->test_data_buf; + } else { + stylus_rawdata = (u16 *) kzalloc(tsdata_frame_size, GFP_KERNEL); + if (stylus_rawdata == NULL) { + cts_err("Alloc mem for stylus_rawdata failed"); + ret = -ENOMEM; + goto show_test_result; + } + } + + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + goto free_mem; + } + cts_lock_device(cts_dev); + + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_product_en(cts_dev, 1); + if (ret) { + cts_err("Set product en failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Set firmware work mode to WORK_MODE_CONFIG failed %d", ret); + goto unlock_device; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_CFG_MODE); + if (ret) { + cts_err("Wait CTS_CFG_MODE failed %d", ret); + goto unlock_device; + } + + ret = cts_tcs_set_krang_workmode(cts_dev, CTS_KRANG_MNT_DBG_MODE); + if (ret) { + cts_err("Set krang CTS_KRANG_MNT_DBG_MODE failed"); + goto unlock_device; + } + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_MNT_DBG_MODE); + if (ret) { + cts_err("Wait krang CTS_KRANG_MNT_DBG_MODE failed"); + goto unlock_device; + } + + ret = cts_tcs_set_pwr_mode(cts_dev, CTS_PWR_MODE_MNT_DBG); + if (ret) { + cts_err("Set CTS_PWR_MODE_MNT_DBG failed"); + goto unlock_device; + } + + data_type = INT_DATA_TYPE_RAWDATA | BIT(14); + ret = cts_set_int_data_types(cts_dev, data_type); + if (ret) { + cts_err("Set data_type '0x%04x' failed", data_type); + goto unlock_device; + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_POLLING); + if (ret) { + cts_err("Set data_method polling failed"); + goto unlock_device; + } + + if (dump_test_data_to_file) { + int r = cts_start_dump_test_data_to_file(param->test_data_filepath, + !!(param->flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE_APPEND)); + if (r) { + cts_err("Start dump test data to file failed %d", r); + } + } + + for (frame = 0; frame < priv_param->frames; frame++) { + for (i = 0; i < 3; i++) { + ret = cts_polling_test_data(cts_dev, (u8 *)stylus_rawdata, + tsdata_frame_size, data_type); + if (ret < 0) { + cts_err("Get stylus mnt rawdata failed: %d", ret); + mdelay(30); + } else { + break; + } + } + + if (i >= 3) { + cts_err("Read stylus mnt rawdata failed"); + ret = -EIO; + goto disable_get_tsdata; + } + + if (dump_test_data_to_user) { + *param->test_data_wr_size += 2 * num_nodes; + } + + if (dump_test_data_to_console || dump_test_data_to_file) { + cts_dump_stylus_data(cts_dev, "Stylus-mnt-Rawdata-pc", stylus_rawdata, + pc_rows_used, pc_cols_used * pen_freq_num, dump_test_data_to_console); + } + + if (driver_validate_data) { + /* Only validate pc data */ + ret = validate_stylus_pc_data(cts_dev, "Stylus-mnt-Rawdata-pc", + stylus_rawdata, param->invalid_nodes, + param->num_invalid_node, validate_data_per_node, param->min, param->max); + if (ret) { + cts_err("Stylus mnt rawdata test failed %d", ret); + fail_frame++; + cts_err("Stylus mnt rawdata test has %d nodes failed", ret); + if (stop_test_if_validate_fail) { + break; + } + } + } + } + +disable_get_tsdata: + if (dump_test_data_to_file) { + cts_stop_dump_test_data_to_file(); + } + +unlock_device: + cts_reset_device(cts_dev); + + ret = cts_wait_current_mode(cts_dev, CTS_KRANG_NORMAL_MODE); + if (ret) { + cts_err("Wait fw to normal work failed %d", ret); + } + + ret = cts_set_int_data_types(cts_dev, INT_DATA_TYPE_NONE); + if (ret) { + cts_err("Set INT_DATA_TYPE_NONE failed %d", ret); + } + + ret = cts_set_int_data_method(cts_dev, INT_DATA_METHOD_NONE); + if (ret) { + cts_err("Set INT_DATA_METHOD_NONE failed %d", ret); + } + + cts_unlock_device(cts_dev); + + cts_start_device(cts_dev); + +free_mem: + if (!dump_test_data_to_user && stylus_rawdata) { + kfree(stylus_rawdata); + } + +show_test_result: + end_time = ktime_get(); + delta_time = ktime_sub(end_time, start_time); + if (ret > 0) { + cts_info("Stylus-mnt-rawdata test has %d nodes FAIL, ELAPSED TIME: %lldms", + ret, ktime_to_ms(delta_time)); + } else if (ret < 0) { + cts_info("Stylus-mnt-rawdata test FAIL %d(%s), ELAPSED TIME: %lldms", + ret, cts_strerror(ret), ktime_to_ms(delta_time)); + } else { + cts_info("Stylus-mnt-rawdata test PASS, ELAPSED TIME: %lldms", + ktime_to_ms(delta_time)); + } + + return ret; +} + diff --git a/drivers/input/touchscreen/chipone_tddi/cts_test.h b/drivers/input/touchscreen/chipone_tddi/cts_test.h new file mode 100644 index 000000000000..b4b6ce4ffb5c --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_test.h @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CTS_TEST_H +#define CTS_TEST_H + +struct cts_device; + +#define CTS_TEST_FLAG_RESET_BEFORE_TEST (1u << 0) +#define CTS_TEST_FLAG_RESET_AFTER_TEST (1u << 1) +#define CTS_TEST_FLAG_DISPLAY_ON (1u << 2) +#define CTS_TEST_FLAG_DISABLE_GAS (1u << 3) +#define CTS_TEST_FLAG_DISABLE_LINESHIFT (1u << 4) + +#define CTS_TEST_FLAG_VALIDATE_DATA (1u << 8) +#define CTS_TEST_FLAG_VALIDATE_PER_NODE (1u << 9) +#define CTS_TEST_FLAG_VALIDATE_MIN (1u << 10) +#define CTS_TEST_FLAG_VALIDATE_MAX (1u << 11) +#define CTS_TEST_FLAG_VALIDATE_SKIP_INVALID_NODE (1u << 12) +#define CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED (1u << 13) + +#define CTS_TEST_FLAG_DUMP_TEST_DATA_TO_CONSOLE (1u << 16) +#define CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE (1u << 17) +#define CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE (1u << 18) +#define CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE_APPEND (1u << 19) +#define CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE_CSV (1u << 20) + +#define CTS_TEST_FLAG_DRIVER_LOG_TO_USERSPACE (1u << 24) +#define CTS_TEST_FLAG_DRIVER_LOG_TO_FILE (1u << 25) +#define CTS_TEST_FLAG_DRIVER_LOG_TO_FILE_APPEND (1u << 26) + +#define MAKE_INVALID_NODE(r, c) (((c) << 16) | (r)) +#define INVALID_NODE_ROW(node) ((u16)(node)) +#define INVALID_NODE_COL(node) ((u16)((node) >> 16)) + +enum cts_test_item { + CTS_TEST_RESET_PIN = 1, + CTS_TEST_INT_PIN, + CTS_TEST_RAWDATA, + CTS_TEST_NOISE, + CTS_TEST_OPEN, + CTS_TEST_SHORT, + CTS_TEST_COMPENSATE_CAP, + CTS_TEST_STYLUS_RAWDATA, + CTS_TEST_STYLUS_NOISE, +}; + +struct cts_test_param { + int test_item; + + __u32 flags; + + __u32 num_invalid_node; + __u32 *invalid_nodes; + int *min; + int *max; + + int *test_result; + s64 *elapsed_time_ms; + + void *test_data_buf; + int test_data_buf_size; + int *test_data_wr_size; + const char *test_data_filepath; + + int driver_log_level; + char *driver_log_buf; + int driver_log_buf_size; + int *driver_log_wr_size; + const char *driver_log_filepath; + + void *priv_param; + int priv_param_size; +}; + +struct cts_rawdata_test_priv_param { + __u32 frames; + //__u8 work_mode; +}; + +struct cts_noise_test_priv_param { + __u32 frames; + //__u8 work_mode; +}; + +extern const char *cts_test_item_str(int test_item); +extern int cts_write_file(struct file *filp, const void *data, size_t size); +extern int cts_mkdir_for_file(const char *filepath, umode_t mode); + +extern int cts_start_dump_test_data_to_file(const char *filepath, + bool append_to_file); +extern void cts_stop_dump_test_data_to_file(void); + +extern int cts_test_reset_pin(struct cts_device *cts_dev, + struct cts_test_param *param); +extern int cts_test_int_pin(struct cts_device *cts_dev, + struct cts_test_param *param); +extern int cts_test_rawdata(struct cts_device *cts_dev, + struct cts_test_param *param); +extern int cts_test_noise(struct cts_device *cts_dev, + struct cts_test_param *param); +extern int cts_test_open(struct cts_device *cts_dev, + struct cts_test_param *param); +extern int cts_test_short(struct cts_device *cts_dev, + struct cts_test_param *param); +extern int cts_test_compensate_cap(struct cts_device *cts_dev, + struct cts_test_param *param); +extern int cts_test_stylus_rawdata(struct cts_device *cts_dev, + struct cts_test_param *param); +extern int cts_test_stylus_noise(struct cts_device *cts_dev, + struct cts_test_param *param); +extern int cts_test_stylus_mnt_rawdata(struct cts_device *cts_dev, + struct cts_test_param *param); +#endif /* CTS_TEST_H */ diff --git a/drivers/input/touchscreen/chipone_tddi/cts_tool.c b/drivers/input/touchscreen/chipone_tddi/cts_tool.c new file mode 100644 index 000000000000..355c410f81cb --- /dev/null +++ b/drivers/input/touchscreen/chipone_tddi/cts_tool.c @@ -0,0 +1,1207 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define LOG_TAG "Tool" + +#include "cts_config.h" +#include "cts_platform.h" +#include "cts_core.h" +#include "cts_firmware.h" +#include "cts_test.h" +#include "cts_tcs.h" + +#ifdef CONFIG_CTS_LEGACY_TOOL + +#define CTS_TOOL_IOCTL_TCS_RW_BUF_MAX_SIZ 1024 +#define CTS_TOOL_IOCTL_TCS_RW_OPFLAG_READ 1 +#define CTS_TOOL_IOCTL_TCS_RW_OPFLAG_WRITE 2 + +#pragma pack(1) +/** Tool command structure */ +struct cts_tool_cmd { + u8 cmd; + u8 flag; + u8 circle; + u8 times; + u8 retry; + u32 data_len; + u8 addr_len; + u8 addr[4]; + u8 tcs[2]; + u8 data[PAGE_SIZE]; +}; +struct cts_ioctl_tcs_rw_data { + u32 opflags; + u32 txlen; + u32 rxlen; + u8 __user *txbuf; + u8 __user *rxbuf; +}; +#pragma pack() + +#define CTS_TOOL_CMD_HEADER_LENGTH (16) + +enum cts_tool_cmd_code { + CTS_TOOL_CMD_GET_PANEL_PARAM = 0, + CTS_TOOL_CMD_GET_DOWNLOAD_STATUS = 2, + CTS_TOOL_CMD_GET_RAW_DATA = 4, + CTS_TOOL_CMD_GET_DIFF_DATA = 6, + CTS_TOOL_CMD_READ_HOSTCOMM = 12, + CTS_TOOL_CMD_READ_ADC_STATUS = 14, + CTS_TOOL_CMD_READ_GESTURE_INFO = 16, + CTS_TOOL_CMD_READ_HOSTCOMM_MULTIBYTE = 18, + CTS_TOOL_CMD_READ_PROGRAM_MODE_MULTIBYTE = 20, + CTS_TOOL_CMD_READ_ICTYPE = 22, + CTS_TOOL_CMD_I2C_DIRECT_READ = 24, + CTS_TOOL_CMD_GET_DRIVER_INFO = 26, + CTS_TOOL_CMD_TCS_READ_HW_CMD = 28, + CTS_TOOL_CMD_TCS_READ_DDI_CMD = 30, + CTS_TOOL_CMD_TCS_READ_FW_CMD = 32, + CTS_TOOL_CMD_GET_BASE_DATA = 34, + CTS_TOOL_CMD_GET_INT_DATAS = 36, + + CTS_TOOL_CMD_UPDATE_PANEL_PARAM_IN_SRAM = 1, + CTS_TOOL_CMD_DOWNLOAD_FIRMWARE_WITH_FILENAME = 3, + CTS_TOOL_CMD_DOWNLOAD_FIRMWARE = 5, + CTS_TOOL_CMD_WRITE_HOSTCOMM = 11, + CTS_TOOL_CMD_WRITE_HOSTCOMM_MULTIBYTE = 15, + CTS_TOOL_CMD_WRITE_PROGRAM_MODE_MULTIBYTE = 17, + CTS_TOOL_CMD_I2C_DIRECT_WRITE = 19, + CTS_TOOL_CMD_TCS_WRITE_HW_CMD = 21, + CTS_TOOL_CMD_TCS_WRITE_DDI_CMD = 23, + CTS_TOOL_CMD_TCS_WRITE_FW_CMD = 25, + CTS_TOOL_CMD_TCS_ENABLE_GET_RAWDATA_CMD = 27, + CTS_TOOL_CMD_TCS_DISABLE_GET_RAWDATA_CMD = 29, + CTS_TOOL_CMD_SET_INT_DATA_TYPE_AND_METHOD = 31, + +}; + +struct cts_test_ioctl_data { + __u32 ntests; + struct cts_test_param __user *tests; +}; + + +#define CTS_TOOL_IOCTL_GET_DRIVER_VERSION _IOR('C', 0x00, u32 *) +#define CTS_TOOL_IOCTL_GET_DEVICE_TYPE _IOR('C', 0x01, u32 *) +#define CTS_TOOL_IOCTL_GET_FW_VERSION _IOR('C', 0x02, u16 *) +#define CTS_TOOL_IOCTL_GET_RESOLUTION _IOR('C', 0x03, u32 *) /* X in LSW, Y in MSW */ +#define CTS_TOOL_IOCTL_GET_ROW_COL _IOR('C', 0x04, u16 *) /* row in LSW, col in MSW */ +#define CTS_TOOL_IOCTL_GET_MODULE_ID _IOWR('C', 0x05, u32 *) + +#define CTS_TOOL_IOCTL_TEST _IOWR('C', 0x10, struct cts_test_ioctl_data *) +#define CTS_TOOL_IOCTL_RDWR_REG _IOWR('C', 0x20, struct cts_rdwr_reg_ioctl_data *) +#define CTS_TOOL_IOCTL_UPGRADE_FW _IOWR('C', 0x21, struct cts_upgrade_fw_ioctl_data *) + +#define CTS_TOOL_IOCTL_TCS_RW _IOWR('C', 0x30, struct cts_ioctl_tcs_rw_data *) + +#define CTS_DRIVER_VERSION_CODE ((CFG_CTS_DRIVER_MAJOR_VERSION << 16) | \ + (CFG_CTS_DRIVER_MINOR_VERSION << 8) | \ + (CFG_CTS_DRIVER_PATCH_VERSION << 0)) + +static struct cts_tool_cmd cts_tool_cmd; +static char cts_tool_firmware_filepath[PATH_MAX]; +/* If CFG_CTS_MAX_I2C_XFER_SIZE < 58(PC tool length), this is neccessary */ +#ifdef CONFIG_CTS_I2C_HOST +static u32 cts_tool_direct_access_addr = 0; +#endif /* CONFIG_CTS_I2C_HOST */ + +static int cts_tool_open(struct inode *inode, struct file *file) +{ + file->private_data = pde_data(inode); + return 0; +} + +static ssize_t cts_tool_read(struct file *file, + char __user *buffer, size_t count, loff_t *ppos) +{ +#define RAWDATA_BUFFER_SIZE(cts_dev) \ + (cts_dev->hwdata->num_row * cts_dev->hwdata->num_col * 2) + struct chipone_ts_data *cts_data; + struct cts_tool_cmd *cmd; + struct cts_device *cts_dev; + int ret = 0; + + cts_data = (struct chipone_ts_data *)file->private_data; + if (cts_data == NULL) { + cts_err("Read with private_data = NULL"); + return -EIO; + } + + cmd = &cts_tool_cmd; + cts_dev = &cts_data->cts_dev; + cts_lock_device(cts_dev); + + cts_dbg("read: flag:%d, addr:0x%06x, tcm:0x%04x, datalen:%d", + cmd->cmd, get_unaligned_le32(cmd->addr), + get_unaligned_le16(cmd->tcs), cmd->data_len); + + switch (cmd->cmd) { + case CTS_TOOL_CMD_TCS_READ_HW_CMD: + cts_tcs_read_hw_reg(cts_dev, get_unaligned_le32(cmd->addr), + cmd->data, cmd->data_len); + break; + case CTS_TOOL_CMD_TCS_READ_DDI_CMD: + cts_tcs_read_ddi_reg(cts_dev, get_unaligned_le32(cmd->addr), + cmd->data, cmd->data_len); + break; + case CTS_TOOL_CMD_TCS_READ_FW_CMD: + cts_tcs_read_reg(cts_dev, get_unaligned_le16(cmd->tcs), + cmd->data, cmd->data_len); + break; + case CTS_TOOL_CMD_GET_PANEL_PARAM: + cts_info("Get panel param len: %u", cmd->data_len); + break; + case CTS_TOOL_CMD_GET_DOWNLOAD_STATUS: + cmd->data[0] = 100; + cts_info("Get update status = %hhu", cmd->data[0]); + break; + + case CTS_TOOL_CMD_GET_RAW_DATA: + case CTS_TOOL_CMD_GET_DIFF_DATA: + case CTS_TOOL_CMD_GET_BASE_DATA: + cts_err("Get %s data row: %u col: %u len: %u, source: %x", + cmd->cmd == CTS_TOOL_CMD_GET_RAW_DATA ? "raw" : "diff", + cmd->addr[1], cmd->addr[0], cmd->data_len, (cmd->data[1] & 0xf0)); + + if (cmd->cmd == CTS_TOOL_CMD_GET_RAW_DATA) { + ret = cts_tcs_tool_get_rawdata(cts_dev, (u8 *) cmd->data, (cmd->data[1] & 0xf0) << 8); + } else if (cmd->cmd == CTS_TOOL_CMD_GET_DIFF_DATA) { + ret = cts_tcs_tool_get_manual_diff(cts_dev, (u8 *) cmd->data, (cmd->data[1] & 0xf0) << 8); + } else if (cmd->cmd == CTS_TOOL_CMD_GET_BASE_DATA) { + ret = cts_tcs_tool_get_basedata(cts_dev, (u8 *) cmd->data, (cmd->data[1] & 0xf0) << 8); + } + if (ret) { + cts_err("Get %s data failed %d", + cmd->cmd == CTS_TOOL_CMD_GET_RAW_DATA ? "raw" : + cmd->cmd == + CTS_TOOL_CMD_GET_DIFF_DATA ? "diff" : "base", + ret); + } + break; + case CTS_TOOL_CMD_GET_INT_DATAS: + memcpy((u8 *)cmd->data, cts_dev->int_data, cts_dev->fwdata.int_data_size); + cmd->data_len = cts_dev->fwdata.int_data_size; + break; + case CTS_TOOL_CMD_READ_HOSTCOMM: + ret = cts_fw_reg_readb(cts_dev, get_unaligned_le16(cmd->addr), cmd->data); + if (ret) { + cts_err("Read firmware reg addr 0x%04x failed %d", + get_unaligned_le16(cmd->addr), ret); + } else { + cts_dbg("Read firmware reg addr 0x%04x, val=0x%02x", + get_unaligned_le16(cmd->addr), cmd->data[0]); + } + break; + +#ifdef CFG_CTS_GESTURE + case CTS_TOOL_CMD_READ_GESTURE_INFO: + ret = cts_get_gesture_info(cts_dev, cmd->data); + if (ret) + cts_err("Get gesture info failed %d", ret); + break; +#endif /* CFG_CTS_GESTURE */ + + case CTS_TOOL_CMD_READ_HOSTCOMM_MULTIBYTE: + cmd->data_len = min((size_t)cmd->data_len, sizeof(cmd->data)); + ret = cts_fw_reg_readsb(cts_dev, get_unaligned_le16(cmd->addr), + cmd->data, cmd->data_len); + if (ret) { + cts_err("Read firmware reg addr 0x%04x len %u failed %d", + get_unaligned_le16(cmd->addr), cmd->data_len, ret); + } else { + cts_dbg("Read firmware reg addr 0x%04x len %u", + get_unaligned_le16(cmd->addr), cmd->data_len); + } + break; + + case CTS_TOOL_CMD_READ_PROGRAM_MODE_MULTIBYTE: + cts_dbg("Read under program mode addr 0x%06x len %u", + (cmd->flag << 16) | get_unaligned_le16(cmd->addr), + cmd->data_len); + ret = cts_enter_program_mode(cts_dev); + if (ret) { + cts_err("Enter program mode failed %d", ret); + break; + } + + ret = cts_sram_readsb(&cts_data->cts_dev, + get_unaligned_le16(cmd->addr), cmd->data, cmd->data_len); + if (ret) { + cts_err("Read under program mode I2C xfer failed %d", + ret); + } + + ret = cts_enter_normal_mode(cts_dev); + if (ret) { + cts_err("Enter normal mode failed %d", ret); + break; + } + break; + + case CTS_TOOL_CMD_READ_ICTYPE: + cts_info("Get IC type"); + break; + +#ifdef CONFIG_CTS_I2C_HOST + case CTS_TOOL_CMD_I2C_DIRECT_READ: + { + u32 addr_width; + char *wr_buff = NULL; + u8 addr_buff[4]; + size_t left_size, max_xfer_size; + u8 *data; + + if (cmd->addr[0] != CTS_DEV_PROGRAM_MODE_I2CADDR) { + cmd->addr[0] = CTS_DEV_NORMAL_MODE_I2CADDR; + addr_width = 2; + } else { + addr_width = + cts_dev->hwdata->program_addr_width; + } + + cts_dbg("Direct read from i2c_addr 0x%02x addr 0x%06x size %u", + cmd->addr[0], cts_tool_direct_access_addr, cmd->data_len); + + left_size = cmd->data_len; + max_xfer_size = cts_plat_get_max_i2c_xfer_size(cts_dev->pdata); + data = cmd->data; + while (left_size) { + size_t xfer_size = min(left_size, max_xfer_size); + ret = cts_plat_i2c_read(cts_data->pdata, cmd->addr[0], wr_buff, + addr_width, data, xfer_size, 1, 0); + if (ret) { + cts_err("Direct read i2c_addr 0x%02x addr 0x%06x " + "len %zu failed %d", cmd->addr[0], + cts_tool_direct_access_addr, + xfer_size, ret); + break; + } + + left_size -= xfer_size; + if (left_size) { + data += xfer_size; + cts_tool_direct_access_addr += xfer_size; + if (addr_width == 2) { + put_unaligned_be16(cts_tool_direct_access_addr, addr_buff); + } else if (addr_width == 3) { + put_unaligned_be24(cts_tool_direct_access_addr, addr_buff); + } + wr_buff = addr_buff; + } + } + } + break; +#endif + case CTS_TOOL_CMD_GET_DRIVER_INFO: + break; + + default: + cts_err("Read unknown command %u", cmd->cmd); + ret = -EINVAL; + break; + } + + cts_unlock_device(cts_dev); + + if (ret == 0) { + if (cmd->cmd == CTS_TOOL_CMD_I2C_DIRECT_READ) { + ret = copy_to_user(buffer + CTS_TOOL_CMD_HEADER_LENGTH, + cmd->data, cmd->data_len); + } else { + ret = copy_to_user(buffer, cmd->data, cmd->data_len); + } + if (ret) { + cts_err("Copy data to user buffer failed %d", ret); + return 0; + } + + return cmd->data_len; + } + + return 0; +} + +static ssize_t cts_tool_write(struct file *file, + const char __user *buffer, size_t count, loff_t *ppos) +{ + struct chipone_ts_data *cts_data; + struct cts_device *cts_dev; + struct cts_tool_cmd *cmd; + int ret = 0; + + if (count < CTS_TOOL_CMD_HEADER_LENGTH || count > PAGE_SIZE) { + cts_err("Write len %zu invalid", count); + return -EFAULT; + } + + cts_data = (struct chipone_ts_data *)file->private_data; + if (cts_data == NULL) { + cts_err("Write with private_data = NULL"); + return -EIO; + } + + cmd = &cts_tool_cmd; + ret = copy_from_user(cmd, buffer, CTS_TOOL_CMD_HEADER_LENGTH); + cts_info("write: flag:%d, addr:0x%06x, tcmd:0x%04x, datalen:%d", + cmd->cmd, get_unaligned_le32(cmd->addr), + get_unaligned_le16(cmd->tcs), cmd->data_len); + + if (ret) { + cts_err("Copy command header from user buffer failed %d", ret); + return -EIO; + } else + ret = CTS_TOOL_CMD_HEADER_LENGTH; + + if (cmd->data_len > PAGE_SIZE) { + cts_err("Write with invalid count %d", cmd->data_len); + return -EIO; + } + + if (count > CTS_TOOL_CMD_HEADER_LENGTH) { + ret = copy_from_user(cmd->data, buffer + CTS_TOOL_CMD_HEADER_LENGTH, + count - CTS_TOOL_CMD_HEADER_LENGTH); + if (ret) { + cts_err("Copy command payload from user buffer len %u failed %d", + cmd->data_len, ret); + return -EIO; + } + } + + if (cmd->cmd & BIT(0)) { + if (cmd->data_len) { + ret = copy_from_user(cmd->data, buffer + CTS_TOOL_CMD_HEADER_LENGTH, + cmd->data_len); + if (ret) { + cts_err("Copy command payload from user buffer len %u failed %d", + cmd->data_len, ret); + return -EIO; + } + } + } else { + /* Prepare for read command */ + cts_dbg("Write read command(%d) header, prepare read size: %d", + cmd->cmd, cmd->data_len); + return CTS_TOOL_CMD_HEADER_LENGTH + cmd->data_len; + } + + cts_dev = &cts_data->cts_dev; + cts_lock_device(cts_dev); + + switch (cmd->cmd) { + case CTS_TOOL_CMD_TCS_WRITE_HW_CMD: + cts_tcs_write_hw_reg(cts_dev, get_unaligned_le32(cmd->addr), + cmd->data, cmd->data_len); + break; + case CTS_TOOL_CMD_TCS_WRITE_DDI_CMD: + cts_tcs_write_ddi_reg(cts_dev, get_unaligned_le32(cmd->addr), + cmd->data, cmd->data_len); + break; + case CTS_TOOL_CMD_TCS_WRITE_FW_CMD: + cts_tcs_write_reg(cts_dev, get_unaligned_le16(cmd->tcs), + cmd->data, cmd->data_len); + break; + case CTS_TOOL_CMD_TCS_ENABLE_GET_RAWDATA_CMD: + case CTS_TOOL_CMD_TCS_DISABLE_GET_RAWDATA_CMD: + cts_info("Do nothing"); + break; + case CTS_TOOL_CMD_SET_INT_DATA_TYPE_AND_METHOD: + cts_set_int_data_types(cts_dev, cmd->data[0] | (cmd->data[1] << 8)); + cts_set_int_data_method(cts_dev, cmd->data[2]); + break; + case CTS_TOOL_CMD_UPDATE_PANEL_PARAM_IN_SRAM: + break; + case CTS_TOOL_CMD_DOWNLOAD_FIRMWARE_WITH_FILENAME: + cts_info("Write firmware path: '%.*s'", cmd->data_len, cmd->data); + + memcpy(cts_tool_firmware_filepath, cmd->data, cmd->data_len); + cts_tool_firmware_filepath[cmd->data_len] = '\0'; + break; + + case CTS_TOOL_CMD_DOWNLOAD_FIRMWARE: + cts_info("Start download firmware path: '%s'", cts_tool_firmware_filepath); + + ret = cts_stop_device(cts_dev); + if (ret) { + cts_err("Stop device failed %d", ret); + break; + } + + ret = cts_update_firmware_from_file(cts_dev, + cts_tool_firmware_filepath); + if (ret) + cts_err("Updata firmware failed %d", ret); + + ret = cts_start_device(cts_dev); + if (ret) { + cts_err("Start device failed %d", ret); + break; + } + break; + + case CTS_TOOL_CMD_WRITE_HOSTCOMM: + cts_dbg("Write firmware reg addr: 0x%04x val=0x%02x", + get_unaligned_le16(cmd->addr), cmd->data[0]); + + ret = cts_fw_reg_writeb(cts_dev, get_unaligned_le16(cmd->addr), + cmd->data[0]); + if (ret) { + cts_err("Write firmware reg addr: 0x%04x val=0x%02x failed %d", + get_unaligned_le16(cmd->addr), cmd->data[0], ret); + } + break; + + case CTS_TOOL_CMD_WRITE_HOSTCOMM_MULTIBYTE: + cts_dbg("Write firmare reg addr: 0x%04x len %u", + get_unaligned_le16(cmd->addr), cmd->data_len); + ret = cts_fw_reg_writesb(cts_dev, get_unaligned_le16(cmd->addr), + cmd->data, cmd->data_len); + if (ret) { + cts_err("Write firmare reg addr: 0x%04x len %u failed %d", + get_unaligned_le16(cmd->addr), cmd->data_len, ret); + } + break; + + case CTS_TOOL_CMD_WRITE_PROGRAM_MODE_MULTIBYTE: + cts_dbg("Write to addr 0x%06x size %u under program mode", + (cmd->flag << 16) | (cmd->addr[1] << 8) | cmd->addr[0], + cmd->data_len); + ret = cts_enter_program_mode(cts_dev); + if (ret) { + cts_err("Enter program mode failed %d", ret); + break; + } + + ret = cts_sram_writesb(cts_dev, (cmd->flag << 16) | (cmd->addr[1] << 8) | + cmd->addr[0], cmd->data, cmd->data_len); + if (ret) + cts_err("Write program mode multibyte failed %d", ret); + + ret = cts_enter_normal_mode(cts_dev); + if (ret) { + cts_err("Enter normal mode failed %d", ret); + break; + } + + break; + +#ifdef CONFIG_CTS_I2C_HOST + case CTS_TOOL_CMD_I2C_DIRECT_WRITE: + { + u32 addr_width; + size_t left_payload_size; /* Payload exclude address field */ + size_t max_xfer_size; + char *payload; + + if (cmd->addr[0] != CTS_DEV_PROGRAM_MODE_I2CADDR) { + cmd->addr[0] = CTS_DEV_NORMAL_MODE_I2CADDR; + addr_width = 2; + cts_tool_direct_access_addr = get_unaligned_be16(cmd->data); + } else { + addr_width = cts_dev->hwdata->program_addr_width; + cts_tool_direct_access_addr = get_unaligned_be24(cmd->data); + } + + if (cmd->data_len < addr_width) { + cts_err("Direct write too short %d < address width %d", + cmd->data_len, addr_width); + ret = -EINVAL; + break; + } + + cts_dbg("Direct write to i2c_addr 0x%02x addr 0x%06x size %u", + cmd->addr[0], cts_tool_direct_access_addr, cmd->data_len); + + left_payload_size = cmd->data_len - addr_width; + max_xfer_size = cts_plat_get_max_i2c_xfer_size(cts_dev->pdata); + payload = cmd->data + addr_width; + do { + size_t xfer_payload_size = min(left_payload_size, + max_xfer_size - addr_width); + size_t xfer_len = xfer_payload_size + addr_width; + + ret = cts_plat_i2c_write(cts_data->pdata, + cmd->addr[0], payload - addr_width, xfer_len, 1, 0); + if (ret) { + cts_err("Direct write i2c_addr 0x%02x addr 0x%06x len %zu failed %d", + cmd->addr[0], cts_tool_direct_access_addr, + xfer_len, ret); + break; + } + + left_payload_size -= xfer_payload_size; + if (left_payload_size) { + payload += xfer_payload_size; + cts_tool_direct_access_addr += xfer_payload_size; + if (addr_width == 2) { + put_unaligned_be16(cts_tool_direct_access_addr, + payload - addr_width); + } else if (addr_width == 3) { + put_unaligned_be24(cts_tool_direct_access_addr, + payload - addr_width); + } + } + } while (left_payload_size); + } + break; +#endif + default: + cts_warn("Write unknown command %u", cmd->cmd); + ret = -EINVAL; + break; + } + + cts_unlock_device(cts_dev); + + return ret ? 0 : cmd->data_len + CTS_TOOL_CMD_HEADER_LENGTH; +} + +static long cts_ioctl_tcs_rw(struct cts_device *cts_dev, + struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret; + struct cts_ioctl_tcs_rw_data rwdata; + u8 *txbuf = NULL; + u8 *rxbuf = NULL; + + if (copy_from_user(&rwdata, + (struct cts_ioctl_tcs_rw_data __user *)arg, + sizeof(rwdata))) { + cts_err("Copy ioctl tcs rw arg to kernel failed"); + return -EFAULT; + } + + if ((rwdata.opflags != CTS_TOOL_IOCTL_TCS_RW_OPFLAG_READ) && + (rwdata.opflags != CTS_TOOL_IOCTL_TCS_RW_OPFLAG_WRITE)) { + cts_err("ioctl tcs rw with invalid opflags %u", rwdata.opflags); + return -EINVAL; + } + + if (rwdata.txlen > CTS_TOOL_IOCTL_TCS_RW_BUF_MAX_SIZ) { + cts_err("Send too long: %d", rwdata.txlen); + return -EINVAL; + } + + if (rwdata.rxlen > CTS_TOOL_IOCTL_TCS_RW_BUF_MAX_SIZ) { + cts_err("Recv too long: %d", rwdata.rxlen); + return -EINVAL; + } + + if (!rwdata.txbuf || !rwdata.rxbuf) { + cts_err("Txbuf of Rxbuf is NULL!"); + return -EINVAL; + } + + txbuf = memdup_user(rwdata.txbuf, rwdata.txlen); + if (IS_ERR(txbuf)) { + ret = PTR_ERR(txbuf); + cts_err("Memdup txbuf failed %d", ret); + txbuf = NULL; + goto free_buf; + } + + rxbuf = memdup_user(rwdata.rxbuf, rwdata.rxlen); + if (IS_ERR(rxbuf)) { + ret = PTR_ERR(rxbuf); + cts_err("Memdup rxbuf failed %d", ret); + rxbuf = NULL; + goto free_buf; + } + + cts_lock_device(cts_dev); + ret = cts_tcs_tool_xtrans(cts_dev, txbuf, rwdata.txlen, rxbuf, rwdata.rxlen); + cts_unlock_device(cts_dev); + if (ret < 0) { + cts_err("Trans failed %d", ret); + goto free_buf; + } + + ret = copy_to_user(rwdata.rxbuf, rxbuf, rwdata.rxlen); + if (ret < 0) { + cts_err("Copy to user failed %d", ret); + goto free_buf; + } + + ret = 0; + +free_buf: + if (txbuf) { + kfree(txbuf); + txbuf = NULL; + } + if (rxbuf) { + kfree(rxbuf); + rxbuf = NULL; + } + + return ret; +} + +static int cts_ioctl_test(struct cts_device *cts_dev, + u32 ntests, struct cts_test_param *tests) +{ + u32 num_nodes = 0; + int i, ret = 0; + + cts_info("ioctl test total %u items", ntests); + + num_nodes = cts_dev->fwdata.rows * cts_dev->fwdata.cols; + + for (i = 0; i < ntests; i++) { + bool validate_data = false; + bool validate_data_per_node = false; + bool validate_min = false; + bool validate_max = false; + bool skip_invalid_node = false; + bool stop_test_if_validate_fail = false; + bool dump_test_data_to_console = false; + bool dump_test_data_to_user = false; + bool dump_test_data_to_file = false; + bool dump_test_data_to_file_append = false; + bool driver_log_to_user = false; + bool driver_log_to_file = false; + bool driver_log_to_file_append = false; + u32 __user *user_min_threshold = NULL; + u32 __user *user_max_threshold = NULL; + u32 __user *user_invalid_nodes = NULL; + int __user *user_test_result = NULL; + void __user *user_test_data = NULL; + int __user *user_test_data_wr_size = NULL; + const char __user *user_test_data_filepath = NULL; + void __user *user_driver_log_buf = NULL; + int __user *user_driver_log_wr_size = NULL; + const char __user *user_driver_log_filepath = NULL; + void __user *user_priv_param = NULL; + int test_result = 0; + int test_data_wr_size = 0; + int driver_log_wr_size = 0; + + cts_info("ioctl test item %d: %d(%s) flags: %08x priv param size: %d", + i, tests[i].test_item, cts_test_item_str(tests[i].test_item), + tests[i].flags, tests[i].priv_param_size); + /* + * Validate arguement + */ + validate_data = + !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_DATA); + validate_data_per_node = + !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_PER_NODE); + validate_min = + !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_MIN); + validate_max = + !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_MAX); + skip_invalid_node = + !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_SKIP_INVALID_NODE); + stop_test_if_validate_fail = + !!(tests[i].flags & CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED); + dump_test_data_to_user = + !!(tests[i].flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE); + dump_test_data_to_file = + !!(tests[i].flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE); + dump_test_data_to_file_append = + !!(tests[i].flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE_APPEND); + driver_log_to_user = + !!(tests[i].flags & CTS_TEST_FLAG_DRIVER_LOG_TO_USERSPACE); + driver_log_to_file = + !!(tests[i].flags & CTS_TEST_FLAG_DRIVER_LOG_TO_FILE); + driver_log_to_file_append = + !!(tests[i].flags & CTS_TEST_FLAG_DRIVER_LOG_TO_FILE_APPEND); + + if (tests[i].test_result == NULL) { + cts_err("Result pointer = NULL"); + return -EFAULT; + } + + if (validate_data) { + cts_info(" - Flag: Validate data"); + + if (validate_data_per_node) { + cts_info(" - Flag: Validate data per-node"); + } + if (validate_min) { + cts_info(" - Flag: Validate min threshold"); + } + if (validate_max) { + cts_info(" - Flag: Validate max threshold"); + } + if (stop_test_if_validate_fail) { + cts_info(" - Flag: Stop test if validate fail"); + } + + if (validate_min && tests[i].min == NULL) { + cts_err("Min threshold pointer = NULL"); + ret = -EINVAL; + goto store_result; + } + if (validate_max && tests[i].max == NULL) { + cts_err("Max threshold pointer = NULL"); + ret = -EINVAL; + goto store_result; + } + if (skip_invalid_node) { + cts_info(" - Flag: Skip invalid node"); + + if (tests[i].num_invalid_node == 0 || + tests[i].num_invalid_node >= num_nodes) { + cts_err("Num invalid node %u out of range[0, %u]", + tests[i].num_invalid_node, num_nodes); + ret = -EINVAL; + goto store_result; + } + + if (tests[i].invalid_nodes == NULL) { + cts_err("Invalid nodes pointer = NULL"); + ret = -EINVAL; + goto store_result; + } + } + } + + if (dump_test_data_to_console) { + cts_info(" - Flag: Dump test data to console"); + } + + if (dump_test_data_to_user) { + cts_info(" - Flag: Dump test data to user, size: %d", + tests[i].test_data_buf_size); + + if (tests[i].test_data_buf == NULL) { + cts_err("Test data pointer = NULL"); + ret = -EINVAL; + goto store_result; + } + if (tests[i].test_data_wr_size == NULL) { + cts_err("Test data write size pointer = NULL"); + ret = -EINVAL; + goto store_result; + } + if (tests[i].test_data_buf_size < num_nodes) { + cts_err("Test data size %d too small < %u", + tests[i].test_data_buf_size, num_nodes); + ret = -EINVAL; + goto store_result; + } + } + + if (dump_test_data_to_file) { + cts_info(" - Flag: Dump test data to file%s", + dump_test_data_to_file_append ? "[Append]" : ""); + + if (tests[i].test_data_filepath == NULL) { + cts_err("Test data filepath = NULL"); + ret = -EINVAL; + goto store_result; + } + } + + if (driver_log_to_user) { + cts_info(" - Flag: Dump driver log to user, size: %d", + tests[i].driver_log_buf_size); + + if (tests[i].driver_log_buf == NULL) { + cts_err("Driver log buf pointer = NULL"); + ret = -EINVAL; + goto store_result; + } + if (tests[i].driver_log_wr_size == NULL) { + cts_err("Driver log write size pointer = NULL"); + ret = -EINVAL; + goto store_result; + } + if (tests[i].driver_log_buf_size < 1024) { + cts_err("Driver log buf size %d too small < 1024", + tests[i].test_data_buf_size); + ret = -EINVAL; + goto store_result; + } + } + + if (driver_log_to_file) { + cts_info(" - Flag: Dump driver log to file %s", + driver_log_to_file_append ? "[Append]" : ""); + + if (tests[i].driver_log_filepath == NULL) { + cts_err("Driver log filepath = NULL"); + ret = -EINVAL; + goto store_result; + } + } + + /* + * Dump input parameter from user, + * Aallocate memory for output, + * Replace __user pointer with kernel pointer. + */ + user_test_result = (int __user *)tests[i].test_result; + tests[i].test_result = &test_result; + + if (validate_data) { + int num_threshold = validate_data_per_node ? num_nodes : 1; + int threshold_size = num_threshold * sizeof(tests[i].min[0]); + + if (validate_min) { + user_min_threshold = (int __user *)tests[i].min; + tests[i].min = memdup_user(user_min_threshold, threshold_size); + if (IS_ERR(tests[i].min)) { + ret = PTR_ERR(tests[i].min); + tests[i].min = NULL; + cts_err("Memdup min threshold from user failed %d", ret); + goto store_result; + } + } else { + tests[i].min = NULL; + } + if (validate_max) { + user_max_threshold = (int __user *)tests[i].max; + tests[i].max = memdup_user(user_max_threshold, threshold_size); + if (IS_ERR(tests[i].max)) { + ret = PTR_ERR(tests[i].max); + tests[i].max = NULL; + cts_err("Memdup max threshold from user failed %d", ret); + goto store_result; + } + } else { + tests[i].max = NULL; + } + if (skip_invalid_node) { + user_invalid_nodes = (u32 __user *)tests[i].invalid_nodes; + tests[i].invalid_nodes = memdup_user(user_invalid_nodes, + tests[i].num_invalid_node * sizeof(tests[i].invalid_nodes[0])); + if (IS_ERR(tests[i].invalid_nodes)) { + ret = PTR_ERR(tests[i].invalid_nodes); + tests[i].invalid_nodes = NULL; + cts_err("Memdup invalid node from user failed %d", ret); + goto store_result; + } + } + } + + if (dump_test_data_to_user) { + user_test_data = (void __user *)tests[i].test_data_buf; + tests[i].test_data_buf = vmalloc(tests[i].test_data_buf_size); + if (tests[i].test_data_buf == NULL) { + ret = -ENOMEM; + cts_err("Alloc test data mem failed"); + goto store_result; + } + user_test_data_wr_size = (int __user *)tests[i].test_data_wr_size; + tests[i].test_data_wr_size = &test_data_wr_size; + } + + if (dump_test_data_to_file) { +#ifdef CFG_CTS_FOR_GKI + cts_info("%s(): strndup_user is forbiddon with GKI Version!", __func__); +#else + user_test_data_filepath = (const char __user *)tests[i].test_data_filepath; + tests[i].test_data_filepath = strndup_user(user_test_data_filepath, PATH_MAX); + if (tests[i].test_data_filepath == NULL) { + cts_err("Strdup test data filepath failed"); + goto store_result; + } +#endif + } + + if (driver_log_to_user) { + user_driver_log_buf = (void __user *)tests[i].driver_log_buf; + tests[i].driver_log_buf = kmalloc(tests[i].driver_log_buf_size, GFP_KERNEL); + if (tests[i].driver_log_buf == NULL) { + ret = -ENOMEM; + cts_err("Alloc driver log mem failed"); + goto store_result; + } + user_driver_log_wr_size = (int __user *)tests[i].driver_log_wr_size; + tests[i].driver_log_wr_size = &driver_log_wr_size; + } + + if (driver_log_to_file) { +#ifdef CFG_CTS_FOR_GKI + cts_info("%s(): strndup_user is forbiddon with GKI Version!", __func__); +#else + user_driver_log_filepath = (const char __user *)tests[i].driver_log_filepath; + tests[i].driver_log_filepath = strndup_user(user_driver_log_filepath, PATH_MAX); + if (tests[i].driver_log_filepath == NULL) { + cts_err("Strdup driver log filepath failed"); + goto store_result; + } +#endif + cts_info("Log driver log to file '%s'", tests[i].driver_log_filepath); + } + + if (driver_log_to_file || driver_log_to_user) { + ret = cts_start_driver_log_redirect( + tests[i].driver_log_filepath, driver_log_to_file_append, + tests[i].driver_log_buf, tests[i].driver_log_buf_size, + tests[i].driver_log_level); + if (ret) { + cts_err("Start driver log redirect failed %d", ret); + goto store_result; + } + } + + if (tests[i].priv_param_size && tests[i].priv_param) { + user_priv_param = (void __user *)tests[i].priv_param; + tests[i].priv_param = memdup_user(user_priv_param, tests[i].priv_param_size); + if (IS_ERR(tests[i].priv_param)) { + ret = PTR_ERR(tests[i].priv_param); + tests[i].priv_param = NULL; + cts_err("Memdup priv param from user failed %d", ret); + goto store_result; + } + } + + switch (tests[i].test_item) { + case CTS_TEST_RESET_PIN: + ret = cts_test_reset_pin(cts_dev, &tests[i]); + break; + case CTS_TEST_INT_PIN: + ret = cts_test_int_pin(cts_dev, &tests[i]); + break; + case CTS_TEST_RAWDATA: + ret = cts_test_rawdata(cts_dev, &tests[i]); + break; + case CTS_TEST_NOISE: + ret = cts_test_noise(cts_dev, &tests[i]); + break; + case CTS_TEST_OPEN: + ret = cts_test_open(cts_dev, &tests[i]); + break; + case CTS_TEST_SHORT: + ret = cts_test_short(cts_dev, &tests[i]); + break; + case CTS_TEST_COMPENSATE_CAP: + ret = cts_test_compensate_cap(cts_dev, &tests[i]); + break; + case CTS_TEST_STYLUS_RAWDATA: + ret = cts_test_stylus_rawdata(cts_dev, &tests[i]); + break; + case CTS_TEST_STYLUS_NOISE: + ret = cts_test_stylus_noise(cts_dev, &tests[i]); + break; + default: + ret = ENOTSUPP; + cts_err("Un-supported test item"); + break; + } + +/* + * Copy result and test data back to userspace. + */ +store_result: + if (dump_test_data_to_user) { + if (user_test_data != NULL && test_data_wr_size > 0) { + cts_info("Copy test data to user, size: %d", test_data_wr_size); + if (copy_to_user(user_test_data, tests[i].test_data_buf, + test_data_wr_size)) { + cts_err("Copy test data to user failed"); + test_data_wr_size = 0; + // Skip this error + } + } + + if (user_test_data_wr_size != NULL) { + put_user(test_data_wr_size, user_test_data_wr_size); + } + } + + if (driver_log_to_user) { + driver_log_wr_size = cts_get_driver_log_redirect_size(); + if (user_driver_log_buf != NULL && driver_log_wr_size > 0) { + cts_info("Copy driver log to user, size: %d", driver_log_wr_size); + if (copy_to_user(user_driver_log_buf, tests[i].driver_log_buf, + driver_log_wr_size)) { + cts_err("Copy driver log to user failed"); + driver_log_wr_size = 0; + // Skip this error + } + } + + if (user_driver_log_wr_size != NULL) { + put_user(driver_log_wr_size, user_driver_log_wr_size); + } + } + + if (user_test_result != NULL) { + put_user(ret, user_test_result); + } else if (tests[i].test_result != NULL) { + put_user(ret, tests[i].test_result); + } + + /* + * Free memory + */ + if (dump_test_data_to_user) { + if (user_test_data != NULL && tests[i].test_data_buf != NULL) { + vfree(tests[i].test_data_buf); + } + } + if (validate_data) { + if (validate_min && user_min_threshold != NULL && tests[i].min != NULL) { + kfree(tests[i].min); + } + if (validate_max && user_max_threshold != NULL && tests[i].max != NULL) { + kfree(tests[i].max); + } + if (skip_invalid_node) { + if (user_invalid_nodes != NULL && tests[i].invalid_nodes != NULL) { + kfree(tests[i].invalid_nodes); + } + } + } + + if (dump_test_data_to_file && + user_test_data_filepath != NULL && + tests[i].test_data_filepath != NULL) { + kfree(tests[i].test_data_filepath); + } + + if (driver_log_to_user) { + if (user_driver_log_buf != NULL && + tests[i].driver_log_buf != NULL) { + kfree(tests[i].driver_log_buf); + } + } + + if (driver_log_to_file) { + if (user_driver_log_filepath != NULL && + tests[i].driver_log_filepath != NULL) { + kfree(tests[i].driver_log_filepath); + } + } + + if (driver_log_to_file || driver_log_to_user) { + cts_stop_driver_log_redirect(); + } + + if (user_priv_param && tests[i].priv_param) { + kfree(tests[i].priv_param); + } + + if (ret && stop_test_if_validate_fail) { + break; + } + } + + kfree(tests); + + return ret; +} + + +static long cts_tool_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct chipone_ts_data *cts_data; + struct cts_device *cts_dev; + unsigned int modid; + int ret; + + cts_info("ioctl, cmd=0x%016x, arg=0x%016lx", cmd, arg); + + cts_data = file->private_data; + if (cts_data == NULL) { + cts_err("IOCTL with private data = NULL"); + return -EFAULT; + } + + cts_dev = &cts_data->cts_dev; + + switch (cmd) { + case CTS_TOOL_IOCTL_GET_DRIVER_VERSION: + return put_user(CTS_DRIVER_VERSION_CODE, + (unsigned int __user *)arg); + case CTS_TOOL_IOCTL_GET_DEVICE_TYPE: + return put_user(cts_dev->hwdata->hwid, + (unsigned int __user *)arg); + case CTS_TOOL_IOCTL_GET_FW_VERSION: + return put_user(cts_dev->fwdata.version, + (unsigned short __user *)arg); + case CTS_TOOL_IOCTL_GET_ROW_COL:{ + u16 rows_cols = (cts_dev->fwdata.rows << 8) | cts_dev->fwdata.cols; + return put_user(rows_cols, + (unsigned short __user *)arg); + } + case CTS_TOOL_IOCTL_GET_MODULE_ID: + ret = cts_tcs_get_module_id(cts_dev, &modid); + if (ret) { + cts_err("Get module Id failed"); + return -EFAULT; + } + return put_user(modid, + (unsigned int __user *)arg); + + case CTS_TOOL_IOCTL_TEST:{ + struct cts_test_ioctl_data test_arg; + struct cts_test_param *tests_pa; + + if (copy_from_user(&test_arg, (struct cts_test_ioctl_data __user *)arg, + sizeof(test_arg))) { + cts_err("Copy ioctl test arg to kernel failed"); + return -EFAULT; + } + + if (test_arg.ntests > 8) { + cts_err("ioctl test with too many tests %u", test_arg.ntests); + return -EINVAL; + } + + tests_pa = memdup_user(test_arg.tests, + test_arg.ntests * sizeof(struct cts_test_param)); + if (IS_ERR(tests_pa)) { + int ret = PTR_ERR(tests_pa); + cts_err("Memdump test param to kernel failed %d", ret); + return ret; + } + + return cts_ioctl_test(cts_dev, test_arg.ntests, tests_pa); + } + case CTS_TOOL_IOCTL_TCS_RW:{ + return cts_ioctl_tcs_rw(cts_dev, file, cmd, arg); + } + + default: + break; + } + + return -ENOTSUPP; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) +static struct file_operations cts_tool_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = cts_tool_open, + .read = cts_tool_read, + .write = cts_tool_write, + .unlocked_ioctl = cts_tool_ioctl, +}; +#else +static struct proc_ops cts_tool_fops = { + .proc_lseek = no_llseek, + .proc_open = cts_tool_open, + .proc_read = cts_tool_read, + .proc_write = cts_tool_write, + .proc_ioctl = cts_tool_ioctl, +}; +#endif + +int cts_tool_init(struct chipone_ts_data *cts_data) +{ + cts_info("Init"); + + cts_data->procfs_entry = proc_create_data(CFG_CTS_TOOL_PROC_FILENAME, + 0660, NULL, &cts_tool_fops, cts_data); + if (cts_data->procfs_entry == NULL) { + cts_err("Create proc entry failed"); + return -EFAULT; + } + + return 0; +} + +void cts_tool_deinit(struct chipone_ts_data *data) +{ + cts_info("Deinit"); + + if (data->procfs_entry) + remove_proc_entry(CFG_CTS_TOOL_PROC_FILENAME, NULL); +} +#endif /* CONFIG_CTS_LEGACY_TOOL */ From f1fd603e9a6ff7ae7f966acc586b58e67286247e Mon Sep 17 00:00:00 2001 From: Wang Panzhenzhuan Date: Wed, 21 Feb 2024 09:41:10 +0800 Subject: [PATCH 09/28] media: i2c: gc05a2: adjust power sequence to suit spec 1. adjust power sequence to suit spec. 2. fix bayer pattern to suit setting. Signed-off-by: Wang Panzhenzhuan Change-Id: Ic62aa778c051607df0313722e0963b2a1f9ae3a5 --- drivers/media/i2c/gc05a2.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/media/i2c/gc05a2.c b/drivers/media/i2c/gc05a2.c index f323061635c0..233063592e03 100644 --- a/drivers/media/i2c/gc05a2.c +++ b/drivers/media/i2c/gc05a2.c @@ -5,6 +5,9 @@ * Copyright (C) 2023 Rockchip Electronics Co., Ltd. * * V0.0X01.0X01 init first version. + * V0.0X01.0X01 update sensor driver. + * 1. adjust power sequence to suit spec. + * 2. fix bayer pattern to suit setting. */ #include @@ -84,7 +87,7 @@ #define OF_CAMERA_PINCTRL_STATE_SLEEP "rockchip,camera_sleep" #define GC05A2_NAME "gc05a2" -#define GC05A2_MEDIA_BUS_FMT MEDIA_BUS_FMT_SRGGB10_1X10 +#define GC05A2_MEDIA_BUS_FMT MEDIA_BUS_FMT_SGRBG10_1X10 static const char * const gc05a2_supply_names[] = { "avdd", /* Analog power */ @@ -1185,14 +1188,16 @@ static int __gc05a2_power_on(struct gc05a2 *gc05a2) dev_err(dev, "Failed to enable xvclk\n"); return ret; } - if (!IS_ERR(gc05a2->reset_gpio)) - gpiod_set_value_cansleep(gc05a2->reset_gpio, 0); ret = regulator_bulk_enable(GC05A2_NUM_SUPPLIES, gc05a2->supplies); if (ret < 0) { dev_err(dev, "Failed to enable regulators\n"); goto disable_clk; } + usleep_range(2000, 2100); + + if (!IS_ERR(gc05a2->reset_gpio)) + gpiod_set_value_cansleep(gc05a2->reset_gpio, 0); usleep_range(1000, 1100); if (!IS_ERR(gc05a2->reset_gpio)) From c14093df2da306b0cf9687d8ca856043571662fb Mon Sep 17 00:00:00 2001 From: Wang Panzhenzhuan Date: Wed, 21 Feb 2024 09:29:54 +0800 Subject: [PATCH 10/28] media: i2c: s5k3l8xx: adjust power sequence to suit spec 1. adjust power sequence to suit spec 2. add read/write reg error debug log Signed-off-by: Wang Panzhenzhuan Change-Id: Ic857ffc8075ecc636d0652da9eeaf142fc4563fb --- drivers/media/i2c/s5k3l8xx.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/drivers/media/i2c/s5k3l8xx.c b/drivers/media/i2c/s5k3l8xx.c index 36591b30ea4d..61ef08729abc 100644 --- a/drivers/media/i2c/s5k3l8xx.c +++ b/drivers/media/i2c/s5k3l8xx.c @@ -5,6 +5,9 @@ * Copyright (C) 2023 Rockchip Electronics Co., Ltd. * * V0.0X01.0X00 first version. + * V0.0X01.0X01 update sensor driver. + * 1. adjust power sequence to suit spec. + * 2. add read/write reg error debug log. * */ @@ -28,7 +31,7 @@ #include #include -#define DRIVER_VERSION KERNEL_VERSION(0, 0x01, 0x00) +#define DRIVER_VERSION KERNEL_VERSION(0, 0x01, 0x01) #define S5K3L8XX_MAJOR_I2C_ADDR 0x10 #define S5K3L8XX_MINOR_I2C_ADDR 0x2D @@ -671,8 +674,11 @@ static int s5k3l8xx_write_reg(struct i2c_client *client, u16 reg, while (val_i < 4) buf[buf_i++] = val_p[val_i++]; - if (i2c_master_send(client, buf, len + 2) != len + 2) + if (i2c_master_send(client, buf, len + 2) != len + 2) { + dev_err(&client->dev, + "write reg(0x%x val:0x%x) failed !\n", reg, val); return -EIO; + } return 0; } @@ -718,8 +724,11 @@ static int s5k3l8xx_read_reg(struct i2c_client *client, u16 reg, msgs[1].buf = &data_be_p[4 - len]; ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); - if (ret != ARRAY_SIZE(msgs)) + if (ret != ARRAY_SIZE(msgs)) { + dev_err(&client->dev, + "read reg:0x%x failed !\n", reg); return -EIO; + } *val = be32_to_cpu(data_be); @@ -1126,8 +1135,6 @@ static int __s5k3l8xx_power_on(struct s5k3l8xx *s5k3l8xx) dev_err(dev, "Failed to enable xvclk\n"); return ret; } - if (!IS_ERR(s5k3l8xx->reset_gpio)) - gpiod_set_value_cansleep(s5k3l8xx->reset_gpio, 0); ret = regulator_bulk_enable(S5K3L8XX_NUM_SUPPLIES, s5k3l8xx->supplies); if (ret < 0) { @@ -1135,6 +1142,14 @@ static int __s5k3l8xx_power_on(struct s5k3l8xx *s5k3l8xx) goto disable_clk; } + /* need reset after all vdd power on */ + usleep_range(2000, 2100); + + if (!IS_ERR(s5k3l8xx->reset_gpio)) + gpiod_set_value_cansleep(s5k3l8xx->reset_gpio, 0); + + usleep_range(1000, 1100); + if (!IS_ERR(s5k3l8xx->reset_gpio)) gpiod_set_value_cansleep(s5k3l8xx->reset_gpio, 1); @@ -1142,8 +1157,8 @@ static int __s5k3l8xx_power_on(struct s5k3l8xx *s5k3l8xx) if (!IS_ERR(s5k3l8xx->pwdn_gpio)) gpiod_set_value_cansleep(s5k3l8xx->pwdn_gpio, 1); - /* 8192 cycles prior to first SCCB transaction */ - delay_us = s5k3l8xx_cal_delay(8192); + /* 23000 cycles prior to first SCCB transaction */ + delay_us = s5k3l8xx_cal_delay(23000); usleep_range(delay_us, delay_us * 2); return 0; From bca95c28ebc85b8fbf25fc2c92d06d698e8f0472 Mon Sep 17 00:00:00 2001 From: Wang Panzhenzhuan Date: Thu, 22 Feb 2024 06:26:23 +0000 Subject: [PATCH 11/28] media: i2c: add vcm driver ces6301 Signed-off-by: Wang Panzhenzhuan Change-Id: Iebd899fffc3499beb3f7bde753de2f94efd1e25e --- drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ces6301.c | 1133 +++++++++++++++++++++++++++++++++++ 3 files changed, 1145 insertions(+) create mode 100644 drivers/media/i2c/ces6301.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 71464bbe2d8b..88a15a1f4b49 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -2068,6 +2068,17 @@ config VIDEO_AW8601 capability. This is designed for linear control of voice coil motors, controlled via I2C serial interface. +config VIDEO_CES6301 + tristate "CES6301 lens voice coil support" + depends on I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This is a driver for the CES6301 camera lens voice coil. + CES6301 is a 10 bit DAC with 120mA output current sink + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + config VIDEO_CN3927V tristate "CN3927V lens voice coil support" depends on I2C && VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index d5e9cb7db292..75846babaf14 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_VIDEO_BT866) += bt866.o obj-$(CONFIG_VIDEO_CAM_SLEEP_WAKEUP) += cam-sleep-wakeup.o obj-$(CONFIG_VIDEO_CCS) += ccs/ obj-$(CONFIG_VIDEO_CCS_PLL) += ccs-pll.o +obj-$(CONFIG_VIDEO_CES6301) += ces6301.o obj-$(CONFIG_VIDEO_CN3927V) += cn3927v.o obj-$(CONFIG_VIDEO_CS3308) += cs3308.o obj-$(CONFIG_VIDEO_CS5345) += cs5345.o diff --git a/drivers/media/i2c/ces6301.c b/drivers/media/i2c/ces6301.c new file mode 100644 index 000000000000..95391d6acb99 --- /dev/null +++ b/drivers/media/i2c/ces6301.c @@ -0,0 +1,1133 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ces6301 vcm driver + * + * Copyright (C) 2024 Rockchip Electronics Co., Ltd. + * V0.0X01.0X00 init version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_VERSION KERNEL_VERSION(0, 0x01, 0x0) +#define CES6301_NAME "ces6301" + +#define CES6301_MAX_CURRENT 120U +#define CES6301_MAX_REG 1023U +#define CES6301_GRADUAL_MOVELENS_STEPS 32 + +#define CES6301_DEFAULT_START_CURRENT 0 +#define CES6301_DEFAULT_RATED_CURRENT 100 +#define CES6301_DEFAULT_STEP_MODE 0xd +#define CES6301_DEFAULT_DLC_EN 0x0 +#define CES6301_DEFAULT_MCLK 0x1 +#define CES6301_DEFAULT_T_SRC 0x0 +#define REG_NULL 0xFF + +/* ces6301 device structure */ +struct ces6301_device { + struct v4l2_ctrl_handler ctrls_vcm; + struct v4l2_ctrl *focus; + struct v4l2_subdev sd; + struct v4l2_device vdev; + u16 current_val; + + unsigned short current_related_pos; + unsigned short current_lens_pos; + unsigned int max_current; + unsigned int start_current; + unsigned int rated_current; + unsigned int step_mode; + unsigned int vcm_movefull_t; + unsigned int dlc_enable; + unsigned int t_src; + unsigned int mclk; + unsigned int max_logicalpos; + + struct __kernel_old_timeval start_move_tv; + struct __kernel_old_timeval end_move_tv; + unsigned long move_ms; + + u32 module_index; + const char *module_facing; + struct rk_cam_vcm_cfg vcm_cfg; + + struct gpio_desc *xsd_gpio; + struct regulator *supply; + struct i2c_client *client; + bool power_on; +}; + +struct TimeTabel_s { + unsigned int t_src;/* time of slew rate control */ + unsigned int step00;/* S[1:0] /MCLK[1:0] step period */ + unsigned int step01; + unsigned int step10; + unsigned int step11; +}; + +static const struct TimeTabel_s ces6301_lsc_time_table[] = {/* 1/10us */ + {0b10000, 1360, 2720, 5440, 10880}, + {0b10001, 1300, 2600, 5200, 10400}, + {0b10010, 1250, 2500, 5000, 10000}, + {0b10011, 1200, 2400, 4800, 9600}, + {0b10100, 1160, 2320, 4640, 9280}, + {0b10101, 1120, 2240, 4480, 8960}, + {0b10110, 1080, 2160, 4320, 8640}, + {0b10111, 1040, 2080, 4160, 8320}, + {0b11000, 1010, 2020, 4040, 8080}, + {0b11001, 980, 1960, 3920, 7840}, + {0b11010, 950, 1900, 3800, 7600}, + {0b11011, 920, 1840, 3680, 7360}, + {0b11100, 890, 1780, 3560, 7120}, + {0b11101, 870, 1740, 3480, 6960}, + {0b11110, 850, 1700, 3400, 6800}, + {0b11111, 830, 1660, 3320, 6640}, + {0b00000, 810, 1620, 3240, 6480}, + {0b00001, 790, 1580, 3160, 6320}, + {0b00010, 775, 1550, 3100, 6200}, + {0b00011, 760, 1520, 3040, 6080}, + {0b00100, 745, 1490, 2980, 5960}, + {0b00101, 730, 1460, 2920, 5840}, + {0b00110, 715, 1430, 2860, 5720}, + {0b00111, 700, 1400, 2800, 5600}, + {0b01000, 690, 1380, 2760, 5520}, + {0b01001, 680, 1360, 2720, 5440}, + {0b01010, 670, 1340, 2680, 5360}, + {0b01011, 660, 1320, 2640, 5280}, + {0b01100, 655, 1310, 2620, 5240}, + {0b01101, 650, 1300, 2600, 5200}, + {0b01110, 645, 1290, 2580, 5160}, + {0b01111, 640, 1280, 2560, 5120}, + {REG_NULL, 0, 0, 0, 0}, +}; + +static const struct TimeTabel_s ces6301_dlc_time_table[] = {/* us */ + {0b10000, 21250, 10630, 5310, 2660}, + {0b10001, 20310, 10160, 5080, 2540}, + {0b10010, 19530, 9770, 4880, 2440}, + {0b10011, 18750, 9380, 4690, 2340}, + {0b10100, 18130, 9060, 4530, 2270}, + {0b10101, 17500, 8750, 4380, 2190}, + {0b10110, 16880, 8440, 4220, 2110}, + {0b10111, 16250, 8130, 4060, 2030}, + {0b11000, 15780, 7890, 3950, 1970}, + {0b11001, 15310, 7660, 3830, 1910}, + {0b11010, 14840, 7420, 3710, 1860}, + {0b11011, 14380, 7190, 3590, 1800}, + {0b11100, 13910, 6950, 3480, 1740}, + {0b11101, 13590, 6800, 3400, 1700}, + {0b11110, 13280, 6640, 3320, 1660}, + {0b11111, 12970, 6480, 3240, 1620}, + {0b00000, 12660, 6330, 3160, 1580}, + {0b00001, 12340, 6170, 3090, 1540}, + {0b00010, 12110, 6050, 3030, 1510}, + {0b00011, 11880, 5940, 2970, 1480}, + {0b00100, 11640, 5820, 2910, 1460}, + {0b00101, 11410, 5700, 2850, 1430}, + {0b00110, 11170, 5590, 2790, 1400}, + {0b00111, 10940, 5470, 2730, 1370}, + {0b01000, 10780, 5390, 2700, 1350}, + {0b01001, 10630, 5310, 2660, 1330}, + {0b01010, 10470, 5230, 2620, 1310}, + {0b01011, 10310, 5160, 2580, 1290}, + {0b01100, 10230, 5120, 2560, 1280}, + {0b01101, 10160, 5080, 2540, 1270}, + {0b01110, 10080, 5040, 2520, 1260}, + {0b01111, 10000, 5000, 2500, 1250}, + {REG_NULL, 0, 0, 0, 0}, +}; + +static inline struct ces6301_device *to_ces6301_vcm(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct ces6301_device, ctrls_vcm); +} + +static inline struct ces6301_device *sd_to_ces6301_vcm(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct ces6301_device, sd); +} + +static int ces6301_read_msg(struct i2c_client *client, + unsigned char *msb, unsigned char *lsb) +{ + int ret = 0; + struct i2c_msg msg[1]; + unsigned char data[2]; + int retries; + + if (!client->adapter) { + dev_err(&client->dev, "client->adapter NULL\n"); + return -ENODEV; + } + + for (retries = 0; retries < 5; retries++) { + msg->addr = client->addr; + msg->flags = I2C_M_RD; + msg->len = 2; + msg->buf = data; + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret == 1) { + dev_dbg(&client->dev, + "%s: vcm i2c ok, addr 0x%x, data 0x%x, 0x%x\n", + __func__, msg->addr, data[0], data[1]); + + *msb = data[0]; + *lsb = data[1]; + return 0; + } + + dev_info(&client->dev, + "retrying I2C... %d\n", retries); + retries++; + msleep(20); + } + dev_err(&client->dev, + "%s: i2c read to failed with error %d\n", __func__, ret); + return ret; +} + +static int ces6301_write_msg(struct i2c_client *client, + u8 msb, u8 lsb) +{ + int ret = 0; + struct i2c_msg msg[1]; + unsigned char data[2]; + int retries; + + if (!client->adapter) { + dev_err(&client->dev, "client->adapter NULL\n"); + return -ENODEV; + } + + for (retries = 0; retries < 5; retries++) { + msg->addr = client->addr; + msg->flags = 0; + msg->len = 2; + msg->buf = data; + + data[0] = msb; + data[1] = lsb; + + ret = i2c_transfer(client->adapter, msg, 1); + usleep_range(50, 100); + + if (ret == 1) { + dev_dbg(&client->dev, + "%s: vcm i2c ok, addr 0x%x, data 0x%x, 0x%x\n", + __func__, msg->addr, data[0], data[1]); + return 0; + } + + dev_info(&client->dev, + "retrying I2C... %d\n", retries); + msleep(20); + } + dev_err(&client->dev, + "i2c write to failed with error %d\n", ret); + return ret; +} + +static unsigned int ces6301_move_time(struct ces6301_device *dev_vcm, + unsigned int move_pos) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + unsigned int move_time_ms = 200; + unsigned int step_period_lsc = 0; + unsigned int step_period_dlc = 0; + unsigned int codes_per_step = 1; + unsigned int step_case; + int table_cnt = 0; + int i = 0; + + if (dev_vcm->dlc_enable) { + step_case = dev_vcm->mclk & 0x3; + table_cnt = sizeof(ces6301_dlc_time_table) / + sizeof(struct TimeTabel_s); + for (i = 0; i < table_cnt; i++) { + if (ces6301_dlc_time_table[i].t_src == dev_vcm->t_src) + break; + } + } else { + step_case = dev_vcm->step_mode & 0x3; + table_cnt = sizeof(ces6301_lsc_time_table) / + sizeof(struct TimeTabel_s); + for (i = 0; i < table_cnt; i++) { + if (ces6301_lsc_time_table[i].t_src == dev_vcm->t_src) + break; + } + } + + if (i >= table_cnt) + i = 0; + + switch (step_case) { + case 0: + step_period_lsc = ces6301_lsc_time_table[i].step00; + step_period_dlc = ces6301_dlc_time_table[i].step00; + break; + case 1: + step_period_lsc = ces6301_lsc_time_table[i].step01; + step_period_dlc = ces6301_dlc_time_table[i].step01; + break; + case 2: + step_period_lsc = ces6301_lsc_time_table[i].step10; + step_period_dlc = ces6301_dlc_time_table[i].step10; + break; + case 3: + step_period_lsc = ces6301_lsc_time_table[i].step11; + step_period_dlc = ces6301_dlc_time_table[i].step11; + break; + default: + dev_err(&client->dev, + "%s: step_case is error %d\n", + __func__, step_case); + break; + } + codes_per_step = (dev_vcm->step_mode & 0x0c) >> 2; + if (codes_per_step > 1) + codes_per_step = 1 << (codes_per_step - 1); + + if (!dev_vcm->dlc_enable) { + if (!codes_per_step) + move_time_ms = (step_period_lsc * move_pos + 9999) / 10000; + else + move_time_ms = (step_period_lsc * move_pos / codes_per_step + 9999) / 10000; + } else { + move_time_ms = (step_period_dlc + 999) / 1000; + } + + return move_time_ms; +} + +static int ces6301_get_dac(struct ces6301_device *dev_vcm, unsigned int *cur_dac) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + int ret; + unsigned char lsb = 0; + unsigned char msb = 0; + unsigned int abs_step; + + ret = ces6301_read_msg(client, &msb, &lsb); + if (ret != 0) + goto err; + + abs_step = (((unsigned int)(msb & 0x3FU)) << 4U) | + (((unsigned int)lsb) >> 4U); + + *cur_dac = abs_step; + dev_dbg(&client->dev, "%s: get dac %d\n", __func__, *cur_dac); + return 0; + +err: + dev_err(&client->dev, + "%s: failed with error %d\n", __func__, ret); + return ret; +} + +static int ces6301_set_dac(struct ces6301_device *dev_vcm, + unsigned int dest_dac) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + int ret; + + unsigned char msb, lsb; + + msb = (0x00U | ((dest_dac & 0x3F0U) >> 4U)); + lsb = (((dest_dac & 0x0FU) << 4U) | dev_vcm->step_mode); + ret = ces6301_write_msg(client, msb, lsb); + if (ret != 0) + goto err; + + return ret; +err: + dev_err(&client->dev, + "%s: failed with error %d\n", __func__, ret); + return ret; +} + +static int ces6301_get_pos(struct ces6301_device *dev_vcm, + unsigned int *cur_pos) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + unsigned int dac, position, range; + int ret; + + range = dev_vcm->rated_current - dev_vcm->start_current; + ret = ces6301_get_dac(dev_vcm, &dac); + if (!ret) { + if (dac <= dev_vcm->start_current) { + position = dev_vcm->max_logicalpos; + } else if ((dac > dev_vcm->start_current) && + (dac <= dev_vcm->rated_current)) { + position = (dac - dev_vcm->start_current) * dev_vcm->max_logicalpos / range; + position = dev_vcm->max_logicalpos - position; + } else { + position = 0; + } + + *cur_pos = position; + + dev_dbg(&client->dev, "%s: get position %d, dac %d\n", __func__, *cur_pos, dac); + return 0; + } + + dev_err(&client->dev, + "%s: failed with error %d\n", __func__, ret); + return ret; +} + +static int ces6301_set_pos(struct ces6301_device *dev_vcm, + unsigned int dest_pos) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + unsigned int position; + unsigned int range; + int ret; + + range = dev_vcm->rated_current - dev_vcm->start_current; + if (dest_pos >= dev_vcm->max_logicalpos) + position = dev_vcm->start_current; + else + position = dev_vcm->start_current + + (range * (dev_vcm->max_logicalpos - dest_pos) / dev_vcm->max_logicalpos); + + if (position > CES6301_MAX_REG) + position = CES6301_MAX_REG; + + dev_vcm->current_lens_pos = position; + dev_vcm->current_related_pos = dest_pos; + + ret = ces6301_set_dac(dev_vcm, position); + dev_dbg(&client->dev, "%s: set position %d, dac %d\n", __func__, dest_pos, position); + + return ret; +} + +static int ces6301_get_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ces6301_device *dev_vcm = to_ces6301_vcm(ctrl); + + if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) + return ces6301_get_pos(dev_vcm, &ctrl->val); + + return -EINVAL; +} + +static int ces6301_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ces6301_device *dev_vcm = to_ces6301_vcm(ctrl); + struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + unsigned int dest_pos = ctrl->val; + int move_pos; + long mv_us; + int ret = 0; + + if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) { + if (dest_pos > dev_vcm->max_logicalpos) { + dev_err(&client->dev, + "%s dest_pos is error. %d > %d\n", + __func__, dest_pos, dev_vcm->max_logicalpos); + return -EINVAL; + } + /* calculate move time */ + move_pos = dev_vcm->current_related_pos - dest_pos; + if (move_pos < 0) + move_pos = -move_pos; + + ret = ces6301_set_pos(dev_vcm, dest_pos); + if (dev_vcm->dlc_enable) + dev_vcm->move_ms = dev_vcm->vcm_movefull_t; + else + dev_vcm->move_ms = + ((dev_vcm->vcm_movefull_t * (uint32_t)move_pos) / + dev_vcm->max_logicalpos); + + dev_dbg(&client->dev, + "dest_pos %d, dac %d, move_ms %ld\n", + dest_pos, dev_vcm->current_lens_pos, dev_vcm->move_ms); + + dev_vcm->start_move_tv = ns_to_kernel_old_timeval(ktime_get_ns()); + mv_us = dev_vcm->start_move_tv.tv_usec + + dev_vcm->move_ms * 1000; + if (mv_us >= 1000000) { + dev_vcm->end_move_tv.tv_sec = + dev_vcm->start_move_tv.tv_sec + 1; + dev_vcm->end_move_tv.tv_usec = mv_us - 1000000; + } else { + dev_vcm->end_move_tv.tv_sec = + dev_vcm->start_move_tv.tv_sec; + dev_vcm->end_move_tv.tv_usec = mv_us; + } + } + + return ret; +} + +static const struct v4l2_ctrl_ops ces6301_vcm_ctrl_ops = { + .g_volatile_ctrl = ces6301_get_ctrl, + .s_ctrl = ces6301_set_ctrl, +}; + +static int ces6301_init(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ces6301_device *ces6301_dev = sd_to_ces6301_vcm(sd); + unsigned char data = 0x0; + int ret = 0; + + // need to wait 5ms after power on reset + usleep_range(5000, 6000); + + /* 1. Protection off*/ + ret = ces6301_write_msg(client, 0xEC, 0xA3); + if (ret) + goto err; + + /* 2 DLC and MCLK[1:0] setting */ + data = (ces6301_dev->mclk & 0x3) | 0x04 | + ((ces6301_dev->dlc_enable << 0x3) & 0x08); + ret = ces6301_write_msg(client, 0xA1, data); + if (ret) + goto err; + + /* 3 T_SRC[4:0] setting */ + data = (ces6301_dev->t_src << 0x3) & 0xf8; + ret = ces6301_write_msg(client, 0xF2, data); + if (ret) + goto err; + + /* 4 Protection on */ + ret = ces6301_write_msg(client, 0xDC, 0x51); + if (ret) + goto err; + + return 0; +err: + dev_err(&client->dev, "failed with error %d\n", ret); + return -1; +} + +static int ces6301_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ces6301_device *dev_vcm = sd_to_ces6301_vcm(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned int move_time; + int dac = 0; + int rval; + + rval = pm_runtime_get_sync(sd->dev); + if (rval < 0) { + pm_runtime_put_noidle(sd->dev); + return rval; + } + + ces6301_init(client); + + dev_dbg(&client->dev, "%s: current_lens_pos %d, current_related_pos %d\n", + __func__, dev_vcm->current_lens_pos, dev_vcm->current_related_pos); + move_time = 1000 * ces6301_move_time(dev_vcm, CES6301_GRADUAL_MOVELENS_STEPS); + while (dac <= dev_vcm->current_lens_pos) { + ces6301_set_dac(dev_vcm, dac); + usleep_range(move_time, move_time + 1000); + dac += CES6301_GRADUAL_MOVELENS_STEPS; + if (dac >= dev_vcm->current_lens_pos) + break; + } + + if (dac > dev_vcm->current_lens_pos) { + dac = dev_vcm->current_lens_pos; + ces6301_set_dac(dev_vcm, dac); + } + + return 0; +} + +static int ces6301_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ces6301_device *dev_vcm = sd_to_ces6301_vcm(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int dac = dev_vcm->current_lens_pos; + unsigned int move_time; + + dev_dbg(&client->dev, "%s: current_lens_pos %d, current_related_pos %d\n", + __func__, dev_vcm->current_lens_pos, dev_vcm->current_related_pos); + move_time = 1000 * ces6301_move_time(dev_vcm, CES6301_GRADUAL_MOVELENS_STEPS); + while (dac >= 0) { + ces6301_set_dac(dev_vcm, dac); + usleep_range(move_time, move_time + 1000); + dac -= CES6301_GRADUAL_MOVELENS_STEPS; + if (dac <= 0) + break; + } + + if (dac < 0) { + dac = 0; + ces6301_set_dac(dev_vcm, dac); + } + + pm_runtime_put(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops ces6301_int_ops = { + .open = ces6301_open, + .close = ces6301_close, +}; + +static void ces6301_update_vcm_cfg(struct ces6301_device *dev_vcm) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + + if (dev_vcm->max_current == 0) { + dev_err(&client->dev, "max current is zero"); + return; + } + + dev_vcm->start_current = dev_vcm->vcm_cfg.start_ma * + CES6301_MAX_REG / dev_vcm->max_current; + dev_vcm->rated_current = dev_vcm->vcm_cfg.rated_ma * + CES6301_MAX_REG / dev_vcm->max_current; + dev_vcm->step_mode = dev_vcm->vcm_cfg.step_mode; + + dev_dbg(&client->dev, + "vcm_cfg: %d, %d, %d, max_current %d\n", + dev_vcm->vcm_cfg.start_ma, + dev_vcm->vcm_cfg.rated_ma, + dev_vcm->vcm_cfg.step_mode, + dev_vcm->max_current); +} + +static long ces6301_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + struct ces6301_device *dev_vcm = sd_to_ces6301_vcm(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct rk_cam_vcm_tim *vcm_tim; + struct rk_cam_vcm_cfg *vcm_cfg; + unsigned int max_logicalpos; + int ret = 0; + + if (cmd == RK_VIDIOC_VCM_TIMEINFO) { + vcm_tim = (struct rk_cam_vcm_tim *)arg; + + vcm_tim->vcm_start_t.tv_sec = dev_vcm->start_move_tv.tv_sec; + vcm_tim->vcm_start_t.tv_usec = + dev_vcm->start_move_tv.tv_usec; + vcm_tim->vcm_end_t.tv_sec = dev_vcm->end_move_tv.tv_sec; + vcm_tim->vcm_end_t.tv_usec = dev_vcm->end_move_tv.tv_usec; + + dev_dbg(&client->dev, "ces6301_get_move_res 0x%lx, 0x%lx, 0x%lx, 0x%lx\n", + vcm_tim->vcm_start_t.tv_sec, + vcm_tim->vcm_start_t.tv_usec, + vcm_tim->vcm_end_t.tv_sec, + vcm_tim->vcm_end_t.tv_usec); + } else if (cmd == RK_VIDIOC_GET_VCM_CFG) { + vcm_cfg = (struct rk_cam_vcm_cfg *)arg; + + vcm_cfg->start_ma = dev_vcm->vcm_cfg.start_ma; + vcm_cfg->rated_ma = dev_vcm->vcm_cfg.rated_ma; + vcm_cfg->step_mode = dev_vcm->vcm_cfg.step_mode; + } else if (cmd == RK_VIDIOC_SET_VCM_CFG) { + vcm_cfg = (struct rk_cam_vcm_cfg *)arg; + + dev_vcm->vcm_cfg.start_ma = vcm_cfg->start_ma; + dev_vcm->vcm_cfg.rated_ma = vcm_cfg->rated_ma; + dev_vcm->vcm_cfg.step_mode = vcm_cfg->step_mode; + ces6301_update_vcm_cfg(dev_vcm); + } else if (cmd == RK_VIDIOC_SET_VCM_MAX_LOGICALPOS) { + max_logicalpos = *(unsigned int *)arg; + + if (max_logicalpos > 0) { + dev_vcm->max_logicalpos = max_logicalpos; + __v4l2_ctrl_modify_range(dev_vcm->focus, + 0, dev_vcm->max_logicalpos, 1, dev_vcm->max_logicalpos); + } + dev_dbg(&client->dev, + "max_logicalpos %d\n", max_logicalpos); + } else { + dev_err(&client->dev, + "cmd 0x%x not supported\n", cmd); + return -EINVAL; + } + + return ret; +} + +#ifdef CONFIG_COMPAT +static long ces6301_compat_ioctl32(struct v4l2_subdev *sd, + unsigned int cmd, unsigned long arg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + void __user *up = compat_ptr(arg); + struct rk_cam_compat_vcm_tim compat_vcm_tim; + struct rk_cam_vcm_tim vcm_tim; + struct rk_cam_vcm_cfg vcm_cfg; + unsigned int max_logicalpos; + long ret; + + if (cmd == RK_VIDIOC_COMPAT_VCM_TIMEINFO) { + struct rk_cam_compat_vcm_tim __user *p32 = up; + + ret = ces6301_ioctl(sd, RK_VIDIOC_VCM_TIMEINFO, &vcm_tim); + compat_vcm_tim.vcm_start_t.tv_sec = vcm_tim.vcm_start_t.tv_sec; + compat_vcm_tim.vcm_start_t.tv_usec = vcm_tim.vcm_start_t.tv_usec; + compat_vcm_tim.vcm_end_t.tv_sec = vcm_tim.vcm_end_t.tv_sec; + compat_vcm_tim.vcm_end_t.tv_usec = vcm_tim.vcm_end_t.tv_usec; + + put_user(compat_vcm_tim.vcm_start_t.tv_sec, + &p32->vcm_start_t.tv_sec); + put_user(compat_vcm_tim.vcm_start_t.tv_usec, + &p32->vcm_start_t.tv_usec); + put_user(compat_vcm_tim.vcm_end_t.tv_sec, + &p32->vcm_end_t.tv_sec); + put_user(compat_vcm_tim.vcm_end_t.tv_usec, + &p32->vcm_end_t.tv_usec); + } else if (cmd == RK_VIDIOC_GET_VCM_CFG) { + ret = ces6301_ioctl(sd, RK_VIDIOC_GET_VCM_CFG, &vcm_cfg); + if (!ret) { + ret = copy_to_user(up, &vcm_cfg, sizeof(vcm_cfg)); + if (ret) + ret = -EFAULT; + } + } else if (cmd == RK_VIDIOC_SET_VCM_CFG) { + ret = copy_from_user(&vcm_cfg, up, sizeof(vcm_cfg)); + if (!ret) + ret = ces6301_ioctl(sd, cmd, &vcm_cfg); + else + ret = -EFAULT; + } else if (cmd == RK_VIDIOC_SET_VCM_MAX_LOGICALPOS) { + ret = copy_from_user(&max_logicalpos, up, sizeof(max_logicalpos)); + if (!ret) + ret = ces6301_ioctl(sd, cmd, &max_logicalpos); + else + ret = -EFAULT; + } else { + dev_err(&client->dev, + "cmd 0x%x not supported\n", cmd); + return -EINVAL; + } + + return ret; +} +#endif + +static const struct v4l2_subdev_core_ops ces6301_core_ops = { + .ioctl = ces6301_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = ces6301_compat_ioctl32 +#endif +}; + +static const struct v4l2_subdev_ops ces6301_ops = { + .core = &ces6301_core_ops, +}; + +static void ces6301_subdev_cleanup(struct ces6301_device *ces6301_dev) +{ + v4l2_device_unregister_subdev(&ces6301_dev->sd); + v4l2_device_unregister(&ces6301_dev->vdev); + v4l2_ctrl_handler_free(&ces6301_dev->ctrls_vcm); + media_entity_cleanup(&ces6301_dev->sd.entity); +} + +static int ces6301_init_controls(struct ces6301_device *dev_vcm) +{ + struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm; + const struct v4l2_ctrl_ops *ops = &ces6301_vcm_ctrl_ops; + + v4l2_ctrl_handler_init(hdl, 1); + + dev_vcm->focus = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, + 0, dev_vcm->max_logicalpos, 1, dev_vcm->max_logicalpos); + + if (hdl->error) + dev_err(dev_vcm->sd.dev, "%s fail error: 0x%x\n", + __func__, hdl->error); + dev_vcm->sd.ctrl_handler = hdl; + return hdl->error; +} + +#define USED_SYS_DEBUG +#ifdef USED_SYS_DEBUG +static ssize_t set_dacval(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ces6301_device *dev_vcm = sd_to_ces6301_vcm(sd); + int val = 0; + int ret = 0; + + ret = kstrtoint(buf, 0, &val); + if (!ret) + ces6301_set_dac(dev_vcm, val); + + return count; +} + +static ssize_t get_dacval(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ces6301_device *dev_vcm = sd_to_ces6301_vcm(sd); + unsigned int dac = 0; + + ces6301_get_dac(dev_vcm, &dac); + return sprintf(buf, "%u\n", dac); +} + +static struct device_attribute attributes[] = { + __ATTR(dacval, 0600, get_dacval, set_dacval), +}; + +static int add_sysfs_interfaces(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(attributes); i++) + if (device_create_file(dev, attributes + i)) + goto undo; + return 0; +undo: + for (i--; i >= 0 ; i--) + device_remove_file(dev, attributes + i); + dev_err(dev, "%s: failed to create sysfs interface\n", __func__); + return -ENODEV; +} + +static int remove_sysfs_interfaces(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(attributes); i++) + device_remove_file(dev, attributes + i); + return 0; +} +#else +static inline int add_sysfs_interfaces(struct device *dev) +{ + return 0; +} + +static inline int remove_sysfs_interfaces(struct device *dev) +{ + return 0; +} +#endif + +static int ces6301_set_power(struct ces6301_device *ces6301, bool on) +{ + struct i2c_client *client = ces6301->client; + int ret = 0; + + dev_info(&client->dev, "%s(%d) on(%d)\n", __func__, __LINE__, on); + + if (ces6301->power_on == !!on) + goto unlock_and_return; + + if (on) { + ret = regulator_enable(ces6301->supply); + if (ret < 0) { + dev_err(&client->dev, "Failed to enable regulator\n"); + goto unlock_and_return; + } + ces6301->power_on = true; + } else { + ret = regulator_disable(ces6301->supply); + if (ret < 0) { + dev_err(&client->dev, "Failed to disable regulator\n"); + goto unlock_and_return; + } + ces6301->power_on = false; + } + +unlock_and_return: + return ret; +} + +static int ces6301_check_i2c(struct ces6301_device *ces6301, + struct i2c_client *client) +{ + struct device *dev = &client->dev; + int ret; + + // need to wait 12ms after poweron + usleep_range(12000, 12500); + ret = ces6301_write_msg(client, 0xEC, 0xA3); + + if (!ret) + dev_info(dev, "check ces6301 connection OK!\n"); + else + dev_info(dev, "ces6301 not connect!\n"); + + return ret; +} + +static int ces6301_configure_regulator(struct ces6301_device *ces6301) +{ + struct i2c_client *client = ces6301->client; + int ret = 0; + + ces6301->supply = devm_regulator_get(&client->dev, "avdd"); + if (IS_ERR(ces6301->supply)) { + ret = PTR_ERR(ces6301->supply); + if (ret != -EPROBE_DEFER) + dev_err(&client->dev, "could not get regulator avdd\n"); + return ret; + } + ces6301->power_on = false; + return ret; +} + +static int ces6301_parse_dt_property(struct i2c_client *client, + struct ces6301_device *dev_vcm) +{ + struct device_node *np = of_node_get(client->dev.of_node); + int ret; + + if (of_property_read_u32(np, + OF_CAMERA_VCMDRV_MAX_CURRENT, + (unsigned int *)&dev_vcm->max_current)) { + dev_vcm->max_current = CES6301_MAX_CURRENT; + dev_info(&client->dev, + "could not get module %s from dts!\n", + OF_CAMERA_VCMDRV_MAX_CURRENT); + } + if (dev_vcm->max_current == 0) + dev_vcm->max_current = CES6301_MAX_CURRENT; + + if (of_property_read_u32(np, + OF_CAMERA_VCMDRV_START_CURRENT, + (unsigned int *)&dev_vcm->vcm_cfg.start_ma)) { + dev_vcm->vcm_cfg.start_ma = CES6301_DEFAULT_START_CURRENT; + dev_info(&client->dev, + "could not get module %s from dts!\n", + OF_CAMERA_VCMDRV_START_CURRENT); + } + if (of_property_read_u32(np, + OF_CAMERA_VCMDRV_RATED_CURRENT, + (unsigned int *)&dev_vcm->vcm_cfg.rated_ma)) { + dev_vcm->vcm_cfg.rated_ma = CES6301_DEFAULT_RATED_CURRENT; + dev_info(&client->dev, + "could not get module %s from dts!\n", + OF_CAMERA_VCMDRV_RATED_CURRENT); + } + if (of_property_read_u32(np, + OF_CAMERA_VCMDRV_STEP_MODE, + (unsigned int *)&dev_vcm->vcm_cfg.step_mode)) { + dev_vcm->vcm_cfg.step_mode = CES6301_DEFAULT_STEP_MODE; + dev_info(&client->dev, + "could not get module %s from dts!\n", + OF_CAMERA_VCMDRV_STEP_MODE); + } + if (of_property_read_u32(np, + OF_CAMERA_VCMDRV_DLC_ENABLE, + (unsigned int *)&dev_vcm->dlc_enable)) { + dev_vcm->dlc_enable = CES6301_DEFAULT_DLC_EN; + dev_info(&client->dev, + "could not get module %s from dts!\n", + OF_CAMERA_VCMDRV_DLC_ENABLE); + } + if (of_property_read_u32(np, + OF_CAMERA_VCMDRV_MCLK, + (unsigned int *)&dev_vcm->mclk)) { + dev_vcm->mclk = CES6301_DEFAULT_MCLK; + dev_info(&client->dev, + "could not get module %s from dts!\n", + OF_CAMERA_VCMDRV_MCLK); + } + if (of_property_read_u32(np, + OF_CAMERA_VCMDRV_T_SRC, + (unsigned int *)&dev_vcm->t_src)) { + dev_vcm->t_src = CES6301_DEFAULT_T_SRC; + dev_info(&client->dev, + "could not get module %s from dts!\n", + OF_CAMERA_VCMDRV_T_SRC); + } + + dev_vcm->xsd_gpio = devm_gpiod_get(&client->dev, "xsd", GPIOD_OUT_HIGH); + if (IS_ERR(dev_vcm->xsd_gpio)) + dev_warn(&client->dev, "Failed to get xsd-gpios\n"); + + ret = of_property_read_u32(np, RKMODULE_CAMERA_MODULE_INDEX, + &dev_vcm->module_index); + ret |= of_property_read_string(np, RKMODULE_CAMERA_MODULE_FACING, + &dev_vcm->module_facing); + if (ret) { + dev_err(&client->dev, + "could not get module information!\n"); + return -EINVAL; + } + + dev_vcm->client = client; + ret = ces6301_configure_regulator(dev_vcm); + if (ret) { + dev_err(&client->dev, "Failed to get power regulator!\n"); + return ret; + } + + dev_dbg(&client->dev, "current: %d, %d, %d, dlc_en: %d, t_src: %d, mclk: %d", + dev_vcm->max_current, + dev_vcm->start_current, + dev_vcm->rated_current, + dev_vcm->dlc_enable, + dev_vcm->t_src, + dev_vcm->mclk); + + return 0; +} + +static int ces6301_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ces6301_device *ces6301_dev; + struct v4l2_subdev *sd; + char facing[2]; + int ret; + + dev_info(&client->dev, "probing...\n"); + ces6301_dev = devm_kzalloc(&client->dev, sizeof(*ces6301_dev), + GFP_KERNEL); + if (ces6301_dev == NULL) + return -ENOMEM; + + ret = ces6301_parse_dt_property(client, ces6301_dev); + if (ret) + return ret; + v4l2_i2c_subdev_init(&ces6301_dev->sd, client, &ces6301_ops); + ces6301_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ces6301_dev->sd.internal_ops = &ces6301_int_ops; + + ces6301_dev->max_logicalpos = VCMDRV_MAX_LOG; + ret = ces6301_init_controls(ces6301_dev); + if (ret) + goto err_cleanup; + + ret = media_entity_pads_init(&ces6301_dev->sd.entity, 0, NULL); + if (ret < 0) + goto err_cleanup; + + ret = ces6301_set_power(ces6301_dev, true); + if (ret) + goto err_cleanup; + + ret = ces6301_check_i2c(ces6301_dev, client); + if (ret) + goto err_power_off; + + sd = &ces6301_dev->sd; + sd->entity.function = MEDIA_ENT_F_LENS; + + memset(facing, 0, sizeof(facing)); + if (strcmp(ces6301_dev->module_facing, "back") == 0) + facing[0] = 'b'; + else + facing[0] = 'f'; + + snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s", + ces6301_dev->module_index, facing, + CES6301_NAME, dev_name(sd->dev)); + ret = v4l2_async_register_subdev(sd); + if (ret) + dev_err(&client->dev, "v4l2 async register subdev failed\n"); + + ces6301_update_vcm_cfg(ces6301_dev); + ces6301_dev->move_ms = 0; + ces6301_dev->current_related_pos = ces6301_dev->max_logicalpos; + ces6301_dev->current_lens_pos = ces6301_dev->start_current; + ces6301_dev->start_move_tv = ns_to_kernel_old_timeval(ktime_get_ns()); + ces6301_dev->end_move_tv = ns_to_kernel_old_timeval(ktime_get_ns()); + ces6301_dev->vcm_movefull_t = + ces6301_move_time(ces6301_dev, CES6301_MAX_REG); + pm_runtime_enable(&client->dev); + + add_sysfs_interfaces(&client->dev); + dev_info(&client->dev, "probing successful\n"); + + return 0; + +err_power_off: + ces6301_set_power(ces6301_dev, false); +err_cleanup: + ces6301_subdev_cleanup(ces6301_dev); + dev_err(&client->dev, "Probe failed: %d\n", ret); + return ret; +} + +static void ces6301_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ces6301_device *ces6301_dev = sd_to_ces6301_vcm(sd); + + remove_sysfs_interfaces(&client->dev); + pm_runtime_disable(&client->dev); + ces6301_subdev_cleanup(ces6301_dev); + +} + +static int __maybe_unused ces6301_vcm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ces6301_device *dev_vcm = sd_to_ces6301_vcm(sd); + + ces6301_set_power(dev_vcm, false); + return 0; +} + +static int __maybe_unused ces6301_vcm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ces6301_device *dev_vcm = sd_to_ces6301_vcm(sd); + + ces6301_set_power(dev_vcm, true); + return 0; +} + +static const struct i2c_device_id ces6301_id_table[] = { + { CES6301_NAME, 0 }, + { { 0 } } +}; +MODULE_DEVICE_TABLE(i2c, ces6301_id_table); + +static const struct of_device_id ces6301_of_table[] = { + { .compatible = "chipextra,ces6301" }, + { { 0 } } +}; +MODULE_DEVICE_TABLE(of, ces6301_of_table); + +static const struct dev_pm_ops ces6301_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ces6301_vcm_suspend, ces6301_vcm_resume) + SET_RUNTIME_PM_OPS(ces6301_vcm_suspend, ces6301_vcm_resume, NULL) +}; + +static struct i2c_driver ces6301_i2c_driver = { + .driver = { + .name = CES6301_NAME, + .pm = &ces6301_pm_ops, + .of_match_table = ces6301_of_table, + }, + .probe = &ces6301_probe, + .remove = &ces6301_remove, + .id_table = ces6301_id_table, +}; + +module_i2c_driver(ces6301_i2c_driver); + +MODULE_DESCRIPTION("CES6301 VCM driver"); +MODULE_LICENSE("GPL"); From afdcc361c7c2c2fd3aa75654a8bba2d2ea4227ab Mon Sep 17 00:00:00 2001 From: Jianwei Fan Date: Mon, 26 Feb 2024 14:58:36 +0800 Subject: [PATCH 12/28] media: i2c: ov8865: add camera driver Signed-off-by: Jianwei Fan Change-Id: I8e0209d87cb15a71dcca7e1121cc239906c2d0b6 --- drivers/media/i2c/ov8865.c | 4364 ++++++++++++------------------------ 1 file changed, 1413 insertions(+), 2951 deletions(-) diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c index cae1866134a0..25aff0a8d307 100644 --- a/drivers/media/i2c/ov8865.c +++ b/drivers/media/i2c/ov8865.c @@ -1,3169 +1,1631 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: GPL-2.0 /* - * Copyright 2020 Kévin L'hôpital - * Copyright 2020 Bootlin - * Author: Paul Kocialkowski + * ov8865 driver + * Copyright (C) 2024 Rockchip Electronics Co., Ltd. */ #include -#include #include +#include +#include #include -#include #include -#include #include +#include +#include #include -#include +#include +#include +#include +#include +#include + +#include +#include +#include #include #include +#include #include #include #include +#include -/* Register definitions */ +#define DRIVER_VERSION KERNEL_VERSION(0, 0x01, 0x00) -/* System */ +#ifndef V4L2_CID_DIGITAL_GAIN +#define V4L2_CID_DIGITAL_GAIN V4L2_CID_GAIN +#endif -#define OV8865_SW_STANDBY_REG 0x100 -#define OV8865_SW_STANDBY_STREAM_ON BIT(0) +#define MIPI_FREQ 360000000U +#define OV8865_PIXEL_RATE (MIPI_FREQ * 2LL * 4LL / 10LL) -#define OV8865_SW_RESET_REG 0x103 -#define OV8865_SW_RESET_RESET BIT(0) +#define OV8865_XVCLK_FREQ 24000000 -#define OV8865_PLL_CTRL0_REG 0x300 -#define OV8865_PLL_CTRL0_PRE_DIV(v) ((v) & GENMASK(2, 0)) -#define OV8865_PLL_CTRL1_REG 0x301 -#define OV8865_PLL_CTRL1_MUL_H(v) (((v) & GENMASK(9, 8)) >> 8) -#define OV8865_PLL_CTRL2_REG 0x302 -#define OV8865_PLL_CTRL2_MUL_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_PLL_CTRL3_REG 0x303 -#define OV8865_PLL_CTRL3_M_DIV(v) (((v) - 1) & GENMASK(3, 0)) -#define OV8865_PLL_CTRL4_REG 0x304 -#define OV8865_PLL_CTRL4_MIPI_DIV(v) ((v) & GENMASK(1, 0)) -#define OV8865_PLL_CTRL5_REG 0x305 -#define OV8865_PLL_CTRL5_SYS_PRE_DIV(v) ((v) & GENMASK(1, 0)) -#define OV8865_PLL_CTRL6_REG 0x306 -#define OV8865_PLL_CTRL6_SYS_DIV(v) (((v) - 1) & BIT(0)) +#define CHIP_ID 0x008865 +#define OV8865_REG_CHIP_ID 0x300a -#define OV8865_PLL_CTRL8_REG 0x308 -#define OV8865_PLL_CTRL9_REG 0x309 -#define OV8865_PLL_CTRLA_REG 0x30a -#define OV8865_PLL_CTRLA_PRE_DIV_HALF(v) (((v) - 1) & BIT(0)) -#define OV8865_PLL_CTRLB_REG 0x30b -#define OV8865_PLL_CTRLB_PRE_DIV(v) ((v) & GENMASK(2, 0)) -#define OV8865_PLL_CTRLC_REG 0x30c -#define OV8865_PLL_CTRLC_MUL_H(v) (((v) & GENMASK(9, 8)) >> 8) -#define OV8865_PLL_CTRLD_REG 0x30d -#define OV8865_PLL_CTRLD_MUL_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_PLL_CTRLE_REG 0x30e -#define OV8865_PLL_CTRLE_SYS_DIV(v) ((v) & GENMASK(2, 0)) -#define OV8865_PLL_CTRLF_REG 0x30f -#define OV8865_PLL_CTRLF_SYS_PRE_DIV(v) (((v) - 1) & GENMASK(3, 0)) -#define OV8865_PLL_CTRL10_REG 0x310 -#define OV8865_PLL_CTRL11_REG 0x311 -#define OV8865_PLL_CTRL12_REG 0x312 -#define OV8865_PLL_CTRL12_PRE_DIV_HALF(v) ((((v) - 1) << 4) & BIT(4)) -#define OV8865_PLL_CTRL12_DAC_DIV(v) (((v) - 1) & GENMASK(3, 0)) +#define OV8865_REG_CTRL_MODE 0x0100 +#define OV8865_MODE_SW_STANDBY 0x0 +#define OV8865_MODE_STREAMING 0x1 -#define OV8865_PLL_CTRL1B_REG 0x31b -#define OV8865_PLL_CTRL1C_REG 0x31c +#define OV8865_REG_EXPOSURE 0x3500 +#define OV8865_EXPOSURE_MIN 2 +#define OV8865_EXPOSURE_STEP 1 +#define OV8865_VTS_MAX 0x7fff -#define OV8865_PLL_CTRL1E_REG 0x31e -#define OV8865_PLL_CTRL1E_PLL1_NO_LAT BIT(3) +#define OV8865_REG_GAIN_H 0x3508 +#define OV8865_REG_GAIN_L 0x3509 +#define OV8865_GAIN_H_MASK 0x1f +#define OV8865_GAIN_H_SHIFT 8 +#define OV8865_GAIN_L_MASK 0xff +#define OV8865_GAIN_MIN 0x80 +#define OV8865_GAIN_MAX 0x1fff +#define OV8865_GAIN_STEP 1 +#define OV8865_GAIN_DEFAULT 0x80 -#define OV8865_PAD_OEN0_REG 0x3000 +#define OV8865_REG_TEST_PATTERN 0x5e00 +#define OV8865_TEST_PATTERN_ENABLE 0x80 +#define OV8865_TEST_PATTERN_DISABLE 0x0 -#define OV8865_PAD_OEN2_REG 0x3002 +#define OV8865_REG_VTS 0x380e -#define OV8865_CLK_RST5_REG 0x3005 +#define REG_NULL 0xFFFF -#define OV8865_CHIP_ID_HH_REG 0x300a -#define OV8865_CHIP_ID_HH_VALUE 0x00 -#define OV8865_CHIP_ID_H_REG 0x300b -#define OV8865_CHIP_ID_H_VALUE 0x88 -#define OV8865_CHIP_ID_L_REG 0x300c -#define OV8865_CHIP_ID_L_VALUE 0x65 -#define OV8865_PAD_OUT2_REG 0x300d +#define OV8865_REG_VALUE_08BIT 1 +#define OV8865_REG_VALUE_16BIT 2 +#define OV8865_REG_VALUE_24BIT 3 -#define OV8865_PAD_SEL2_REG 0x3010 -#define OV8865_PAD_PK_REG 0x3011 -#define OV8865_PAD_PK_DRIVE_STRENGTH_1X (0 << 5) -#define OV8865_PAD_PK_DRIVE_STRENGTH_2X (1 << 5) -#define OV8865_PAD_PK_DRIVE_STRENGTH_3X (2 << 5) -#define OV8865_PAD_PK_DRIVE_STRENGTH_4X (3 << 5) +#define OV8865_LANES 4 +#define OV8865_BITS_PER_SAMPLE 10 -#define OV8865_PUMP_CLK_DIV_REG 0x3015 -#define OV8865_PUMP_CLK_DIV_PUMP_N(v) (((v) << 4) & GENMASK(6, 4)) -#define OV8865_PUMP_CLK_DIV_PUMP_P(v) ((v) & GENMASK(2, 0)) +#define OV8865_CHIP_REVISION_REG 0x302A -#define OV8865_MIPI_SC_CTRL0_REG 0x3018 -#define OV8865_MIPI_SC_CTRL0_LANES(v) ((((v) - 1) << 5) & \ - GENMASK(7, 5)) -#define OV8865_MIPI_SC_CTRL0_MIPI_EN BIT(4) -#define OV8865_MIPI_SC_CTRL0_UNKNOWN BIT(1) -#define OV8865_MIPI_SC_CTRL0_LANES_PD_MIPI BIT(0) -#define OV8865_MIPI_SC_CTRL1_REG 0x3019 -#define OV8865_CLK_RST0_REG 0x301a -#define OV8865_CLK_RST1_REG 0x301b -#define OV8865_CLK_RST2_REG 0x301c -#define OV8865_CLK_RST3_REG 0x301d -#define OV8865_CLK_RST4_REG 0x301e +#define OF_CAMERA_PINCTRL_STATE_DEFAULT "rockchip,camera_default" +#define OF_CAMERA_PINCTRL_STATE_SLEEP "rockchip,camera_sleep" -#define OV8865_PCLK_SEL_REG 0x3020 -#define OV8865_PCLK_SEL_PCLK_DIV_MASK BIT(3) -#define OV8865_PCLK_SEL_PCLK_DIV(v) ((((v) - 1) << 3) & BIT(3)) +#define OV8865_NAME "ov8865" +#define OV8865_MEDIA_BUS_FMT MEDIA_BUS_FMT_SBGGR10_1X10 -#define OV8865_MISC_CTRL_REG 0x3021 -#define OV8865_MIPI_SC_CTRL2_REG 0x3022 -#define OV8865_MIPI_SC_CTRL2_CLK_LANES_PD_MIPI BIT(1) -#define OV8865_MIPI_SC_CTRL2_PD_MIPI_RST_SYNC BIT(0) - -#define OV8865_MIPI_BIT_SEL_REG 0x3031 -#define OV8865_MIPI_BIT_SEL(v) (((v) << 0) & GENMASK(4, 0)) -#define OV8865_CLK_SEL0_REG 0x3032 -#define OV8865_CLK_SEL0_PLL1_SYS_SEL(v) (((v) << 7) & BIT(7)) -#define OV8865_CLK_SEL1_REG 0x3033 -#define OV8865_CLK_SEL1_MIPI_EOF BIT(5) -#define OV8865_CLK_SEL1_UNKNOWN BIT(2) -#define OV8865_CLK_SEL1_PLL_SCLK_SEL_MASK BIT(1) -#define OV8865_CLK_SEL1_PLL_SCLK_SEL(v) (((v) << 1) & BIT(1)) - -#define OV8865_SCLK_CTRL_REG 0x3106 -#define OV8865_SCLK_CTRL_SCLK_DIV(v) (((v) << 4) & GENMASK(7, 4)) -#define OV8865_SCLK_CTRL_SCLK_PRE_DIV(v) (((v) << 2) & GENMASK(3, 2)) -#define OV8865_SCLK_CTRL_UNKNOWN BIT(0) - -/* Exposure/gain */ - -#define OV8865_EXPOSURE_CTRL_HH_REG 0x3500 -#define OV8865_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(19, 16)) >> 16) -#define OV8865_EXPOSURE_CTRL_H_REG 0x3501 -#define OV8865_EXPOSURE_CTRL_H(v) (((v) & GENMASK(15, 8)) >> 8) -#define OV8865_EXPOSURE_CTRL_L_REG 0x3502 -#define OV8865_EXPOSURE_CTRL_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_EXPOSURE_GAIN_MANUAL_REG 0x3503 -#define OV8865_INTEGRATION_TIME_MARGIN 8 - -#define OV8865_GAIN_CTRL_H_REG 0x3508 -#define OV8865_GAIN_CTRL_H(v) (((v) & GENMASK(12, 8)) >> 8) -#define OV8865_GAIN_CTRL_L_REG 0x3509 -#define OV8865_GAIN_CTRL_L(v) ((v) & GENMASK(7, 0)) - -/* Timing */ - -#define OV8865_CROP_START_X_H_REG 0x3800 -#define OV8865_CROP_START_X_H(v) (((v) & GENMASK(11, 8)) >> 8) -#define OV8865_CROP_START_X_L_REG 0x3801 -#define OV8865_CROP_START_X_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_CROP_START_Y_H_REG 0x3802 -#define OV8865_CROP_START_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) -#define OV8865_CROP_START_Y_L_REG 0x3803 -#define OV8865_CROP_START_Y_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_CROP_END_X_H_REG 0x3804 -#define OV8865_CROP_END_X_H(v) (((v) & GENMASK(11, 8)) >> 8) -#define OV8865_CROP_END_X_L_REG 0x3805 -#define OV8865_CROP_END_X_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_CROP_END_Y_H_REG 0x3806 -#define OV8865_CROP_END_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) -#define OV8865_CROP_END_Y_L_REG 0x3807 -#define OV8865_CROP_END_Y_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_OUTPUT_SIZE_X_H_REG 0x3808 -#define OV8865_OUTPUT_SIZE_X_H(v) (((v) & GENMASK(11, 8)) >> 8) -#define OV8865_OUTPUT_SIZE_X_L_REG 0x3809 -#define OV8865_OUTPUT_SIZE_X_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_OUTPUT_SIZE_Y_H_REG 0x380a -#define OV8865_OUTPUT_SIZE_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) -#define OV8865_OUTPUT_SIZE_Y_L_REG 0x380b -#define OV8865_OUTPUT_SIZE_Y_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_HTS_H_REG 0x380c -#define OV8865_HTS_H(v) (((v) & GENMASK(11, 8)) >> 8) -#define OV8865_HTS_L_REG 0x380d -#define OV8865_HTS_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_VTS_H_REG 0x380e -#define OV8865_VTS_H(v) (((v) & GENMASK(11, 8)) >> 8) -#define OV8865_VTS_L_REG 0x380f -#define OV8865_VTS_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_TIMING_MAX_VTS 0xffff -#define OV8865_TIMING_MIN_VTS 0x04 -#define OV8865_OFFSET_X_H_REG 0x3810 -#define OV8865_OFFSET_X_H(v) (((v) & GENMASK(15, 8)) >> 8) -#define OV8865_OFFSET_X_L_REG 0x3811 -#define OV8865_OFFSET_X_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_OFFSET_Y_H_REG 0x3812 -#define OV8865_OFFSET_Y_H(v) (((v) & GENMASK(14, 8)) >> 8) -#define OV8865_OFFSET_Y_L_REG 0x3813 -#define OV8865_OFFSET_Y_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_INC_X_ODD_REG 0x3814 -#define OV8865_INC_X_ODD(v) ((v) & GENMASK(4, 0)) -#define OV8865_INC_X_EVEN_REG 0x3815 -#define OV8865_INC_X_EVEN(v) ((v) & GENMASK(4, 0)) -#define OV8865_VSYNC_START_H_REG 0x3816 -#define OV8865_VSYNC_START_H(v) (((v) & GENMASK(15, 8)) >> 8) -#define OV8865_VSYNC_START_L_REG 0x3817 -#define OV8865_VSYNC_START_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_VSYNC_END_H_REG 0x3818 -#define OV8865_VSYNC_END_H(v) (((v) & GENMASK(15, 8)) >> 8) -#define OV8865_VSYNC_END_L_REG 0x3819 -#define OV8865_VSYNC_END_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_HSYNC_FIRST_H_REG 0x381a -#define OV8865_HSYNC_FIRST_H(v) (((v) & GENMASK(15, 8)) >> 8) -#define OV8865_HSYNC_FIRST_L_REG 0x381b -#define OV8865_HSYNC_FIRST_L(v) ((v) & GENMASK(7, 0)) - -#define OV8865_FORMAT1_REG 0x3820 -#define OV8865_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -#define OV8865_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) -#define OV8865_FORMAT2_REG 0x3821 -#define OV8865_FORMAT2_HSYNC_EN BIT(6) -#define OV8865_FORMAT2_FST_VBIN_EN BIT(5) -#define OV8865_FORMAT2_FST_HBIN_EN BIT(4) -#define OV8865_FORMAT2_ISP_HORZ_VAR2_EN BIT(3) -#define OV8865_FORMAT2_FLIP_HORZ_ISP_EN BIT(2) -#define OV8865_FORMAT2_FLIP_HORZ_SENSOR_EN BIT(1) -#define OV8865_FORMAT2_SYNC_HBIN_EN BIT(0) - -#define OV8865_INC_Y_ODD_REG 0x382a -#define OV8865_INC_Y_ODD(v) ((v) & GENMASK(4, 0)) -#define OV8865_INC_Y_EVEN_REG 0x382b -#define OV8865_INC_Y_EVEN(v) ((v) & GENMASK(4, 0)) - -#define OV8865_ABLC_NUM_REG 0x3830 -#define OV8865_ABLC_NUM(v) ((v) & GENMASK(4, 0)) - -#define OV8865_ZLINE_NUM_REG 0x3836 -#define OV8865_ZLINE_NUM(v) ((v) & GENMASK(4, 0)) - -#define OV8865_AUTO_SIZE_CTRL_REG 0x3841 -#define OV8865_AUTO_SIZE_CTRL_OFFSET_Y_REG BIT(5) -#define OV8865_AUTO_SIZE_CTRL_OFFSET_X_REG BIT(4) -#define OV8865_AUTO_SIZE_CTRL_CROP_END_Y_REG BIT(3) -#define OV8865_AUTO_SIZE_CTRL_CROP_END_X_REG BIT(2) -#define OV8865_AUTO_SIZE_CTRL_CROP_START_Y_REG BIT(1) -#define OV8865_AUTO_SIZE_CTRL_CROP_START_X_REG BIT(0) -#define OV8865_AUTO_SIZE_X_OFFSET_H_REG 0x3842 -#define OV8865_AUTO_SIZE_X_OFFSET_L_REG 0x3843 -#define OV8865_AUTO_SIZE_Y_OFFSET_H_REG 0x3844 -#define OV8865_AUTO_SIZE_Y_OFFSET_L_REG 0x3845 -#define OV8865_AUTO_SIZE_BOUNDARIES_REG 0x3846 -#define OV8865_AUTO_SIZE_BOUNDARIES_Y(v) (((v) << 4) & GENMASK(7, 4)) -#define OV8865_AUTO_SIZE_BOUNDARIES_X(v) ((v) & GENMASK(3, 0)) - -/* PSRAM */ - -#define OV8865_PSRAM_CTRL8_REG 0x3f08 - -/* Black Level */ - -#define OV8865_BLC_CTRL0_REG 0x4000 -#define OV8865_BLC_CTRL0_TRIG_RANGE_EN BIT(7) -#define OV8865_BLC_CTRL0_TRIG_FORMAT_EN BIT(6) -#define OV8865_BLC_CTRL0_TRIG_GAIN_EN BIT(5) -#define OV8865_BLC_CTRL0_TRIG_EXPOSURE_EN BIT(4) -#define OV8865_BLC_CTRL0_TRIG_MANUAL_EN BIT(3) -#define OV8865_BLC_CTRL0_FREEZE_EN BIT(2) -#define OV8865_BLC_CTRL0_ALWAYS_EN BIT(1) -#define OV8865_BLC_CTRL0_FILTER_EN BIT(0) -#define OV8865_BLC_CTRL1_REG 0x4001 -#define OV8865_BLC_CTRL1_DITHER_EN BIT(7) -#define OV8865_BLC_CTRL1_ZERO_LINE_DIFF_EN BIT(6) -#define OV8865_BLC_CTRL1_COL_SHIFT_256 (0 << 4) -#define OV8865_BLC_CTRL1_COL_SHIFT_128 (1 << 4) -#define OV8865_BLC_CTRL1_COL_SHIFT_64 (2 << 4) -#define OV8865_BLC_CTRL1_COL_SHIFT_32 (3 << 4) -#define OV8865_BLC_CTRL1_OFFSET_LIMIT_EN BIT(2) -#define OV8865_BLC_CTRL1_COLUMN_CANCEL_EN BIT(1) -#define OV8865_BLC_CTRL2_REG 0x4002 -#define OV8865_BLC_CTRL3_REG 0x4003 -#define OV8865_BLC_CTRL4_REG 0x4004 -#define OV8865_BLC_CTRL5_REG 0x4005 -#define OV8865_BLC_CTRL6_REG 0x4006 -#define OV8865_BLC_CTRL7_REG 0x4007 -#define OV8865_BLC_CTRL8_REG 0x4008 -#define OV8865_BLC_CTRL9_REG 0x4009 -#define OV8865_BLC_CTRLA_REG 0x400a -#define OV8865_BLC_CTRLB_REG 0x400b -#define OV8865_BLC_CTRLC_REG 0x400c -#define OV8865_BLC_CTRLD_REG 0x400d -#define OV8865_BLC_CTRLD_OFFSET_TRIGGER(v) ((v) & GENMASK(7, 0)) - -#define OV8865_BLC_CTRL1F_REG 0x401f -#define OV8865_BLC_CTRL1F_RB_REVERSE BIT(3) -#define OV8865_BLC_CTRL1F_INTERPOL_X_EN BIT(2) -#define OV8865_BLC_CTRL1F_INTERPOL_Y_EN BIT(1) - -#define OV8865_BLC_ANCHOR_LEFT_START_H_REG 0x4020 -#define OV8865_BLC_ANCHOR_LEFT_START_H(v) (((v) & GENMASK(11, 8)) >> 8) -#define OV8865_BLC_ANCHOR_LEFT_START_L_REG 0x4021 -#define OV8865_BLC_ANCHOR_LEFT_START_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_BLC_ANCHOR_LEFT_END_H_REG 0x4022 -#define OV8865_BLC_ANCHOR_LEFT_END_H(v) (((v) & GENMASK(11, 8)) >> 8) -#define OV8865_BLC_ANCHOR_LEFT_END_L_REG 0x4023 -#define OV8865_BLC_ANCHOR_LEFT_END_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_BLC_ANCHOR_RIGHT_START_H_REG 0x4024 -#define OV8865_BLC_ANCHOR_RIGHT_START_H(v) (((v) & GENMASK(11, 8)) >> 8) -#define OV8865_BLC_ANCHOR_RIGHT_START_L_REG 0x4025 -#define OV8865_BLC_ANCHOR_RIGHT_START_L(v) ((v) & GENMASK(7, 0)) -#define OV8865_BLC_ANCHOR_RIGHT_END_H_REG 0x4026 -#define OV8865_BLC_ANCHOR_RIGHT_END_H(v) (((v) & GENMASK(11, 8)) >> 8) -#define OV8865_BLC_ANCHOR_RIGHT_END_L_REG 0x4027 -#define OV8865_BLC_ANCHOR_RIGHT_END_L(v) ((v) & GENMASK(7, 0)) - -#define OV8865_BLC_TOP_ZLINE_START_REG 0x4028 -#define OV8865_BLC_TOP_ZLINE_START(v) ((v) & GENMASK(5, 0)) -#define OV8865_BLC_TOP_ZLINE_NUM_REG 0x4029 -#define OV8865_BLC_TOP_ZLINE_NUM(v) ((v) & GENMASK(4, 0)) -#define OV8865_BLC_TOP_BLKLINE_START_REG 0x402a -#define OV8865_BLC_TOP_BLKLINE_START(v) ((v) & GENMASK(5, 0)) -#define OV8865_BLC_TOP_BLKLINE_NUM_REG 0x402b -#define OV8865_BLC_TOP_BLKLINE_NUM(v) ((v) & GENMASK(4, 0)) -#define OV8865_BLC_BOT_ZLINE_START_REG 0x402c -#define OV8865_BLC_BOT_ZLINE_START(v) ((v) & GENMASK(5, 0)) -#define OV8865_BLC_BOT_ZLINE_NUM_REG 0x402d -#define OV8865_BLC_BOT_ZLINE_NUM(v) ((v) & GENMASK(4, 0)) -#define OV8865_BLC_BOT_BLKLINE_START_REG 0x402e -#define OV8865_BLC_BOT_BLKLINE_START(v) ((v) & GENMASK(5, 0)) -#define OV8865_BLC_BOT_BLKLINE_NUM_REG 0x402f -#define OV8865_BLC_BOT_BLKLINE_NUM(v) ((v) & GENMASK(4, 0)) - -#define OV8865_BLC_OFFSET_LIMIT_REG 0x4034 -#define OV8865_BLC_OFFSET_LIMIT(v) ((v) & GENMASK(7, 0)) - -/* VFIFO */ - -#define OV8865_VFIFO_READ_START_H_REG 0x4600 -#define OV8865_VFIFO_READ_START_H(v) (((v) & GENMASK(15, 8)) >> 8) -#define OV8865_VFIFO_READ_START_L_REG 0x4601 -#define OV8865_VFIFO_READ_START_L(v) ((v) & GENMASK(7, 0)) - -/* MIPI */ - -#define OV8865_MIPI_CTRL0_REG 0x4800 -#define OV8865_MIPI_CTRL1_REG 0x4801 -#define OV8865_MIPI_CTRL2_REG 0x4802 -#define OV8865_MIPI_CTRL3_REG 0x4803 -#define OV8865_MIPI_CTRL4_REG 0x4804 -#define OV8865_MIPI_CTRL5_REG 0x4805 -#define OV8865_MIPI_CTRL6_REG 0x4806 -#define OV8865_MIPI_CTRL7_REG 0x4807 -#define OV8865_MIPI_CTRL8_REG 0x4808 - -#define OV8865_MIPI_FCNT_MAX_H_REG 0x4810 -#define OV8865_MIPI_FCNT_MAX_L_REG 0x4811 - -#define OV8865_MIPI_CTRL13_REG 0x4813 -#define OV8865_MIPI_CTRL14_REG 0x4814 -#define OV8865_MIPI_CTRL15_REG 0x4815 -#define OV8865_MIPI_EMBEDDED_DT_REG 0x4816 - -#define OV8865_MIPI_HS_ZERO_MIN_H_REG 0x4818 -#define OV8865_MIPI_HS_ZERO_MIN_L_REG 0x4819 -#define OV8865_MIPI_HS_TRAIL_MIN_H_REG 0x481a -#define OV8865_MIPI_HS_TRAIL_MIN_L_REG 0x481b -#define OV8865_MIPI_CLK_ZERO_MIN_H_REG 0x481c -#define OV8865_MIPI_CLK_ZERO_MIN_L_REG 0x481d -#define OV8865_MIPI_CLK_PREPARE_MAX_REG 0x481e -#define OV8865_MIPI_CLK_PREPARE_MIN_REG 0x481f -#define OV8865_MIPI_CLK_POST_MIN_H_REG 0x4820 -#define OV8865_MIPI_CLK_POST_MIN_L_REG 0x4821 -#define OV8865_MIPI_CLK_TRAIL_MIN_H_REG 0x4822 -#define OV8865_MIPI_CLK_TRAIL_MIN_L_REG 0x4823 -#define OV8865_MIPI_LPX_P_MIN_H_REG 0x4824 -#define OV8865_MIPI_LPX_P_MIN_L_REG 0x4825 -#define OV8865_MIPI_HS_PREPARE_MIN_REG 0x4826 -#define OV8865_MIPI_HS_PREPARE_MAX_REG 0x4827 -#define OV8865_MIPI_HS_EXIT_MIN_H_REG 0x4828 -#define OV8865_MIPI_HS_EXIT_MIN_L_REG 0x4829 -#define OV8865_MIPI_UI_HS_ZERO_MIN_REG 0x482a -#define OV8865_MIPI_UI_HS_TRAIL_MIN_REG 0x482b -#define OV8865_MIPI_UI_CLK_ZERO_MIN_REG 0x482c -#define OV8865_MIPI_UI_CLK_PREPARE_REG 0x482d -#define OV8865_MIPI_UI_CLK_POST_MIN_REG 0x482e -#define OV8865_MIPI_UI_CLK_TRAIL_MIN_REG 0x482f -#define OV8865_MIPI_UI_LPX_P_MIN_REG 0x4830 -#define OV8865_MIPI_UI_HS_PREPARE_REG 0x4831 -#define OV8865_MIPI_UI_HS_EXIT_MIN_REG 0x4832 -#define OV8865_MIPI_PKT_START_SIZE_REG 0x4833 - -#define OV8865_MIPI_PCLK_PERIOD_REG 0x4837 -#define OV8865_MIPI_LP_GPIO0_REG 0x4838 -#define OV8865_MIPI_LP_GPIO1_REG 0x4839 - -#define OV8865_MIPI_CTRL3C_REG 0x483c -#define OV8865_MIPI_LP_GPIO4_REG 0x483d - -#define OV8865_MIPI_CTRL4A_REG 0x484a -#define OV8865_MIPI_CTRL4B_REG 0x484b -#define OV8865_MIPI_CTRL4C_REG 0x484c -#define OV8865_MIPI_LANE_TEST_PATTERN_REG 0x484d -#define OV8865_MIPI_FRAME_END_DELAY_REG 0x484e -#define OV8865_MIPI_CLOCK_TEST_PATTERN_REG 0x484f -#define OV8865_MIPI_LANE_SEL01_REG 0x4850 -#define OV8865_MIPI_LANE_SEL01_LANE0(v) (((v) << 0) & GENMASK(2, 0)) -#define OV8865_MIPI_LANE_SEL01_LANE1(v) (((v) << 4) & GENMASK(6, 4)) -#define OV8865_MIPI_LANE_SEL23_REG 0x4851 -#define OV8865_MIPI_LANE_SEL23_LANE2(v) (((v) << 0) & GENMASK(2, 0)) -#define OV8865_MIPI_LANE_SEL23_LANE3(v) (((v) << 4) & GENMASK(6, 4)) - -/* ISP */ - -#define OV8865_ISP_CTRL0_REG 0x5000 -#define OV8865_ISP_CTRL0_LENC_EN BIT(7) -#define OV8865_ISP_CTRL0_WHITE_BALANCE_EN BIT(4) -#define OV8865_ISP_CTRL0_DPC_BLACK_EN BIT(2) -#define OV8865_ISP_CTRL0_DPC_WHITE_EN BIT(1) -#define OV8865_ISP_CTRL1_REG 0x5001 -#define OV8865_ISP_CTRL1_BLC_EN BIT(0) -#define OV8865_ISP_CTRL2_REG 0x5002 -#define OV8865_ISP_CTRL2_DEBUG BIT(3) -#define OV8865_ISP_CTRL2_VARIOPIXEL_EN BIT(2) -#define OV8865_ISP_CTRL2_VSYNC_LATCH_EN BIT(0) -#define OV8865_ISP_CTRL3_REG 0x5003 - -#define OV8865_ISP_GAIN_RED_H_REG 0x5018 -#define OV8865_ISP_GAIN_RED_H(v) (((v) & GENMASK(13, 6)) >> 6) -#define OV8865_ISP_GAIN_RED_L_REG 0x5019 -#define OV8865_ISP_GAIN_RED_L(v) ((v) & GENMASK(5, 0)) -#define OV8865_ISP_GAIN_GREEN_H_REG 0x501a -#define OV8865_ISP_GAIN_GREEN_H(v) (((v) & GENMASK(13, 6)) >> 6) -#define OV8865_ISP_GAIN_GREEN_L_REG 0x501b -#define OV8865_ISP_GAIN_GREEN_L(v) ((v) & GENMASK(5, 0)) -#define OV8865_ISP_GAIN_BLUE_H_REG 0x501c -#define OV8865_ISP_GAIN_BLUE_H(v) (((v) & GENMASK(13, 6)) >> 6) -#define OV8865_ISP_GAIN_BLUE_L_REG 0x501d -#define OV8865_ISP_GAIN_BLUE_L(v) ((v) & GENMASK(5, 0)) - -/* VarioPixel */ - -#define OV8865_VAP_CTRL0_REG 0x5900 -#define OV8865_VAP_CTRL1_REG 0x5901 -#define OV8865_VAP_CTRL1_HSUB_COEF(v) ((((v) - 1) << 2) & \ - GENMASK(3, 2)) -#define OV8865_VAP_CTRL1_VSUB_COEF(v) (((v) - 1) & GENMASK(1, 0)) - -/* Pre-DSP */ - -#define OV8865_PRE_CTRL0_REG 0x5e00 -#define OV8865_PRE_CTRL0_PATTERN_EN BIT(7) -#define OV8865_PRE_CTRL0_ROLLING_BAR_EN BIT(6) -#define OV8865_PRE_CTRL0_TRANSPARENT_MODE BIT(5) -#define OV8865_PRE_CTRL0_SQUARES_BW_MODE BIT(4) -#define OV8865_PRE_CTRL0_PATTERN_COLOR_BARS 0 -#define OV8865_PRE_CTRL0_PATTERN_RANDOM_DATA 1 -#define OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES 2 -#define OV8865_PRE_CTRL0_PATTERN_BLACK 3 - -/* Pixel Array */ - -#define OV8865_NATIVE_WIDTH 3296 -#define OV8865_NATIVE_HEIGHT 2528 -#define OV8865_ACTIVE_START_LEFT 16 -#define OV8865_ACTIVE_START_TOP 40 -#define OV8865_ACTIVE_WIDTH 3264 -#define OV8865_ACTIVE_HEIGHT 2448 - -/* Macros */ - -#define ov8865_subdev_sensor(s) \ - container_of(s, struct ov8865_sensor, subdev) - -#define ov8865_ctrl_subdev(c) \ - (&container_of((c)->handler, struct ov8865_sensor, \ - ctrls.handler)->subdev) - -/* Data structures */ - -struct ov8865_register_value { - u16 address; - u8 value; - unsigned int delay_ms; +static const char * const ov8865_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ }; -/* - * PLL1 Clock Tree: - * - * +-< EXTCLK - * | - * +-+ pll_pre_div_half (0x30a [0]) - * | - * +-+ pll_pre_div (0x300 [2:0], special values: - * | 0: 1, 1: 1.5, 3: 2.5, 4: 3, 5: 4, 7: 8) - * +-+ pll_mul (0x301 [1:0], 0x302 [7:0]) - * | - * +-+ m_div (0x303 [3:0]) - * | | - * | +-> PHY_SCLK - * | | - * | +-+ mipi_div (0x304 [1:0], special values: 0: 4, 1: 5, 2: 6, 3: 8) - * | | - * | +-+ pclk_div (0x3020 [3]) - * | | - * | +-> PCLK - * | - * +-+ sys_pre_div (0x305 [1:0], special values: 0: 3, 1: 4, 2: 5, 3: 6) - * | - * +-+ sys_div (0x306 [0]) - * | - * +-+ sys_sel (0x3032 [7], 0: PLL1, 1: PLL2) - * | - * +-+ sclk_sel (0x3033 [1], 0: sys_sel, 1: PLL2 DAC_CLK) - * | - * +-+ sclk_pre_div (0x3106 [3:2], special values: - * | 0: 1, 1: 2, 2: 4, 3: 1) - * | - * +-+ sclk_div (0x3106 [7:4], special values: 0: 1) - * | - * +-> SCLK - */ +#define OV8865_NUM_SUPPLIES ARRAY_SIZE(ov8865_supply_names) -struct ov8865_pll1_config { - unsigned int pll_pre_div_half; - unsigned int pll_pre_div; - unsigned int pll_mul; - unsigned int m_div; - unsigned int mipi_div; - unsigned int pclk_div; - unsigned int sys_pre_div; - unsigned int sys_div; +struct regval { + u16 addr; + u8 val; }; -/* - * PLL2 Clock Tree: - * - * +-< EXTCLK - * | - * +-+ pll_pre_div_half (0x312 [4]) - * | - * +-+ pll_pre_div (0x30b [2:0], special values: - * | 0: 1, 1: 1.5, 3: 2.5, 4: 3, 5: 4, 7: 8) - * +-+ pll_mul (0x30c [1:0], 0x30d [7:0]) - * | - * +-+ dac_div (0x312 [3:0]) - * | | - * | +-> DAC_CLK - * | - * +-+ sys_pre_div (0x30f [3:0]) - * | - * +-+ sys_div (0x30e [2:0], special values: - * | 0: 1, 1: 1.5, 3: 2.5, 4: 3, 5: 3.5, 6: 4, 7:5) - * | - * +-+ sys_sel (0x3032 [7], 0: PLL1, 1: PLL2) - * | - * +-+ sclk_sel (0x3033 [1], 0: sys_sel, 1: PLL2 DAC_CLK) - * | - * +-+ sclk_pre_div (0x3106 [3:2], special values: - * | 0: 1, 1: 2, 2: 4, 3: 1) - * | - * +-+ sclk_div (0x3106 [7:4], special values: 0: 1) - * | - * +-> SCLK - */ - -struct ov8865_pll2_config { - unsigned int pll_pre_div_half; - unsigned int pll_pre_div; - unsigned int pll_mul; - unsigned int dac_div; - unsigned int sys_pre_div; - unsigned int sys_div; -}; - -struct ov8865_sclk_config { - unsigned int sys_sel; - unsigned int sclk_sel; - unsigned int sclk_pre_div; - unsigned int sclk_div; -}; - -struct ov8865_pll_configs { - const struct ov8865_pll1_config *pll1_config; - const struct ov8865_pll2_config *pll2_config_native; - const struct ov8865_pll2_config *pll2_config_binning; -}; - -/* Clock rate */ - -enum extclk_rate { - OV8865_19_2_MHZ, - OV8865_24_MHZ, - OV8865_NUM_SUPPORTED_RATES -}; - -static const unsigned long supported_extclk_rates[] = { - [OV8865_19_2_MHZ] = 19200000, - [OV8865_24_MHZ] = 24000000, -}; - -/* - * General formulas for (array-centered) mode calculation: - * - photo_array_width = 3296 - * - crop_start_x = (photo_array_width - output_size_x) / 2 - * - crop_end_x = crop_start_x + offset_x + output_size_x - 1 - * - * - photo_array_height = 2480 - * - crop_start_y = (photo_array_height - output_size_y) / 2 - * - crop_end_y = crop_start_y + offset_y + output_size_y - 1 - */ - struct ov8865_mode { - unsigned int crop_start_x; - unsigned int offset_x; - unsigned int output_size_x; - unsigned int crop_end_x; - unsigned int hts; - - unsigned int crop_start_y; - unsigned int offset_y; - unsigned int output_size_y; - unsigned int crop_end_y; - unsigned int vts; - - /* With auto size, only output and total sizes need to be set. */ - bool size_auto; - unsigned int size_auto_boundary_x; - unsigned int size_auto_boundary_y; - - bool binning_x; - bool binning_y; - bool variopixel; - unsigned int variopixel_hsub_coef; - unsigned int variopixel_vsub_coef; - - /* Bits for the format register, used for binning. */ - bool sync_hbin; - bool horz_var2; - - unsigned int inc_x_odd; - unsigned int inc_x_even; - unsigned int inc_y_odd; - unsigned int inc_y_even; - - unsigned int vfifo_read_start; - - unsigned int ablc_num; - unsigned int zline_num; - - unsigned int blc_top_zero_line_start; - unsigned int blc_top_zero_line_num; - unsigned int blc_top_black_line_start; - unsigned int blc_top_black_line_num; - - unsigned int blc_bottom_zero_line_start; - unsigned int blc_bottom_zero_line_num; - unsigned int blc_bottom_black_line_start; - unsigned int blc_bottom_black_line_num; - - u8 blc_col_shift_mask; - - unsigned int blc_anchor_left_start; - unsigned int blc_anchor_left_end; - unsigned int blc_anchor_right_start; - unsigned int blc_anchor_right_end; - - bool pll2_binning; - - const struct ov8865_register_value *register_values; - unsigned int register_values_count; + u32 bus_fmt; + u32 width; + u32 height; + struct v4l2_fract max_fps; + u32 hts_def; + u32 vts_def; + u32 exp_def; + u32 mipi_freq_idx; + u32 bpp; + const struct regval *reg_list; + u32 vc[PAD_MAX]; }; -struct ov8865_state { - const struct ov8865_mode *mode; - u32 mbus_code; +struct ov8865 { + struct i2c_client *client; + struct clk *xvclk; + struct gpio_desc *power_gpio; + struct gpio_desc *reset_gpio; + struct gpio_desc *pwdn_gpio; + struct regulator_bulk_data supplies[OV8865_NUM_SUPPLIES]; - bool streaming; + struct pinctrl *pinctrl; + struct pinctrl_state *pins_default; + struct pinctrl_state *pins_sleep; + + struct v4l2_subdev subdev; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *anal_gain; + struct v4l2_ctrl *digi_gain; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *test_pattern; + struct v4l2_ctrl *h_flip; + struct v4l2_ctrl *v_flip; + struct mutex mutex; + bool streaming; + const struct ov8865_mode *cur_mode; + + unsigned int lane_num; + unsigned int cfg_num; + + bool power_on; + + u32 module_index; + const char *module_facing; + const char *module_name; + const char *len_name; + struct rkmodule_inf module_inf; }; -struct ov8865_ctrls { - struct v4l2_ctrl *link_freq; - struct v4l2_ctrl *pixel_rate; - struct v4l2_ctrl *hblank; - struct v4l2_ctrl *vblank; - struct v4l2_ctrl *exposure; - - struct v4l2_ctrl_handler handler; -}; - -struct ov8865_sensor { - struct device *dev; - struct i2c_client *i2c_client; - struct gpio_desc *reset; - struct gpio_desc *powerdown; - struct regulator *avdd; - struct regulator *dvdd; - struct regulator *dovdd; - - unsigned long extclk_rate; - const struct ov8865_pll_configs *pll_configs; - struct clk *extclk; - - struct v4l2_fwnode_endpoint endpoint; - struct v4l2_subdev subdev; - struct media_pad pad; - - struct mutex mutex; - - struct ov8865_state state; - struct ov8865_ctrls ctrls; -}; - -/* Static definitions */ +#define to_ov8865(sd) container_of(sd, struct ov8865, subdev) /* - * PHY_SCLK = 720 MHz - * MIPI_PCLK = 90 MHz + * Xclk 24Mhz */ - -static const struct ov8865_pll1_config ov8865_pll1_config_native_19_2mhz = { - .pll_pre_div_half = 1, - .pll_pre_div = 2, - .pll_mul = 75, - .m_div = 1, - .mipi_div = 3, - .pclk_div = 1, - .sys_pre_div = 1, - .sys_div = 2, -}; - -static const struct ov8865_pll1_config ov8865_pll1_config_native_24mhz = { - .pll_pre_div_half = 1, - .pll_pre_div = 0, - .pll_mul = 30, - .m_div = 1, - .mipi_div = 3, - .pclk_div = 1, - .sys_pre_div = 1, - .sys_div = 2, +static const struct regval ov8865_global_regs[] = { + {0x0103, 0x01}, + {0x0100, 0x00}, + {0x3638, 0xff}, + {0x0302, 0x1e}, + {0x0303, 0x00}, + {0x0304, 0x03}, + {0x030e, 0x00}, + {0x030f, 0x09}, + {0x0312, 0x01}, + {0x031e, 0x0c}, + {0x3015, 0x01}, + {0x3018, 0x72}, + {0x3020, 0x93}, + {0x3022, 0x01}, + {0x3031, 0x0a}, + {0x3106, 0x01}, + {0x3305, 0xf1}, + {0x3308, 0x00}, + {0x3309, 0x28}, + {0x330a, 0x00}, + {0x330b, 0x20}, + {0x330c, 0x00}, + {0x330d, 0x00}, + {0x330e, 0x00}, + {0x330f, 0x40}, + {0x3307, 0x04}, + {0x3604, 0x04}, + {0x3602, 0x30}, + {0x3605, 0x00}, + {0x3607, 0x20}, + {0x3608, 0x11}, + {0x3609, 0x68}, + {0x360a, 0x40}, + {0x360c, 0xdd}, + {0x360e, 0x0c}, + {0x3610, 0x07}, + {0x3612, 0x86}, + {0x3613, 0x58}, + {0x3614, 0x28}, + {0x3617, 0x40}, + {0x3618, 0x5a}, + {0x3619, 0x9b}, + {0x361c, 0x00}, + {0x361d, 0x60}, + {0x3631, 0x60}, + {0x3633, 0x10}, + {0x3634, 0x10}, + {0x3635, 0x10}, + {0x3636, 0x10}, + {0x3641, 0x55}, + {0x3646, 0x86}, + {0x3647, 0x27}, + {0x364a, 0x1b}, + {0x3500, 0x00}, + {0x3501, 0x4c}, + {0x3502, 0x00}, + {0x3503, 0x00}, + {0x3508, 0x02}, + {0x3509, 0x00}, + {0x3700, 0x24}, + {0x3701, 0x0c}, + {0x3702, 0x28}, + {0x3703, 0x19}, + {0x3704, 0x14}, + {0x3705, 0x00}, + {0x3706, 0x38}, + {0x3707, 0x04}, + {0x3708, 0x24}, + {0x3709, 0x40}, + {0x370a, 0x00}, + {0x370b, 0xb8}, + {0x370c, 0x04}, + {0x3718, 0x12}, + {0x3719, 0x31}, + {0x3712, 0x42}, + {0x3714, 0x12}, + {0x371e, 0x19}, + {0x371f, 0x40}, + {0x3720, 0x05}, + {0x3721, 0x05}, + {0x3724, 0x02}, + {0x3725, 0x02}, + {0x3726, 0x06}, + {0x3728, 0x05}, + {0x3729, 0x02}, + {0x372a, 0x03}, + {0x372b, 0x53}, + {0x372c, 0xa3}, + {0x372d, 0x53}, + {0x372e, 0x06}, + {0x372f, 0x10}, + {0x3730, 0x01}, + {0x3731, 0x06}, + {0x3732, 0x14}, + {0x3733, 0x10}, + {0x3734, 0x40}, + {0x3736, 0x20}, + {0x373a, 0x02}, + {0x373b, 0x0c}, + {0x373c, 0x0a}, + {0x373e, 0x03}, + {0x3755, 0x40}, + {0x3758, 0x00}, + {0x3759, 0x4c}, + {0x375a, 0x06}, + {0x375b, 0x13}, + {0x375c, 0x40}, + {0x375d, 0x02}, + {0x375e, 0x00}, + {0x375f, 0x14}, + {0x3767, 0x1c}, + {0x3768, 0x04}, + {0x3769, 0x20}, + {0x376c, 0xc0}, + {0x376d, 0xc0}, + {0x376a, 0x08}, + {0x3761, 0x00}, + {0x3762, 0x00}, + {0x3763, 0x00}, + {0x3766, 0xff}, + {0x376b, 0x42}, + {0x3772, 0x23}, + {0x3773, 0x02}, + {0x3774, 0x16}, + {0x3775, 0x12}, + {0x3776, 0x08}, + {0x37a0, 0x44}, + {0x37a1, 0x3d}, + {0x37a2, 0x3d}, + {0x37a3, 0x01}, + {0x37a4, 0x00}, + {0x37a5, 0x08}, + {0x37a6, 0x00}, + {0x37a7, 0x44}, + {0x37a8, 0x58}, + {0x37a9, 0x58}, + {0x3760, 0x00}, + {0x376f, 0x01}, + {0x37aa, 0x44}, + {0x37ab, 0x2e}, + {0x37ac, 0x2e}, + {0x37ad, 0x33}, + {0x37ae, 0x0d}, + {0x37af, 0x0d}, + {0x37b0, 0x00}, + {0x37b1, 0x00}, + {0x37b2, 0x00}, + {0x37b3, 0x42}, + {0x37b4, 0x42}, + {0x37b5, 0x33}, + {0x37b6, 0x00}, + {0x37b7, 0x00}, + {0x37b8, 0x00}, + {0x37b9, 0xff}, + {0x3800, 0x00}, + {0x3801, 0x0c}, + {0x3802, 0x00}, + {0x3803, 0x0c}, + {0x3804, 0x0c}, + {0x3805, 0xd3}, + {0x3806, 0x09}, + {0x3807, 0xa3}, + {0x3808, 0x06}, + {0x3809, 0x60}, + {0x380a, 0x04}, + {0x380b, 0xc8}, + {0x380c, 0x07}, + {0x380d, 0x83}, + {0x380e, 0x04}, + {0x380f, 0xe0}, + {0x3810, 0x00}, + {0x3811, 0x04}, + {0x3813, 0x04}, + {0x3814, 0x03}, + {0x3815, 0x01}, + {0x3820, 0x00}, + {0x3821, 0x67}, + {0x382a, 0x03}, + {0x382b, 0x01}, + {0x3830, 0x08}, + {0x3836, 0x02}, + {0x3837, 0x18}, + {0x3841, 0xff}, + {0x3846, 0x88}, + {0x3d85, 0x06}, + {0x3d8c, 0x75}, + {0x3d8d, 0xef}, + {0x3f08, 0x0b}, + {0x4000, 0xf1}, + {0x4001, 0x14}, + {0x4005, 0x10}, + {0x400b, 0x0c}, + {0x400d, 0x10}, + {0x401b, 0x00}, + {0x401d, 0x00}, + {0x4020, 0x01}, + {0x4021, 0x20}, + {0x4022, 0x01}, + {0x4023, 0x9f}, + {0x4024, 0x03}, + {0x4025, 0xe0}, + {0x4026, 0x04}, + {0x4027, 0x5f}, + {0x4028, 0x00}, + {0x4029, 0x02}, + {0x402a, 0x04}, + {0x402b, 0x04}, + {0x402c, 0x02}, + {0x402d, 0x02}, + {0x402e, 0x08}, + {0x402f, 0x02}, + {0x401f, 0x00}, + {0x4034, 0x3f}, + {0x4300, 0xff}, + {0x4301, 0x00}, + {0x4302, 0x0f}, + {0x4500, 0x40}, + {0x4503, 0x10}, + {0x4601, 0x74}, + {0x481f, 0x32}, + {0x4837, 0x16}, + {0x4850, 0x10}, + {0x4851, 0x32}, + {0x4b00, 0x2a}, + {0x4b0d, 0x00}, + {0x4d00, 0x04}, + {0x4d01, 0x18}, + {0x4d02, 0xc3}, + {0x4d03, 0xff}, + {0x4d04, 0xff}, + {0x4d05, 0xff}, + {0x5000, 0x96}, + {0x5001, 0x01}, + {0x5002, 0x08}, + {0x5901, 0x00}, + {0x5e00, 0x00}, + {0x5e01, 0x41}, + {0x0100, 0x01}, + {0x5b00, 0x02}, + {0x5b01, 0xd0}, + {0x5b02, 0x03}, + {0x5b03, 0xff}, + {0x5b05, 0x6c}, + {0x5780, 0xfc}, + {0x5781, 0xdf}, + {0x5782, 0x3f}, + {0x5783, 0x08}, + {0x5784, 0x0c}, + {0x5786, 0x20}, + {0x5787, 0x40}, + {0x5788, 0x08}, + {0x5789, 0x08}, + {0x578a, 0x02}, + {0x578b, 0x01}, + {0x578c, 0x01}, + {0x578d, 0x0c}, + {0x578e, 0x02}, + {0x578f, 0x01}, + {0x5790, 0x01}, + {0x5800, 0x1d}, + {0x5801, 0x0e}, + {0x5802, 0x0c}, + {0x5803, 0x0c}, + {0x5804, 0x0f}, + {0x5805, 0x22}, + {0x5806, 0x0a}, + {0x5807, 0x06}, + {0x5808, 0x05}, + {0x5809, 0x05}, + {0x580a, 0x07}, + {0x580b, 0x0a}, + {0x580c, 0x06}, + {0x580d, 0x02}, + {0x580e, 0x00}, + {0x580f, 0x00}, + {0x5810, 0x03}, + {0x5811, 0x07}, + {0x5812, 0x06}, + {0x5813, 0x02}, + {0x5814, 0x00}, + {0x5815, 0x00}, + {0x5816, 0x03}, + {0x5817, 0x07}, + {0x5818, 0x09}, + {0x5819, 0x06}, + {0x581a, 0x04}, + {0x581b, 0x04}, + {0x581c, 0x06}, + {0x581d, 0x0a}, + {0x581e, 0x19}, + {0x581f, 0x0d}, + {0x5820, 0x0b}, + {0x5821, 0x0b}, + {0x5822, 0x0e}, + {0x5823, 0x22}, + {0x5824, 0x23}, + {0x5825, 0x28}, + {0x5826, 0x29}, + {0x5827, 0x27}, + {0x5828, 0x13}, + {0x5829, 0x26}, + {0x582a, 0x33}, + {0x582b, 0x32}, + {0x582c, 0x33}, + {0x582d, 0x16}, + {0x582e, 0x14}, + {0x582f, 0x30}, + {0x5830, 0x31}, + {0x5831, 0x30}, + {0x5832, 0x15}, + {0x5833, 0x26}, + {0x5834, 0x23}, + {0x5835, 0x21}, + {0x5836, 0x23}, + {0x5837, 0x05}, + {0x5838, 0x36}, + {0x5839, 0x27}, + {0x583a, 0x28}, + {0x583b, 0x26}, + {0x583c, 0x24}, + {0x583d, 0xdf}, + {REG_NULL, 0x00}, }; /* - * DAC_CLK = 360 MHz - * SCLK = 144 MHz + * XVCLK=24Mhz, sysclk=144Mhz + * MIPI 4 lane, 720Mbps/lane */ - -static const struct ov8865_pll2_config ov8865_pll2_config_native_19_2mhz = { - .pll_pre_div_half = 1, - .pll_pre_div = 5, - .pll_mul = 75, - .dac_div = 1, - .sys_pre_div = 1, - .sys_div = 3, +static const struct regval ov8865_3264x2448_30fps_regs[] = { + {0x0100, 0x00}, + {0x030f, 0x04}, + {0x3018, 0x72}, + {0x3106, 0x01}, + {0x3501, 0x98}, + {0x3502, 0x60}, + {0x3700, 0x48}, + {0x3701, 0x18}, + {0x3702, 0x50}, + {0x3703, 0x32}, + {0x3704, 0x28}, + {0x3706, 0x70}, + {0x3707, 0x08}, + {0x3708, 0x48}, + {0x3709, 0x80}, + {0x370a, 0x01}, + {0x370b, 0x70}, + {0x370c, 0x07}, + {0x3718, 0x14}, + {0x3712, 0x44}, + {0x371e, 0x31}, + {0x371f, 0x7f}, + {0x3720, 0x0a}, + {0x3721, 0x0a}, + {0x3724, 0x04}, + {0x3725, 0x04}, + {0x3726, 0x0c}, + {0x3728, 0x0a}, + {0x3729, 0x03}, + {0x372a, 0x06}, + {0x372b, 0xa6}, + {0x372c, 0xa6}, + {0x372d, 0xa6}, + {0x372e, 0x0c}, + {0x372f, 0x20}, + {0x3730, 0x02}, + {0x3731, 0x0c}, + {0x3732, 0x28}, + {0x3736, 0x30}, + {0x373a, 0x04}, + {0x373b, 0x18}, + {0x373c, 0x14}, + {0x373e, 0x06}, + {0x375a, 0x0c}, + {0x375b, 0x26}, + {0x375d, 0x04}, + {0x375f, 0x28}, + {0x3767, 0x1e}, + {0x3772, 0x46}, + {0x3773, 0x04}, + {0x3774, 0x2c}, + {0x3775, 0x13}, + {0x3776, 0x10}, + {0x37a0, 0x88}, + {0x37a1, 0x7a}, + {0x37a2, 0x7a}, + {0x37a3, 0x02}, + {0x37a5, 0x09}, + {0x37a7, 0x88}, + {0x37a8, 0xb0}, + {0x37a9, 0xb0}, + {0x37aa, 0x88}, + {0x37ab, 0x5c}, + {0x37ac, 0x5c}, + {0x37ad, 0x55}, + {0x37ae, 0x19}, + {0x37af, 0x19}, + {0x37b3, 0x84}, + {0x37b4, 0x84}, + {0x37b5, 0x66}, + {0x3808, 0x0c}, + {0x3809, 0xc0}, + {0x380a, 0x09}, + {0x380b, 0x90}, + {0x380c, 0x07}, + {0x380d, 0x98}, + {0x380e, 0x09}, + {0x380f, 0xa6}, + {0x3813, 0x02}, + {0x3814, 0x01}, + {0x3821, 0x46}, + {0x382a, 0x01}, + {0x382b, 0x01}, + {0x3830, 0x04}, + {0x3836, 0x01}, + {0x3846, 0x48}, + {0x3f08, 0x16}, + {0x4000, 0xf1}, + {0x4001, 0x04}, + {0x4020, 0x02}, + {0x4021, 0x40}, + {0x4022, 0x03}, + {0x4023, 0x3f}, + {0x4024, 0x07}, + {0x4025, 0xc0}, + {0x4026, 0x08}, + {0x4027, 0xbf}, + {0x402a, 0x04}, + {0x402b, 0x04}, + {0x402c, 0x02}, + {0x402d, 0x02}, + {0x402e, 0x08}, + {0x4500, 0x68}, + {0x4601, 0x10}, + {0x5002, 0x08}, + {0x5901, 0x00}, + {REG_NULL, 0x00}, }; -static const struct ov8865_pll2_config ov8865_pll2_config_native_24mhz = { - .pll_pre_div_half = 1, - .pll_pre_div = 0, - .pll_mul = 30, - .dac_div = 2, - .sys_pre_div = 5, - .sys_div = 0, -}; - -/* - * DAC_CLK = 360 MHz - * SCLK = 72 MHz - */ - -static const struct ov8865_pll2_config ov8865_pll2_config_binning_19_2mhz = { - .pll_pre_div_half = 1, - .pll_pre_div = 2, - .pll_mul = 75, - .dac_div = 2, - .sys_pre_div = 10, - .sys_div = 0, -}; - -static const struct ov8865_pll2_config ov8865_pll2_config_binning_24mhz = { - .pll_pre_div_half = 1, - .pll_pre_div = 0, - .pll_mul = 30, - .dac_div = 2, - .sys_pre_div = 10, - .sys_div = 0, -}; - -static const struct ov8865_pll_configs ov8865_pll_configs_19_2mhz = { - .pll1_config = &ov8865_pll1_config_native_19_2mhz, - .pll2_config_native = &ov8865_pll2_config_native_19_2mhz, - .pll2_config_binning = &ov8865_pll2_config_binning_19_2mhz, -}; - -static const struct ov8865_pll_configs ov8865_pll_configs_24mhz = { - .pll1_config = &ov8865_pll1_config_native_24mhz, - .pll2_config_native = &ov8865_pll2_config_native_24mhz, - .pll2_config_binning = &ov8865_pll2_config_binning_24mhz, -}; - -static const struct ov8865_pll_configs *ov8865_pll_configs[] = { - &ov8865_pll_configs_19_2mhz, - &ov8865_pll_configs_24mhz, -}; - -static const struct ov8865_sclk_config ov8865_sclk_config_native = { - .sys_sel = 1, - .sclk_sel = 0, - .sclk_pre_div = 0, - .sclk_div = 0, -}; - -static const struct ov8865_register_value ov8865_register_values_native[] = { - /* Sensor */ - - { 0x3700, 0x48 }, - { 0x3701, 0x18 }, - { 0x3702, 0x50 }, - { 0x3703, 0x32 }, - { 0x3704, 0x28 }, - { 0x3706, 0x70 }, - { 0x3707, 0x08 }, - { 0x3708, 0x48 }, - { 0x3709, 0x80 }, - { 0x370a, 0x01 }, - { 0x370b, 0x70 }, - { 0x370c, 0x07 }, - { 0x3718, 0x14 }, - { 0x3712, 0x44 }, - { 0x371e, 0x31 }, - { 0x371f, 0x7f }, - { 0x3720, 0x0a }, - { 0x3721, 0x0a }, - { 0x3724, 0x04 }, - { 0x3725, 0x04 }, - { 0x3726, 0x0c }, - { 0x3728, 0x0a }, - { 0x3729, 0x03 }, - { 0x372a, 0x06 }, - { 0x372b, 0xa6 }, - { 0x372c, 0xa6 }, - { 0x372d, 0xa6 }, - { 0x372e, 0x0c }, - { 0x372f, 0x20 }, - { 0x3730, 0x02 }, - { 0x3731, 0x0c }, - { 0x3732, 0x28 }, - { 0x3736, 0x30 }, - { 0x373a, 0x04 }, - { 0x373b, 0x18 }, - { 0x373c, 0x14 }, - { 0x373e, 0x06 }, - { 0x375a, 0x0c }, - { 0x375b, 0x26 }, - { 0x375d, 0x04 }, - { 0x375f, 0x28 }, - { 0x3767, 0x1e }, - { 0x3772, 0x46 }, - { 0x3773, 0x04 }, - { 0x3774, 0x2c }, - { 0x3775, 0x13 }, - { 0x3776, 0x10 }, - { 0x37a0, 0x88 }, - { 0x37a1, 0x7a }, - { 0x37a2, 0x7a }, - { 0x37a3, 0x02 }, - { 0x37a5, 0x09 }, - { 0x37a7, 0x88 }, - { 0x37a8, 0xb0 }, - { 0x37a9, 0xb0 }, - { 0x37aa, 0x88 }, - { 0x37ab, 0x5c }, - { 0x37ac, 0x5c }, - { 0x37ad, 0x55 }, - { 0x37ae, 0x19 }, - { 0x37af, 0x19 }, - { 0x37b3, 0x84 }, - { 0x37b4, 0x84 }, - { 0x37b5, 0x66 }, - - /* PSRAM */ - - { OV8865_PSRAM_CTRL8_REG, 0x16 }, - - /* ADC Sync */ - - { 0x4500, 0x68 }, -}; - -static const struct ov8865_register_value ov8865_register_values_binning[] = { - /* Sensor */ - - { 0x3700, 0x24 }, - { 0x3701, 0x0c }, - { 0x3702, 0x28 }, - { 0x3703, 0x19 }, - { 0x3704, 0x14 }, - { 0x3706, 0x38 }, - { 0x3707, 0x04 }, - { 0x3708, 0x24 }, - { 0x3709, 0x40 }, - { 0x370a, 0x00 }, - { 0x370b, 0xb8 }, - { 0x370c, 0x04 }, - { 0x3718, 0x12 }, - { 0x3712, 0x42 }, - { 0x371e, 0x19 }, - { 0x371f, 0x40 }, - { 0x3720, 0x05 }, - { 0x3721, 0x05 }, - { 0x3724, 0x02 }, - { 0x3725, 0x02 }, - { 0x3726, 0x06 }, - { 0x3728, 0x05 }, - { 0x3729, 0x02 }, - { 0x372a, 0x03 }, - { 0x372b, 0x53 }, - { 0x372c, 0xa3 }, - { 0x372d, 0x53 }, - { 0x372e, 0x06 }, - { 0x372f, 0x10 }, - { 0x3730, 0x01 }, - { 0x3731, 0x06 }, - { 0x3732, 0x14 }, - { 0x3736, 0x20 }, - { 0x373a, 0x02 }, - { 0x373b, 0x0c }, - { 0x373c, 0x0a }, - { 0x373e, 0x03 }, - { 0x375a, 0x06 }, - { 0x375b, 0x13 }, - { 0x375d, 0x02 }, - { 0x375f, 0x14 }, - { 0x3767, 0x1c }, - { 0x3772, 0x23 }, - { 0x3773, 0x02 }, - { 0x3774, 0x16 }, - { 0x3775, 0x12 }, - { 0x3776, 0x08 }, - { 0x37a0, 0x44 }, - { 0x37a1, 0x3d }, - { 0x37a2, 0x3d }, - { 0x37a3, 0x01 }, - { 0x37a5, 0x08 }, - { 0x37a7, 0x44 }, - { 0x37a8, 0x58 }, - { 0x37a9, 0x58 }, - { 0x37aa, 0x44 }, - { 0x37ab, 0x2e }, - { 0x37ac, 0x2e }, - { 0x37ad, 0x33 }, - { 0x37ae, 0x0d }, - { 0x37af, 0x0d }, - { 0x37b3, 0x42 }, - { 0x37b4, 0x42 }, - { 0x37b5, 0x33 }, - - /* PSRAM */ - - { OV8865_PSRAM_CTRL8_REG, 0x0b }, - - /* ADC Sync */ - - { 0x4500, 0x40 }, -}; - -static const struct ov8865_mode ov8865_modes[] = { - /* 3264x2448 */ +static const struct ov8865_mode supported_modes[] = { { - /* Horizontal */ - .output_size_x = 3264, - .hts = 3888, - - /* Vertical */ - .output_size_y = 2448, - .vts = 2470, - - .size_auto = true, - .size_auto_boundary_x = 8, - .size_auto_boundary_y = 4, - - /* Subsample increase */ - .inc_x_odd = 1, - .inc_x_even = 1, - .inc_y_odd = 1, - .inc_y_even = 1, - - /* VFIFO */ - .vfifo_read_start = 16, - - .ablc_num = 4, - .zline_num = 1, - - /* Black Level */ - - .blc_top_zero_line_start = 0, - .blc_top_zero_line_num = 2, - .blc_top_black_line_start = 4, - .blc_top_black_line_num = 4, - - .blc_bottom_zero_line_start = 2, - .blc_bottom_zero_line_num = 2, - .blc_bottom_black_line_start = 8, - .blc_bottom_black_line_num = 2, - - .blc_anchor_left_start = 576, - .blc_anchor_left_end = 831, - .blc_anchor_right_start = 1984, - .blc_anchor_right_end = 2239, - - /* PLL */ - .pll2_binning = false, - - /* Registers */ - .register_values = ov8865_register_values_native, - .register_values_count = - ARRAY_SIZE(ov8865_register_values_native), - }, - /* 3264x1836 */ - { - /* Horizontal */ - .output_size_x = 3264, - .hts = 3888, - - /* Vertical */ - .output_size_y = 1836, - .vts = 2470, - - .size_auto = true, - .size_auto_boundary_x = 8, - .size_auto_boundary_y = 4, - - /* Subsample increase */ - .inc_x_odd = 1, - .inc_x_even = 1, - .inc_y_odd = 1, - .inc_y_even = 1, - - /* VFIFO */ - .vfifo_read_start = 16, - - .ablc_num = 4, - .zline_num = 1, - - /* Black Level */ - - .blc_top_zero_line_start = 0, - .blc_top_zero_line_num = 2, - .blc_top_black_line_start = 4, - .blc_top_black_line_num = 4, - - .blc_bottom_zero_line_start = 2, - .blc_bottom_zero_line_num = 2, - .blc_bottom_black_line_start = 8, - .blc_bottom_black_line_num = 2, - - .blc_anchor_left_start = 576, - .blc_anchor_left_end = 831, - .blc_anchor_right_start = 1984, - .blc_anchor_right_end = 2239, - - /* PLL */ - .pll2_binning = false, - - /* Registers */ - .register_values = ov8865_register_values_native, - .register_values_count = - ARRAY_SIZE(ov8865_register_values_native), - }, - /* 1632x1224 */ - { - /* Horizontal */ - .output_size_x = 1632, - .hts = 1923, - - /* Vertical */ - .output_size_y = 1224, - .vts = 1248, - - .size_auto = true, - .size_auto_boundary_x = 8, - .size_auto_boundary_y = 8, - - /* Subsample increase */ - .inc_x_odd = 3, - .inc_x_even = 1, - .inc_y_odd = 3, - .inc_y_even = 1, - - /* Binning */ - .binning_y = true, - .sync_hbin = true, - - /* VFIFO */ - .vfifo_read_start = 116, - - .ablc_num = 8, - .zline_num = 2, - - /* Black Level */ - - .blc_top_zero_line_start = 0, - .blc_top_zero_line_num = 2, - .blc_top_black_line_start = 4, - .blc_top_black_line_num = 4, - - .blc_bottom_zero_line_start = 2, - .blc_bottom_zero_line_num = 2, - .blc_bottom_black_line_start = 8, - .blc_bottom_black_line_num = 2, - - .blc_anchor_left_start = 288, - .blc_anchor_left_end = 415, - .blc_anchor_right_start = 992, - .blc_anchor_right_end = 1119, - - /* PLL */ - .pll2_binning = true, - - /* Registers */ - .register_values = ov8865_register_values_binning, - .register_values_count = - ARRAY_SIZE(ov8865_register_values_binning), - }, - /* 800x600 (SVGA) */ - { - /* Horizontal */ - .output_size_x = 800, - .hts = 1250, - - /* Vertical */ - .output_size_y = 600, - .vts = 640, - - .size_auto = true, - .size_auto_boundary_x = 8, - .size_auto_boundary_y = 8, - - /* Subsample increase */ - .inc_x_odd = 3, - .inc_x_even = 1, - .inc_y_odd = 5, - .inc_y_even = 3, - - /* Binning */ - .binning_y = true, - .variopixel = true, - .variopixel_hsub_coef = 2, - .variopixel_vsub_coef = 1, - .sync_hbin = true, - .horz_var2 = true, - - /* VFIFO */ - .vfifo_read_start = 80, - - .ablc_num = 8, - .zline_num = 2, - - /* Black Level */ - - .blc_top_zero_line_start = 0, - .blc_top_zero_line_num = 2, - .blc_top_black_line_start = 2, - .blc_top_black_line_num = 2, - - .blc_bottom_zero_line_start = 0, - .blc_bottom_zero_line_num = 0, - .blc_bottom_black_line_start = 4, - .blc_bottom_black_line_num = 2, - - .blc_col_shift_mask = OV8865_BLC_CTRL1_COL_SHIFT_128, - - .blc_anchor_left_start = 288, - .blc_anchor_left_end = 415, - .blc_anchor_right_start = 992, - .blc_anchor_right_end = 1119, - - /* PLL */ - .pll2_binning = true, - - /* Registers */ - .register_values = ov8865_register_values_binning, - .register_values_count = - ARRAY_SIZE(ov8865_register_values_binning), + .width = 3264, + .height = 2448, + .max_fps = { + .numerator = 10000, + .denominator = 300000, + }, + .exp_def = 0x09a2, + .hts_def = 0x0798 * 2, + .vts_def = 0x09a6, + .reg_list = ov8865_3264x2448_30fps_regs, + .mipi_freq_idx = 0, + .bus_fmt = MEDIA_BUS_FMT_SGBRG10_1X10, + .bpp = 10, + .vc[PAD0] = 0, }, }; -static const u32 ov8865_mbus_codes[] = { - MEDIA_BUS_FMT_SBGGR10_1X10, +static const s64 link_freq_menu_items[] = { + MIPI_FREQ }; -static const struct ov8865_register_value ov8865_init_sequence[] = { - /* Analog */ - - { 0x3604, 0x04 }, - { 0x3602, 0x30 }, - { 0x3605, 0x00 }, - { 0x3607, 0x20 }, - { 0x3608, 0x11 }, - { 0x3609, 0x68 }, - { 0x360a, 0x40 }, - { 0x360c, 0xdd }, - { 0x360e, 0x0c }, - { 0x3610, 0x07 }, - { 0x3612, 0x86 }, - { 0x3613, 0x58 }, - { 0x3614, 0x28 }, - { 0x3617, 0x40 }, - { 0x3618, 0x5a }, - { 0x3619, 0x9b }, - { 0x361c, 0x00 }, - { 0x361d, 0x60 }, - { 0x3631, 0x60 }, - { 0x3633, 0x10 }, - { 0x3634, 0x10 }, - { 0x3635, 0x10 }, - { 0x3636, 0x10 }, - { 0x3638, 0xff }, - { 0x3641, 0x55 }, - { 0x3646, 0x86 }, - { 0x3647, 0x27 }, - { 0x364a, 0x1b }, - - /* Sensor */ - - { 0x3700, 0x24 }, - { 0x3701, 0x0c }, - { 0x3702, 0x28 }, - { 0x3703, 0x19 }, - { 0x3704, 0x14 }, - { 0x3705, 0x00 }, - { 0x3706, 0x38 }, - { 0x3707, 0x04 }, - { 0x3708, 0x24 }, - { 0x3709, 0x40 }, - { 0x370a, 0x00 }, - { 0x370b, 0xb8 }, - { 0x370c, 0x04 }, - { 0x3718, 0x12 }, - { 0x3719, 0x31 }, - { 0x3712, 0x42 }, - { 0x3714, 0x12 }, - { 0x371e, 0x19 }, - { 0x371f, 0x40 }, - { 0x3720, 0x05 }, - { 0x3721, 0x05 }, - { 0x3724, 0x02 }, - { 0x3725, 0x02 }, - { 0x3726, 0x06 }, - { 0x3728, 0x05 }, - { 0x3729, 0x02 }, - { 0x372a, 0x03 }, - { 0x372b, 0x53 }, - { 0x372c, 0xa3 }, - { 0x372d, 0x53 }, - { 0x372e, 0x06 }, - { 0x372f, 0x10 }, - { 0x3730, 0x01 }, - { 0x3731, 0x06 }, - { 0x3732, 0x14 }, - { 0x3733, 0x10 }, - { 0x3734, 0x40 }, - { 0x3736, 0x20 }, - { 0x373a, 0x02 }, - { 0x373b, 0x0c }, - { 0x373c, 0x0a }, - { 0x373e, 0x03 }, - { 0x3755, 0x40 }, - { 0x3758, 0x00 }, - { 0x3759, 0x4c }, - { 0x375a, 0x06 }, - { 0x375b, 0x13 }, - { 0x375c, 0x40 }, - { 0x375d, 0x02 }, - { 0x375e, 0x00 }, - { 0x375f, 0x14 }, - { 0x3767, 0x1c }, - { 0x3768, 0x04 }, - { 0x3769, 0x20 }, - { 0x376c, 0xc0 }, - { 0x376d, 0xc0 }, - { 0x376a, 0x08 }, - { 0x3761, 0x00 }, - { 0x3762, 0x00 }, - { 0x3763, 0x00 }, - { 0x3766, 0xff }, - { 0x376b, 0x42 }, - { 0x3772, 0x23 }, - { 0x3773, 0x02 }, - { 0x3774, 0x16 }, - { 0x3775, 0x12 }, - { 0x3776, 0x08 }, - { 0x37a0, 0x44 }, - { 0x37a1, 0x3d }, - { 0x37a2, 0x3d }, - { 0x37a3, 0x01 }, - { 0x37a4, 0x00 }, - { 0x37a5, 0x08 }, - { 0x37a6, 0x00 }, - { 0x37a7, 0x44 }, - { 0x37a8, 0x58 }, - { 0x37a9, 0x58 }, - { 0x3760, 0x00 }, - { 0x376f, 0x01 }, - { 0x37aa, 0x44 }, - { 0x37ab, 0x2e }, - { 0x37ac, 0x2e }, - { 0x37ad, 0x33 }, - { 0x37ae, 0x0d }, - { 0x37af, 0x0d }, - { 0x37b0, 0x00 }, - { 0x37b1, 0x00 }, - { 0x37b2, 0x00 }, - { 0x37b3, 0x42 }, - { 0x37b4, 0x42 }, - { 0x37b5, 0x33 }, - { 0x37b6, 0x00 }, - { 0x37b7, 0x00 }, - { 0x37b8, 0x00 }, - { 0x37b9, 0xff }, - - /* ADC Sync */ - - { 0x4503, 0x10 }, -}; - -static const s64 ov8865_link_freq_menu[] = { - 360000000, -}; - -static const char *const ov8865_test_pattern_menu[] = { +static const char * const ov8865_test_pattern_menu[] = { "Disabled", - "Random data", - "Color bars", - "Color bars with rolling bar", - "Color squares", - "Color squares with rolling bar" + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" }; -static const u8 ov8865_test_pattern_bits[] = { - 0, - OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_PATTERN_RANDOM_DATA, - OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_PATTERN_COLOR_BARS, - OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_ROLLING_BAR_EN | - OV8865_PRE_CTRL0_PATTERN_COLOR_BARS, - OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES, - OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_ROLLING_BAR_EN | - OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES, -}; - -/* Input/Output */ - -static int ov8865_read(struct ov8865_sensor *sensor, u16 address, u8 *value) +/* Write registers up to 4 at a time */ +static int ov8865_write_reg(struct i2c_client *client, u16 reg, + u32 len, u32 val) { - unsigned char data[2] = { address >> 8, address & 0xff }; - struct i2c_client *client = sensor->i2c_client; - int ret; + u32 buf_i, val_i; + u8 buf[6]; + u8 *val_p; + __be32 val_be; - ret = i2c_master_send(client, data, sizeof(data)); - if (ret < 0) { - dev_dbg(&client->dev, "i2c send error at address %#04x\n", - address); - return ret; - } + if (len > 4) + return -EINVAL; - ret = i2c_master_recv(client, value, 1); - if (ret < 0) { - dev_dbg(&client->dev, "i2c recv error at address %#04x\n", - address); - return ret; - } + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val_be = cpu_to_be32(val); + val_p = (u8 *)&val_be; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; return 0; } -static int ov8865_write(struct ov8865_sensor *sensor, u16 address, u8 value) +static int ov8865_write_array(struct i2c_client *client, + const struct regval *regs) { - unsigned char data[3] = { address >> 8, address & 0xff, value }; - struct i2c_client *client = sensor->i2c_client; - int ret; - - ret = i2c_master_send(client, data, sizeof(data)); - if (ret < 0) { - dev_dbg(&client->dev, "i2c send error at address %#04x\n", - address); - return ret; - } - - return 0; -} - -static int ov8865_write_sequence(struct ov8865_sensor *sensor, - const struct ov8865_register_value *sequence, - unsigned int sequence_count) -{ - unsigned int i; + u32 i; int ret = 0; - for (i = 0; i < sequence_count; i++) { - ret = ov8865_write(sensor, sequence[i].address, - sequence[i].value); - if (ret) - break; + for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++) + ret = ov8865_write_reg(client, regs[i].addr, + OV8865_REG_VALUE_08BIT, + regs[i].val); - if (sequence[i].delay_ms) - msleep(sequence[i].delay_ms); + return ret; +} + +/* Read registers up to 4 at a time */ +static int ov8865_read_reg(struct i2c_client *client, u16 reg, + unsigned int len, u32 *val) +{ + struct i2c_msg msgs[2]; + u8 *data_be_p; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + int ret; + + if (len > 4 || !len) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +static int ov8865_get_reso_dist(const struct ov8865_mode *mode, + struct v4l2_mbus_framefmt *framefmt) +{ + return abs(mode->width - framefmt->width) + + abs(mode->height - framefmt->height); +} + +static const struct ov8865_mode * +ov8865_find_best_fit(struct ov8865 *ov8865, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt = &fmt->format; + int dist; + int cur_best_fit = 0; + int cur_best_fit_dist = -1; + unsigned int i; + + for (i = 0; i < ov8865->cfg_num; i++) { + dist = ov8865_get_reso_dist(&supported_modes[i], framefmt); + if (cur_best_fit_dist == -1 || dist < cur_best_fit_dist) { + cur_best_fit_dist = dist; + cur_best_fit = i; + } + } + + return &supported_modes[cur_best_fit]; +} + +static int ov8865_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov8865 *ov8865 = to_ov8865(sd); + const struct ov8865_mode *mode; + s64 h_blank, vblank_def; + u64 pixel_rate = 0; + u32 lane_num = OV8865_LANES; + + mutex_lock(&ov8865->mutex); + + mode = ov8865_find_best_fit(ov8865, fmt); + fmt->format.code = OV8865_MEDIA_BUS_FMT; + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format; +#else + mutex_unlock(&ov8865->mutex); + return -ENOTTY; +#endif + } else { + ov8865->cur_mode = mode; + h_blank = mode->hts_def - mode->width; + __v4l2_ctrl_modify_range(ov8865->hblank, h_blank, + h_blank, 1, h_blank); + vblank_def = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(ov8865->vblank, vblank_def, + OV8865_VTS_MAX - mode->height, + 1, vblank_def); + __v4l2_ctrl_s_ctrl(ov8865->vblank, vblank_def); + pixel_rate = (u32)link_freq_menu_items[mode->mipi_freq_idx] / + mode->bpp * 2 * lane_num; + + __v4l2_ctrl_s_ctrl_int64(ov8865->pixel_rate, + pixel_rate); + __v4l2_ctrl_s_ctrl(ov8865->link_freq, + mode->mipi_freq_idx); + } + dev_info(&ov8865->client->dev, "%s: mode->mipi_freq_idx(%d)", + __func__, mode->mipi_freq_idx); + + mutex_unlock(&ov8865->mutex); + + return 0; +} + +static int ov8865_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov8865 *ov8865 = to_ov8865(sd); + const struct ov8865_mode *mode = ov8865->cur_mode; + + mutex_lock(&ov8865->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + fmt->format = *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); +#else + mutex_unlock(&ov8865->mutex); + return -ENOTTY; +#endif + } else { + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = OV8865_MEDIA_BUS_FMT; + fmt->format.field = V4L2_FIELD_NONE; + } + mutex_unlock(&ov8865->mutex); + + return 0; +} + +static int ov8865_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + code->code = OV8865_MEDIA_BUS_FMT; + + return 0; +} + +static int ov8865_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct ov8865 *ov8865 = to_ov8865(sd); + + if (fse->index >= ov8865->cfg_num) + return -EINVAL; + + if (fse->code != OV8865_MEDIA_BUS_FMT) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = supported_modes[fse->index].width; + fse->max_height = supported_modes[fse->index].height; + fse->min_height = supported_modes[fse->index].height; + + return 0; +} + +static int ov8865_enable_test_pattern(struct ov8865 *ov8865, u32 pattern) +{ + u32 val; + + if (pattern) + val = (pattern - 1) | OV8865_TEST_PATTERN_ENABLE; + else + val = OV8865_TEST_PATTERN_DISABLE; + + return ov8865_write_reg(ov8865->client, + OV8865_REG_TEST_PATTERN, + OV8865_REG_VALUE_08BIT, + val); +} + +static int ov8865_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct ov8865 *ov8865 = to_ov8865(sd); + const struct ov8865_mode *mode = ov8865->cur_mode; + + fi->interval = mode->max_fps; + + return 0; +} + +static void ov8865_get_module_inf(struct ov8865 *ov8865, + struct rkmodule_inf *inf) +{ + memset(inf, 0, sizeof(*inf)); + strscpy(inf->base.sensor, OV8865_NAME, sizeof(inf->base.sensor)); + strscpy(inf->base.module, ov8865->module_name, sizeof(inf->base.module)); + strscpy(inf->base.lens, ov8865->len_name, sizeof(inf->base.lens)); + +} + +static long ov8865_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + struct ov8865 *ov8865 = to_ov8865(sd); + long ret = 0; + u32 stream = 0; + + switch (cmd) { + case RKMODULE_GET_MODULE_INFO: + ov8865_get_module_inf(ov8865, (struct rkmodule_inf *)arg); + break; + case RKMODULE_SET_QUICK_STREAM: + + stream = *((u32 *)arg); + + if (stream) + ret = ov8865_write_reg(ov8865->client, + OV8865_REG_CTRL_MODE, + OV8865_REG_VALUE_08BIT, + OV8865_MODE_STREAMING); + else + ret = ov8865_write_reg(ov8865->client, + OV8865_REG_CTRL_MODE, + OV8865_REG_VALUE_08BIT, + OV8865_MODE_SW_STANDBY); + break; + default: + ret = -ENOTTY; + break; } return ret; } -static int ov8865_update_bits(struct ov8865_sensor *sensor, u16 address, - u8 mask, u8 bits) +#ifdef CONFIG_COMPAT +static long ov8865_compat_ioctl32(struct v4l2_subdev *sd, + unsigned int cmd, unsigned long arg) { - u8 value = 0; - int ret; + void __user *up = compat_ptr(arg); + struct rkmodule_inf *inf; + long ret = 0; + u32 stream = 0; - ret = ov8865_read(sensor, address, &value); - if (ret) - return ret; - - value &= ~mask; - value |= bits; - - return ov8865_write(sensor, address, value); -} - -/* Sensor */ - -static int ov8865_sw_reset(struct ov8865_sensor *sensor) -{ - return ov8865_write(sensor, OV8865_SW_RESET_REG, OV8865_SW_RESET_RESET); -} - -static int ov8865_sw_standby(struct ov8865_sensor *sensor, int standby) -{ - u8 value = 0; - - if (!standby) - value = OV8865_SW_STANDBY_STREAM_ON; - - return ov8865_write(sensor, OV8865_SW_STANDBY_REG, value); -} - -static int ov8865_chip_id_check(struct ov8865_sensor *sensor) -{ - u16 regs[] = { OV8865_CHIP_ID_HH_REG, OV8865_CHIP_ID_H_REG, - OV8865_CHIP_ID_L_REG }; - u8 values[] = { OV8865_CHIP_ID_HH_VALUE, OV8865_CHIP_ID_H_VALUE, - OV8865_CHIP_ID_L_VALUE }; - unsigned int i; - u8 value; - int ret; - - for (i = 0; i < ARRAY_SIZE(regs); i++) { - ret = ov8865_read(sensor, regs[i], &value); - if (ret < 0) + switch (cmd) { + case RKMODULE_GET_MODULE_INFO: + inf = kzalloc(sizeof(*inf), GFP_KERNEL); + if (!inf) { + ret = -ENOMEM; return ret; - - if (value != values[i]) { - dev_err(sensor->dev, - "chip id value mismatch: %#x instead of %#x\n", - value, values[i]); - return -EINVAL; } - } - return 0; -} - -static int ov8865_charge_pump_configure(struct ov8865_sensor *sensor) -{ - return ov8865_write(sensor, OV8865_PUMP_CLK_DIV_REG, - OV8865_PUMP_CLK_DIV_PUMP_P(1)); -} - -static int ov8865_mipi_configure(struct ov8865_sensor *sensor) -{ - struct v4l2_mbus_config_mipi_csi2 *bus_mipi_csi2 = - &sensor->endpoint.bus.mipi_csi2; - unsigned int lanes_count = bus_mipi_csi2->num_data_lanes; - int ret; - - ret = ov8865_write(sensor, OV8865_MIPI_SC_CTRL0_REG, - OV8865_MIPI_SC_CTRL0_LANES(lanes_count) | - OV8865_MIPI_SC_CTRL0_MIPI_EN | - OV8865_MIPI_SC_CTRL0_UNKNOWN); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_MIPI_SC_CTRL2_REG, - OV8865_MIPI_SC_CTRL2_PD_MIPI_RST_SYNC); - if (ret) - return ret; - - if (lanes_count >= 2) { - ret = ov8865_write(sensor, OV8865_MIPI_LANE_SEL01_REG, - OV8865_MIPI_LANE_SEL01_LANE0(0) | - OV8865_MIPI_LANE_SEL01_LANE1(1)); - if (ret) - return ret; - } - - if (lanes_count >= 4) { - ret = ov8865_write(sensor, OV8865_MIPI_LANE_SEL23_REG, - OV8865_MIPI_LANE_SEL23_LANE2(2) | - OV8865_MIPI_LANE_SEL23_LANE3(3)); - if (ret) - return ret; - } - - ret = ov8865_update_bits(sensor, OV8865_CLK_SEL1_REG, - OV8865_CLK_SEL1_MIPI_EOF, - OV8865_CLK_SEL1_MIPI_EOF); - if (ret) - return ret; - - /* - * This value might need to change depending on PCLK rate, - * but it's unclear how. This value seems to generally work - * while the default value was found to cause transmission errors. - */ - return ov8865_write(sensor, OV8865_MIPI_PCLK_PERIOD_REG, 0x16); -} - -static int ov8865_black_level_configure(struct ov8865_sensor *sensor) -{ - int ret; - - /* Trigger BLC on relevant events and enable filter. */ - ret = ov8865_write(sensor, OV8865_BLC_CTRL0_REG, - OV8865_BLC_CTRL0_TRIG_RANGE_EN | - OV8865_BLC_CTRL0_TRIG_FORMAT_EN | - OV8865_BLC_CTRL0_TRIG_GAIN_EN | - OV8865_BLC_CTRL0_TRIG_EXPOSURE_EN | - OV8865_BLC_CTRL0_FILTER_EN); - if (ret) - return ret; - - /* Lower BLC offset trigger threshold. */ - ret = ov8865_write(sensor, OV8865_BLC_CTRLD_REG, - OV8865_BLC_CTRLD_OFFSET_TRIGGER(16)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_BLC_CTRL1F_REG, 0); - if (ret) - return ret; - - /* Increase BLC offset maximum limit. */ - return ov8865_write(sensor, OV8865_BLC_OFFSET_LIMIT_REG, - OV8865_BLC_OFFSET_LIMIT(63)); -} - -static int ov8865_isp_configure(struct ov8865_sensor *sensor) -{ - int ret; - - /* Disable lens correction. */ - ret = ov8865_write(sensor, OV8865_ISP_CTRL0_REG, - OV8865_ISP_CTRL0_WHITE_BALANCE_EN | - OV8865_ISP_CTRL0_DPC_BLACK_EN | - OV8865_ISP_CTRL0_DPC_WHITE_EN); - if (ret) - return ret; - - return ov8865_write(sensor, OV8865_ISP_CTRL1_REG, - OV8865_ISP_CTRL1_BLC_EN); -} - -static unsigned long ov8865_mode_pll1_rate(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) -{ - const struct ov8865_pll1_config *config; - unsigned long pll1_rate; - - config = sensor->pll_configs->pll1_config; - pll1_rate = sensor->extclk_rate * config->pll_mul / config->pll_pre_div_half; - - switch (config->pll_pre_div) { - case 0: + ret = ov8865_ioctl(sd, cmd, inf); + if (!ret) { + ret = copy_to_user(up, inf, sizeof(*inf)); + if (ret) + ret = -EFAULT; + } + kfree(inf); break; - case 1: - pll1_rate *= 3; - pll1_rate /= 2; - break; - case 3: - pll1_rate *= 5; - pll1_rate /= 2; - break; - case 4: - pll1_rate /= 3; - break; - case 5: - pll1_rate /= 4; - break; - case 7: - pll1_rate /= 8; + case RKMODULE_SET_QUICK_STREAM: + ret = copy_from_user(&stream, up, sizeof(u32)); + if (!ret) + ret = ov8865_ioctl(sd, cmd, &stream); + else + ret = -EFAULT; break; default: - pll1_rate /= config->pll_pre_div; + ret = -ENOTTY; break; } - return pll1_rate; + return ret; } +#endif -static int ov8865_mode_pll1_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode, - u32 mbus_code) -{ - const struct ov8865_pll1_config *config; - u8 value; - int ret; - - config = sensor->pll_configs->pll1_config; - - switch (mbus_code) { - case MEDIA_BUS_FMT_SBGGR10_1X10: - value = OV8865_MIPI_BIT_SEL(10); - break; - default: - return -EINVAL; - } - - ret = ov8865_write(sensor, OV8865_MIPI_BIT_SEL_REG, value); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_PLL_CTRLA_REG, - OV8865_PLL_CTRLA_PRE_DIV_HALF(config->pll_pre_div_half)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_PLL_CTRL0_REG, - OV8865_PLL_CTRL0_PRE_DIV(config->pll_pre_div)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_PLL_CTRL1_REG, - OV8865_PLL_CTRL1_MUL_H(config->pll_mul)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_PLL_CTRL2_REG, - OV8865_PLL_CTRL2_MUL_L(config->pll_mul)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_PLL_CTRL3_REG, - OV8865_PLL_CTRL3_M_DIV(config->m_div)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_PLL_CTRL4_REG, - OV8865_PLL_CTRL4_MIPI_DIV(config->mipi_div)); - if (ret) - return ret; - - ret = ov8865_update_bits(sensor, OV8865_PCLK_SEL_REG, - OV8865_PCLK_SEL_PCLK_DIV_MASK, - OV8865_PCLK_SEL_PCLK_DIV(config->pclk_div)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_PLL_CTRL5_REG, - OV8865_PLL_CTRL5_SYS_PRE_DIV(config->sys_pre_div)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_PLL_CTRL6_REG, - OV8865_PLL_CTRL6_SYS_DIV(config->sys_div)); - if (ret) - return ret; - - return ov8865_update_bits(sensor, OV8865_PLL_CTRL1E_REG, - OV8865_PLL_CTRL1E_PLL1_NO_LAT, - OV8865_PLL_CTRL1E_PLL1_NO_LAT); -} - -static int ov8865_mode_pll2_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) -{ - const struct ov8865_pll2_config *config; - int ret; - - config = mode->pll2_binning ? sensor->pll_configs->pll2_config_binning : - sensor->pll_configs->pll2_config_native; - - ret = ov8865_write(sensor, OV8865_PLL_CTRL12_REG, - OV8865_PLL_CTRL12_PRE_DIV_HALF(config->pll_pre_div_half) | - OV8865_PLL_CTRL12_DAC_DIV(config->dac_div)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_PLL_CTRLB_REG, - OV8865_PLL_CTRLB_PRE_DIV(config->pll_pre_div)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_PLL_CTRLC_REG, - OV8865_PLL_CTRLC_MUL_H(config->pll_mul)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_PLL_CTRLD_REG, - OV8865_PLL_CTRLD_MUL_L(config->pll_mul)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_PLL_CTRLF_REG, - OV8865_PLL_CTRLF_SYS_PRE_DIV(config->sys_pre_div)); - if (ret) - return ret; - - return ov8865_write(sensor, OV8865_PLL_CTRLE_REG, - OV8865_PLL_CTRLE_SYS_DIV(config->sys_div)); -} - -static int ov8865_mode_sclk_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) -{ - const struct ov8865_sclk_config *config = &ov8865_sclk_config_native; - int ret; - - ret = ov8865_write(sensor, OV8865_CLK_SEL0_REG, - OV8865_CLK_SEL0_PLL1_SYS_SEL(config->sys_sel)); - if (ret) - return ret; - - ret = ov8865_update_bits(sensor, OV8865_CLK_SEL1_REG, - OV8865_CLK_SEL1_PLL_SCLK_SEL_MASK, - OV8865_CLK_SEL1_PLL_SCLK_SEL(config->sclk_sel)); - if (ret) - return ret; - - return ov8865_write(sensor, OV8865_SCLK_CTRL_REG, - OV8865_SCLK_CTRL_UNKNOWN | - OV8865_SCLK_CTRL_SCLK_DIV(config->sclk_div) | - OV8865_SCLK_CTRL_SCLK_PRE_DIV(config->sclk_pre_div)); -} - -static int ov8865_mode_binning_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) -{ - unsigned int variopixel_hsub_coef, variopixel_vsub_coef; - u8 value; - int ret; - - ret = ov8865_write(sensor, OV8865_FORMAT1_REG, 0); - if (ret) - return ret; - - value = OV8865_FORMAT2_HSYNC_EN; - - if (mode->binning_x) - value |= OV8865_FORMAT2_FST_HBIN_EN; - - if (mode->binning_y) - value |= OV8865_FORMAT2_FST_VBIN_EN; - - if (mode->sync_hbin) - value |= OV8865_FORMAT2_SYNC_HBIN_EN; - - if (mode->horz_var2) - value |= OV8865_FORMAT2_ISP_HORZ_VAR2_EN; - - ret = ov8865_write(sensor, OV8865_FORMAT2_REG, value); - if (ret) - return ret; - - ret = ov8865_update_bits(sensor, OV8865_ISP_CTRL2_REG, - OV8865_ISP_CTRL2_VARIOPIXEL_EN, - mode->variopixel ? - OV8865_ISP_CTRL2_VARIOPIXEL_EN : 0); - if (ret) - return ret; - - if (mode->variopixel) { - /* VarioPixel coefs needs to be > 1. */ - variopixel_hsub_coef = mode->variopixel_hsub_coef; - variopixel_vsub_coef = mode->variopixel_vsub_coef; - } else { - variopixel_hsub_coef = 1; - variopixel_vsub_coef = 1; - } - - ret = ov8865_write(sensor, OV8865_VAP_CTRL1_REG, - OV8865_VAP_CTRL1_HSUB_COEF(variopixel_hsub_coef) | - OV8865_VAP_CTRL1_VSUB_COEF(variopixel_vsub_coef)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_INC_X_ODD_REG, - OV8865_INC_X_ODD(mode->inc_x_odd)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_INC_X_EVEN_REG, - OV8865_INC_X_EVEN(mode->inc_x_even)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_INC_Y_ODD_REG, - OV8865_INC_Y_ODD(mode->inc_y_odd)); - if (ret) - return ret; - - return ov8865_write(sensor, OV8865_INC_Y_EVEN_REG, - OV8865_INC_Y_EVEN(mode->inc_y_even)); -} - -static int ov8865_mode_black_level_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) +static int __ov8865_start_stream(struct ov8865 *ov8865) { int ret; - /* Note that a zero value for blc_col_shift_mask is the default 256. */ - ret = ov8865_write(sensor, OV8865_BLC_CTRL1_REG, - mode->blc_col_shift_mask | - OV8865_BLC_CTRL1_OFFSET_LIMIT_EN); + ret = ov8865_write_array(ov8865->client, ov8865_global_regs); if (ret) return ret; - /* BLC top zero line */ - - ret = ov8865_write(sensor, OV8865_BLC_TOP_ZLINE_START_REG, - OV8865_BLC_TOP_ZLINE_START(mode->blc_top_zero_line_start)); + ret = ov8865_write_array(ov8865->client, ov8865->cur_mode->reg_list); if (ret) return ret; - ret = ov8865_write(sensor, OV8865_BLC_TOP_ZLINE_NUM_REG, - OV8865_BLC_TOP_ZLINE_NUM(mode->blc_top_zero_line_num)); + /* In case these controls are set before streaming */ + mutex_unlock(&ov8865->mutex); + ret = v4l2_ctrl_handler_setup(&ov8865->ctrl_handler); + mutex_lock(&ov8865->mutex); if (ret) return ret; - /* BLC top black line */ - - ret = ov8865_write(sensor, OV8865_BLC_TOP_BLKLINE_START_REG, - OV8865_BLC_TOP_BLKLINE_START(mode->blc_top_black_line_start)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_BLC_TOP_BLKLINE_NUM_REG, - OV8865_BLC_TOP_BLKLINE_NUM(mode->blc_top_black_line_num)); - if (ret) - return ret; - - /* BLC bottom zero line */ - - ret = ov8865_write(sensor, OV8865_BLC_BOT_ZLINE_START_REG, - OV8865_BLC_BOT_ZLINE_START(mode->blc_bottom_zero_line_start)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_BLC_BOT_ZLINE_NUM_REG, - OV8865_BLC_BOT_ZLINE_NUM(mode->blc_bottom_zero_line_num)); - if (ret) - return ret; - - /* BLC bottom black line */ - - ret = ov8865_write(sensor, OV8865_BLC_BOT_BLKLINE_START_REG, - OV8865_BLC_BOT_BLKLINE_START(mode->blc_bottom_black_line_start)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_BLC_BOT_BLKLINE_NUM_REG, - OV8865_BLC_BOT_BLKLINE_NUM(mode->blc_bottom_black_line_num)); - if (ret) - return ret; - - /* BLC anchor */ - - ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_LEFT_START_H_REG, - OV8865_BLC_ANCHOR_LEFT_START_H(mode->blc_anchor_left_start)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_LEFT_START_L_REG, - OV8865_BLC_ANCHOR_LEFT_START_L(mode->blc_anchor_left_start)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_LEFT_END_H_REG, - OV8865_BLC_ANCHOR_LEFT_END_H(mode->blc_anchor_left_end)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_LEFT_END_L_REG, - OV8865_BLC_ANCHOR_LEFT_END_L(mode->blc_anchor_left_end)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_RIGHT_START_H_REG, - OV8865_BLC_ANCHOR_RIGHT_START_H(mode->blc_anchor_right_start)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_RIGHT_START_L_REG, - OV8865_BLC_ANCHOR_RIGHT_START_L(mode->blc_anchor_right_start)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_RIGHT_END_H_REG, - OV8865_BLC_ANCHOR_RIGHT_END_H(mode->blc_anchor_right_end)); - if (ret) - return ret; - - return ov8865_write(sensor, OV8865_BLC_ANCHOR_RIGHT_END_L_REG, - OV8865_BLC_ANCHOR_RIGHT_END_L(mode->blc_anchor_right_end)); + return ov8865_write_reg(ov8865->client, + OV8865_REG_CTRL_MODE, + OV8865_REG_VALUE_08BIT, + OV8865_MODE_STREAMING); } -static int ov8865_mode_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode, u32 mbus_code) +static int __ov8865_stop_stream(struct ov8865 *ov8865) { - int ret; - - /* Output Size X */ - - ret = ov8865_write(sensor, OV8865_OUTPUT_SIZE_X_H_REG, - OV8865_OUTPUT_SIZE_X_H(mode->output_size_x)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_OUTPUT_SIZE_X_L_REG, - OV8865_OUTPUT_SIZE_X_L(mode->output_size_x)); - if (ret) - return ret; - - /* Horizontal Total Size */ - - ret = ov8865_write(sensor, OV8865_HTS_H_REG, OV8865_HTS_H(mode->hts)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_HTS_L_REG, OV8865_HTS_L(mode->hts)); - if (ret) - return ret; - - /* Output Size Y */ - - ret = ov8865_write(sensor, OV8865_OUTPUT_SIZE_Y_H_REG, - OV8865_OUTPUT_SIZE_Y_H(mode->output_size_y)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_OUTPUT_SIZE_Y_L_REG, - OV8865_OUTPUT_SIZE_Y_L(mode->output_size_y)); - if (ret) - return ret; - - /* Vertical Total Size */ - - ret = ov8865_write(sensor, OV8865_VTS_H_REG, OV8865_VTS_H(mode->vts)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_VTS_L_REG, OV8865_VTS_L(mode->vts)); - if (ret) - return ret; - - if (mode->size_auto) { - /* Auto Size */ - - ret = ov8865_write(sensor, OV8865_AUTO_SIZE_CTRL_REG, - OV8865_AUTO_SIZE_CTRL_OFFSET_Y_REG | - OV8865_AUTO_SIZE_CTRL_OFFSET_X_REG | - OV8865_AUTO_SIZE_CTRL_CROP_END_Y_REG | - OV8865_AUTO_SIZE_CTRL_CROP_END_X_REG | - OV8865_AUTO_SIZE_CTRL_CROP_START_Y_REG | - OV8865_AUTO_SIZE_CTRL_CROP_START_X_REG); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_AUTO_SIZE_BOUNDARIES_REG, - OV8865_AUTO_SIZE_BOUNDARIES_Y(mode->size_auto_boundary_y) | - OV8865_AUTO_SIZE_BOUNDARIES_X(mode->size_auto_boundary_x)); - if (ret) - return ret; - } else { - /* Crop Start X */ - - ret = ov8865_write(sensor, OV8865_CROP_START_X_H_REG, - OV8865_CROP_START_X_H(mode->crop_start_x)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_CROP_START_X_L_REG, - OV8865_CROP_START_X_L(mode->crop_start_x)); - if (ret) - return ret; - - /* Offset X */ - - ret = ov8865_write(sensor, OV8865_OFFSET_X_H_REG, - OV8865_OFFSET_X_H(mode->offset_x)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_OFFSET_X_L_REG, - OV8865_OFFSET_X_L(mode->offset_x)); - if (ret) - return ret; - - /* Crop End X */ - - ret = ov8865_write(sensor, OV8865_CROP_END_X_H_REG, - OV8865_CROP_END_X_H(mode->crop_end_x)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_CROP_END_X_L_REG, - OV8865_CROP_END_X_L(mode->crop_end_x)); - if (ret) - return ret; - - /* Crop Start Y */ - - ret = ov8865_write(sensor, OV8865_CROP_START_Y_H_REG, - OV8865_CROP_START_Y_H(mode->crop_start_y)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_CROP_START_Y_L_REG, - OV8865_CROP_START_Y_L(mode->crop_start_y)); - if (ret) - return ret; - - /* Offset Y */ - - ret = ov8865_write(sensor, OV8865_OFFSET_Y_H_REG, - OV8865_OFFSET_Y_H(mode->offset_y)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_OFFSET_Y_L_REG, - OV8865_OFFSET_Y_L(mode->offset_y)); - if (ret) - return ret; - - /* Crop End Y */ - - ret = ov8865_write(sensor, OV8865_CROP_END_Y_H_REG, - OV8865_CROP_END_Y_H(mode->crop_end_y)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_CROP_END_Y_L_REG, - OV8865_CROP_END_Y_L(mode->crop_end_y)); - if (ret) - return ret; - } - - /* VFIFO */ - - ret = ov8865_write(sensor, OV8865_VFIFO_READ_START_H_REG, - OV8865_VFIFO_READ_START_H(mode->vfifo_read_start)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_VFIFO_READ_START_L_REG, - OV8865_VFIFO_READ_START_L(mode->vfifo_read_start)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_ABLC_NUM_REG, - OV8865_ABLC_NUM(mode->ablc_num)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_ZLINE_NUM_REG, - OV8865_ZLINE_NUM(mode->zline_num)); - if (ret) - return ret; - - /* Binning */ - - ret = ov8865_mode_binning_configure(sensor, mode); - if (ret) - return ret; - - /* Black Level */ - - ret = ov8865_mode_black_level_configure(sensor, mode); - if (ret) - return ret; - - /* PLLs */ - - ret = ov8865_mode_pll1_configure(sensor, mode, mbus_code); - if (ret) - return ret; - - ret = ov8865_mode_pll2_configure(sensor, mode); - if (ret) - return ret; - - ret = ov8865_mode_sclk_configure(sensor, mode); - if (ret) - return ret; - - /* Extra registers */ - - if (mode->register_values) { - ret = ov8865_write_sequence(sensor, mode->register_values, - mode->register_values_count); - if (ret) - return ret; - } - - return 0; + return ov8865_write_reg(ov8865->client, + OV8865_REG_CTRL_MODE, + OV8865_REG_VALUE_08BIT, + OV8865_MODE_SW_STANDBY); } -static unsigned long ov8865_mode_mipi_clk_rate(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) +static int ov8865_s_stream(struct v4l2_subdev *sd, int on) { - const struct ov8865_pll1_config *config; - unsigned long pll1_rate; - - config = sensor->pll_configs->pll1_config; - - pll1_rate = ov8865_mode_pll1_rate(sensor, mode); - - return pll1_rate / config->m_div / 2; -} - -/* Exposure */ - -static int ov8865_exposure_configure(struct ov8865_sensor *sensor, u32 exposure) -{ - int ret; - - /* The sensor stores exposure in units of 1/16th of a line */ - exposure *= 16; - - ret = ov8865_write(sensor, OV8865_EXPOSURE_CTRL_HH_REG, - OV8865_EXPOSURE_CTRL_HH(exposure)); - if (ret) - return ret; - - ret = ov8865_write(sensor, OV8865_EXPOSURE_CTRL_H_REG, - OV8865_EXPOSURE_CTRL_H(exposure)); - if (ret) - return ret; - - return ov8865_write(sensor, OV8865_EXPOSURE_CTRL_L_REG, - OV8865_EXPOSURE_CTRL_L(exposure)); -} - -/* Gain */ - -static int ov8865_analog_gain_configure(struct ov8865_sensor *sensor, u32 gain) -{ - int ret; - - ret = ov8865_write(sensor, OV8865_GAIN_CTRL_H_REG, - OV8865_GAIN_CTRL_H(gain)); - if (ret) - return ret; - - return ov8865_write(sensor, OV8865_GAIN_CTRL_L_REG, - OV8865_GAIN_CTRL_L(gain)); -} - -/* White Balance */ - -static int ov8865_red_balance_configure(struct ov8865_sensor *sensor, - u32 red_balance) -{ - int ret; - - ret = ov8865_write(sensor, OV8865_ISP_GAIN_RED_H_REG, - OV8865_ISP_GAIN_RED_H(red_balance)); - if (ret) - return ret; - - return ov8865_write(sensor, OV8865_ISP_GAIN_RED_L_REG, - OV8865_ISP_GAIN_RED_L(red_balance)); -} - -static int ov8865_blue_balance_configure(struct ov8865_sensor *sensor, - u32 blue_balance) -{ - int ret; - - ret = ov8865_write(sensor, OV8865_ISP_GAIN_BLUE_H_REG, - OV8865_ISP_GAIN_BLUE_H(blue_balance)); - if (ret) - return ret; - - return ov8865_write(sensor, OV8865_ISP_GAIN_BLUE_L_REG, - OV8865_ISP_GAIN_BLUE_L(blue_balance)); -} - -/* Flip */ - -static int ov8865_flip_vert_configure(struct ov8865_sensor *sensor, bool enable) -{ - u8 bits = OV8865_FORMAT1_FLIP_VERT_ISP_EN | - OV8865_FORMAT1_FLIP_VERT_SENSOR_EN; - - return ov8865_update_bits(sensor, OV8865_FORMAT1_REG, bits, - enable ? bits : 0); -} - -static int ov8865_flip_horz_configure(struct ov8865_sensor *sensor, bool enable) -{ - u8 bits = OV8865_FORMAT2_FLIP_HORZ_ISP_EN | - OV8865_FORMAT2_FLIP_HORZ_SENSOR_EN; - - return ov8865_update_bits(sensor, OV8865_FORMAT2_REG, bits, - enable ? bits : 0); -} - -/* Test Pattern */ - -static int ov8865_test_pattern_configure(struct ov8865_sensor *sensor, - unsigned int index) -{ - if (index >= ARRAY_SIZE(ov8865_test_pattern_bits)) - return -EINVAL; - - return ov8865_write(sensor, OV8865_PRE_CTRL0_REG, - ov8865_test_pattern_bits[index]); -} - -/* Blanking */ - -static int ov8865_vts_configure(struct ov8865_sensor *sensor, u32 vblank) -{ - u16 vts = sensor->state.mode->output_size_y + vblank; - int ret; - - ret = ov8865_write(sensor, OV8865_VTS_H_REG, OV8865_VTS_H(vts)); - if (ret) - return ret; - - return ov8865_write(sensor, OV8865_VTS_L_REG, OV8865_VTS_L(vts)); -} - -/* State */ - -static int ov8865_state_mipi_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode, - u32 mbus_code) -{ - struct ov8865_ctrls *ctrls = &sensor->ctrls; - struct v4l2_mbus_config_mipi_csi2 *bus_mipi_csi2 = - &sensor->endpoint.bus.mipi_csi2; - unsigned long mipi_clk_rate; - unsigned int bits_per_sample; - unsigned int lanes_count; - unsigned int i, j; - s64 mipi_pixel_rate; - - mipi_clk_rate = ov8865_mode_mipi_clk_rate(sensor, mode); - if (!mipi_clk_rate) - return -EINVAL; - - for (i = 0; i < ARRAY_SIZE(ov8865_link_freq_menu); i++) { - s64 freq = ov8865_link_freq_menu[i]; - - if (freq == mipi_clk_rate) - break; - } - - for (j = 0; j < sensor->endpoint.nr_of_link_frequencies; j++) { - u64 freq = sensor->endpoint.link_frequencies[j]; - - if (freq == mipi_clk_rate) - break; - } - - if (i == ARRAY_SIZE(ov8865_link_freq_menu)) { - dev_err(sensor->dev, - "failed to find %lu clk rate in link freq\n", - mipi_clk_rate); - } else if (j == sensor->endpoint.nr_of_link_frequencies) { - dev_err(sensor->dev, - "failed to find %lu clk rate in endpoint link-frequencies\n", - mipi_clk_rate); - } else { - __v4l2_ctrl_s_ctrl(ctrls->link_freq, i); - } - - switch (mbus_code) { - case MEDIA_BUS_FMT_SBGGR10_1X10: - bits_per_sample = 10; - break; - default: - return -EINVAL; - } - - lanes_count = bus_mipi_csi2->num_data_lanes; - mipi_pixel_rate = mipi_clk_rate * 2 * lanes_count / bits_per_sample; - - __v4l2_ctrl_s_ctrl_int64(ctrls->pixel_rate, mipi_pixel_rate); - - return 0; -} - -static int ov8865_state_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode, - u32 mbus_code) -{ - int ret; - - if (sensor->state.streaming) - return -EBUSY; - - /* State will be configured at first power on otherwise. */ - if (pm_runtime_enabled(sensor->dev) && - !pm_runtime_suspended(sensor->dev)) { - ret = ov8865_mode_configure(sensor, mode, mbus_code); - if (ret) - return ret; - } - - ret = ov8865_state_mipi_configure(sensor, mode, mbus_code); - if (ret) - return ret; - - sensor->state.mode = mode; - sensor->state.mbus_code = mbus_code; - - return 0; -} - -static int ov8865_state_init(struct ov8865_sensor *sensor) -{ - return ov8865_state_configure(sensor, &ov8865_modes[0], - ov8865_mbus_codes[0]); -} - -/* Sensor Base */ - -static int ov8865_sensor_init(struct ov8865_sensor *sensor) -{ - int ret; - - ret = ov8865_sw_reset(sensor); - if (ret) { - dev_err(sensor->dev, "failed to perform sw reset\n"); - return ret; - } - - ret = ov8865_sw_standby(sensor, 1); - if (ret) { - dev_err(sensor->dev, "failed to set sensor standby\n"); - return ret; - } - - ret = ov8865_chip_id_check(sensor); - if (ret) { - dev_err(sensor->dev, "failed to check sensor chip id\n"); - return ret; - } - - ret = ov8865_write_sequence(sensor, ov8865_init_sequence, - ARRAY_SIZE(ov8865_init_sequence)); - if (ret) { - dev_err(sensor->dev, "failed to write init sequence\n"); - return ret; - } - - ret = ov8865_charge_pump_configure(sensor); - if (ret) { - dev_err(sensor->dev, "failed to configure pad\n"); - return ret; - } - - ret = ov8865_mipi_configure(sensor); - if (ret) { - dev_err(sensor->dev, "failed to configure MIPI\n"); - return ret; - } - - ret = ov8865_isp_configure(sensor); - if (ret) { - dev_err(sensor->dev, "failed to configure ISP\n"); - return ret; - } - - ret = ov8865_black_level_configure(sensor); - if (ret) { - dev_err(sensor->dev, "failed to configure black level\n"); - return ret; - } - - /* Configure current mode. */ - ret = ov8865_state_configure(sensor, sensor->state.mode, - sensor->state.mbus_code); - if (ret) { - dev_err(sensor->dev, "failed to configure state\n"); - return ret; - } - - return 0; -} - -static int ov8865_sensor_power(struct ov8865_sensor *sensor, bool on) -{ - /* Keep initialized to zero for disable label. */ + struct ov8865 *ov8865 = to_ov8865(sd); + struct i2c_client *client = ov8865->client; int ret = 0; + dev_info(&client->dev, "%s: on: %d, %dx%d@%d\n", __func__, on, + ov8865->cur_mode->width, + ov8865->cur_mode->height, + DIV_ROUND_CLOSEST(ov8865->cur_mode->max_fps.denominator, + ov8865->cur_mode->max_fps.numerator)); + + mutex_lock(&ov8865->mutex); + on = !!on; + if (on == ov8865->streaming) + goto unlock_and_return; + if (on) { - gpiod_set_value_cansleep(sensor->reset, 1); - gpiod_set_value_cansleep(sensor->powerdown, 1); - - ret = regulator_enable(sensor->dovdd); - if (ret) { - dev_err(sensor->dev, - "failed to enable DOVDD regulator\n"); - return ret; + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto unlock_and_return; } - ret = regulator_enable(sensor->avdd); + ret = __ov8865_start_stream(ov8865); if (ret) { - dev_err(sensor->dev, - "failed to enable AVDD regulator\n"); - goto disable_dovdd; + v4l2_err(sd, "start stream failed while write regs\n"); + pm_runtime_put(&client->dev); + goto unlock_and_return; } - - ret = regulator_enable(sensor->dvdd); - if (ret) { - dev_err(sensor->dev, - "failed to enable DVDD regulator\n"); - goto disable_avdd; - } - - ret = clk_prepare_enable(sensor->extclk); - if (ret) { - dev_err(sensor->dev, "failed to enable EXTCLK clock\n"); - goto disable_dvdd; - } - - gpiod_set_value_cansleep(sensor->reset, 0); - gpiod_set_value_cansleep(sensor->powerdown, 0); - - /* Time to enter streaming mode according to power timings. */ - usleep_range(10000, 12000); } else { - gpiod_set_value_cansleep(sensor->powerdown, 1); - gpiod_set_value_cansleep(sensor->reset, 1); - - clk_disable_unprepare(sensor->extclk); - -disable_dvdd: - regulator_disable(sensor->dvdd); -disable_avdd: - regulator_disable(sensor->avdd); -disable_dovdd: - regulator_disable(sensor->dovdd); + __ov8865_stop_stream(ov8865); + pm_runtime_put(&client->dev); } + ov8865->streaming = on; + +unlock_and_return: + mutex_unlock(&ov8865->mutex); + return ret; } -/* Controls */ - -static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) +static int ov8865_s_power(struct v4l2_subdev *sd, int on) { - struct v4l2_subdev *subdev = ov8865_ctrl_subdev(ctrl); - struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); - unsigned int index; - int ret; + struct ov8865 *ov8865 = to_ov8865(sd); + struct i2c_client *client = ov8865->client; + int ret = 0; - /* If VBLANK is altered we need to update exposure to compensate */ - if (ctrl->id == V4L2_CID_VBLANK) { - int exposure_max; + dev_info(&client->dev, "%s: on: %d\n", __func__, on); - exposure_max = sensor->state.mode->output_size_y + ctrl->val - - OV8865_INTEGRATION_TIME_MARGIN; - __v4l2_ctrl_modify_range(sensor->ctrls.exposure, - sensor->ctrls.exposure->minimum, - exposure_max, - sensor->ctrls.exposure->step, - min(sensor->ctrls.exposure->val, - exposure_max)); + mutex_lock(&ov8865->mutex); + + /* If the power state is not modified - no work to do. */ + if (ov8865->power_on == !!on) + goto unlock_and_return; + + if (on) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto unlock_and_return; + } + + ov8865->power_on = true; + } else { + pm_runtime_put(&client->dev); + ov8865->power_on = false; } - /* Wait for the sensor to be on before setting controls. */ - if (pm_runtime_suspended(sensor->dev)) +unlock_and_return: + mutex_unlock(&ov8865->mutex); + + return ret; +} + +/* Calculate the delay in us by clock rate and clock cycles */ +static inline u32 ov8865_cal_delay(u32 cycles) +{ + return DIV_ROUND_UP(cycles, OV8865_XVCLK_FREQ / 1000 / 1000); +} + +static int __ov8865_power_on(struct ov8865 *ov8865) +{ + int ret; + u32 delay_us; + struct device *dev = &ov8865->client->dev; + + if (!IS_ERR(ov8865->power_gpio)) + gpiod_set_value_cansleep(ov8865->power_gpio, 1); + + usleep_range(1000, 2000); + + if (!IS_ERR_OR_NULL(ov8865->pins_default)) { + ret = pinctrl_select_state(ov8865->pinctrl, + ov8865->pins_default); + if (ret < 0) + dev_err(dev, "could not set pins\n"); + } + + ret = clk_set_rate(ov8865->xvclk, OV8865_XVCLK_FREQ); + if (ret < 0) + dev_warn(dev, "Failed to set xvclk rate (24MHz)\n"); + if (clk_get_rate(ov8865->xvclk) != OV8865_XVCLK_FREQ) + dev_warn(dev, "xvclk mismatched, modes are based on 24MHz\n"); + ret = clk_prepare_enable(ov8865->xvclk); + if (ret < 0) { + dev_err(dev, "Failed to enable xvclk\n"); + return ret; + } + + if (!IS_ERR(ov8865->reset_gpio)) + gpiod_set_value_cansleep(ov8865->reset_gpio, 0); + + ret = regulator_bulk_enable(OV8865_NUM_SUPPLIES, ov8865->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators\n"); + goto disable_clk; + } + + if (!IS_ERR(ov8865->reset_gpio)) + gpiod_set_value_cansleep(ov8865->reset_gpio, 1); + + usleep_range(1000, 2000); + if (!IS_ERR(ov8865->pwdn_gpio)) + gpiod_set_value_cansleep(ov8865->pwdn_gpio, 1); + + /* 8192 cycles prior to first SCCB transaction */ + delay_us = ov8865_cal_delay(8192); + usleep_range(delay_us, delay_us * 2); + + return 0; + +disable_clk: + clk_disable_unprepare(ov8865->xvclk); + + return ret; +} + +static void __ov8865_power_off(struct ov8865 *ov8865) +{ + int ret; + struct device *dev = &ov8865->client->dev; + + if (!IS_ERR(ov8865->pwdn_gpio)) + gpiod_set_value_cansleep(ov8865->pwdn_gpio, 0); + clk_disable_unprepare(ov8865->xvclk); + if (!IS_ERR(ov8865->reset_gpio)) + gpiod_set_value_cansleep(ov8865->reset_gpio, 0); + if (!IS_ERR_OR_NULL(ov8865->pins_sleep)) { + ret = pinctrl_select_state(ov8865->pinctrl, + ov8865->pins_sleep); + if (ret < 0) + dev_dbg(dev, "could not set pins\n"); + } + + if (!IS_ERR(ov8865->power_gpio)) + gpiod_set_value_cansleep(ov8865->power_gpio, 0); + + regulator_bulk_disable(OV8865_NUM_SUPPLIES, ov8865->supplies); +} + +static int __maybe_unused ov8865_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov8865 *ov8865 = to_ov8865(sd); + + return __ov8865_power_on(ov8865); +} + +static int __maybe_unused ov8865_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov8865 *ov8865 = to_ov8865(sd); + + __ov8865_power_off(ov8865); + + return 0; +} + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static int ov8865_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov8865 *ov8865 = to_ov8865(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->state, 0); + const struct ov8865_mode *def_mode = &supported_modes[0]; + + mutex_lock(&ov8865->mutex); + /* Initialize try_fmt */ + try_fmt->width = def_mode->width; + try_fmt->height = def_mode->height; + try_fmt->code = OV8865_MEDIA_BUS_FMT; + try_fmt->field = V4L2_FIELD_NONE; + + mutex_unlock(&ov8865->mutex); + /* No crop or compose */ + + return 0; +} +#endif + +static int ov8865_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + if (fie->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + fie->code = OV8865_MEDIA_BUS_FMT; + fie->width = supported_modes[fie->index].width; + fie->height = supported_modes[fie->index].height; + fie->interval = supported_modes[fie->index].max_fps; + return 0; +} + +static int ov8865_g_mbus_config(struct v4l2_subdev *sd, unsigned int pad_id, + struct v4l2_mbus_config *config) +{ + config->type = V4L2_MBUS_CSI2_DPHY; + config->bus.mipi_csi2.num_data_lanes = OV8865_LANES; + + return 0; +} + +static const struct dev_pm_ops ov8865_pm_ops = { + SET_RUNTIME_PM_OPS(ov8865_runtime_suspend, + ov8865_runtime_resume, NULL) +}; + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static const struct v4l2_subdev_internal_ops ov8865_internal_ops = { + .open = ov8865_open, +}; +#endif + +static const struct v4l2_subdev_core_ops ov8865_core_ops = { + .s_power = ov8865_s_power, + .ioctl = ov8865_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = ov8865_compat_ioctl32, +#endif +}; + +static const struct v4l2_subdev_video_ops ov8865_video_ops = { + .s_stream = ov8865_s_stream, + .g_frame_interval = ov8865_g_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops ov8865_pad_ops = { + .enum_mbus_code = ov8865_enum_mbus_code, + .enum_frame_size = ov8865_enum_frame_sizes, + .enum_frame_interval = ov8865_enum_frame_interval, + .get_fmt = ov8865_get_fmt, + .set_fmt = ov8865_set_fmt, + .get_mbus_config = ov8865_g_mbus_config, +}; + +static const struct v4l2_subdev_ops ov8865_subdev_ops = { + .core = &ov8865_core_ops, + .video = &ov8865_video_ops, + .pad = &ov8865_pad_ops, +}; + +static int ov8865_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov8865 *ov8865 = container_of(ctrl->handler, + struct ov8865, ctrl_handler); + struct i2c_client *client = ov8865->client; + s64 max; + int ret = 0; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = ov8865->cur_mode->height + ctrl->val - 4; + __v4l2_ctrl_modify_range(ov8865->exposure, + ov8865->exposure->minimum, max, + ov8865->exposure->step, + ov8865->exposure->default_value); + break; + } + + if (!pm_runtime_get_if_in_use(&client->dev)) return 0; switch (ctrl->id) { case V4L2_CID_EXPOSURE: - ret = ov8865_exposure_configure(sensor, ctrl->val); - if (ret) - return ret; + /* 4 least significant bits of expsoure are fractional part */ + dev_dbg(&client->dev, "set exposure value 0x%x\n", ctrl->val); + ret = ov8865_write_reg(ov8865->client, + OV8865_REG_EXPOSURE, + OV8865_REG_VALUE_24BIT, + ctrl->val << 4); break; case V4L2_CID_ANALOGUE_GAIN: - ret = ov8865_analog_gain_configure(sensor, ctrl->val); - if (ret) - return ret; + dev_dbg(&client->dev, "set analog gain value 0x%x\n", ctrl->val); + ret = ov8865_write_reg(ov8865->client, + OV8865_REG_GAIN_H, + OV8865_REG_VALUE_08BIT, + (ctrl->val >> OV8865_GAIN_H_SHIFT) & + OV8865_GAIN_H_MASK); + ret |= ov8865_write_reg(ov8865->client, + OV8865_REG_GAIN_L, + OV8865_REG_VALUE_08BIT, + ctrl->val & OV8865_GAIN_L_MASK); break; - case V4L2_CID_RED_BALANCE: - return ov8865_red_balance_configure(sensor, ctrl->val); - case V4L2_CID_BLUE_BALANCE: - return ov8865_blue_balance_configure(sensor, ctrl->val); - case V4L2_CID_HFLIP: - return ov8865_flip_horz_configure(sensor, !!ctrl->val); - case V4L2_CID_VFLIP: - return ov8865_flip_vert_configure(sensor, !!ctrl->val); - case V4L2_CID_TEST_PATTERN: - index = (unsigned int)ctrl->val; - return ov8865_test_pattern_configure(sensor, index); case V4L2_CID_VBLANK: - return ov8865_vts_configure(sensor, ctrl->val); + dev_dbg(&client->dev, "set vb value 0x%x\n", ctrl->val); + ret = ov8865_write_reg(ov8865->client, + OV8865_REG_VTS, + OV8865_REG_VALUE_16BIT, + ctrl->val + ov8865->cur_mode->height); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov8865_enable_test_pattern(ov8865, ctrl->val); + break; default: - return -EINVAL; + dev_warn(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n", + __func__, ctrl->id, ctrl->val); + break; } - return 0; + pm_runtime_put(&client->dev); + + return ret; } static const struct v4l2_ctrl_ops ov8865_ctrl_ops = { - .s_ctrl = ov8865_s_ctrl, + .s_ctrl = ov8865_set_ctrl, }; -static int ov8865_ctrls_init(struct ov8865_sensor *sensor) +static int ov8865_initialize_controls(struct ov8865 *ov8865) { - struct ov8865_ctrls *ctrls = &sensor->ctrls; - struct v4l2_ctrl_handler *handler = &ctrls->handler; - const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; - const struct ov8865_mode *mode = &ov8865_modes[0]; - struct v4l2_fwnode_device_properties props; - unsigned int vblank_max, vblank_def; - unsigned int hblank; + const struct ov8865_mode *mode; + struct v4l2_ctrl_handler *handler; + s64 exposure_max, vblank_def; + u32 h_blank; int ret; + u64 dst_pixel_rate = 0; + u32 lane_num = OV8865_LANES; - v4l2_ctrl_handler_init(handler, 32); - - /* Use our mutex for ctrl locking. */ - handler->lock = &sensor->mutex; - - /* Exposure */ - - ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 2, - 65535, 1, 32); - - /* Gain */ - - v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 2048, 128, - 128); - - /* White Balance */ - - v4l2_ctrl_new_std(handler, ops, V4L2_CID_RED_BALANCE, 1, 32767, 1, - 1024); - - v4l2_ctrl_new_std(handler, ops, V4L2_CID_BLUE_BALANCE, 1, 32767, 1, - 1024); - - /* Flip */ - - v4l2_ctrl_new_std(handler, ops, V4L2_CID_HFLIP, 0, 1, 1, 0); - v4l2_ctrl_new_std(handler, ops, V4L2_CID_VFLIP, 0, 1, 1, 0); - - /* Test Pattern */ - - v4l2_ctrl_new_std_menu_items(handler, ops, V4L2_CID_TEST_PATTERN, - ARRAY_SIZE(ov8865_test_pattern_menu) - 1, - 0, 0, ov8865_test_pattern_menu); - - /* Blanking */ - hblank = mode->hts - mode->output_size_x; - ctrls->hblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_HBLANK, hblank, - hblank, 1, hblank); - - if (ctrls->hblank) - ctrls->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; - - vblank_max = OV8865_TIMING_MAX_VTS - mode->output_size_y; - vblank_def = mode->vts - mode->output_size_y; - ctrls->vblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_VBLANK, - OV8865_TIMING_MIN_VTS, vblank_max, 1, - vblank_def); - - /* MIPI CSI-2 */ - - ctrls->link_freq = - v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, - ARRAY_SIZE(ov8865_link_freq_menu) - 1, - 0, ov8865_link_freq_menu); - - ctrls->pixel_rate = - v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 1, - INT_MAX, 1, 1); - - /* set properties from fwnode (e.g. rotation, orientation) */ - ret = v4l2_fwnode_device_parse(sensor->dev, &props); + handler = &ov8865->ctrl_handler; + mode = ov8865->cur_mode; + ret = v4l2_ctrl_handler_init(handler, 8); if (ret) - goto error_ctrls; + return ret; + handler->lock = &ov8865->mutex; - ret = v4l2_ctrl_new_fwnode_properties(handler, ops, &props); - if (ret) - goto error_ctrls; + ov8865->link_freq = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, + 0, 0, link_freq_menu_items); + + dst_pixel_rate = (u32)link_freq_menu_items[mode->mipi_freq_idx] / mode->bpp * 2 * lane_num; + + ov8865->pixel_rate = v4l2_ctrl_new_std(handler, NULL, + V4L2_CID_PIXEL_RATE, + 0, OV8865_PIXEL_RATE, + 1, dst_pixel_rate); + + __v4l2_ctrl_s_ctrl(ov8865->link_freq, + mode->mipi_freq_idx); + + h_blank = mode->hts_def - mode->width; + ov8865->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK, + h_blank, h_blank, 1, h_blank); + if (ov8865->hblank) + ov8865->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + vblank_def = mode->vts_def - mode->height; + ov8865->vblank = v4l2_ctrl_new_std(handler, &ov8865_ctrl_ops, + V4L2_CID_VBLANK, vblank_def, + OV8865_VTS_MAX - mode->height, + 1, vblank_def); + + exposure_max = mode->vts_def - 4; + ov8865->exposure = v4l2_ctrl_new_std(handler, &ov8865_ctrl_ops, + V4L2_CID_EXPOSURE, OV8865_EXPOSURE_MIN, + exposure_max, OV8865_EXPOSURE_STEP, + mode->exp_def); + + ov8865->anal_gain = v4l2_ctrl_new_std(handler, &ov8865_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, OV8865_GAIN_MIN, + OV8865_GAIN_MAX, OV8865_GAIN_STEP, + OV8865_GAIN_DEFAULT); + + ov8865->test_pattern = v4l2_ctrl_new_std_menu_items(handler, + &ov8865_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov8865_test_pattern_menu) - 1, + 0, 0, ov8865_test_pattern_menu); if (handler->error) { ret = handler->error; - goto error_ctrls; + dev_err(&ov8865->client->dev, + "Failed to init controls(%d)\n", ret); + goto err_free_handler; } - ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; - ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; - - sensor->subdev.ctrl_handler = handler; + ov8865->subdev.ctrl_handler = handler; return 0; -error_ctrls: +err_free_handler: v4l2_ctrl_handler_free(handler); return ret; } -/* Subdev Video Operations */ - -static int ov8865_s_stream(struct v4l2_subdev *subdev, int enable) +static int ov8865_check_sensor_id(struct ov8865 *ov8865, + struct i2c_client *client) { - struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); - struct ov8865_state *state = &sensor->state; + struct device *dev = &ov8865->client->dev; + u32 id = 0; int ret; - if (enable) { - ret = pm_runtime_resume_and_get(sensor->dev); - if (ret < 0) - return ret; + ret = ov8865_read_reg(client, OV8865_REG_CHIP_ID, + OV8865_REG_VALUE_24BIT, &id); + if (id != CHIP_ID) { + dev_err(dev, "Unexpected sensor id(%06x), ret(%d)\n", id, ret); + return -ENODEV; } - mutex_lock(&sensor->mutex); - ret = ov8865_sw_standby(sensor, !enable); - mutex_unlock(&sensor->mutex); - - if (ret) - return ret; - - state->streaming = !!enable; - - if (!enable) - pm_runtime_put(sensor->dev); + ret = ov8865_read_reg(client, OV8865_CHIP_REVISION_REG, + OV8865_REG_VALUE_08BIT, &id); + if (ret) { + dev_err(dev, "Read chip revision register error\n"); + return -ENODEV; + } + dev_info(dev, "Detected OV%06x sensor, REVISION 0x%x\n", CHIP_ID, id); return 0; } -static int ov8865_g_frame_interval(struct v4l2_subdev *subdev, - struct v4l2_subdev_frame_interval *interval) +static int ov8865_configure_regulators(struct ov8865 *ov8865) { - struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); - const struct ov8865_mode *mode; - unsigned int framesize; - unsigned int fps; + unsigned int i; - mutex_lock(&sensor->mutex); + for (i = 0; i < OV8865_NUM_SUPPLIES; i++) + ov8865->supplies[i].supply = ov8865_supply_names[i]; - mode = sensor->state.mode; - framesize = mode->hts * (mode->output_size_y + - sensor->ctrls.vblank->val); - fps = DIV_ROUND_CLOSEST(sensor->ctrls.pixel_rate->val, framesize); - - interval->interval.numerator = 1; - interval->interval.denominator = fps; - - mutex_unlock(&sensor->mutex); - - return 0; + return devm_regulator_bulk_get(&ov8865->client->dev, + OV8865_NUM_SUPPLIES, + ov8865->supplies); } -static const struct v4l2_subdev_video_ops ov8865_subdev_video_ops = { - .s_stream = ov8865_s_stream, - .g_frame_interval = ov8865_g_frame_interval, - .s_frame_interval = ov8865_g_frame_interval, -}; - -/* Subdev Pad Operations */ - -static int ov8865_enum_mbus_code(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_mbus_code_enum *code_enum) -{ - if (code_enum->index >= ARRAY_SIZE(ov8865_mbus_codes)) - return -EINVAL; - - code_enum->code = ov8865_mbus_codes[code_enum->index]; - - return 0; -} - -static void ov8865_mbus_format_fill(struct v4l2_mbus_framefmt *mbus_format, - u32 mbus_code, - const struct ov8865_mode *mode) -{ - mbus_format->width = mode->output_size_x; - mbus_format->height = mode->output_size_y; - mbus_format->code = mbus_code; - - mbus_format->field = V4L2_FIELD_NONE; - mbus_format->colorspace = V4L2_COLORSPACE_RAW; - mbus_format->ycbcr_enc = - V4L2_MAP_YCBCR_ENC_DEFAULT(mbus_format->colorspace); - mbus_format->quantization = V4L2_QUANTIZATION_FULL_RANGE; - mbus_format->xfer_func = - V4L2_MAP_XFER_FUNC_DEFAULT(mbus_format->colorspace); -} - -static int ov8865_get_fmt(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *format) -{ - struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - - mutex_lock(&sensor->mutex); - - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *mbus_format = *v4l2_subdev_get_try_format(subdev, sd_state, - format->pad); - else - ov8865_mbus_format_fill(mbus_format, sensor->state.mbus_code, - sensor->state.mode); - - mutex_unlock(&sensor->mutex); - - return 0; -} - -static int ov8865_set_fmt(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *format) -{ - struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - const struct ov8865_mode *mode; - u32 mbus_code = 0; - unsigned int hblank; - unsigned int index; - int exposure_max; - int ret = 0; - - mutex_lock(&sensor->mutex); - - if (sensor->state.streaming) { - ret = -EBUSY; - goto complete; - } - - /* Try to find requested mbus code. */ - for (index = 0; index < ARRAY_SIZE(ov8865_mbus_codes); index++) { - if (ov8865_mbus_codes[index] == mbus_format->code) { - mbus_code = mbus_format->code; - break; - } - } - - /* Fallback to default. */ - if (!mbus_code) - mbus_code = ov8865_mbus_codes[0]; - - /* Find the mode with nearest dimensions. */ - mode = v4l2_find_nearest_size(ov8865_modes, ARRAY_SIZE(ov8865_modes), - output_size_x, output_size_y, - mbus_format->width, mbus_format->height); - if (!mode) { - ret = -EINVAL; - goto complete; - } - - ov8865_mbus_format_fill(mbus_format, mbus_code, mode); - - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *v4l2_subdev_get_try_format(subdev, sd_state, format->pad) = - *mbus_format; - else if (sensor->state.mode != mode || - sensor->state.mbus_code != mbus_code) - ret = ov8865_state_configure(sensor, mode, mbus_code); - - __v4l2_ctrl_modify_range(sensor->ctrls.vblank, OV8865_TIMING_MIN_VTS, - OV8865_TIMING_MAX_VTS - mode->output_size_y, - 1, mode->vts - mode->output_size_y); - - hblank = mode->hts - mode->output_size_x; - __v4l2_ctrl_modify_range(sensor->ctrls.hblank, hblank, hblank, 1, - hblank); - - exposure_max = mode->vts - OV8865_INTEGRATION_TIME_MARGIN; - __v4l2_ctrl_modify_range(sensor->ctrls.exposure, - sensor->ctrls.exposure->minimum, exposure_max, - sensor->ctrls.exposure->step, - min(sensor->ctrls.exposure->val, - exposure_max)); - -complete: - mutex_unlock(&sensor->mutex); - - return ret; -} - -static int ov8865_enum_frame_size(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_frame_size_enum *size_enum) -{ - const struct ov8865_mode *mode; - - if (size_enum->index >= ARRAY_SIZE(ov8865_modes)) - return -EINVAL; - - mode = &ov8865_modes[size_enum->index]; - - size_enum->min_width = size_enum->max_width = mode->output_size_x; - size_enum->min_height = size_enum->max_height = mode->output_size_y; - - return 0; -} - -static void -__ov8865_get_pad_crop(struct ov8865_sensor *sensor, - struct v4l2_subdev_state *state, unsigned int pad, - enum v4l2_subdev_format_whence which, struct v4l2_rect *r) -{ - const struct ov8865_mode *mode = sensor->state.mode; - - switch (which) { - case V4L2_SUBDEV_FORMAT_TRY: - *r = *v4l2_subdev_get_try_crop(&sensor->subdev, state, pad); - break; - case V4L2_SUBDEV_FORMAT_ACTIVE: - r->height = mode->output_size_y; - r->width = mode->output_size_x; - r->top = (OV8865_NATIVE_HEIGHT - mode->output_size_y) / 2; - r->left = (OV8865_NATIVE_WIDTH - mode->output_size_x) / 2; - break; - } -} - -static int ov8865_get_selection(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *state, - struct v4l2_subdev_selection *sel) -{ - struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); - - switch (sel->target) { - case V4L2_SEL_TGT_CROP: - mutex_lock(&sensor->mutex); - __ov8865_get_pad_crop(sensor, state, sel->pad, - sel->which, &sel->r); - mutex_unlock(&sensor->mutex); - break; - case V4L2_SEL_TGT_NATIVE_SIZE: - sel->r.top = 0; - sel->r.left = 0; - sel->r.width = OV8865_NATIVE_WIDTH; - sel->r.height = OV8865_NATIVE_HEIGHT; - break; - case V4L2_SEL_TGT_CROP_BOUNDS: - case V4L2_SEL_TGT_CROP_DEFAULT: - sel->r.top = OV8865_ACTIVE_START_TOP; - sel->r.left = OV8865_ACTIVE_START_LEFT; - sel->r.width = OV8865_ACTIVE_WIDTH; - sel->r.height = OV8865_ACTIVE_HEIGHT; - break; - default: - return -EINVAL; - } - - return 0; -} - -static const struct v4l2_subdev_pad_ops ov8865_subdev_pad_ops = { - .enum_mbus_code = ov8865_enum_mbus_code, - .get_fmt = ov8865_get_fmt, - .set_fmt = ov8865_set_fmt, - .enum_frame_size = ov8865_enum_frame_size, - .get_selection = ov8865_get_selection, - .set_selection = ov8865_get_selection, -}; - -static const struct v4l2_subdev_ops ov8865_subdev_ops = { - .video = &ov8865_subdev_video_ops, - .pad = &ov8865_subdev_pad_ops, -}; - -static int ov8865_suspend(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct v4l2_subdev *subdev = i2c_get_clientdata(client); - struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); - struct ov8865_state *state = &sensor->state; - int ret = 0; - - mutex_lock(&sensor->mutex); - - if (state->streaming) { - ret = ov8865_sw_standby(sensor, true); - if (ret) - goto complete; - } - - ret = ov8865_sensor_power(sensor, false); - if (ret) - ov8865_sw_standby(sensor, false); - -complete: - mutex_unlock(&sensor->mutex); - - return ret; -} - -static int ov8865_resume(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct v4l2_subdev *subdev = i2c_get_clientdata(client); - struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); - struct ov8865_state *state = &sensor->state; - int ret = 0; - - mutex_lock(&sensor->mutex); - - ret = ov8865_sensor_power(sensor, true); - if (ret) - goto complete; - - ret = ov8865_sensor_init(sensor); - if (ret) - goto error_power; - - ret = __v4l2_ctrl_handler_setup(&sensor->ctrls.handler); - if (ret) - goto error_power; - - if (state->streaming) { - ret = ov8865_sw_standby(sensor, false); - if (ret) - goto error_power; - } - - goto complete; - -error_power: - ov8865_sensor_power(sensor, false); - -complete: - mutex_unlock(&sensor->mutex); - - return ret; -} - -static int ov8865_probe(struct i2c_client *client) +static int ov8865_probe(struct i2c_client *client, + const struct i2c_device_id *id) { struct device *dev = &client->dev; - struct fwnode_handle *handle; - struct ov8865_sensor *sensor; - struct v4l2_subdev *subdev; - struct media_pad *pad; - unsigned int rate = 0; - unsigned int i; + struct device_node *node = dev->of_node; + struct ov8865 *ov8865; + struct v4l2_subdev *sd; + char facing[2]; int ret; - sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); - if (!sensor) + dev_info(dev, "driver version: %02x.%02x.%02x", + DRIVER_VERSION >> 16, + (DRIVER_VERSION & 0xff00) >> 8, + DRIVER_VERSION & 0x00ff); + + ov8865 = devm_kzalloc(dev, sizeof(*ov8865), GFP_KERNEL); + if (!ov8865) return -ENOMEM; - sensor->dev = dev; - sensor->i2c_client = client; - - /* Regulators */ - - /* DVDD: digital core */ - sensor->dvdd = devm_regulator_get(dev, "dvdd"); - if (IS_ERR(sensor->dvdd)) - return dev_err_probe(dev, PTR_ERR(sensor->dvdd), - "cannot get DVDD regulator\n"); - - /* DOVDD: digital I/O */ - sensor->dovdd = devm_regulator_get(dev, "dovdd"); - if (IS_ERR(sensor->dovdd)) - return dev_err_probe(dev, PTR_ERR(sensor->dovdd), - "cannot get DOVDD regulator\n"); - - /* AVDD: analog */ - sensor->avdd = devm_regulator_get(dev, "avdd"); - if (IS_ERR(sensor->avdd)) - return dev_err_probe(dev, PTR_ERR(sensor->avdd), - "cannot get AVDD (analog) regulator\n"); - - /* Graph Endpoint */ - - handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); - if (!handle) - return -EPROBE_DEFER; - - sensor->endpoint.bus_type = V4L2_MBUS_CSI2_DPHY; - - ret = v4l2_fwnode_endpoint_alloc_parse(handle, &sensor->endpoint); - fwnode_handle_put(handle); + ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX, + &ov8865->module_index); + ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING, + &ov8865->module_facing); + ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME, + &ov8865->module_name); + ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME, + &ov8865->len_name); if (ret) { - dev_err(dev, "failed to parse endpoint node\n"); + dev_err(dev, + "could not get module information!\n"); + return -EINVAL; + } + + ov8865->client = client; + ov8865->cur_mode = &supported_modes[0]; + ov8865->cfg_num = ARRAY_SIZE(supported_modes); + ov8865->xvclk = devm_clk_get(dev, "xvclk"); + if (IS_ERR(ov8865->xvclk)) { + dev_err(dev, "Failed to get xvclk\n"); + return -EINVAL; + } + + ov8865->power_gpio = devm_gpiod_get(dev, "power", GPIOD_OUT_LOW); + if (IS_ERR(ov8865->power_gpio)) + dev_warn(dev, "Failed to get power-gpios, maybe no use\n"); + + ov8865->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ov8865->reset_gpio)) + dev_warn(dev, "Failed to get reset-gpios, maybe no use\n"); + + ov8865->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_OUT_LOW); + if (IS_ERR(ov8865->pwdn_gpio)) + dev_warn(dev, "Failed to get pwdn-gpios, maybe no use\n"); + + ret = ov8865_configure_regulators(ov8865); + if (ret) { + dev_err(dev, "Failed to get power regulators\n"); return ret; } - /* GPIOs */ + ov8865->pinctrl = devm_pinctrl_get(dev); + if (!IS_ERR(ov8865->pinctrl)) { + ov8865->pins_default = + pinctrl_lookup_state(ov8865->pinctrl, + OF_CAMERA_PINCTRL_STATE_DEFAULT); + if (IS_ERR(ov8865->pins_default)) + dev_err(dev, "could not get default pinstate\n"); - sensor->powerdown = devm_gpiod_get_optional(dev, "powerdown", - GPIOD_OUT_HIGH); - if (IS_ERR(sensor->powerdown)) { - ret = PTR_ERR(sensor->powerdown); - goto error_endpoint; + ov8865->pins_sleep = + pinctrl_lookup_state(ov8865->pinctrl, + OF_CAMERA_PINCTRL_STATE_SLEEP); + if (IS_ERR(ov8865->pins_sleep)) + dev_err(dev, "could not get sleep pinstate\n"); } - sensor->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); - if (IS_ERR(sensor->reset)) { - ret = PTR_ERR(sensor->reset); - goto error_endpoint; - } + mutex_init(&ov8865->mutex); - /* External Clock */ - - sensor->extclk = devm_clk_get(dev, NULL); - if (PTR_ERR(sensor->extclk) == -ENOENT) { - dev_info(dev, "no external clock found, continuing...\n"); - sensor->extclk = NULL; - } else if (IS_ERR(sensor->extclk)) { - dev_err(dev, "failed to get external clock\n"); - ret = PTR_ERR(sensor->extclk); - goto error_endpoint; - } - - /* - * We could have either a 24MHz or 19.2MHz clock rate from either dt or - * ACPI...but we also need to support the weird IPU3 case which will - * have an external clock AND a clock-frequency property. Check for the - * clock-frequency property and if found, set that rate if we managed - * to acquire a clock. This should cover the ACPI case. If the system - * uses devicetree then the configured rate should already be set, so - * we can just read it. - */ - ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", - &rate); - if (!ret && sensor->extclk) { - ret = clk_set_rate(sensor->extclk, rate); - if (ret) { - dev_err_probe(dev, ret, "failed to set clock rate\n"); - goto error_endpoint; - } - } else if (ret && !sensor->extclk) { - dev_err_probe(dev, ret, "invalid clock config\n"); - goto error_endpoint; - } - - sensor->extclk_rate = rate ? rate : clk_get_rate(sensor->extclk); - - for (i = 0; i < ARRAY_SIZE(supported_extclk_rates); i++) { - if (sensor->extclk_rate == supported_extclk_rates[i]) - break; - } - - if (i == ARRAY_SIZE(supported_extclk_rates)) { - dev_err(dev, "clock rate %lu Hz is unsupported\n", - sensor->extclk_rate); - ret = -EINVAL; - goto error_endpoint; - } - - sensor->pll_configs = ov8865_pll_configs[i]; - - /* Subdev, entity and pad */ - - subdev = &sensor->subdev; - v4l2_i2c_subdev_init(subdev, client, &ov8865_subdev_ops); - - subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - subdev->entity.function = MEDIA_ENT_F_CAM_SENSOR; - - pad = &sensor->pad; - pad->flags = MEDIA_PAD_FL_SOURCE; - - ret = media_entity_pads_init(&subdev->entity, 1, pad); + sd = &ov8865->subdev; + v4l2_i2c_subdev_init(sd, client, &ov8865_subdev_ops); + ret = ov8865_initialize_controls(ov8865); if (ret) - goto error_entity; + goto err_destroy_mutex; - /* Mutex */ - - mutex_init(&sensor->mutex); - - /* Sensor */ - - ret = ov8865_ctrls_init(sensor); + ret = __ov8865_power_on(ov8865); if (ret) - goto error_mutex; + goto err_free_handler; - mutex_lock(&sensor->mutex); - ret = ov8865_state_init(sensor); - mutex_unlock(&sensor->mutex); + ret = ov8865_check_sensor_id(ov8865, client); if (ret) - goto error_ctrls; + goto err_power_off; - /* Runtime PM */ +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + sd->internal_ops = &ov8865_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; +#endif +#if defined(CONFIG_MEDIA_CONTROLLER) + ov8865->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &ov8865->pad); + if (ret < 0) + goto err_power_off; +#endif - pm_runtime_set_suspended(sensor->dev); - pm_runtime_enable(sensor->dev); + memset(facing, 0, sizeof(facing)); + if (strcmp(ov8865->module_facing, "back") == 0) + facing[0] = 'b'; + else + facing[0] = 'f'; - /* V4L2 subdev register */ + snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s", + ov8865->module_index, facing, + OV8865_NAME, dev_name(sd->dev)); + ret = v4l2_async_register_subdev_sensor(sd); + if (ret) { + dev_err(dev, "v4l2 async register subdev failed\n"); + goto err_clean_entity; + } - ret = v4l2_async_register_subdev_sensor(subdev); - if (ret) - goto error_pm; + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); return 0; -error_pm: - pm_runtime_disable(sensor->dev); - -error_ctrls: - v4l2_ctrl_handler_free(&sensor->ctrls.handler); - -error_mutex: - mutex_destroy(&sensor->mutex); - -error_entity: - media_entity_cleanup(&sensor->subdev.entity); - -error_endpoint: - v4l2_fwnode_endpoint_free(&sensor->endpoint); +err_clean_entity: +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&sd->entity); +#endif +err_power_off: + __ov8865_power_off(ov8865); +err_free_handler: + v4l2_ctrl_handler_free(&ov8865->ctrl_handler); +err_destroy_mutex: + mutex_destroy(&ov8865->mutex); return ret; } static void ov8865_remove(struct i2c_client *client) { - struct v4l2_subdev *subdev = i2c_get_clientdata(client); - struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov8865 *ov8865 = to_ov8865(sd); - v4l2_async_unregister_subdev(subdev); - pm_runtime_disable(sensor->dev); - v4l2_ctrl_handler_free(&sensor->ctrls.handler); - mutex_destroy(&sensor->mutex); - media_entity_cleanup(&subdev->entity); + v4l2_async_unregister_subdev(sd); +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&sd->entity); +#endif + v4l2_ctrl_handler_free(&ov8865->ctrl_handler); + mutex_destroy(&ov8865->mutex); - v4l2_fwnode_endpoint_free(&sensor->endpoint); + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + __ov8865_power_off(ov8865); + pm_runtime_set_suspended(&client->dev); } -static const struct dev_pm_ops ov8865_pm_ops = { - SET_RUNTIME_PM_OPS(ov8865_suspend, ov8865_resume, NULL) -}; - -static const struct acpi_device_id ov8865_acpi_match[] = { - {"INT347A"}, - { } -}; -MODULE_DEVICE_TABLE(acpi, ov8865_acpi_match); - +#if IS_ENABLED(CONFIG_OF) static const struct of_device_id ov8865_of_match[] = { { .compatible = "ovti,ov8865" }, - { } + {}, }; MODULE_DEVICE_TABLE(of, ov8865_of_match); +#endif -static struct i2c_driver ov8865_driver = { - .driver = { - .name = "ov8865", - .of_match_table = ov8865_of_match, - .acpi_match_table = ov8865_acpi_match, - .pm = &ov8865_pm_ops, - }, - .probe_new = ov8865_probe, - .remove = ov8865_remove, +static const struct i2c_device_id ov8865_match_id[] = { + { "ovti,ov8865", 0 }, + { }, }; -module_i2c_driver(ov8865_driver); +static struct i2c_driver ov8865_i2c_driver = { + .driver = { + .name = OV8865_NAME, + .pm = &ov8865_pm_ops, + .of_match_table = of_match_ptr(ov8865_of_match), + }, + .probe = &ov8865_probe, + .remove = &ov8865_remove, + .id_table = ov8865_match_id, +}; -MODULE_AUTHOR("Paul Kocialkowski "); -MODULE_DESCRIPTION("V4L2 driver for the OmniVision OV8865 image sensor"); +static int __init sensor_mod_init(void) +{ + return i2c_add_driver(&ov8865_i2c_driver); +} + +static void __exit sensor_mod_exit(void) +{ + i2c_del_driver(&ov8865_i2c_driver); +} + +device_initcall_sync(sensor_mod_init); +module_exit(sensor_mod_exit); + +MODULE_DESCRIPTION("OmniVision ov8865 sensor driver"); MODULE_LICENSE("GPL v2"); From 4f9780e374c97f892ce83b0c3ec49cc5ee1ea363 Mon Sep 17 00:00:00 2001 From: Jon Lin Date: Sat, 24 Feb 2024 16:39:24 +0800 Subject: [PATCH 13/28] spi: spidev-rkslv: Fix compiler error Change-Id: Ie6e3d11b43b557fcfd7e5f1a412a32ff00d84068 Signed-off-by: Jon Lin --- drivers/spi/spidev-rkmst.c | 10 ++++------ drivers/spi/spidev-rkslv.c | 5 ++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spidev-rkmst.c b/drivers/spi/spidev-rkmst.c index a969adf5ec91..118aa3c4b559 100644 --- a/drivers/spi/spidev-rkmst.c +++ b/drivers/spi/spidev-rkmst.c @@ -442,11 +442,11 @@ static ssize_t spidev_rkmst_misc_write(struct file *filp, const char __user *buf if (!tempbuf) return -ENOMEM; - prandom_bytes(tempbuf, len); + get_random_bytes(tempbuf, len); spidev_rkmst_xfer(spidev, tempbuf, NULL, addr, len); start_time = ktime_get(); for (i = 0; i < loops; i++) { - prandom_bytes(spidev->txbuf, len); + get_random_bytes(spidev->txbuf, len); spidev_rkmst_xfer(spidev, spidev->txbuf, spidev->rxbuf, addr, len); if (memcmp(spidev->rxbuf, tempbuf, len)) { dev_err(&spi->dev, "dulplex autotest failed, loops=%d\n", i); @@ -487,7 +487,7 @@ static ssize_t spidev_rkmst_misc_write(struct file *filp, const char __user *buf start_time = ktime_get(); for (i = 0; i < loops; i++) { - prandom_bytes(spidev->txbuf, len); + get_random_bytes(spidev->txbuf, len); spidev_rkmst_xfer(spidev, spidev->txbuf, NULL, addr, len); spidev_rkmst_xfer(spidev, NULL, spidev->rxbuf, addr, len); if (memcmp(spidev->rxbuf, spidev->txbuf, len)) { @@ -601,13 +601,11 @@ static int spidev_rkmst_probe(struct spi_device *spi) return 0; } -static int spidev_rkmst_remove(struct spi_device *spi) +static void spidev_rkmst_remove(struct spi_device *spi) { struct spidev_rkmst_data *spidev = dev_get_drvdata(&spi->dev); misc_deregister(&spidev->misc_dev); - - return 0; } #ifdef CONFIG_OF diff --git a/drivers/spi/spidev-rkslv.c b/drivers/spi/spidev-rkslv.c index 9f8cfcc6d8ee..e0971ac52a1e 100644 --- a/drivers/spi/spidev-rkslv.c +++ b/drivers/spi/spidev-rkslv.c @@ -108,6 +108,7 @@ static int spidev_slv_write_and_read(struct spidev_rkslv_data *spidev, const voi .tx_buf = tx_buf, .rx_buf = rx_buf, .len = len, + .bits_per_word = bit_per_word, }; struct spi_message m; int ret; @@ -375,7 +376,7 @@ static int spidev_rkslv_probe(struct spi_device *spi) return 0; } -static int spidev_rkslv_remove(struct spi_device *spi) +static void spidev_rkslv_remove(struct spi_device *spi) { struct spidev_rkslv_data *spidev = dev_get_drvdata(&spi->dev); @@ -383,8 +384,6 @@ static int spidev_rkslv_remove(struct spi_device *spi) spi_slave_abort(spi); kthread_stop(spidev->tsk); misc_deregister(&spidev->misc_dev); - - return 0; } #ifdef CONFIG_OF From 82596d73d064dd8b03c04ce604b401ecdce26a50 Mon Sep 17 00:00:00 2001 From: Sugar Zhang Date: Wed, 21 Feb 2024 15:57:07 +0800 Subject: [PATCH 14/28] ASoC: rockchip: spdifrx: Add legacy_dai_naming flag This patch add legacy_dai_naming flag to solve the probe failure. Ref: commit d48a77173534 ("ASoC: rockchip: Migrate to new style legacy DAI naming flag") Signed-off-by: Sugar Zhang Change-Id: I7f0d69db6fb7d814944fa6e73df1a0cead3b39ef --- sound/soc/rockchip/rockchip_spdifrx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/rockchip/rockchip_spdifrx.c b/sound/soc/rockchip/rockchip_spdifrx.c index 0e457232116d..9856f4a2bc7b 100644 --- a/sound/soc/rockchip/rockchip_spdifrx.c +++ b/sound/soc/rockchip/rockchip_spdifrx.c @@ -163,6 +163,7 @@ static struct snd_soc_dai_driver rk_spdifrx_dai = { static const struct snd_soc_component_driver rk_spdifrx_component = { .name = "rockchip-spdifrx", + .legacy_dai_naming = 1, }; static bool rk_spdifrx_wr_reg(struct device *dev, unsigned int reg) From 9d969d5904a394e9b49a08e0e9b1e48e503654b1 Mon Sep 17 00:00:00 2001 From: Sugar Zhang Date: Sun, 18 Feb 2024 19:47:31 +0800 Subject: [PATCH 15/28] ASoC: rockchip: multicodec: Fix err path for adc-key Signed-off-by: Sugar Zhang Change-Id: Ia05050fcd124dc5537548b4d9dd3aa6bb1a50792 --- sound/soc/rockchip/rockchip_multicodecs.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sound/soc/rockchip/rockchip_multicodecs.c b/sound/soc/rockchip/rockchip_multicodecs.c index aeb7a84cb6a5..4bf9cfa1d802 100644 --- a/sound/soc/rockchip/rockchip_multicodecs.c +++ b/sound/soc/rockchip/rockchip_multicodecs.c @@ -691,10 +691,13 @@ static int rk_multicodecs_probe(struct platform_device *pdev) mc_data->adc = devm_iio_channel_get(&pdev->dev, "adc-detect"); if (IS_ERR(mc_data->adc)) { - if (PTR_ERR(mc_data->adc) != -EPROBE_DEFER) { - mc_data->adc = NULL; - dev_warn(&pdev->dev, "Failed to get ADC channel"); + if (PTR_ERR(mc_data->adc) == -EPROBE_DEFER) { + dev_warn(&pdev->dev, "deferred by saradc not ready\n"); + return -EPROBE_DEFER; } + + mc_data->adc = NULL; + dev_warn(&pdev->dev, "Has no ADC channel\n"); } else { if (mc_data->adc->channel->type != IIO_VOLTAGE) return -EINVAL; From f24d59c6dd8b0e429dbea030aca2e60b09401bdb Mon Sep 17 00:00:00 2001 From: Sandy Huang Date: Thu, 4 Jul 2019 14:09:42 +0800 Subject: [PATCH 16/28] drm/panel: panel-simple: Don't init gpio value at probe When enable display on loader, init gpio would change gpio status, that would make screen flash. Change-Id: I4b69a8d3d83c5bef09014c2134abaee6522a7046 Signed-off-by: Mark Yao Signed-off-by: Sandy Huang Signed-off-by: Guochun Huang --- drivers/gpu/drm/panel/panel-simple.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 205dacba201c..866834f3ecd2 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -585,8 +585,8 @@ static int panel_simple_suspend(struct device *dev) } } - gpiod_set_value_cansleep(p->reset_gpio, 1); - gpiod_set_value_cansleep(p->enable_gpio, 0); + gpiod_direction_output(p->reset_gpio, 1); + gpiod_direction_output(p->enable_gpio, 0); panel_simple_regulator_disable(p); @@ -630,7 +630,7 @@ static int panel_simple_resume(struct device *dev) return err; } - gpiod_set_value_cansleep(p->enable_gpio, 1); + gpiod_direction_output(p->enable_gpio, 1); if (p->desc->delay.prepare) panel_simple_msleep(p->desc->delay.prepare); @@ -642,12 +642,12 @@ static int panel_simple_resume(struct device *dev) return 0; } - gpiod_set_value_cansleep(p->reset_gpio, 1); + gpiod_direction_output(p->reset_gpio, 1); if (p->desc->delay.reset) panel_simple_msleep(p->desc->delay.reset); - gpiod_set_value_cansleep(p->reset_gpio, 0); + gpiod_direction_output(p->reset_gpio, 0); if (p->desc->delay.init) panel_simple_msleep(p->desc->delay.init); From c7827afc11fc2afb1e3d395c95e164e712c8d103 Mon Sep 17 00:00:00 2001 From: Shunhua Lan Date: Tue, 9 Jan 2024 15:35:43 +0800 Subject: [PATCH 17/28] media: i2c: rk628: add hdmirx extcon Signed-off-by: Shunhua Lan Change-Id: I0dc585a1de7221c0676d323041fc4f6e923cf11c --- drivers/media/i2c/rk628/rk628_csi_v4l2.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/media/i2c/rk628/rk628_csi_v4l2.c b/drivers/media/i2c/rk628/rk628_csi_v4l2.c index 15c970f73d2f..5046a99f487e 100644 --- a/drivers/media/i2c/rk628/rk628_csi_v4l2.c +++ b/drivers/media/i2c/rk628/rk628_csi_v4l2.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -121,6 +122,7 @@ struct rk628_csi { struct rk628_hdcp hdcp; bool i2s_enable_default; HAUDINFO audio_info; + struct extcon_dev *extcon; struct rk628_combtxphy *txphy; struct rk628_dsi dsi; const struct rk628_plat_data *plat_data; @@ -228,6 +230,11 @@ static u8 rk628f_edid_init_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD3, }; +static const unsigned int rk628_csi_extcon_cable[] = { + EXTCON_JACK_VIDEO_IN, + EXTCON_NONE, +}; + static const struct mipi_timing rk628d_csi_mipi = { 0x4a, 0xf, 0x5d, 0x3a, 0x3a, 0x5a, 0x1f }; @@ -497,6 +504,7 @@ static void rk628_csi_delayed_work_enable_hotplug(struct work_struct *work) v4l2_ctrl_s_ctrl(csi->detect_tx_5v_ctrl, plugin); v4l2_dbg(1, debug, sd, "%s: 5v_det:%d\n", __func__, plugin); if (plugin) { + extcon_set_state_sync(csi->extcon, EXTCON_JACK_VIDEO_IN, true); rk628_csi_enable_interrupts(sd, false); rk628_hdmirx_audio_setup(csi->audio_info); rk628_hdmirx_set_hdcp(csi->rk628, &csi->hdcp, csi->enable_hdcp); @@ -507,6 +515,7 @@ static void rk628_csi_delayed_work_enable_hotplug(struct work_struct *work) rk628_hdmirx_cec_state_reconfiguration(csi->rk628, csi->cec); rk628_csi_enable_interrupts(sd, true); } else { + extcon_set_state_sync(csi->extcon, EXTCON_JACK_VIDEO_IN, false); rk628_hdmirx_plugout(sd); } mutex_unlock(&csi->confctl_mutex); @@ -3094,6 +3103,19 @@ static int rk628_csi_probe(struct i2c_client *client, if (IS_ERR(csi->classdev)) goto err_hdl; + csi->extcon = devm_extcon_dev_allocate(dev, rk628_csi_extcon_cable); + if (IS_ERR(csi->extcon)) { + err = PTR_ERR(csi->extcon); + v4l2_err(sd, "allocate extcon failed\n"); + goto err_hdl; + } + + err = devm_extcon_dev_register(dev, csi->extcon); + if (err) { + v4l2_err(sd, "failed to register extcon: %d\n", err); + goto err_hdl; + } + INIT_DELAYED_WORK(&csi->delayed_work_enable_hotplug, rk628_csi_delayed_work_enable_hotplug); INIT_DELAYED_WORK(&csi->delayed_work_res_change, @@ -3104,6 +3126,7 @@ static int rk628_csi_probe(struct i2c_client *client, rk628, csi->i2s_enable_default); if (!csi->audio_info) { + err = -ENOMEM; v4l2_err(sd, "request audio info fail\n"); goto err_work_queues; } From 570ffc5278f695ff94da4f9bc8da1c0a70343bcb Mon Sep 17 00:00:00 2001 From: Zain Wang Date: Fri, 23 Feb 2024 17:55:43 +0800 Subject: [PATCH 18/28] arm64: dts: rockchip: AMP memory use reserved-memory Signed-off-by: Zain Wang Change-Id: I05d80fab58e047cc7bdac8ac34d1f541b556cb64 --- .../rockchip/rk3562-evb1-lp4x-v10-linux-amp.dts | 13 +++++++++---- .../rockchip/rk3562-evb2-ddr4-v10-linux-amp.dts | 13 +++++++++---- .../rockchip/rk3568-evb1-ddr4-v10-linux-amp.dts | 13 +++++++++---- .../rockchip/rk3588-evb1-lp4-v10-linux-amp.dts | 15 +++++++++------ 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3562-evb1-lp4x-v10-linux-amp.dts b/arch/arm64/boot/dts/rockchip/rk3562-evb1-lp4x-v10-linux-amp.dts index d39eba9b3a96..a05e80c4304b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3562-evb1-lp4x-v10-linux-amp.dts +++ b/arch/arm64/boot/dts/rockchip/rk3562-evb1-lp4x-v10-linux-amp.dts @@ -10,10 +10,15 @@ #include "rk3562-amp.dtsi" / { - memory { - device_type = "memory"; - reg = <0x0 0x02000000 0x0 0x06400000>, - <0x0 0x0a200000 0x0 0xf1e00000>; + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + amp_reserved: amp@800000 { + reg = <0x0 0x00800000 0x0 0x01800000>; + no-map; + }; }; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3562-evb2-ddr4-v10-linux-amp.dts b/arch/arm64/boot/dts/rockchip/rk3562-evb2-ddr4-v10-linux-amp.dts index 99385760cbed..97b855e9efb0 100644 --- a/arch/arm64/boot/dts/rockchip/rk3562-evb2-ddr4-v10-linux-amp.dts +++ b/arch/arm64/boot/dts/rockchip/rk3562-evb2-ddr4-v10-linux-amp.dts @@ -11,10 +11,15 @@ #include "rk3562-amp.dtsi" / { - memory { - device_type = "memory"; - reg = <0x0 0x02000000 0x0 0x04b80000>, - <0x0 0x0a200000 0x0 0x75e00000>; + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + amp_reserved: amp@800000 { + reg = <0x0 0x00800000 0x0 0x01800000>; + no-map; + }; }; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10-linux-amp.dts b/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10-linux-amp.dts index 0c1b2156261e..80889b7aebd3 100644 --- a/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10-linux-amp.dts +++ b/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10-linux-amp.dts @@ -8,10 +8,15 @@ #include "rk3568-amp.dtsi" / { - memory { - device_type = "memory"; - reg = <0x0 0x03880000 0x0 0x04b80000>, - <0x0 0x0a200000 0x0 0x75e00000>; + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + amp_reserved: amp@1800000 { + reg = <0x0 0x01800000 0x0 0x01800000>; + no-map; + }; }; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-lp4-v10-linux-amp.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-lp4-v10-linux-amp.dts index 23ec15727ec0..48c1d122b36b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-lp4-v10-linux-amp.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-lp4-v10-linux-amp.dts @@ -23,12 +23,15 @@ }; }; - memory { - device_type = "memory"; - reg = <0x0 0x02000000 0x0 0x06400000>, - <0x0 0x09400000 0x0 0xe6c00000>, - <0x1 0x00000000 0x1 0x00000000>, - <0x2 0xf0000000 0x0 0x10000000>; + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + amp_reserved: amp@800000 { + reg = <0x0 0x00800000 0x0 0x01800000>; + no-map; + }; }; }; From 286ef81333ac174b65984c358e0cde32705536cc Mon Sep 17 00:00:00 2001 From: Simon Xue Date: Tue, 27 Feb 2024 11:56:47 +0800 Subject: [PATCH 19/28] mm: memblock: fix only first memblock be freed. Allow up to 4 memblocks to be freed deferred. Fixes: b6cd53a3a262 ("init: defer free large memblock to Buddy allocator when CONFIG_ROCKCHIP_THUNDER_BOOT=y") Change-Id: Ie85d4802e639a5d11d64a4ab5f4f74647b7081fb Signed-off-by: Simon Xue --- mm/memblock.c | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/mm/memblock.c b/mm/memblock.c index c4453bdfe6df..1ee24945bf31 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -102,8 +102,13 @@ EXPORT_SYMBOL(contig_page_data); #endif #if defined(CONFIG_ROCKCHIP_THUNDER_BOOT) && defined(CONFIG_SMP) -static unsigned long defer_start __initdata; -static unsigned long defer_end __initdata; +static int db_count __initdata; +#define DB_COUNT_MAX 4 + +static struct deferred_block { + unsigned long defer_start; + unsigned long defer_end; +} db[DB_COUNT_MAX] __initdata; #define DEFAULT_DEFER_FREE_BLOCK_SIZE SZ_256M static unsigned long defer_free_block_size __initdata = @@ -2064,21 +2069,23 @@ static void __init __free_pages_memory(unsigned long start, unsigned long end) #if defined(CONFIG_ROCKCHIP_THUNDER_BOOT) && defined(CONFIG_SMP) int __init defer_free_memblock(void *unused) { - if (defer_start == 0) - return 0; + int i; - pr_debug("start = %ld, end = %ld\n", defer_start, defer_end); + for (i = 0; i < db_count; i++) { + pr_debug("%s: start = %ld, end = %ld\n", + __func__, db[i].defer_start, db[i].defer_end); - __free_pages_memory(defer_start, defer_end); + __free_pages_memory(db[i].defer_start, db[i].defer_end); - totalram_pages_add(defer_end - defer_start); + totalram_pages_add(db[i].defer_end - db[i].defer_start); - pr_info("%s: size %luM free %luM [%luM - %luM] total %luM\n", __func__, - defer_free_block_size >> 20, - (defer_end - defer_start) >> (20 - PAGE_SHIFT), - defer_end >> (20 - PAGE_SHIFT), - defer_start >> (20 - PAGE_SHIFT), - totalram_pages() >> (20 - PAGE_SHIFT)); + pr_info("%s: size %luM free %luM [%luM - %luM] total %luM\n", + __func__, defer_free_block_size >> 20, + (db[i].defer_end - db[i].defer_start) >> (20 - PAGE_SHIFT), + db[i].defer_start >> (20 - PAGE_SHIFT), + db[i].defer_end >> (20 - PAGE_SHIFT), + totalram_pages() >> (20 - PAGE_SHIFT)); + } return 0; } #endif @@ -2094,10 +2101,11 @@ static unsigned long __init __free_memory_core(phys_addr_t start, return 0; #if defined(CONFIG_ROCKCHIP_THUNDER_BOOT) && defined(CONFIG_SMP) - if ((end - start) > defer_free_block_size) { - defer_start = start_pfn; - defer_end = end_pfn; - + pr_debug("%s, start = %pa, end = %pa\n", __func__, &start, &end); + if ((end - start) > defer_free_block_size && (db_count < ARRAY_SIZE(db))) { + db[db_count].defer_start = start_pfn; + db[db_count].defer_end = end_pfn; + db_count++; return 0; } #endif From 8ca3ffc967acded5184a1f4338f32aedaafef5e1 Mon Sep 17 00:00:00 2001 From: Tao Huang Date: Wed, 28 Feb 2024 17:04:57 +0800 Subject: [PATCH 20/28] init: Add CONFIG_ROCKCHIP_THUNDER_BOOT_DEFER_FREE_MEMBLOCK Allow defer free large memblock to Buddy allocator work on !CONFIG_ROCKCHIP_THUNDER_BOOT. Change-Id: I30f851f648b007d1629eed27ba464ad2d7425577 Signed-off-by: Tao Huang --- drivers/soc/rockchip/Kconfig | 15 +++++++++++++++ include/linux/memblock.h | 2 +- init/main.c | 2 +- mm/memblock.c | 6 +++--- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/drivers/soc/rockchip/Kconfig b/drivers/soc/rockchip/Kconfig index c3f7a24942a8..2f284f17602e 100644 --- a/drivers/soc/rockchip/Kconfig +++ b/drivers/soc/rockchip/Kconfig @@ -250,10 +250,25 @@ config ROCKCHIP_MINI_KERNEL config ROCKCHIP_THUNDER_BOOT bool "Rockchip Thunder Boot support" depends on NO_GKI + select ROCKCHIP_THUNDER_BOOT_DEFER_FREE_MEMBLOCK if SMP help Say y here to enable Rockchip thunder boot support. This option make the kernel boot faster. +config ROCKCHIP_THUNDER_BOOT_DEFER_FREE_MEMBLOCK + bool "Defer free large memblock to Buddy allocator" + depends on SMP + depends on NO_GKI + help + The physical memory of a system is divided into several types, like + memory reserved for device, for kernel pagetable, etc. The remaining + area is for Buddy allocator. Normally, The memory for Buddy is consist + of different size blocks, so, under meeting the memory request of kernel + booting, we can defer free the large block size to Buddy which can be + done later in work queue in parallel to other kernel threads, and the + size of the large block can be defined in kernel command line + by "defer_free_block_size" boot parameter. + config ROCKCHIP_THUNDER_BOOT_MMC bool "Rockchip Thunder Boot from MMC" depends on ROCKCHIP_THUNDER_BOOT diff --git a/include/linux/memblock.h b/include/linux/memblock.h index a3eb462a4032..758a063f9b8f 100644 --- a/include/linux/memblock.h +++ b/include/linux/memblock.h @@ -24,7 +24,7 @@ extern unsigned long max_pfn; */ extern unsigned long long max_possible_pfn; -#ifdef CONFIG_ROCKCHIP_THUNDER_BOOT +#ifdef CONFIG_ROCKCHIP_THUNDER_BOOT_DEFER_FREE_MEMBLOCK extern int defer_free_memblock(void *unused); #endif diff --git a/init/main.c b/init/main.c index 396246088c63..fc86c67eeb57 100644 --- a/init/main.c +++ b/init/main.c @@ -1819,7 +1819,7 @@ static noinline void __init kernel_init_freeable(void) smp_init(); sched_init_smp(); -#if defined(CONFIG_ROCKCHIP_THUNDER_BOOT) && defined(CONFIG_SMP) +#ifdef CONFIG_ROCKCHIP_THUNDER_BOOT_DEFER_FREE_MEMBLOCK kthread_run(defer_free_memblock, NULL, "defer_mem"); #endif diff --git a/mm/memblock.c b/mm/memblock.c index 1ee24945bf31..f71c8b0f8ac7 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -101,7 +101,7 @@ struct pglist_data __refdata contig_page_data; EXPORT_SYMBOL(contig_page_data); #endif -#if defined(CONFIG_ROCKCHIP_THUNDER_BOOT) && defined(CONFIG_SMP) +#ifdef CONFIG_ROCKCHIP_THUNDER_BOOT_DEFER_FREE_MEMBLOCK static int db_count __initdata; #define DB_COUNT_MAX 4 @@ -2066,7 +2066,7 @@ static void __init __free_pages_memory(unsigned long start, unsigned long end) } } -#if defined(CONFIG_ROCKCHIP_THUNDER_BOOT) && defined(CONFIG_SMP) +#ifdef CONFIG_ROCKCHIP_THUNDER_BOOT_DEFER_FREE_MEMBLOCK int __init defer_free_memblock(void *unused) { int i; @@ -2100,7 +2100,7 @@ static unsigned long __init __free_memory_core(phys_addr_t start, if (start_pfn >= end_pfn) return 0; -#if defined(CONFIG_ROCKCHIP_THUNDER_BOOT) && defined(CONFIG_SMP) +#ifdef CONFIG_ROCKCHIP_THUNDER_BOOT_DEFER_FREE_MEMBLOCK pr_debug("%s, start = %pa, end = %pa\n", __func__, &start, &end); if ((end - start) > defer_free_block_size && (db_count < ARRAY_SIZE(db))) { db[db_count].defer_start = start_pfn; From 8bcccec31dd02145898520793aed7e75d71232a7 Mon Sep 17 00:00:00 2001 From: Finley Xiao Date: Thu, 22 Feb 2024 16:07:45 +0800 Subject: [PATCH 21/28] soc: rockchip: opp_select: Add support to set regulator helper Signed-off-by: Finley Xiao Change-Id: Ic60ed0614f90c8a883d72f1f1a3c1353b7feda87 --- drivers/soc/rockchip/rockchip_opp_select.c | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/soc/rockchip/rockchip_opp_select.c b/drivers/soc/rockchip/rockchip_opp_select.c index 9e3b93fa7920..b8af89974e9c 100644 --- a/drivers/soc/rockchip/rockchip_opp_select.c +++ b/drivers/soc/rockchip/rockchip_opp_select.c @@ -1498,6 +1498,25 @@ static void rockchip_get_scale_volt_sel(struct device *dev, char *lkg_name, of_node_put(np); } +static int rockchip_opp_set_regulator_helper(struct device *dev, + struct rockchip_opp_info *info) +{ + struct opp_table *opp_table; + + if (!info || !info->data || !info->data->config_regulators) + return 0; + + opp_table = dev_pm_opp_get_opp_table(dev); + if (IS_ERR(opp_table)) + return PTR_ERR(opp_table); + + opp_table->config_regulators = info->data->config_regulators; + + dev_pm_opp_put_opp_table(opp_table); + + return 0; +} + static int rockchip_opp_set_config(struct device *dev, struct rockchip_opp_info *info, const char *clk_name, const char *reg_name) { @@ -1560,6 +1579,16 @@ static int rockchip_opp_set_config(struct device *dev, struct rockchip_opp_info return info->opp_token; } + /* + * The dev_pm_opp_set_config() only support setting regulator helper + * for multiple regulators, but on some platforms, still need to set + * regulator helper for single regulator. + */ + if (rockchip_opp_set_regulator_helper(dev, info)) { + dev_err(dev, "failed to set opp regulator helper\n"); + return -EINVAL; + } + return 0; } From 9e4a1dc7e017d019955f472d6969b64d6306002c Mon Sep 17 00:00:00 2001 From: Finley Xiao Date: Thu, 22 Feb 2024 16:20:09 +0800 Subject: [PATCH 22/28] soc: rockchip: opp_select: Fix clk error for normal pll clk Signed-off-by: Finley Xiao Change-Id: Ie7d980655f1c3c8cdc03206ec712789e916cfcc6 --- drivers/soc/rockchip/rockchip_opp_select.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soc/rockchip/rockchip_opp_select.c b/drivers/soc/rockchip/rockchip_opp_select.c index b8af89974e9c..e8e6e018584a 100644 --- a/drivers/soc/rockchip/rockchip_opp_select.c +++ b/drivers/soc/rockchip/rockchip_opp_select.c @@ -2307,7 +2307,7 @@ int rockchip_opp_config_clks(struct device *dev, struct opp_table *opp_table, unsigned long *target = data; int ret; - if (!info->is_runtime_active) + if (info->is_scmi_clk && !info->is_runtime_active) return 0; ret = clk_bulk_prepare_enable(info->nclocks, info->clocks); From 08ee72d40f4b85a6c79908cc2400ee394d82cbe8 Mon Sep 17 00:00:00 2001 From: Finley Xiao Date: Thu, 22 Feb 2024 11:28:55 +0800 Subject: [PATCH 23/28] dt-bindings: opp: rockchip: Document CCI GRF Signed-off-by: Finley Xiao Change-Id: I555f4a7774f7345cf5cebca8790eda817668a209 --- Documentation/devicetree/bindings/opp/rockchip-opp.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/opp/rockchip-opp.txt b/Documentation/devicetree/bindings/opp/rockchip-opp.txt index fa018aa9b574..c4a87aed6653 100644 --- a/Documentation/devicetree/bindings/opp/rockchip-opp.txt +++ b/Documentation/devicetree/bindings/opp/rockchip-opp.txt @@ -51,6 +51,7 @@ In 'operating-points-v2' table: - rockchip,grf: The phandle of the syscon node for GRF register. - rockchip,dsu-grf: the phandle of the syscon node for DSU GRF register. +- rockchip,cci-grf: the phandle of the syscon node for CCI GRF register. - rockchip,opp-clocks: List of clocks for accessing pvtpll and read margin. - volt-mem-read-margin: The property is an array of 2-tuples items, and each item consists voltage an memory read margin like From 3c25f86ddf34d7939724d00e99d27d5926e6373a Mon Sep 17 00:00:00 2001 From: Finley Xiao Date: Fri, 23 Feb 2024 18:00:19 +0800 Subject: [PATCH 24/28] soc: rockchip: opp_select: Add support to parse cci grf Signed-off-by: Finley Xiao Change-Id: I0413fe5893c862c1c45dbb67d9966e9e8245166d --- drivers/soc/rockchip/rockchip_opp_select.c | 6 +++++- include/soc/rockchip/rockchip_opp_select.h | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/soc/rockchip/rockchip_opp_select.c b/drivers/soc/rockchip/rockchip_opp_select.c index e8e6e018584a..02ffa2dfb31f 100644 --- a/drivers/soc/rockchip/rockchip_opp_select.c +++ b/drivers/soc/rockchip/rockchip_opp_select.c @@ -1700,8 +1700,12 @@ int rockchip_init_opp_info(struct device *dev, struct rockchip_opp_info *info, if (IS_ERR(info->grf)) info->grf = NULL; info->dsu_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,dsu-grf"); - if (IS_ERR(info->dsu_grf)) + if (IS_ERR(info->dsu_grf)) { info->dsu_grf = NULL; + info->cci_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,cci-grf"); + if (IS_ERR(info->cci_grf)) + info->cci_grf = NULL; + } ret = rockchip_get_opp_clk(dev, np, info); if (ret) diff --git a/include/soc/rockchip/rockchip_opp_select.h b/include/soc/rockchip/rockchip_opp_select.h index f241978549f5..490cec31a88a 100644 --- a/include/soc/rockchip/rockchip_opp_select.h +++ b/include/soc/rockchip/rockchip_opp_select.h @@ -76,6 +76,7 @@ struct pvtpll_opp_table { * @volt_rm_tbl: Pointer to voltage to memory read margin conversion table. * @grf: General Register Files regmap. * @dsu_grf: DSU General Register Files regmap. + * @cci_grf: CCI General Register Files regmap. * @clocks: Pvtpll clocks. * @nclocks: Number of pvtpll clock. * @intermediate_threshold_freq: The frequency threshold of intermediate rate. @@ -109,7 +110,10 @@ struct rockchip_opp_info { struct volt_rm_table *volt_rm_tbl; struct regmap *grf; - struct regmap *dsu_grf; + union { + struct regmap *dsu_grf; + struct regmap *cci_grf; + }; struct clk_bulk_data *clocks; int nclocks; unsigned long intermediate_threshold_freq; From 0883a1b15e5db2847faa2c4eac4b8f2902d5000f Mon Sep 17 00:00:00 2001 From: Finley Xiao Date: Mon, 26 Feb 2024 10:30:46 +0800 Subject: [PATCH 25/28] soc: rockchip: opp_select: Add support to parse rockchip,pvtpll Signed-off-by: Finley Xiao Change-Id: Ic6c7b2cc7a17ca1a33245c5c164cf586f33a9064 --- drivers/soc/rockchip/rockchip_opp_select.c | 45 ++++++++++++---------- include/soc/rockchip/rockchip_opp_select.h | 1 + 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/drivers/soc/rockchip/rockchip_opp_select.c b/drivers/soc/rockchip/rockchip_opp_select.c index 02ffa2dfb31f..842589fb7083 100644 --- a/drivers/soc/rockchip/rockchip_opp_select.c +++ b/drivers/soc/rockchip/rockchip_opp_select.c @@ -59,7 +59,6 @@ struct pvtm_config { int temp_prop[2]; const char *tz_name; struct thermal_zone_device *tz; - struct regmap *grf; }; struct lkg_conversion_table { @@ -338,9 +337,6 @@ static int rockchip_parse_pvtm_config(struct device_node *np, if (of_property_read_u32(np, "rockchip,pvtm-offset", &pvtm->offset)) return -EINVAL; - pvtm->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); - if (IS_ERR(pvtm->grf)) - return -EINVAL; return 0; } if (of_property_read_u32_array(np, "rockchip,pvtm-ch", pvtm->ch, 2)) @@ -725,11 +721,11 @@ static unsigned long rockchip_pvtpll_get_rate(struct rockchip_opp_info *info) int i; #define MIN_STABLE_DELTA 3 - regmap_read(info->grf, info->pvtpll_avg_offset, &rate0); + regmap_read(info->pvtpll_base, info->pvtpll_avg_offset, &rate0); /* max delay 2ms */ for (i = 0; i < 20; i++) { udelay(100); - regmap_read(info->grf, info->pvtpll_avg_offset, &rate1); + regmap_read(info->pvtpll_base, info->pvtpll_avg_offset, &rate1); delta = abs(rate1 - rate0); rate0 = rate1; if (delta <= MIN_STABLE_DELTA) @@ -854,7 +850,7 @@ static void rockchip_pvtpll_calibrate_opp(struct rockchip_opp_info *info) unsigned long rate, pvtpll_rate, old_rate, cur_rate, delta0, delta1; int i = 0, max_count, step, cur_step, ret; - if (!info || !info->grf) + if (!info || !info->pvtpll_base) return; dev_dbg(info->dev, "calibrating opp ...\n"); @@ -1051,6 +1047,7 @@ out: } static int rockchip_get_pvtm_pvtpll(struct device *dev, struct device_node *np, + struct rockchip_opp_info *info, const char *reg_name) { struct regulator *reg; @@ -1067,6 +1064,9 @@ static int rockchip_get_pvtm_pvtpll(struct device *dev, struct device_node *np, return pvtm_value; } + if (!info || !info->pvtpll_base) + return -ENOMEM; + pvtm = kzalloc(sizeof(*pvtm), GFP_KERNEL); if (!pvtm) return -ENOMEM; @@ -1102,7 +1102,7 @@ static int rockchip_get_pvtm_pvtpll(struct device *dev, struct device_node *np, } usleep_range(pvtm->sample_time, pvtm->sample_time + 100); - ret = regmap_read(pvtm->grf, pvtm->offset, &pvtm_value); + ret = regmap_read(info->pvtpll_base, pvtm->offset, &pvtm_value); if (ret < 0) { dev_err(dev, "failed to get pvtm from 0x%x\n", pvtm->offset); goto resetore_volt; @@ -1179,8 +1179,9 @@ static int rockchip_get_pvtm(struct device *dev, struct device_node *np, } static void rockchip_of_get_pvtm_sel(struct device *dev, struct device_node *np, - const char *reg_name, int bin, int process, - int *volt_sel, int *scale_sel) + struct rockchip_opp_info *info, + const char *reg_name, int *volt_sel, + int *scale_sel) { struct property *prop = NULL; char name[NAME_MAX]; @@ -1188,7 +1189,7 @@ static void rockchip_of_get_pvtm_sel(struct device *dev, struct device_node *np, u32 hw = 0; if (of_property_read_bool(np, "rockchip,pvtm-pvtpll")) - pvtm = rockchip_get_pvtm_pvtpll(dev, np, reg_name); + pvtm = rockchip_get_pvtm_pvtpll(dev, np, info, reg_name); else pvtm = rockchip_get_pvtm(dev, np, reg_name); if (pvtm <= 0) @@ -1196,19 +1197,19 @@ static void rockchip_of_get_pvtm_sel(struct device *dev, struct device_node *np, if (!volt_sel) goto next; - if (process >= 0) { + if (info->process >= 0) { snprintf(name, sizeof(name), - "rockchip,p%d-pvtm-voltage-sel", process); + "rockchip,p%d-pvtm-voltage-sel", info->process); prop = of_find_property(np, name, NULL); - } else if (bin > 0) { + } else if (info->bin > 0) { of_property_read_u32(np, "rockchip,pvtm-hw", &hw); - if (hw && (hw & BIT(bin))) { + if (hw && (hw & BIT(info->bin))) { sprintf(name, "rockchip,pvtm-voltage-sel-hw"); prop = of_find_property(np, name, NULL); } if (!prop) { snprintf(name, sizeof(name), - "rockchip,pvtm-voltage-sel-B%d", bin); + "rockchip,pvtm-voltage-sel-B%d", info->bin); prop = of_find_property(np, name, NULL); } } @@ -1222,9 +1223,9 @@ next: if (!scale_sel) return; prop = NULL; - if (process >= 0) { + if (info->process >= 0) { snprintf(name, sizeof(name), - "rockchip,p%d-pvtm-scaling-sel", process); + "rockchip,p%d-pvtm-scaling-sel", info->process); prop = of_find_property(np, name, NULL); } if (!prop) @@ -1485,8 +1486,8 @@ static void rockchip_get_scale_volt_sel(struct device *dev, char *lkg_name, rockchip_of_get_lkg_sel(dev, np, lkg_name, info->process, &lkg_volt_sel, &lkg_scale); - rockchip_of_get_pvtm_sel(dev, np, reg_name, info->bin, info->process, - &pvtm_volt_sel, &pvtm_scale); + rockchip_of_get_pvtm_sel(dev, np, info, reg_name, &pvtm_volt_sel, + &pvtm_scale); rockchip_of_get_bin_sel(dev, np, info->bin, &bin_scale); rockchip_of_get_bin_volt_sel(dev, np, info->bin, &bin_volt_sel); info->scale = max3(lkg_scale, pvtm_scale, bin_scale); @@ -1707,6 +1708,10 @@ int rockchip_init_opp_info(struct device *dev, struct rockchip_opp_info *info, info->cci_grf = NULL; } + info->pvtpll_base = syscon_regmap_lookup_by_phandle(np, "rockchip,pvtpll"); + if (IS_ERR(info->pvtpll_base)) + info->pvtpll_base = info->grf; + ret = rockchip_get_opp_clk(dev, np, info); if (ret) goto out; diff --git a/include/soc/rockchip/rockchip_opp_select.h b/include/soc/rockchip/rockchip_opp_select.h index 490cec31a88a..c8355ff46d25 100644 --- a/include/soc/rockchip/rockchip_opp_select.h +++ b/include/soc/rockchip/rockchip_opp_select.h @@ -104,6 +104,7 @@ struct rockchip_opp_info { struct mutex dvfs_mutex; const struct rockchip_opp_data *data; struct pvtpll_opp_table *opp_table; + struct regmap *pvtpll_base; unsigned int pvtpll_avg_offset; unsigned int pvtpll_min_rate; unsigned int pvtpll_volt_step; From 25130a98f309636e96589dbdc476bd1e12a6fa46 Mon Sep 17 00:00:00 2001 From: Finley Xiao Date: Mon, 26 Feb 2024 09:35:24 +0800 Subject: [PATCH 26/28] dt-bindings: opp: rockchip: Document rockchip,pvtpll Signed-off-by: Finley Xiao Change-Id: Iaf74ec66027a6364947048807d078c439e3f8846 --- Documentation/devicetree/bindings/opp/rockchip-opp.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/opp/rockchip-opp.txt b/Documentation/devicetree/bindings/opp/rockchip-opp.txt index c4a87aed6653..393b481c7d1b 100644 --- a/Documentation/devicetree/bindings/opp/rockchip-opp.txt +++ b/Documentation/devicetree/bindings/opp/rockchip-opp.txt @@ -62,6 +62,7 @@ In 'operating-points-v2' table: target frequency less than the threshold frequency, there may be no need to set intermediate rate. +- rockchip,pvtpll: The phandle of the syscon node for PVTPLL register. - rockchip,pvtpll-table: The property is an array of 5-tuples items, and each item consists frequency and pvtpll config like , From d7e2ada39537a54426c3a25f20dbb2d1d19d309d Mon Sep 17 00:00:00 2001 From: Frank Wang Date: Fri, 1 Mar 2024 09:31:02 +0800 Subject: [PATCH 27/28] arm64: dts: rockchip: rk3588 boards: fix maximum-speed for usbdp node Since the usbdp phy driver parses "maximum-speed" property in the parent's node not the child, this amends all the related DT to fix it. Signed-off-by: Frank Wang Change-Id: Ic727b1de5f8c59e2bb90c9c1c4b524f22663fb59 --- arch/arm64/boot/dts/rockchip/rk3588-evb7-lp4.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3588-nvr-demo.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb-v20.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb-v21.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3588-vehicle-s66-v10.dtsi | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb7-lp4.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-evb7-lp4.dtsi index d69b8ab52d6a..852d3af0a22b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-evb7-lp4.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-evb7-lp4.dtsi @@ -828,6 +828,7 @@ }; &usbdp_phy1 { + maximum-speed = "high-speed"; rockchip,dp-lane-mux = <3 2 1 0>; status = "okay"; }; @@ -837,7 +838,6 @@ }; &usbdp_phy1_u3 { - maximum-speed = "high-speed"; status = "okay"; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-nvr-demo.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-nvr-demo.dtsi index 925a81e45cd3..b276e9a00b62 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-nvr-demo.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-nvr-demo.dtsi @@ -795,6 +795,7 @@ }; &usbdp_phy1 { + maximum-speed = "high-speed"; rockchip,dp-lane-mux = < 0 1 2 3 >; status = "okay"; }; @@ -804,7 +805,6 @@ }; &usbdp_phy1_u3 { - maximum-speed = "high-speed"; status = "okay"; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb-v20.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb-v20.dtsi index 86b668a8cc28..23620a43634b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb-v20.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb-v20.dtsi @@ -368,6 +368,7 @@ }; &usbdp_phy1 { + maximum-speed = "high-speed"; rockchip,dp-lane-mux = <3 2 1 0>; status = "okay"; }; @@ -377,7 +378,6 @@ }; &usbdp_phy1_u3 { - maximum-speed = "high-speed"; status = "okay"; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb-v21.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb-v21.dtsi index 7413020b1342..3844be4db961 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb-v21.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb-v21.dtsi @@ -388,6 +388,7 @@ }; &usbdp_phy1 { + maximum-speed = "high-speed"; rockchip,dp-lane-mux = <3 2 1 0>; status = "okay"; }; @@ -397,7 +398,6 @@ }; &usbdp_phy1_u3 { - maximum-speed = "high-speed"; status = "okay"; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb.dtsi index 15ccc0f66dba..59e6f5d3b556 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-vehicle-evb.dtsi @@ -647,6 +647,7 @@ }; &usbdp_phy1 { + maximum-speed = "high-speed"; rockchip,dp-lane-mux = <3 2 1 0>; status = "okay"; }; @@ -656,7 +657,6 @@ }; &usbdp_phy1_u3 { - maximum-speed = "high-speed"; status = "okay"; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-vehicle-s66-v10.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-vehicle-s66-v10.dtsi index 6cd0f027bacc..77be7ffaf869 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-vehicle-s66-v10.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-vehicle-s66-v10.dtsi @@ -224,6 +224,7 @@ }; &usbdp_phy1 { + maximum-speed = "high-speed"; rockchip,dp-lane-mux = <3 2 1 0>; status = "disabled"; }; @@ -233,7 +234,6 @@ }; &usbdp_phy1_u3 { - maximum-speed = "high-speed"; status = "okay"; }; From 043518cce67dbcdf345684faacb9aedc54784bda Mon Sep 17 00:00:00 2001 From: Shunhua Lan Date: Fri, 1 Mar 2024 18:10:54 +0800 Subject: [PATCH 28/28] ASoC: rockchip: multi-dais: Add legacy_dai_naming flag Signed-off-by: Shunhua Lan Change-Id: Ia0ebb742dade8ecd3c7a0bae645cc1587e2e9a7e --- sound/soc/rockchip/rockchip_multi_dais.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/rockchip/rockchip_multi_dais.c b/sound/soc/rockchip/rockchip_multi_dais.c index 4b9bb3430878..cd612c02412d 100644 --- a/sound/soc/rockchip/rockchip_multi_dais.c +++ b/sound/soc/rockchip/rockchip_multi_dais.c @@ -322,6 +322,7 @@ static const struct snd_soc_dai_ops rockchip_mdais_dai_ops = { static const struct snd_soc_component_driver rockchip_mdais_component = { .name = DAIS_DRV_NAME, + .legacy_dai_naming = 1, }; static const struct of_device_id rockchip_mdais_match[] = {