diff --git a/MAINTAINERS b/MAINTAINERS index 203e45741c1a8f..7f820080aeed65 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23707,6 +23707,12 @@ S: Maintained W: http://www.winischhofer.at/linuxsisusbvga.shtml F: drivers/usb/misc/sisusbvga/ +SITRONIX ST7123 TOUCHSCREEN DRIVER +M: Waveshare Team +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/input/touchscreen/st7123.c + SL28 CPLD MFD DRIVER M: Michael Walle S: Maintained diff --git a/arch/arm/boot/dts/overlays/README b/arch/arm/boot/dts/overlays/README index 3076ead83fe8dc..cdc6fa1cd727d4 100644 --- a/arch/arm/boot/dts/overlays/README +++ b/arch/arm/boot/dts/overlays/README @@ -5964,6 +5964,7 @@ Params: 3_4_inch_c 3.4" 800x800 2lane 4_0_inch_c 4.0" 720x720 2lane 5_0_inch_a 5.0" 720x1280 2lane 5_5_inch_a 5.5" 720x1280 2lane + 6_25_inch_a 6.25" 720x1560 2lane 7_0_inch_a 7.0" A 720x1280 2lane 7_0_inch_b 7.0" B 720x1280 2lane 7_0_inch_c 7.0" C 1024x600 2lane diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-v2-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-v2-overlay.dts index ddb085d03cfa4b..3fabd3c7f5633e 100644 --- a/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-v2-overlay.dts +++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-v2-overlay.dts @@ -25,7 +25,7 @@ enable-gpio = <&display_mcu 2 GPIO_ACTIVE_HIGH>; }; - touch: goodix@5d { + touch: touch@5d { compatible = "goodix,gt9271"; reg = <0x5d>; reset-gpio = <&display_mcu 9 GPIO_ACTIVE_HIGH>; @@ -82,6 +82,9 @@ 4_0_inch_c = <&dsi_panel>, "compatible=waveshare,4.0-dsi-touch-c"; 5_0_inch_a = <&dsi_panel>, "compatible=waveshare,5.0-dsi-touch-a"; 5_5_inch_a = <&dsi_panel>, "compatible=waveshare,5.5-dsi-touch-a"; + 6_25_inch_a = <&dsi_panel>, "compatible=waveshare,6.25-dsi-touch-a", + <&touch>, "compatible=sitronix,st7123", + <&touch>, "reg:0=0x55"; 7_0_inch_a = <&dsi_panel>, "compatible=waveshare,7.0-dsi-touch-a"; 7_0_inch_b = <&dsi_panel>, "compatible=waveshare,7.0-dsi-touch-b"; 7_0_inch_c = <&dsi_panel>, "compatible=waveshare,7.0-dsi-touch-c"; diff --git a/arch/arm/configs/bcm2709_defconfig b/arch/arm/configs/bcm2709_defconfig index 059182de80ea47..8bf3f33f2d961c 100644 --- a/arch/arm/configs/bcm2709_defconfig +++ b/arch/arm/configs/bcm2709_defconfig @@ -636,6 +636,7 @@ CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m CONFIG_TOUCHSCREEN_USB_COMPOSITE=m CONFIG_TOUCHSCREEN_TSC2007=m CONFIG_TOUCHSCREEN_TSC2007_IIO=y +CONFIG_TOUCHSCREEN_ST7123=m CONFIG_TOUCHSCREEN_STMPE=m CONFIG_TOUCHSCREEN_IQS5XX=m CONFIG_INPUT_MISC=y diff --git a/arch/arm/configs/bcmrpi_defconfig b/arch/arm/configs/bcmrpi_defconfig index 728216544d6038..f8ac94a55ae1f8 100644 --- a/arch/arm/configs/bcmrpi_defconfig +++ b/arch/arm/configs/bcmrpi_defconfig @@ -630,6 +630,7 @@ CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m CONFIG_TOUCHSCREEN_USB_COMPOSITE=m CONFIG_TOUCHSCREEN_TSC2007=m CONFIG_TOUCHSCREEN_TSC2007_IIO=y +CONFIG_TOUCHSCREEN_ST7123=m CONFIG_TOUCHSCREEN_STMPE=m CONFIG_TOUCHSCREEN_IQS5XX=m CONFIG_INPUT_MISC=y diff --git a/arch/arm64/configs/bcm2711_defconfig b/arch/arm64/configs/bcm2711_defconfig index 3ca12507d0f683..b5291c535ee99e 100644 --- a/arch/arm64/configs/bcm2711_defconfig +++ b/arch/arm64/configs/bcm2711_defconfig @@ -707,6 +707,7 @@ CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m CONFIG_TOUCHSCREEN_USB_COMPOSITE=m CONFIG_TOUCHSCREEN_TSC2007=m CONFIG_TOUCHSCREEN_TSC2007_IIO=y +CONFIG_TOUCHSCREEN_ST7123=m CONFIG_TOUCHSCREEN_STMPE=m CONFIG_TOUCHSCREEN_IQS5XX=m CONFIG_INPUT_MISC=y diff --git a/arch/arm64/configs/bcm2711_rt_defconfig b/arch/arm64/configs/bcm2711_rt_defconfig index a90c06af912be9..372275e744592d 100644 --- a/arch/arm64/configs/bcm2711_rt_defconfig +++ b/arch/arm64/configs/bcm2711_rt_defconfig @@ -706,6 +706,7 @@ CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m CONFIG_TOUCHSCREEN_USB_COMPOSITE=m CONFIG_TOUCHSCREEN_TSC2007=m CONFIG_TOUCHSCREEN_TSC2007_IIO=y +CONFIG_TOUCHSCREEN_ST7123=m CONFIG_TOUCHSCREEN_STMPE=m CONFIG_TOUCHSCREEN_IQS5XX=m CONFIG_INPUT_MISC=y diff --git a/arch/arm64/configs/bcm2712_defconfig b/arch/arm64/configs/bcm2712_defconfig index 1d27063139c6d6..513640a49c5bda 100644 --- a/arch/arm64/configs/bcm2712_defconfig +++ b/arch/arm64/configs/bcm2712_defconfig @@ -710,6 +710,7 @@ CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m CONFIG_TOUCHSCREEN_USB_COMPOSITE=m CONFIG_TOUCHSCREEN_TSC2007=m CONFIG_TOUCHSCREEN_TSC2007_IIO=y +CONFIG_TOUCHSCREEN_ST7123=m CONFIG_TOUCHSCREEN_STMPE=m CONFIG_TOUCHSCREEN_IQS5XX=m CONFIG_INPUT_MISC=y diff --git a/drivers/gpu/drm/panel/panel-waveshare-dsi-v2.c b/drivers/gpu/drm/panel/panel-waveshare-dsi-v2.c index e15dae58a19db1..d848bb65d34aba 100644 --- a/drivers/gpu/drm/panel/panel-waveshare-dsi-v2.c +++ b/drivers/gpu/drm/panel/panel-waveshare-dsi-v2.c @@ -1393,6 +1393,73 @@ static const struct panel_init_cmd ws_panel_7_c_init[] = { {}, }; +static const struct panel_init_cmd ws_panel_6_25_a_init[] = { + _INIT_DCS_CMD(0x60, 0x71, 0x23, 0xa2), + _INIT_DCS_CMD(0x60, 0x71, 0x23, 0xa3), + _INIT_DCS_CMD(0x60, 0x71, 0x23, 0xa4), + _INIT_DCS_CMD(0xB0, 0x22, 0x61, 0x11, 0x6B, 0x25, 0x39, 0x39), + _INIT_DCS_CMD(0xB7, 0x00, 0x00, 0x64, 0x64), + _INIT_DCS_CMD(0xBF, 0x95, 0x95), + _INIT_DCS_CMD(0xA4, 0x31), + _INIT_DCS_CMD(0xC8, 0x00, 0x00, 0x10, 0x1F, 0x3A, 0x00, 0x66, 0x03, + 0xA8, 0x04, 0x11, 0x02, 0x06, 0x73, 0x01, 0x21, 0xC1, + 0x02, 0x01, 0x02, 0x22, 0x3E, 0x00, 0x84, 0x09, 0x32, + 0xE9, 0x0B, 0x3B, 0x0D, 0xF3, 0x7E, 0x0D, 0xB2, 0xE6, + 0x03, 0xFC), + _INIT_DCS_CMD(0xC9, 0x00, 0x00, 0x10, 0x1F, 0x3A, 0x00, 0x66, 0x03, + 0xA8, 0x04, 0x11, 0x02, 0x06, 0x73, 0x01, 0x21, 0xC1, + 0x02, 0x01, 0x02, 0x22, 0x3E, 0x00, 0x84, 0x09, 0x32, + 0xE9, 0x0B, 0x3B, 0x0D, 0xF3, 0x7E, 0x0D, 0xB2, 0xE6, + 0x03, 0xFC), + _INIT_DCS_CMD(0xD7, 0x10, 0x12, 0x49, 0x3B, 0x0C, 0x0C), + _INIT_DCS_CMD(0x90, 0x71, 0x23, 0x5A, 0x20, 0x24, 0x09, 0x09), + _INIT_DCS_CMD(0xA3, 0x80, 0x01, 0x88, 0x30, 0x05, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x46, 0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, + 0x00, 0x4F, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, + 0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x6F, 0x58, + 0x00, 0x00, 0x00, 0xFF), + _INIT_DCS_CMD(0xA6, 0x50, 0x00, 0x24, 0x55, 0x3A, 0x00, 0x00, 0x01, + 0x54, 0x54, 0x91, 0xFF, 0x00, 0x24, 0x55, 0x3B, 0x00, + 0x00, 0x00, 0x54, 0x54, 0x91, 0xFF, 0x00, 0x24, 0x11, + 0x00, 0x00, 0x00, 0x00, 0x54, 0x54, 0x91, 0xFF, 0x00, + 0xAC, 0x11, 0x00, 0x00, 0x00, 0x00, 0x54, 0x54, 0xFF, + 0xFF, 0x00, 0x08, 0x80, 0x08, 0x80, 0x06, 0x00, 0x00, + 0x00, 0x00), + _INIT_DCS_CMD(0xA7, 0x1D, 0x1D, 0x80, 0x64, 0x40, 0x07, 0x16, 0x41, + 0x02, 0x48, 0x07, 0x54, 0x54, 0x91, 0xFF, 0x08, 0x80, + 0x64, 0x40, 0x25, 0x34, 0x43, 0x04, 0x06, 0x05, 0x54, + 0x54, 0x91, 0xFF, 0x08, 0x80, 0x64, 0x40, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x54, 0x54, 0x91, 0xFF, 0x08, + 0x80, 0x64, 0x40, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, + 0x54, 0x54, 0x84, 0xFF, 0x08, 0x80, 0x44), + _INIT_DCS_CMD(0xAC, 0x1C, 0x02, 0x09, 0x0B, 0x12, 0x10, 0x18, 0x19, + 0x1C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x08, 0x0A, + 0x13, 0x11, 0x18, 0x19, 0x1C, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + _INIT_DCS_CMD(0xAD, 0xF0, 0x00, 0x46, 0x00, 0x05, 0x50, 0x50, 0xFF, + 0xFF, 0xF0, 0x40, 0x06, 0x01, 0x07, 0x42, 0x42, 0xFF, + 0xFF, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF), + _INIT_DCS_CMD(0xAE, 0xFF, 0x03, 0x00, 0xFF, 0x03, 0x00, 0x01), + _INIT_DCS_CMD(0xB2, 0x15, 0x19, 0x05, 0x23, 0x49, 0xAF, 0x03, 0x2E, + 0x5C, 0xD2, 0xFF, 0x10, 0x20, 0xFD, 0x20, 0xC0, 0x00), + _INIT_DCS_CMD(0xE8, 0x20, 0x6F, 0x04, 0x7C, 0x7C, 0x3E, 0x04, 0xDC, + 0xDC, 0x3E, 0x06, 0xFA, 0x26, 0x3E), + _INIT_DCS_CMD(0x75, 0x03, 0x04), + _INIT_DCS_CMD(0xE7, 0x3B, 0x00, 0x00, 0x7C, 0xA1, 0x8C, 0x20, 0x1A, + 0xF0, 0xB1, 0x50, 0x00, 0x50, 0xB1, 0x50, 0xB1, 0x50, + 0xD8, 0x00, 0x55, 0x00, 0xB1, 0x00, 0x45, 0xC9, 0x6A, + 0xFF, 0x5A, 0xD8, 0x18, 0x88, 0x15, 0xB1, 0x01, 0x01, + 0x77), + _INIT_DCS_CMD(0xEA, 0x13, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x2C), + _INIT_DCS_CMD(0x60, 0x71, 0x23, 0x00), + _INIT_DCS_CMD(0x11), + _INIT_DELAY_CMD(120), + _INIT_DCS_CMD(0x29), + _INIT_DELAY_CMD(50), + {}, +}; + static const struct panel_init_cmd ws_panel_5_5_a_init[] = { _INIT_DCS_CMD(0xB9, 0xFF, 0x83, 0x94), _INIT_DCS_CMD(0xBA, 0x61, 0x03, 0x68, 0x6B, 0xB2, 0xC0), @@ -1935,6 +2002,20 @@ static const struct drm_display_mode ws_panel_7_c_mode = { .height_mm = 85, }; +static const struct drm_display_mode ws_panel_6_25_a_mode = { + .clock = 83000, + .hdisplay = 720, + .hsync_start = 720 + 20, + .hsync_end = 720 + 20 + 20, + .htotal = 720 + 20 + 20 + 20, + .vdisplay = 1560, + .vsync_start = 1560 + 200, + .vsync_end = 1560 + 200 + 8, + .vtotal = 1560 + 200 + 8 + 10, + .width_mm = 62, + .height_mm = 110, +}; + static const struct drm_display_mode ws_panel_5_5_a_mode = { .clock = 65000, .hdisplay = 720, @@ -2243,6 +2324,15 @@ static const struct ws_panel_desc ws_panel_7_inch_c_desc = { .format = MIPI_DSI_FMT_RGB888, }; +static const struct ws_panel_desc ws_panel_6_25_inch_a_desc = { + .init = ws_panel_6_25_a_init, + .mode = &ws_panel_6_25_a_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS, + .lanes = 2, + .format = MIPI_DSI_FMT_RGB888, +}; + static const struct ws_panel_desc ws_panel_5_5_inch_a_desc = { .init = ws_panel_5_5_a_init, .mode = &ws_panel_5_5_a_mode, @@ -2301,6 +2391,8 @@ static const struct of_device_id ws_panel_of_match[] = { { .compatible = "waveshare,7.0-dsi-touch-a", &ws_panel_7_inch_a_desc }, { .compatible = "waveshare,7.0-dsi-touch-b", &ws_panel_7_inch_a_desc }, { .compatible = "waveshare,7.0-dsi-touch-c", &ws_panel_7_inch_c_desc }, + { .compatible = "waveshare,6.25-dsi-touch-a", + &ws_panel_6_25_inch_a_desc }, { .compatible = "waveshare,5.5-dsi-touch-a", &ws_panel_5_5_inch_a_desc }, { .compatible = "waveshare,5.0-dsi-touch-a", &ws_panel_5_inch_a_desc }, diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 9d543ad267ce01..e2ee722a9c8d88 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -1251,6 +1251,18 @@ config TOUCHSCREEN_SIS_I2C To compile this driver as a module, choose M here: the module will be called sis_i2c. +config TOUCHSCREEN_ST7123 + tristate "Sitronix ST7123 touchscreen controllers" + depends on I2C + help + Say Y here if you want to support the Sitronix ST7123 + touchscreen controller. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called st7123. + config TOUCHSCREEN_ST1232 tristate "Sitronix ST1232 or ST1633 touchscreen controllers" depends on I2C diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 53930469552ee8..58094f2c07b6b0 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -83,6 +83,7 @@ obj-$(CONFIG_TOUCHSCREEN_RM_TS) += raydium_i2c_ts.o obj-$(CONFIG_TOUCHSCREEN_S6SY761) += s6sy761.o obj-$(CONFIG_TOUCHSCREEN_SILEAD) += silead.o obj-$(CONFIG_TOUCHSCREEN_SIS_I2C) += sis_i2c.o +obj-$(CONFIG_TOUCHSCREEN_ST7123) += st7123.o obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o obj-$(CONFIG_TOUCHSCREEN_STMFTS) += stmfts.o obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o diff --git a/drivers/input/touchscreen/st7123.c b/drivers/input/touchscreen/st7123.c new file mode 100644 index 00000000000000..7a7832eecce0f8 --- /dev/null +++ b/drivers/input/touchscreen/st7123.c @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sitronix ST7123 touchscreen controller driver + * + * Copyright (C) 2018 Sitronix Technology Co., Ltd. + * Copyright (C) 2026 Waveshare International Limited + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ST7123_TS_NAME "st7123" + +#define ST7123_STATUS_REG 0x0001 +#define ST7123_DEVICE_CONTROL_REG 0x0002 +#define ST7123_X_RESOLUTION_HIGH 0x0005 +#define ST7123_MAX_NUM_TOUCHES 0x0009 +#define ST7123_TOUCH_INFO 0x0010 +#define ST7123_MISC_INFO 0x00f0 +#define ST7123_CHIP_ID 0x00f4 + +#define ST7123_CHIP_ID_VALUE 0x83 +#define ST7123_MISC_COORD_CHKSUM BIT(4) +#define ST7123_MAX_SUPPORTED_TOUCHES 10 +#define ST7123_DEFAULT_MAX_TOUCHES 10 +#define ST7123_DEFAULT_X_RES 720 +#define ST7123_DEFAULT_Y_RES 1560 +#define ST7123_POLL_INTERVAL_MS 17 +#define ST7123_TOUCH_STRIDE 7 +#define ST7123_TOUCH_HEADER_LEN 4 +#define ST7123_CHECKSUM_SEED 0x5a + +struct st7123_ts_data { + struct i2c_client *client; + struct input_dev *input; + struct gpio_desc *reset_gpio; + struct touchscreen_properties prop; + /* Serializes register access from IRQ/polling and PM paths. */ + struct mutex lock; + u8 max_touches; + bool has_coord_checksum; + bool suspended; + u8 coord_buf[ST7123_TOUCH_HEADER_LEN + + ST7123_MAX_SUPPORTED_TOUCHES * ST7123_TOUCH_STRIDE + 1]; +}; + +static int st7123_read(struct st7123_ts_data *ts, u16 reg, void *buf, u16 len) +{ + struct i2c_client *client = ts->client; + u8 addr[2] = { reg >> 8, reg & 0xff }; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .len = sizeof(addr), + .buf = addr, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = buf, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) + return ret < 0 ? ret : -EIO; + + return 0; +} + +static int st7123_write(struct st7123_ts_data *ts, u16 reg, const void *buf, + u16 len) +{ + struct i2c_client *client = ts->client; + u8 *data; + int ret; + + data = kmalloc(len + 2, GFP_KERNEL); + if (!data) + return -ENOMEM; + + data[0] = reg >> 8; + data[1] = reg & 0xff; + memcpy(&data[2], buf, len); + + ret = i2c_master_send(client, data, len + 2); + kfree(data); + + if (ret != len + 2) + return ret < 0 ? ret : -EIO; + + return 0; +} + +static u8 st7123_checksum(const u8 *buf, int len) +{ + u16 checksum = ST7123_CHECKSUM_SEED; + u8 low; + int i; + + for (i = 0; i < len; i++) { + checksum += buf[i]; + low = checksum & 0xff; + low = (low >> 7) | (low << 1); + checksum = (checksum & 0xff00) | low; + } + + return checksum & 0xff; +} + +static void st7123_reset(struct st7123_ts_data *ts) +{ + if (!ts->reset_gpio) + return; + + gpiod_set_value_cansleep(ts->reset_gpio, 1); + fsleep(10000); + gpiod_set_value_cansleep(ts->reset_gpio, 0); + msleep(150); +} + +static int st7123_read_resolution(struct st7123_ts_data *ts, u16 *x, u16 *y) +{ + u8 buf[4]; + int ret; + + ret = st7123_read(ts, ST7123_X_RESOLUTION_HIGH, buf, sizeof(buf)); + if (ret) + return ret; + + *x = ((buf[0] & 0x3f) << 8) | buf[1]; + *y = ((buf[2] & 0x3f) << 8) | buf[3]; + + if (!*x || !*y) + return -EINVAL; + + return 0; +} + +static int st7123_read_max_touches(struct st7123_ts_data *ts) +{ + u8 max_touches; + int ret; + + ret = st7123_read(ts, ST7123_MAX_NUM_TOUCHES, &max_touches, + sizeof(max_touches)); + if (ret) + return ret; + + if (!max_touches || max_touches > ST7123_MAX_SUPPORTED_TOUCHES) + max_touches = ST7123_DEFAULT_MAX_TOUCHES; + + ts->max_touches = max_touches; + + return 0; +} + +static int st7123_read_misc_info(struct st7123_ts_data *ts) +{ + u8 misc; + int ret; + + ret = st7123_read(ts, ST7123_MISC_INFO, &misc, sizeof(misc)); + if (ret) + return ret; + + ts->has_coord_checksum = misc & ST7123_MISC_COORD_CHKSUM; + + return 0; +} + +static int st7123_check_id(struct st7123_ts_data *ts) +{ + struct device *dev = &ts->client->dev; + u8 chip_id; + int ret; + + ret = st7123_read(ts, ST7123_CHIP_ID, &chip_id, sizeof(chip_id)); + if (ret) + return ret; + + if (chip_id != ST7123_CHIP_ID_VALUE) + dev_warn(dev, "unexpected chip id 0x%02x\n", chip_id); + + return 0; +} + +static int st7123_wait_ready(struct st7123_ts_data *ts) +{ + u8 status; + int ret; + int i; + + for (i = 0; i < 100; i++) { + ret = st7123_read(ts, ST7123_STATUS_REG, &status, + sizeof(status)); + if (!ret && ((status & 0x0f) == 0x00 || (status & 0x0f) == 0x04)) + return 0; + + usleep_range(1000, 2000); + } + + return ret ?: -ETIMEDOUT; +} + +static void st7123_report_all_up(struct st7123_ts_data *ts) +{ + int i; + + for (i = 0; i < ts->max_touches; i++) { + input_mt_slot(ts->input, i); + input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, false); + } + + input_mt_sync_frame(ts->input); + input_sync(ts->input); +} + +static int st7123_report_touch_data(struct st7123_ts_data *ts) +{ + struct device *dev = &ts->client->dev; + int read_len; + int ret; + int i; + + read_len = ST7123_TOUCH_HEADER_LEN + + ts->max_touches * ST7123_TOUCH_STRIDE; + if (ts->has_coord_checksum) + read_len++; + + ret = st7123_read(ts, ST7123_TOUCH_INFO, ts->coord_buf, read_len); + if (ret) { + dev_warn(dev, "failed to read touch data: %d\n", ret); + return ret; + } + + if (ts->coord_buf[0] & BIT(7)) { + dev_warn(dev, "controller requested reset\n"); + st7123_report_all_up(ts); + st7123_reset(ts); + return -EIO; + } + + if (ts->has_coord_checksum) { + u8 checksum; + + checksum = st7123_checksum(ts->coord_buf, read_len - 1); + if (checksum != ts->coord_buf[read_len - 1]) { + dev_warn(dev, "touch checksum mismatch\n"); + return -EIO; + } + } + + for (i = 0; i < ts->max_touches; i++) { + u8 *p = &ts->coord_buf[ST7123_TOUCH_HEADER_LEN + + i * ST7123_TOUCH_STRIDE]; + u16 x; + u16 y; + + input_mt_slot(ts->input, i); + + if (!(p[0] & BIT(7))) { + input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, + false); + continue; + } + + x = ((p[0] & 0x3f) << 8) | p[1]; + y = ((p[2] & 0x3f) << 8) | p[3]; + + input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true); + touchscreen_report_pos(ts->input, &ts->prop, x, y, true); + input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, max_t(u8, p[4], 1)); + } + + input_mt_sync_frame(ts->input); + input_sync(ts->input); + + return 0; +} + +static void st7123_poll(struct input_dev *input) +{ + struct st7123_ts_data *ts = input_get_drvdata(input); + + mutex_lock(&ts->lock); + if (!ts->suspended) + st7123_report_touch_data(ts); + mutex_unlock(&ts->lock); +} + +static irqreturn_t st7123_irq_thread(int irq, void *dev_id) +{ + struct st7123_ts_data *ts = dev_id; + + mutex_lock(&ts->lock); + if (!ts->suspended) + st7123_report_touch_data(ts); + mutex_unlock(&ts->lock); + + return IRQ_HANDLED; +} + +static int st7123_powerdown(struct st7123_ts_data *ts, bool powerdown) +{ + u8 ctrl; + int ret; + + ret = st7123_read(ts, ST7123_DEVICE_CONTROL_REG, &ctrl, sizeof(ctrl)); + if (ret) + return ret; + + if (powerdown) + ctrl |= BIT(1); + else + ctrl &= ~BIT(1); + + return st7123_write(ts, ST7123_DEVICE_CONTROL_REG, &ctrl, sizeof(ctrl)); +} + +static int st7123_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct st7123_ts_data *ts; + struct input_dev *input; + u16 max_x = ST7123_DEFAULT_X_RES; + u16 max_y = ST7123_DEFAULT_Y_RES; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(dev, -EIO, "I2C transfers not supported\n"); + + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + input = devm_input_allocate_device(dev); + if (!input) + return -ENOMEM; + + ts->client = client; + ts->input = input; + ts->max_touches = ST7123_DEFAULT_MAX_TOUCHES; + mutex_init(&ts->lock); + i2c_set_clientdata(client, ts); + + ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ts->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ts->reset_gpio), + "failed to request reset GPIO\n"); + + st7123_reset(ts); + + ret = st7123_wait_ready(ts); + if (ret) + return dev_err_probe(dev, ret, "controller not ready\n"); + + ret = st7123_check_id(ts); + if (ret) + return dev_err_probe(dev, ret, "failed to read chip id\n"); + + ret = st7123_read_resolution(ts, &max_x, &max_y); + if (ret) + dev_warn(dev, "using default resolution: %d\n", ret); + + ret = st7123_read_max_touches(ts); + if (ret) + dev_warn(dev, "using default touch count: %d\n", ret); + + ret = st7123_read_misc_info(ts); + if (ret) + dev_warn(dev, "failed to read misc info: %d\n", ret); + + input->name = "Sitronix ST7123 Touchscreen"; + input->id.bustype = BUS_I2C; + input->dev.parent = dev; + input_set_drvdata(input, ts); + + input_set_abs_params(input, ABS_MT_POSITION_X, 0, max_x - 1, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, max_y - 1, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + touchscreen_parse_properties(input, true, &ts->prop); + + ret = input_mt_init_slots(input, ts->max_touches, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize MT slots\n"); + + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, NULL, + st7123_irq_thread, + IRQF_ONESHOT, + client->name, ts); + if (ret) + return dev_err_probe(dev, ret, "failed to request IRQ\n"); + } else { + ret = input_setup_polling(input, st7123_poll); + if (ret) + return dev_err_probe(dev, ret, "failed to set up polling\n"); + + input_set_poll_interval(input, ST7123_POLL_INTERVAL_MS); + } + + ret = input_register_device(input); + if (ret) + return dev_err_probe(dev, ret, "failed to register input\n"); + + return 0; +} + +static int st7123_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct st7123_ts_data *ts = i2c_get_clientdata(client); + bool wakeup = device_may_wakeup(dev); + + if (client->irq) { + if (wakeup) + enable_irq_wake(client->irq); + else + disable_irq(client->irq); + } + + mutex_lock(&ts->lock); + ts->suspended = true; + st7123_report_all_up(ts); + if (!wakeup) + st7123_powerdown(ts, true); + mutex_unlock(&ts->lock); + + return 0; +} + +static int st7123_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct st7123_ts_data *ts = i2c_get_clientdata(client); + bool wakeup = device_may_wakeup(dev); + + mutex_lock(&ts->lock); + if (!wakeup) { + st7123_powerdown(ts, false); + st7123_reset(ts); + } + ts->suspended = false; + mutex_unlock(&ts->lock); + + if (client->irq) { + if (wakeup) + disable_irq_wake(client->irq); + else + enable_irq(client->irq); + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(st7123_pm_ops, st7123_suspend, st7123_resume); + +static const struct i2c_device_id st7123_id[] = { + { "st7123" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, st7123_id); + +static const struct of_device_id st7123_of_match[] = { + { .compatible = "sitronix,st7123" }, + { } +}; +MODULE_DEVICE_TABLE(of, st7123_of_match); + +static struct i2c_driver st7123_driver = { + .probe = st7123_probe, + .id_table = st7123_id, + .driver = { + .name = ST7123_TS_NAME, + .of_match_table = st7123_of_match, + .pm = pm_sleep_ptr(&st7123_pm_ops), + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_i2c_driver(st7123_driver); + +MODULE_AUTHOR("Sitronix Technology Co., Ltd."); +MODULE_DESCRIPTION("Sitronix ST7123 touchscreen controller driver"); +MODULE_LICENSE("GPL");