| // 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 <linux/bitfield.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/input.h> |
| #include <linux/input/mt.h> |
| #include <linux/input/touchscreen.h> |
| #include <linux/regmap.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/sizes.h> |
| #include <linux/unaligned.h> |
| |
| #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); |
| } |
| |
| static ssize_t registers_read(struct file *filp, struct kobject *kobj, |
| struct bin_attribute *bin_attr, |
| char *buf, loff_t off, size_t count) |
| { |
| struct device *dev = kobj_to_dev(kobj); |
| struct goodix_berlin_core *cd = dev_get_drvdata(dev); |
| int error; |
| |
| error = regmap_raw_read(cd->regmap, off, buf, count); |
| |
| return error ? error : count; |
| } |
| |
| static ssize_t registers_write(struct file *filp, struct kobject *kobj, |
| struct bin_attribute *bin_attr, |
| char *buf, loff_t off, size_t count) |
| { |
| struct device *dev = kobj_to_dev(kobj); |
| struct goodix_berlin_core *cd = dev_get_drvdata(dev); |
| int error; |
| |
| error = regmap_raw_write(cd->regmap, off, buf, count); |
| |
| return error ? error : count; |
| } |
| |
| static BIN_ATTR_ADMIN_RW(registers, 0); |
| |
| static struct bin_attribute *goodix_berlin_bin_attrs[] = { |
| &bin_attr_registers, |
| NULL, |
| }; |
| |
| static const struct attribute_group goodix_berlin_attr_group = { |
| .bin_attrs = goodix_berlin_bin_attrs, |
| }; |
| |
| const struct attribute_group *goodix_berlin_groups[] = { |
| &goodix_berlin_attr_group, |
| NULL, |
| }; |
| EXPORT_SYMBOL_GPL(goodix_berlin_groups); |
| |
| 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 <neil.armstrong@linaro.org>"); |