diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index e3e2324547b9..950da599ae1a 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -416,6 +416,9 @@ config TOUCHSCREEN_GOODIX To compile this driver as a module, choose M here: the module will be called goodix. +config TOUCHSCREEN_GOODIX_BERLIN_CORE + tristate + config TOUCHSCREEN_HIDEEP tristate "HiDeep Touch IC" depends on I2C diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 62bd24f3ac8e..2e2f3e70cd2c 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_TOUCHSCREEN_EGALAX_SERIAL) += egalax_ts_serial.o obj-$(CONFIG_TOUCHSCREEN_EXC3000) += exc3000.o obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o obj-$(CONFIG_TOUCHSCREEN_GOODIX) += goodix_ts.o +obj-$(CONFIG_TOUCHSCREEN_GOODIX_BERLIN_CORE) += goodix_berlin_core.o obj-$(CONFIG_TOUCHSCREEN_HIDEEP) += hideep.o obj-$(CONFIG_TOUCHSCREEN_HYNITRON_CSTXXX) += hynitron_cstxxx.o obj-$(CONFIG_TOUCHSCREEN_ILI210X) += ili210x.o diff --git a/drivers/input/touchscreen/goodix_berlin.h b/drivers/input/touchscreen/goodix_berlin.h new file mode 100644 index 000000000000..1fd77eb69c9a --- /dev/null +++ b/drivers/input/touchscreen/goodix_berlin.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Goodix Touchscreen Driver + * Copyright (C) 2020 - 2021 Goodix, Inc. + * Copyright (C) 2023 Linaro Ltd. + * + * Based on goodix_berlin_berlin driver. + */ + +#ifndef __GOODIX_BERLIN_H_ +#define __GOODIX_BERLIN_H_ + +#include + +struct device; +struct input_id; +struct regmap; + +int goodix_berlin_probe(struct device *dev, int irq, const struct input_id *id, + struct regmap *regmap); + +extern const struct dev_pm_ops goodix_berlin_pm_ops; + +#endif diff --git a/drivers/input/touchscreen/goodix_berlin_core.c b/drivers/input/touchscreen/goodix_berlin_core.c new file mode 100644 index 000000000000..e7b41a926ef8 --- /dev/null +++ b/drivers/input/touchscreen/goodix_berlin_core.c @@ -0,0 +1,755 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Goodix "Berlin" Touchscreen IC driver + * Copyright (C) 2020 - 2021 Goodix, Inc. + * Copyright (C) 2023 Linaro Ltd. + * + * Based on goodix_ts_berlin driver. + * + * This driver is distinct from goodix.c since hardware interface + * is different enough to require a new driver. + * None of the register address or data structure are close enough + * to the previous generations. + * + * Currently the driver only handles Multitouch events with already + * programmed firmware and "config" for "Revision D" Berlin IC. + * + * Support is missing for: + * - ESD Management + * - Firmware update/flashing + * - "Config" update/flashing + * - Stylus Events + * - Gesture Events + * - Support for older revisions (A & B) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "goodix_berlin.h" + +#define GOODIX_BERLIN_MAX_TOUCH 10 + +#define GOODIX_BERLIN_NORMAL_RESET_DELAY_MS 100 + +#define GOODIX_BERLIN_TOUCH_EVENT BIT(7) +#define GOODIX_BERLIN_REQUEST_EVENT BIT(6) +#define GOODIX_BERLIN_TOUCH_COUNT_MASK GENMASK(3, 0) + +#define GOODIX_BERLIN_REQUEST_CODE_RESET 3 + +#define GOODIX_BERLIN_POINT_TYPE_MASK GENMASK(3, 0) +#define GOODIX_BERLIN_POINT_TYPE_STYLUS_HOVER 1 +#define GOODIX_BERLIN_POINT_TYPE_STYLUS 3 + +#define GOODIX_BERLIN_TOUCH_ID_MASK GENMASK(7, 4) + +#define GOODIX_BERLIN_DEV_CONFIRM_VAL 0xAA +#define GOODIX_BERLIN_BOOTOPTION_ADDR 0x10000 +#define GOODIX_BERLIN_FW_VERSION_INFO_ADDR 0x10014 + +#define GOODIX_BERLIN_IC_INFO_MAX_LEN SZ_1K +#define GOODIX_BERLIN_IC_INFO_ADDR 0x10070 + +#define GOODIX_BERLIN_CHECKSUM_SIZE sizeof(u16) + +struct goodix_berlin_fw_version { + u8 rom_pid[6]; + u8 rom_vid[3]; + u8 rom_vid_reserved; + u8 patch_pid[8]; + u8 patch_vid[4]; + u8 patch_vid_reserved; + u8 sensor_id; + u8 reserved[2]; + __le16 checksum; +}; + +struct goodix_berlin_ic_info_version { + u8 info_customer_id; + u8 info_version_id; + u8 ic_die_id; + u8 ic_version_id; + __le32 config_id; + u8 config_version; + u8 frame_data_customer_id; + u8 frame_data_version_id; + u8 touch_data_customer_id; + u8 touch_data_version_id; + u8 reserved[3]; +} __packed; + +struct goodix_berlin_ic_info_feature { + __le16 freqhop_feature; + __le16 calibration_feature; + __le16 gesture_feature; + __le16 side_touch_feature; + __le16 stylus_feature; +} __packed; + +struct goodix_berlin_ic_info_misc { + __le32 cmd_addr; + __le16 cmd_max_len; + __le32 cmd_reply_addr; + __le16 cmd_reply_len; + __le32 fw_state_addr; + __le16 fw_state_len; + __le32 fw_buffer_addr; + __le16 fw_buffer_max_len; + __le32 frame_data_addr; + __le16 frame_data_head_len; + __le16 fw_attr_len; + __le16 fw_log_len; + u8 pack_max_num; + u8 pack_compress_version; + __le16 stylus_struct_len; + __le16 mutual_struct_len; + __le16 self_struct_len; + __le16 noise_struct_len; + __le32 touch_data_addr; + __le16 touch_data_head_len; + __le16 point_struct_len; + __le16 reserved1; + __le16 reserved2; + __le32 mutual_rawdata_addr; + __le32 mutual_diffdata_addr; + __le32 mutual_refdata_addr; + __le32 self_rawdata_addr; + __le32 self_diffdata_addr; + __le32 self_refdata_addr; + __le32 iq_rawdata_addr; + __le32 iq_refdata_addr; + __le32 im_rawdata_addr; + __le16 im_readata_len; + __le32 noise_rawdata_addr; + __le16 noise_rawdata_len; + __le32 stylus_rawdata_addr; + __le16 stylus_rawdata_len; + __le32 noise_data_addr; + __le32 esd_addr; +} __packed; + +struct goodix_berlin_touch { + u8 status; + u8 reserved; + __le16 x; + __le16 y; + __le16 w; +}; +#define GOODIX_BERLIN_TOUCH_SIZE sizeof(struct goodix_berlin_touch) + +struct goodix_berlin_header { + u8 status; + u8 reserved1; + u8 request_type; + u8 reserved2[3]; + __le16 checksum; +}; +#define GOODIX_BERLIN_HEADER_SIZE sizeof(struct goodix_berlin_header) + +struct goodix_berlin_event { + struct goodix_berlin_header hdr; + /* The data below is u16/__le16 aligned */ + u8 data[GOODIX_BERLIN_TOUCH_SIZE * GOODIX_BERLIN_MAX_TOUCH + + GOODIX_BERLIN_CHECKSUM_SIZE]; +}; + +struct goodix_berlin_core { + struct device *dev; + struct regmap *regmap; + struct regulator *avdd; + struct regulator *iovdd; + struct gpio_desc *reset_gpio; + struct touchscreen_properties props; + struct goodix_berlin_fw_version fw_version; + struct input_dev *input_dev; + int irq; + + /* Runtime parameters extracted from IC_INFO buffer */ + u32 touch_data_addr; + + struct goodix_berlin_event event; +}; + +static bool goodix_berlin_checksum_valid(const u8 *data, int size) +{ + u32 cal_checksum = 0; + u16 r_checksum; + int i; + + if (size < GOODIX_BERLIN_CHECKSUM_SIZE) + return false; + + for (i = 0; i < size - GOODIX_BERLIN_CHECKSUM_SIZE; i++) + cal_checksum += data[i]; + + r_checksum = get_unaligned_le16(&data[i]); + + return (u16)cal_checksum == r_checksum; +} + +static bool goodix_berlin_is_dummy_data(struct goodix_berlin_core *cd, + const u8 *data, int size) +{ + int i; + + /* + * If the device is missing or doesn't respond the buffer + * could be filled with bus default line state, 0x00 or 0xff, + * so declare success the first time we encounter neither. + */ + for (i = 0; i < size; i++) + if (data[i] > 0 && data[i] < 0xff) + return false; + + return true; +} + +static int goodix_berlin_dev_confirm(struct goodix_berlin_core *cd) +{ + u8 tx_buf[8], rx_buf[8]; + int retry = 3; + int error; + + memset(tx_buf, GOODIX_BERLIN_DEV_CONFIRM_VAL, sizeof(tx_buf)); + while (retry--) { + error = regmap_raw_write(cd->regmap, + GOODIX_BERLIN_BOOTOPTION_ADDR, + tx_buf, sizeof(tx_buf)); + if (error) + return error; + + error = regmap_raw_read(cd->regmap, + GOODIX_BERLIN_BOOTOPTION_ADDR, + rx_buf, sizeof(rx_buf)); + if (error) + return error; + + if (!memcmp(tx_buf, rx_buf, sizeof(tx_buf))) + return 0; + + usleep_range(5000, 5100); + } + + dev_err(cd->dev, "device confirm failed, rx_buf: %*ph\n", + (int)sizeof(rx_buf), rx_buf); + + return -EINVAL; +} + +static int goodix_berlin_power_on(struct goodix_berlin_core *cd) +{ + int error; + + error = regulator_enable(cd->iovdd); + if (error) { + dev_err(cd->dev, "Failed to enable iovdd: %d\n", error); + return error; + } + + /* Vendor waits 3ms for IOVDD to settle */ + usleep_range(3000, 3100); + + error = regulator_enable(cd->avdd); + if (error) { + dev_err(cd->dev, "Failed to enable avdd: %d\n", error); + goto err_iovdd_disable; + } + + /* Vendor waits 15ms for IOVDD to settle */ + usleep_range(15000, 15100); + + gpiod_set_value_cansleep(cd->reset_gpio, 0); + + /* Vendor waits 4ms for Firmware to initialize */ + usleep_range(4000, 4100); + + error = goodix_berlin_dev_confirm(cd); + if (error) + goto err_dev_reset; + + /* Vendor waits 100ms for Firmware to fully boot */ + msleep(GOODIX_BERLIN_NORMAL_RESET_DELAY_MS); + + return 0; + +err_dev_reset: + gpiod_set_value_cansleep(cd->reset_gpio, 1); + regulator_disable(cd->avdd); +err_iovdd_disable: + regulator_disable(cd->iovdd); + return error; +} + +static void goodix_berlin_power_off(struct goodix_berlin_core *cd) +{ + gpiod_set_value_cansleep(cd->reset_gpio, 1); + regulator_disable(cd->avdd); + regulator_disable(cd->iovdd); +} + +static int goodix_berlin_read_version(struct goodix_berlin_core *cd) +{ + int error; + + error = regmap_raw_read(cd->regmap, GOODIX_BERLIN_FW_VERSION_INFO_ADDR, + &cd->fw_version, sizeof(cd->fw_version)); + if (error) { + dev_err(cd->dev, "error reading fw version, %d\n", error); + return error; + } + + if (!goodix_berlin_checksum_valid((u8 *)&cd->fw_version, + sizeof(cd->fw_version))) { + dev_err(cd->dev, "invalid fw version: checksum error\n"); + return -EINVAL; + } + + return 0; +} + +/* Only extract necessary data for runtime */ +static int goodix_berlin_parse_ic_info(struct goodix_berlin_core *cd, + const u8 *data, u16 length) +{ + struct goodix_berlin_ic_info_misc *misc; + unsigned int offset = 0; + + offset += sizeof(__le16); /* length */ + offset += sizeof(struct goodix_berlin_ic_info_version); + offset += sizeof(struct goodix_berlin_ic_info_feature); + + /* IC_INFO Parameters, variable width structure */ + offset += 4 * sizeof(u8); /* drv_num, sen_num, button_num, force_num */ + if (offset >= length) + goto invalid_offset; + +#define ADVANCE_LE16_PARAMS() \ + do { \ + u8 param_num = data[offset++]; \ + offset += param_num * sizeof(__le16); \ + if (offset >= length) \ + goto invalid_offset; \ + } while (0) + ADVANCE_LE16_PARAMS(); /* active_scan_rate_num */ + ADVANCE_LE16_PARAMS(); /* mutual_freq_num*/ + ADVANCE_LE16_PARAMS(); /* self_tx_freq_num */ + ADVANCE_LE16_PARAMS(); /* self_rx_freq_num */ + ADVANCE_LE16_PARAMS(); /* stylus_freq_num */ +#undef ADVANCE_LE16_PARAMS + + misc = (struct goodix_berlin_ic_info_misc *)&data[offset]; + cd->touch_data_addr = le32_to_cpu(misc->touch_data_addr); + + return 0; + +invalid_offset: + dev_err(cd->dev, "ic_info length is invalid (offset %d length %d)\n", + offset, length); + return -EINVAL; +} + +static int goodix_berlin_get_ic_info(struct goodix_berlin_core *cd) +{ + u8 *afe_data __free(kfree) = NULL; + __le16 length_raw; + u16 length; + int error; + + afe_data = kzalloc(GOODIX_BERLIN_IC_INFO_MAX_LEN, GFP_KERNEL); + if (!afe_data) + return -ENOMEM; + + error = regmap_raw_read(cd->regmap, GOODIX_BERLIN_IC_INFO_ADDR, + &length_raw, sizeof(length_raw)); + if (error) { + dev_err(cd->dev, "failed get ic info length, %d\n", error); + return error; + } + + length = le16_to_cpu(length_raw); + if (length >= GOODIX_BERLIN_IC_INFO_MAX_LEN) { + dev_err(cd->dev, "invalid ic info length %d\n", length); + return -EINVAL; + } + + error = regmap_raw_read(cd->regmap, GOODIX_BERLIN_IC_INFO_ADDR, + afe_data, length); + if (error) { + dev_err(cd->dev, "failed get ic info data, %d\n", error); + return error; + } + + /* check whether the data is valid (ex. bus default values) */ + if (goodix_berlin_is_dummy_data(cd, afe_data, length)) { + dev_err(cd->dev, "fw info data invalid\n"); + return -EINVAL; + } + + if (!goodix_berlin_checksum_valid(afe_data, length)) { + dev_err(cd->dev, "fw info checksum error\n"); + return -EINVAL; + } + + error = goodix_berlin_parse_ic_info(cd, afe_data, length); + if (error) + return error; + + /* check some key info */ + if (!cd->touch_data_addr) { + dev_err(cd->dev, "touch_data_addr is null\n"); + return -EINVAL; + } + + return 0; +} + +static int goodix_berlin_get_remaining_contacts(struct goodix_berlin_core *cd, + int n) +{ + size_t offset = 2 * GOODIX_BERLIN_TOUCH_SIZE + + GOODIX_BERLIN_CHECKSUM_SIZE; + u32 addr = cd->touch_data_addr + GOODIX_BERLIN_HEADER_SIZE + offset; + int error; + + error = regmap_raw_read(cd->regmap, addr, + &cd->event.data[offset], + (n - 2) * GOODIX_BERLIN_TOUCH_SIZE); + if (error) { + dev_err_ratelimited(cd->dev, "failed to get touch data, %d\n", + error); + return error; + } + + return 0; +} + +static void goodix_berlin_report_state(struct goodix_berlin_core *cd, int n) +{ + struct goodix_berlin_touch *touch_data = + (struct goodix_berlin_touch *)cd->event.data; + struct goodix_berlin_touch *t; + int i; + u8 type, id; + + for (i = 0; i < n; i++) { + t = &touch_data[i]; + + type = FIELD_GET(GOODIX_BERLIN_POINT_TYPE_MASK, t->status); + if (type == GOODIX_BERLIN_POINT_TYPE_STYLUS || + type == GOODIX_BERLIN_POINT_TYPE_STYLUS_HOVER) { + dev_warn_once(cd->dev, "Stylus event type not handled\n"); + continue; + } + + id = FIELD_GET(GOODIX_BERLIN_TOUCH_ID_MASK, t->status); + if (id >= GOODIX_BERLIN_MAX_TOUCH) { + dev_warn_ratelimited(cd->dev, "invalid finger id %d\n", id); + continue; + } + + input_mt_slot(cd->input_dev, id); + input_mt_report_slot_state(cd->input_dev, MT_TOOL_FINGER, true); + + touchscreen_report_pos(cd->input_dev, &cd->props, + __le16_to_cpu(t->x), __le16_to_cpu(t->y), + true); + input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR, + __le16_to_cpu(t->w)); + } + + input_mt_sync_frame(cd->input_dev); + input_sync(cd->input_dev); +} + +static void goodix_berlin_touch_handler(struct goodix_berlin_core *cd) +{ + u8 touch_num; + int error; + + touch_num = FIELD_GET(GOODIX_BERLIN_TOUCH_COUNT_MASK, + cd->event.hdr.request_type); + if (touch_num > GOODIX_BERLIN_MAX_TOUCH) { + dev_warn(cd->dev, "invalid touch num %d\n", touch_num); + return; + } + + if (touch_num > 2) { + /* read additional contact data if more than 2 touch events */ + error = goodix_berlin_get_remaining_contacts(cd, touch_num); + if (error) + return; + } + + if (touch_num) { + int len = touch_num * GOODIX_BERLIN_TOUCH_SIZE + + GOODIX_BERLIN_CHECKSUM_SIZE; + if (!goodix_berlin_checksum_valid(cd->event.data, len)) { + dev_err(cd->dev, "touch data checksum error: %*ph\n", + len, cd->event.data); + return; + } + } + + goodix_berlin_report_state(cd, touch_num); +} + +static int goodix_berlin_request_handle_reset(struct goodix_berlin_core *cd) +{ + gpiod_set_value_cansleep(cd->reset_gpio, 1); + usleep_range(2000, 2100); + gpiod_set_value_cansleep(cd->reset_gpio, 0); + + msleep(GOODIX_BERLIN_NORMAL_RESET_DELAY_MS); + + return 0; +} + +static irqreturn_t goodix_berlin_irq(int irq, void *data) +{ + struct goodix_berlin_core *cd = data; + int error; + + /* + * First, read buffer with space for 2 touch events: + * - GOODIX_BERLIN_HEADER_SIZE = 8 bytes + * - GOODIX_BERLIN_TOUCH_SIZE * 2 = 16 bytes + * - GOODIX_BERLIN_CHECKLSUM_SIZE = 2 bytes + * For a total of 26 bytes. + * + * If only a single finger is reported, we will read 8 bytes more than + * needed: + * - bytes 0-7: Header (GOODIX_BERLIN_HEADER_SIZE) + * - bytes 8-15: Finger 0 Data + * - bytes 24-25: Checksum + * - bytes 18-25: Unused 8 bytes + * + * If 2 fingers are reported, we would have read the exact needed + * amount of data and checksum would be at the end of the buffer: + * - bytes 0-7: Header (GOODIX_BERLIN_HEADER_SIZE) + * - bytes 8-15: Finger 0 Bytes 0-7 + * - bytes 16-23: Finger 1 Bytes 0-7 + * - bytes 24-25: Checksum + * + * If more than 2 fingers were reported, the "Checksum" bytes would + * in fact contain part of the next finger data, and then + * goodix_berlin_get_remaining_contacts() would complete the buffer + * with the missing bytes, including the trailing checksum. + * For example, if 3 fingers are reported, then we would do: + * Read 1: + * - bytes 0-7: Header (GOODIX_BERLIN_HEADER_SIZE) + * - bytes 8-15: Finger 0 Bytes 0-7 + * - bytes 16-23: Finger 1 Bytes 0-7 + * - bytes 24-25: Finger 2 Bytes 0-1 + * Read 2 (with length of (3 - 2) * 8 = 8 bytes): + * - bytes 26-31: Finger 2 Bytes 2-7 + * - bytes 32-33: Checksum + */ + error = regmap_raw_read(cd->regmap, cd->touch_data_addr, + &cd->event, + GOODIX_BERLIN_HEADER_SIZE + + 2 * GOODIX_BERLIN_TOUCH_SIZE + + GOODIX_BERLIN_CHECKSUM_SIZE); + if (error) { + dev_warn_ratelimited(cd->dev, + "failed get event head data: %d\n", error); + goto out; + } + + if (cd->event.hdr.status == 0) + goto out; + + if (!goodix_berlin_checksum_valid((u8 *)&cd->event.hdr, + GOODIX_BERLIN_HEADER_SIZE)) { + dev_warn_ratelimited(cd->dev, + "touch head checksum error: %*ph\n", + (int)GOODIX_BERLIN_HEADER_SIZE, + &cd->event.hdr); + goto out_clear; + } + + if (cd->event.hdr.status & GOODIX_BERLIN_TOUCH_EVENT) + goodix_berlin_touch_handler(cd); + + if (cd->event.hdr.status & GOODIX_BERLIN_REQUEST_EVENT) { + switch (cd->event.hdr.request_type) { + case GOODIX_BERLIN_REQUEST_CODE_RESET: + if (cd->reset_gpio) + goodix_berlin_request_handle_reset(cd); + break; + + default: + dev_warn(cd->dev, "unsupported request code 0x%x\n", + cd->event.hdr.request_type); + } + } + + +out_clear: + /* Clear up status field */ + regmap_write(cd->regmap, cd->touch_data_addr, 0); + +out: + return IRQ_HANDLED; +} + +static int goodix_berlin_input_dev_config(struct goodix_berlin_core *cd, + const struct input_id *id) +{ + struct input_dev *input_dev; + int error; + + input_dev = devm_input_allocate_device(cd->dev); + if (!input_dev) + return -ENOMEM; + + cd->input_dev = input_dev; + input_set_drvdata(input_dev, cd); + + input_dev->name = "Goodix Berlin Capacitive TouchScreen"; + input_dev->phys = "input/ts"; + + input_dev->id = *id; + + input_set_abs_params(cd->input_dev, ABS_MT_POSITION_X, + 0, SZ_64K - 1, 0, 0); + input_set_abs_params(cd->input_dev, ABS_MT_POSITION_Y, + 0, SZ_64K - 1, 0, 0); + input_set_abs_params(cd->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + + touchscreen_parse_properties(cd->input_dev, true, &cd->props); + + error = input_mt_init_slots(cd->input_dev, GOODIX_BERLIN_MAX_TOUCH, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); + if (error) + return error; + + error = input_register_device(cd->input_dev); + if (error) + return error; + + return 0; +} + +static int goodix_berlin_suspend(struct device *dev) +{ + struct goodix_berlin_core *cd = dev_get_drvdata(dev); + + disable_irq(cd->irq); + goodix_berlin_power_off(cd); + + return 0; +} + +static int goodix_berlin_resume(struct device *dev) +{ + struct goodix_berlin_core *cd = dev_get_drvdata(dev); + int error; + + error = goodix_berlin_power_on(cd); + if (error) + return error; + + enable_irq(cd->irq); + + return 0; +} + +EXPORT_GPL_SIMPLE_DEV_PM_OPS(goodix_berlin_pm_ops, + goodix_berlin_suspend, goodix_berlin_resume); + +static void goodix_berlin_power_off_act(void *data) +{ + struct goodix_berlin_core *cd = data; + + goodix_berlin_power_off(cd); +} + +int goodix_berlin_probe(struct device *dev, int irq, const struct input_id *id, + struct regmap *regmap) +{ + struct goodix_berlin_core *cd; + int error; + + if (irq <= 0) { + dev_err(dev, "Missing interrupt number\n"); + return -EINVAL; + } + + cd = devm_kzalloc(dev, sizeof(*cd), GFP_KERNEL); + if (!cd) + return -ENOMEM; + + cd->dev = dev; + cd->regmap = regmap; + cd->irq = irq; + + cd->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(cd->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(cd->reset_gpio), + "Failed to request reset gpio\n"); + + cd->avdd = devm_regulator_get(dev, "avdd"); + if (IS_ERR(cd->avdd)) + return dev_err_probe(dev, PTR_ERR(cd->avdd), + "Failed to request avdd regulator\n"); + + cd->iovdd = devm_regulator_get(dev, "iovdd"); + if (IS_ERR(cd->iovdd)) + return dev_err_probe(dev, PTR_ERR(cd->iovdd), + "Failed to request iovdd regulator\n"); + + error = goodix_berlin_power_on(cd); + if (error) { + dev_err(dev, "failed power on"); + return error; + } + + error = devm_add_action_or_reset(dev, goodix_berlin_power_off_act, cd); + if (error) + return error; + + error = goodix_berlin_read_version(cd); + if (error) { + dev_err(dev, "failed to get version info"); + return error; + } + + error = goodix_berlin_get_ic_info(cd); + if (error) { + dev_err(dev, "invalid ic info, abort"); + return error; + } + + error = goodix_berlin_input_dev_config(cd, id); + if (error) { + dev_err(dev, "failed set input device"); + return error; + } + + error = devm_request_threaded_irq(dev, cd->irq, NULL, goodix_berlin_irq, + IRQF_ONESHOT, "goodix-berlin", cd); + if (error) { + dev_err(dev, "request threaded irq failed: %d\n", error); + return error; + } + + dev_set_drvdata(dev, cd); + + dev_dbg(dev, "Goodix Berlin %s Touchscreen Controller", + cd->fw_version.patch_pid); + + return 0; +} +EXPORT_SYMBOL_GPL(goodix_berlin_probe); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Goodix Berlin Core Touchscreen driver"); +MODULE_AUTHOR("Neil Armstrong ");