| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * pt_platform.c |
| * Parade TrueTouch(TM) Standard Product Platform Module. |
| * For use with Parade touchscreen controllers. |
| * Supported parts include: |
| * TMA5XX |
| * TMA448 |
| * TMA445A |
| * TT21XXX |
| * TT31XXX |
| * TT4XXXX |
| * TT7XXX |
| * TC3XXX |
| * |
| * Copyright (C) 2015-2020 Parade Technologies |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * version 2, and only version 2, as published by the |
| * Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * Contact Parade Technologies at www.paradetech.com <ttdrivers@paradetech.com> |
| */ |
| |
| #include "pt_regs.h" |
| #include "pt_platform.h" |
| #include <linux/regulator/consumer.h> |
| #ifdef PT_PTSBC_SUPPORT |
| #include <linux/init-input.h> |
| #endif |
| |
| #ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE |
| /* FW for Panel ID = 0x00 */ |
| #include "pt_fw_pid00.h" |
| static struct pt_touch_firmware pt_firmware_pid00 = { |
| .img = pt_img_pid00, |
| .size = ARRAY_SIZE(pt_img_pid00), |
| .ver = pt_ver_pid00, |
| .vsize = ARRAY_SIZE(pt_ver_pid00), |
| .panel_id = 0x00, |
| }; |
| |
| /* FW for Panel ID = 0x01 */ |
| #include "pt_fw_pid01.h" |
| static struct pt_touch_firmware pt_firmware_pid01 = { |
| .img = pt_img_pid01, |
| .size = ARRAY_SIZE(pt_img_pid01), |
| .ver = pt_ver_pid01, |
| .vsize = ARRAY_SIZE(pt_ver_pid01), |
| .panel_id = 0x01, |
| }; |
| |
| /* FW for Panel ID not enabled (legacy) */ |
| #include "pt_fw.h" |
| static struct pt_touch_firmware pt_firmware = { |
| .img = pt_img, |
| .size = ARRAY_SIZE(pt_img), |
| .ver = pt_ver, |
| .vsize = ARRAY_SIZE(pt_ver), |
| }; |
| #else |
| /* FW for Panel ID not enabled (legacy) */ |
| static struct pt_touch_firmware pt_firmware = { |
| .img = NULL, |
| .size = 0, |
| .ver = NULL, |
| .vsize = 0, |
| }; |
| #endif |
| |
| #ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_TTCONFIG_UPGRADE |
| /* TT Config for Panel ID = 0x00 */ |
| #include "pt_params_pid00.h" |
| static struct touch_settings pt_sett_param_regs_pid00 = { |
| .data = (uint8_t *)&pt_param_regs_pid00[0], |
| .size = ARRAY_SIZE(pt_param_regs_pid00), |
| .tag = 0, |
| }; |
| |
| static struct touch_settings pt_sett_param_size_pid00 = { |
| .data = (uint8_t *)&pt_param_size_pid00[0], |
| .size = ARRAY_SIZE(pt_param_size_pid00), |
| .tag = 0, |
| }; |
| |
| static struct pt_touch_config pt_ttconfig_pid00 = { |
| .param_regs = &pt_sett_param_regs_pid00, |
| .param_size = &pt_sett_param_size_pid00, |
| .fw_ver = ttconfig_fw_ver_pid00, |
| .fw_vsize = ARRAY_SIZE(ttconfig_fw_ver_pid00), |
| .panel_id = 0x00, |
| }; |
| |
| /* TT Config for Panel ID = 0x01 */ |
| #include "pt_params_pid01.h" |
| static struct touch_settings pt_sett_param_regs_pid01 = { |
| .data = (uint8_t *)&pt_param_regs_pid01[0], |
| .size = ARRAY_SIZE(pt_param_regs_pid01), |
| .tag = 0, |
| }; |
| |
| static struct touch_settings pt_sett_param_size_pid01 = { |
| .data = (uint8_t *)&pt_param_size_pid01[0], |
| .size = ARRAY_SIZE(pt_param_size_pid01), |
| .tag = 0, |
| }; |
| |
| static struct pt_touch_config pt_ttconfig_pid01 = { |
| .param_regs = &pt_sett_param_regs_pid01, |
| .param_size = &pt_sett_param_size_pid01, |
| .fw_ver = ttconfig_fw_ver_pid01, |
| .fw_vsize = ARRAY_SIZE(ttconfig_fw_ver_pid01), |
| .panel_id = 0x01, |
| }; |
| |
| /* TT Config for Panel ID not enabled (legacy)*/ |
| #include "pt_params.h" |
| static struct touch_settings pt_sett_param_regs = { |
| .data = (uint8_t *)&pt_param_regs[0], |
| .size = ARRAY_SIZE(pt_param_regs), |
| .tag = 0, |
| }; |
| |
| static struct touch_settings pt_sett_param_size = { |
| .data = (uint8_t *)&pt_param_size[0], |
| .size = ARRAY_SIZE(pt_param_size), |
| .tag = 0, |
| }; |
| |
| static struct pt_touch_config pt_ttconfig = { |
| .param_regs = &pt_sett_param_regs, |
| .param_size = &pt_sett_param_size, |
| .fw_ver = ttconfig_fw_ver, |
| .fw_vsize = ARRAY_SIZE(ttconfig_fw_ver), |
| }; |
| #else |
| /* TT Config for Panel ID not enabled (legacy)*/ |
| static struct pt_touch_config pt_ttconfig = { |
| .param_regs = NULL, |
| .param_size = NULL, |
| .fw_ver = NULL, |
| .fw_vsize = 0, |
| }; |
| #endif |
| |
| static struct pt_touch_firmware *pt_firmwares[] = { |
| #ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE |
| &pt_firmware_pid00, |
| &pt_firmware_pid01, |
| #endif |
| NULL, /* Last item should always be NULL */ |
| }; |
| |
| static struct pt_touch_config *pt_ttconfigs[] = { |
| #ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_TTCONFIG_UPGRADE |
| &pt_ttconfig_pid00, |
| &pt_ttconfig_pid01, |
| #endif |
| NULL, /* Last item should always be NULL */ |
| }; |
| |
| struct pt_loader_platform_data _pt_loader_platform_data = { |
| .fw = &pt_firmware, |
| .ttconfig = &pt_ttconfig, |
| .fws = pt_firmwares, |
| .ttconfigs = pt_ttconfigs, |
| .flags = PT_LOADER_FLAG_NONE, |
| }; |
| |
| /******************************************************************************* |
| * FUNCTION: pt_xres |
| * |
| * SUMMARY: Toggles the reset gpio (TP_XRES) to perform a HW reset |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = fail |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * *dev - pointer to Device structure |
| ******************************************************************************/ |
| int pt_xres(struct pt_core_platform_data *pdata, |
| struct device *dev) |
| { |
| int rst_gpio = pdata->rst_gpio; |
| int ddi_rst_gpio = pdata->ddi_rst_gpio; |
| |
| pt_debug(dev, DL_WARN, "%s: 20ms HARD RESET on gpio=%d\n", |
| __func__, pdata->rst_gpio); |
| |
| /* Toggling only TP_XRES as DDI_XRES resets the entire part */ |
| gpio_set_value(rst_gpio, 1); |
| if (ddi_rst_gpio) |
| gpio_set_value(ddi_rst_gpio, 1); |
| usleep_range(3000, 4000); |
| gpio_set_value(rst_gpio, 0); |
| usleep_range(6000, 7000); |
| gpio_set_value(rst_gpio, 1); |
| if (ddi_rst_gpio) |
| gpio_set_value(ddi_rst_gpio, 1); |
| |
| /* Sleep to allow the DUT to boot */ |
| usleep_range(3000, 4000); |
| return 0; |
| } |
| |
| #ifdef PT_PINCTRL_EN |
| /******************************************************************************* |
| * FUNCTION: pt_pinctrl_init |
| * |
| * SUMMARY: Pinctrl method to obtain pin state handler for TP_RST, IRQ |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = fail |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * *dev - pointer to Device structure |
| ******************************************************************************/ |
| static int pt_pinctrl_init(struct pt_core_platform_data *pdata, |
| struct device *dev) |
| { |
| int ret = 0; |
| |
| pdata->pinctrl = devm_pinctrl_get(dev); |
| if (IS_ERR_OR_NULL(pdata->pinctrl)) { |
| pt_debug(dev, DL_ERROR, |
| "Failed to get pinctrl, please check dts\n"); |
| ret = PTR_ERR(pdata->pinctrl); |
| goto err_pinctrl_get; |
| } |
| |
| pdata->pins_active = |
| pinctrl_lookup_state(pdata->pinctrl, "pmx_ts_active"); |
| if (IS_ERR_OR_NULL(pdata->pins_active)) { |
| pt_debug(dev, DL_ERROR, "pmx_ts_active not found\n"); |
| ret = PTR_ERR(pdata->pins_active); |
| goto err_pinctrl_lookup; |
| } |
| |
| pdata->pins_suspend = |
| pinctrl_lookup_state(pdata->pinctrl, "pmx_ts_suspend"); |
| if (IS_ERR_OR_NULL(pdata->pins_suspend)) { |
| pt_debug(dev, DL_ERROR, "pmx_ts_suspend not found\n"); |
| ret = PTR_ERR(pdata->pins_suspend); |
| goto err_pinctrl_lookup; |
| } |
| |
| pdata->pins_release = |
| pinctrl_lookup_state(pdata->pinctrl, "pmx_ts_release"); |
| if (IS_ERR_OR_NULL(pdata->pins_release)) { |
| pt_debug(dev, DL_ERROR, "pmx_ts_release not found\n"); |
| ret = PTR_ERR(pdata->pins_release); |
| goto err_pinctrl_lookup; |
| } |
| |
| return 0; |
| |
| err_pinctrl_lookup: |
| devm_pinctrl_put(pdata->pinctrl); |
| |
| err_pinctrl_get: |
| pdata->pinctrl = NULL; |
| pdata->pins_release = NULL; |
| pdata->pins_suspend = NULL; |
| pdata->pins_active = NULL; |
| return ret; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pinctrl_select_normal |
| * |
| * SUMMARY: Pinctrl method to configure drive mode for TP_RST, IRQ - normal |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = fail |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * *dev - pointer to Device structure |
| ******************************************************************************/ |
| static int pt_pinctrl_select_normal(struct pt_core_platform_data *pdata, |
| struct device *dev) |
| { |
| int ret = 0; |
| |
| if (pdata->pinctrl && pdata->pins_active) { |
| ret = pinctrl_select_state(pdata->pinctrl, pdata->pins_active); |
| if (ret < 0) { |
| pt_debug(dev, DL_ERROR, |
| "Set normal pin state error=%d\n", ret); |
| } |
| } |
| |
| return ret; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pinctrl_select_suspend |
| * |
| * SUMMARY: Pinctrl method to configure drive mode for TP_RST, IRQ - suspend |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = fail |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * *dev - pointer to Device structure |
| ******************************************************************************/ |
| static int pt_pinctrl_select_suspend(struct pt_core_platform_data *pdata, |
| struct device *dev) |
| { |
| int ret = 0; |
| |
| if (pdata->pinctrl && pdata->pins_suspend) { |
| ret = pinctrl_select_state(pdata->pinctrl, pdata->pins_suspend); |
| if (ret < 0) { |
| pt_debug(dev, DL_ERROR, |
| "Set suspend pin state error=%d\n", ret); |
| } |
| } |
| |
| return ret; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pinctrl_select_release |
| * |
| * SUMMARY: Pinctrl method to configure drive mode for TP_RST, IRQ - release |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = fail |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * *dev - pointer to Device structure |
| ******************************************************************************/ |
| static int pt_pinctrl_select_release(struct pt_core_platform_data *pdata, |
| struct device *dev) |
| { |
| int ret = 0; |
| |
| if (pdata->pinctrl) { |
| if (IS_ERR_OR_NULL(pdata->pins_release)) { |
| devm_pinctrl_put(pdata->pinctrl); |
| pdata->pinctrl = NULL; |
| } else { |
| ret = pinctrl_select_state(pdata->pinctrl, |
| pdata->pins_release); |
| if (ret < 0) |
| pt_debug(dev, DL_ERROR, |
| "Set gesture pin state error=%d\n", |
| ret); |
| } |
| } |
| |
| return ret; |
| } |
| #endif /* PT_PINCTRL_EN */ |
| |
| #ifdef PT_REGULATOR_EN |
| #define PT_VCC_MIN_UV (2800000) |
| #define PT_VCC_MAX_UV (3300000) |
| #define PT_VDDI_MIN_UV (1800000) |
| #define PT_VDDI_MAX_UV (1900000) |
| /******************************************************************************* |
| * FUNCTION: pt_regulator_init |
| * |
| * SUMMARY: With regulator framework to get regulator handle and setup voltage |
| * level. |
| * |
| * NOTE: The funtion only contains setup for VCC and VDDI since AVDD AVEE is |
| * usually used by TDDI products while the power is setup on other driver. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = fail |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * *dev - pointer to Device structure |
| ******************************************************************************/ |
| static int pt_regulator_init(struct pt_core_platform_data *pdata, |
| struct device *dev) |
| { |
| int rc = 0; |
| |
| pdata->vcc = devm_regulator_get(dev, "vcc"); |
| if (IS_ERR(pdata->vcc)) { |
| rc = PTR_ERR(pdata->vcc); |
| pt_debug(dev, DL_ERROR, "get vcc regulator failed,rc=%d\n", rc); |
| return rc; |
| } |
| |
| if (regulator_count_voltages(pdata->vcc) > 0) { |
| rc = regulator_set_voltage(pdata->vcc, PT_VCC_MIN_UV, |
| PT_VCC_MAX_UV); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "set vcc regulator failed rc=%d\n", rc); |
| goto error_set_vcc; |
| } |
| } |
| |
| pdata->vddi = devm_regulator_get(dev, "vddi"); |
| if (IS_ERR(pdata->vddi)) { |
| rc = PTR_ERR(pdata->vddi); |
| pt_debug(dev, DL_ERROR, |
| "get vddi regulator failed,rc=%d\n", rc); |
| goto error_get_vcc; |
| } |
| |
| if (regulator_count_voltages(pdata->vddi) > 0) { |
| rc = regulator_set_voltage(pdata->vddi, PT_VDDI_MIN_UV, |
| PT_VDDI_MAX_UV); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "set vddi regulator failed rc=%d\n", rc); |
| goto error_get_vcc; |
| } |
| } |
| |
| return 0; |
| |
| error_get_vcc: |
| /* |
| * regulator_set_voltage always select minimum legal voltage between |
| * min_uV and max_uV. To set the minuV to 0 means to restore the default |
| * value of regulator. Since regulator_set_voltage is the part to |
| * release regulator source, it's not necessary to check the returned |
| * value of it. |
| */ |
| if (regulator_count_voltages(pdata->vcc) > 0) |
| regulator_set_voltage(pdata->vcc, 0, PT_VCC_MAX_UV); |
| error_set_vcc: |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_setup_power_by_regulator |
| * |
| * SUMMARY: With regulator framework to set power on/off. |
| * |
| * NOTE: The funtion only contains setup for VCC and VDDI since AVDD AVEE is |
| * usually used by TDDI products while the power is setup on other driver. |
| * |
| * NOTE: The timing sequence is the EXAMPLE ONLY for TT7XXX: |
| * power up order : VDDI, VCC |
| * power down order : VCC, VDDI |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = fail |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * on - flag to decide power state,PT_MT_POWER_ON/PT_MT_POWER_OFF |
| * *dev - pointer to Device structure |
| * *time - pointer to store how many ms this function will sleep. This is |
| * used to calculate how long to leave XRES asserted during pwr up |
| ******************************************************************************/ |
| static int pt_setup_power_by_regulator(struct pt_core_platform_data *pdata, |
| int on, struct device *dev, int *time) |
| { |
| int rc = 0; |
| |
| if (IS_ERR(pdata->vddi) || IS_ERR(pdata->vcc)) { |
| pt_debug(dev, DL_ERROR, "vddi or vcc is not valid\n"); |
| return -EINVAL; |
| } |
| |
| if (on == PT_MT_POWER_ON) { |
| rc = regulator_enable(pdata->vddi); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "enable vddi regulator failed,rc=%d\n", rc); |
| } |
| |
| rc = regulator_enable(pdata->vcc); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "enable vcc regulator failed,rc=%d\n", rc); |
| } |
| /* |
| * Ensure the power is stable. |
| * This must be a min of 2ms for Gen6 but as short as |
| * possible for TT/TC parts. |
| */ |
| usleep_range(2000, 3000); |
| |
| /* |
| * 2ms after enabling VDDI and VCC, change the direction of the |
| * RST GPIO to be an output so it will no longer float and then |
| * force teh DUT into reset |
| */ |
| rc = gpio_direction_output(pdata->rst_gpio, 0); |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Output Setup ERROR: RST GPIO %d\n", |
| __func__, rst_gpio); |
| } else { |
| /* Force DUT into reset */ |
| gpio_set_value(pdata->rst_gpio, 0); |
| } |
| |
| /* Record the time XRES was enabled */ |
| *time = 0; |
| } else { |
| rc = regulator_disable(pdata->vcc); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "disable vcc regulator failed,rc=%d\n", rc); |
| } |
| rc = regulator_disable(pdata->vddi); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "disable vddi regulator failed,rc=%d\n", rc); |
| } |
| /* Ensure the power ramp down completely */ |
| usleep_range(10000, 12000); |
| } |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_regulator_release |
| * |
| * SUMMARY: With regulator framework to release regulator resource |
| * |
| * NOTE: The regulator MUST be disabled before this call. |
| * NOTE: The funtion only contains setup for VCC and VDDI since AVDD AVEE is |
| * usually used by TDDI products while the power is setup on other driver. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = fail |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * *dev - pointer to Device structure |
| ******************************************************************************/ |
| static int pt_regulator_release(struct pt_core_platform_data *pdata, |
| struct device *dev) |
| { |
| if (IS_ERR(pdata->vddi) || IS_ERR(pdata->vcc)) |
| return -EINVAL; |
| |
| /* |
| * regulator_set_voltage always select minimum legal voltage between |
| * min_uV and max_uV. To set the minuV to 0 means to restore the default |
| * value of regulator. Since regulator_set_voltage is the part to |
| * release regulator source, it's not necessary to check the returned |
| * value of it. |
| */ |
| if (regulator_count_voltages(pdata->vddi) > 0) |
| regulator_set_voltage(pdata->vddi, 0, PT_VDDI_MAX_UV); |
| |
| if (regulator_count_voltages(pdata->vcc) > 0) |
| regulator_set_voltage(pdata->vcc, 0, PT_VCC_MAX_UV); |
| |
| return 0; |
| } |
| #endif /* PT_REGULATOR_EN */ |
| /******************************************************************************* |
| * FUNCTION: pt_init |
| * |
| * SUMMARY: Set up/free gpios for TP_RST, IRQ, DDI_RST. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = fail |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * on - flag to set up or free gpios(0:free; !0:set up) |
| * *dev - pointer to Device structure |
| ******************************************************************************/ |
| int pt_init(struct pt_core_platform_data *pdata, |
| int on, struct device *dev) |
| { |
| int rst_gpio = pdata->rst_gpio; |
| int irq_gpio = pdata->irq_gpio; |
| int ddi_rst_gpio = pdata->ddi_rst_gpio; |
| int rc = 0; |
| |
| if (on) { |
| #ifdef PT_PINCTRL_EN |
| rc = pt_pinctrl_init(pdata, dev); |
| if (!rc) { |
| pt_pinctrl_select_normal(pdata, dev); |
| } else { |
| pt_debug(dev, DL_ERROR, |
| "%s: Failed to request pinctrl\n", __func__); |
| } |
| #endif |
| #ifdef PT_REGULATOR_EN |
| rc = pt_regulator_init(pdata, dev); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Failed requesting regulator rc=%d\n", |
| __func__, rc); |
| } |
| #endif |
| } |
| |
| if (on && rst_gpio) { |
| /* Configure RST GPIO */ |
| pt_debug(dev, DL_WARN, "%s: Request RST GPIO %d\n", |
| __func__, rst_gpio); |
| rc = gpio_request(rst_gpio, NULL); |
| if (rc < 0) { |
| gpio_free(rst_gpio); |
| rc = gpio_request(rst_gpio, NULL); |
| } |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Failed requesting RST GPIO %d\n", |
| __func__, rst_gpio); |
| goto fail_rst_gpio; |
| } else { |
| /* |
| * The RST GPIO is typically an output but during init, |
| * before power is applied, set it up as an input to |
| * allow it to float up with VDDI when power is applied. |
| */ |
| rc = gpio_direction_input(rst_gpio); |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Input Setup ERROR: RST GPIO %d\n", |
| __func__, rst_gpio); |
| goto fail_rst_gpio; |
| } |
| } |
| } |
| |
| if (on && irq_gpio) { |
| /* Configure IRQ GPIO */ |
| pt_debug(dev, DL_WARN, "%s: Request IRQ GPIO %d\n", |
| __func__, irq_gpio); |
| rc = gpio_request(irq_gpio, NULL); |
| if (rc < 0) { |
| gpio_free(irq_gpio); |
| rc = gpio_request(irq_gpio, NULL); |
| } |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Failed requesting IRQ GPIO %d\n", |
| __func__, irq_gpio); |
| goto fail_irq_gpio; |
| } else { |
| /* Set the GPIO direction */ |
| rc = gpio_direction_input(irq_gpio); |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Input Setup ERROR: IRQ GPIO %d\n", |
| __func__, irq_gpio); |
| goto fail_irq_gpio; |
| } |
| } |
| } |
| |
| if (on && ddi_rst_gpio) { |
| /* Configure DDI RST GPIO */ |
| pt_debug(dev, DL_WARN, "%s: Request DDI RST GPIO %d\n", |
| __func__, ddi_rst_gpio); |
| rc = gpio_request(ddi_rst_gpio, NULL); |
| if (rc < 0) { |
| gpio_free(ddi_rst_gpio); |
| rc = gpio_request(ddi_rst_gpio, NULL); |
| } |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Failed requesting DDI RST GPIO %d\n", |
| __func__, ddi_rst_gpio); |
| goto fail_ddi_rst_gpio; |
| } else { |
| /* Set the GPIO direction and the starting level */ |
| rc = gpio_direction_output(ddi_rst_gpio, 0); |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Output Setup ERROR: RST GPIO %d\n", |
| __func__, ddi_rst_gpio); |
| goto fail_ddi_rst_gpio; |
| } |
| } |
| } |
| |
| if (!on) { |
| /* "on" not set, therefore free all gpio's */ |
| if (ddi_rst_gpio) |
| gpio_free(ddi_rst_gpio); |
| if (irq_gpio) |
| gpio_free(irq_gpio); |
| if (rst_gpio) |
| gpio_free(rst_gpio); |
| #ifdef PT_REGULATOR_EN |
| pt_regulator_release(pdata, dev); |
| #endif |
| #ifdef PT_PINCTRL_EN |
| pt_pinctrl_select_release(pdata, dev); |
| #endif |
| } |
| |
| /* All GPIO's created successfully */ |
| goto success; |
| |
| fail_ddi_rst_gpio: |
| pt_debug(dev, DL_ERROR, |
| "%s: ERROR - GPIO setup Failure, freeing DDI_XRES GPIO %d\n", |
| __func__, ddi_rst_gpio); |
| gpio_free(ddi_rst_gpio); |
| fail_irq_gpio: |
| pt_debug(dev, DL_ERROR, |
| "%s: ERROR - GPIO setup Failure, freeing IRQ GPIO %d\n", |
| __func__, irq_gpio); |
| gpio_free(irq_gpio); |
| fail_rst_gpio: |
| pt_debug(dev, DL_ERROR, |
| "%s: ERROR - GPIO setup Failure, freeing TP_XRES GPIO %d\n", |
| __func__, rst_gpio); |
| gpio_free(rst_gpio); |
| |
| success: |
| pt_debug(dev, DL_INFO, |
| "%s: SUCCESS - Configured DDI_XRES GPIO %d, IRQ GPIO %d, TP_XRES GPIO %d\n", |
| __func__, ddi_rst_gpio, irq_gpio, rst_gpio); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_irq_stat |
| * |
| * SUMMARY: Obtain the level state of IRQ gpio. |
| * |
| * RETURN: |
| * level state of IRQ gpio |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * *dev - pointer to Device structure |
| ******************************************************************************/ |
| int pt_irq_stat(struct pt_core_platform_data *pdata, |
| struct device *dev) |
| { |
| return gpio_get_value(pdata->irq_gpio); |
| } |
| |
| #ifdef PT_DETECT_HW |
| /******************************************************************************* |
| * FUNCTION: pt_detect |
| * |
| * SUMMARY: Detect the I2C device by reading one byte(FW sentiel) after the |
| * reset operation. |
| * |
| * RETURN: |
| * 0 - detected |
| * !0 - undetected |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * *dev - pointer to Device structure |
| * read - pointer to the function to perform a read operation |
| ******************************************************************************/ |
| int pt_detect(struct pt_core_platform_data *pdata, |
| struct device *dev, pt_platform_read read) |
| { |
| int retry = 3; |
| int rc; |
| char buf[1]; |
| |
| while (retry--) { |
| /* Perform reset, wait for 100 ms and perform read */ |
| pt_debug(dev, DL_WARN, "%s: Performing a reset\n", |
| __func__); |
| pdata->xres(pdata, dev); |
| msleep(100); |
| rc = read(dev, buf, 1); |
| if (!rc) |
| return 0; |
| |
| pt_debug(dev, DL_ERROR, "%s: Read unsuccessful, try=%d\n", |
| __func__, 3 - retry); |
| } |
| |
| return rc; |
| } |
| #endif |
| |
| #ifndef PT_REGULATOR_EN |
| /******************************************************************************* |
| * FUNCTION: pt_setup_power_by_gpio |
| * |
| * SUMMARY: With GPIOs to control LDO directly to set power on/off. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = fail |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * on - flag to decide power state,PT_MT_POWER_ON/PT_MT_POWER_OFF |
| * *dev - pointer to Device structure |
| * *time - pointer to store how many ms this function will sleep. This is |
| * used to calculate how long to leave XRES asserted during pwr up |
| ******************************************************************************/ |
| static int pt_setup_power_by_gpio(struct pt_core_platform_data *pdata, |
| int on, struct device *dev, int *time) |
| { |
| int rc = 0; |
| int en_vcc = pdata->vcc_gpio; |
| int en_vddi = pdata->vddi_gpio; |
| int en_avdd = pdata->avdd_gpio; |
| int en_avee = pdata->avee_gpio; |
| |
| if (on == PT_MT_POWER_ON) { |
| /* |
| * Enable GPIOs to turn on voltage regulators to pwr up DUT |
| * - TC device power up order: VDDI, VCC, AVDD, AVEE |
| * - TT device power up order: VDDI, VCC |
| * NOTE: VDDI must be stable for >10ms before XRES is released |
| */ |
| pt_debug(dev, DL_INFO, |
| "%s: Enable defined pwr: VDDI, VCC, AVDD, AVEE\n", __func__); |
| |
| /* Turn on VDDI [Digital Interface] (+1.8v) */ |
| if (pdata->vddi_gpio) { |
| rc = gpio_request(en_vddi, NULL); |
| if (rc < 0) { |
| gpio_free(en_vddi); |
| rc = gpio_request(en_vddi, NULL); |
| } |
| if (rc < 0) { |
| pr_err("%s: Failed requesting VDDI GPIO %d\n", |
| __func__, en_vddi); |
| } |
| rc = gpio_direction_output(en_vddi, 1); |
| if (rc) |
| pr_err("%s: setcfg for VDDI GPIO %d failed\n", |
| __func__, en_vddi); |
| gpio_free(en_vddi); |
| } |
| |
| /* Turn on VCC */ |
| if (pdata->vcc_gpio) { |
| rc = gpio_request(en_vcc, NULL); |
| if (rc < 0) { |
| gpio_free(en_vcc); |
| rc = gpio_request(en_vcc, NULL); |
| } |
| if (rc < 0) { |
| pr_err("%s: Failed requesting VCC GPIO %d\n", |
| __func__, en_vcc); |
| } |
| rc = gpio_direction_output(en_vcc, 1); |
| if (rc) |
| pr_err("%s: setcfg for VCC GPIO %d failed\n", |
| __func__, en_vcc); |
| gpio_free(en_vcc); |
| /* |
| * This must be a min of 2ms for Gen6 but as short as |
| * possible for TT/TC parts. |
| */ |
| usleep_range(2000, 3000); |
| } |
| |
| /* |
| * 2-3ms after enabling VDDI and VCC, change the direction of |
| * the RST GPIO to be an output so it will no longer float and |
| * then force teh DUT into reset |
| */ |
| rc = gpio_direction_output(pdata->rst_gpio, 0); |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Output Setup ERROR: RST GPIO %d\n", |
| __func__, pdata->rst_gpio); |
| } else { |
| /* Force DUT into reset */ |
| gpio_set_value(pdata->rst_gpio, 0); |
| } |
| |
| /* Reset ms counter since XRES was enabled */ |
| *time = 0; |
| |
| /* Turn on AVDD (+5.0v) */ |
| if (pdata->avdd_gpio) { |
| rc = gpio_request(en_avdd, NULL); |
| if (rc < 0) { |
| gpio_free(en_avdd); |
| rc = gpio_request(en_avdd, NULL); |
| } |
| if (rc < 0) { |
| pr_err("%s: Failed requesting AVDD GPIO %d\n", |
| __func__, en_avdd); |
| } |
| rc = gpio_direction_output(en_avdd, 1); |
| if (rc) |
| pr_err("%s: setcfg for AVDD GPIO %d failed\n", |
| __func__, en_avdd); |
| gpio_free(en_avdd); |
| usleep_range(3000, 4000); |
| *time += 4; |
| } |
| |
| /* Turn on AVEE (-5.0v) */ |
| if (pdata->avee_gpio) { |
| rc = gpio_request(en_avee, NULL); |
| if (rc < 0) { |
| gpio_free(en_avee); |
| rc = gpio_request(en_avee, NULL); |
| } |
| if (rc < 0) { |
| pr_err("%s: Failed requesting AVEE GPIO %d\n", |
| __func__, en_avee); |
| } |
| rc = gpio_direction_output(en_avee, 1); |
| if (rc) |
| pr_err("%s: setcfg for AVEE GPIO %d failed\n", |
| __func__, en_avee); |
| gpio_free(en_avee); |
| usleep_range(3000, 4000); |
| *time += 4; |
| } |
| } else { |
| /* |
| * Disable GPIOs to turn off voltage regulators to pwr down |
| * TC device The power down order is: AVEE, AVDD, VDDI |
| * TT device The power down order is: VCC, VDDI |
| * |
| * Note:Turn off some of regulators may effect display |
| * parts for TDDI chip |
| */ |
| pt_debug(dev, DL_INFO, |
| "%s: Turn off defined pwr: VCC, AVEE, AVDD, VDDI\n", __func__); |
| |
| /* Turn off VCC */ |
| if (pdata->vcc_gpio) { |
| rc = gpio_request(en_vcc, NULL); |
| if (rc < 0) { |
| gpio_free(en_vcc); |
| rc = gpio_request(en_vcc, NULL); |
| } |
| if (rc < 0) { |
| pr_err("%s: Failed requesting VCC GPIO %d\n", |
| __func__, en_vcc); |
| } |
| rc = gpio_direction_output(en_vcc, 0); |
| if (rc) |
| pr_err("%s: setcfg for VCC GPIO %d failed\n", |
| __func__, en_vcc); |
| gpio_free(en_vcc); |
| } |
| |
| /* Turn off AVEE (-5.0v) */ |
| if (pdata->avee_gpio) { |
| rc = gpio_request(en_avee, NULL); |
| if (rc < 0) { |
| gpio_free(en_avee); |
| rc = gpio_request(en_avee, NULL); |
| } |
| if (rc < 0) { |
| pr_err("%s: Failed requesting AVEE GPIO %d\n", |
| __func__, en_avee); |
| } |
| rc = gpio_direction_output(en_avee, 0); |
| if (rc) |
| pr_err("%s: setcfg for AVEE GPIO %d failed\n", |
| __func__, en_avee); |
| gpio_free(en_avee); |
| } |
| |
| /* Turn off AVDD (+5.0v) */ |
| if (pdata->avdd_gpio) { |
| rc = gpio_request(en_avdd, NULL); |
| if (rc < 0) { |
| gpio_free(en_avdd); |
| rc = gpio_request(en_avdd, NULL); |
| } |
| if (rc < 0) { |
| pr_err("%s: Failed requesting AVDD GPIO %d\n", |
| __func__, en_avdd); |
| } |
| rc = gpio_direction_output(en_avdd, 0); |
| if (rc) |
| pr_err("%s: setcfg for AVDD GPIO %d failed\n", |
| __func__, en_avdd); |
| gpio_free(en_avdd); |
| } |
| |
| /* Turn off VDDI [Digital Interface] (+1.8v) */ |
| if (pdata->vddi_gpio) { |
| rc = gpio_request(en_vddi, NULL); |
| if (rc < 0) { |
| gpio_free(en_vddi); |
| rc = gpio_request(en_vddi, NULL); |
| } |
| if (rc < 0) { |
| pr_err("%s: Failed requesting VDDI GPIO %d\n", |
| __func__, en_vddi); |
| } |
| rc = gpio_direction_output(en_vddi, 0); |
| if (rc) |
| pr_err("%s: setcfg for VDDI GPIO %d failed\n", |
| __func__, en_vddi); |
| gpio_free(en_vddi); |
| usleep_range(10000, 12000); |
| } |
| } |
| |
| return rc; |
| } |
| #endif /* PT_REGULATOR_EN */ |
| /******************************************************************************* |
| * FUNCTION: pt_setup_power |
| * |
| * SUMMARY: Turn on/turn off voltage regulator |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *pdata - pointer to core platform data |
| * on - flag to decide power state,PT_MT_POWER_ON/PT_MT_POWER_OFF |
| * *dev - pointer to device |
| ******************************************************************************/ |
| int pt_setup_power(struct pt_core_platform_data *pdata, int on, |
| struct device *dev) |
| { |
| int rc = 0; |
| int sleep_time = 0; |
| |
| /* |
| * For TDDI parts, force part into RESET by holding DDI XRES |
| * while powering it up |
| */ |
| if (pdata->ddi_rst_gpio) |
| gpio_set_value(pdata->ddi_rst_gpio, 0); |
| |
| /* |
| * When turning power on: |
| * - For Gen6 the RST line must be disabled (set high) when VDDI is |
| * being applied for a min of 2ms, after which it must be enabled (set |
| * low) for 10ms. |
| * - For TT/TC parts it must be low while power is applied but the pwr |
| * sequence for Gen6 is ok. |
| */ |
| if (on && pdata->rst_gpio) { |
| /* |
| * Set initial RST GPIO direction to input to allow it to |
| * float up with VDDI. |
| */ |
| rc = gpio_direction_input(pdata->rst_gpio); |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Input Setup ERROR: RST GPIO %d\n", |
| __func__, pdata->rst_gpio); |
| /* |
| * Not ideal but if setting the direction to input |
| * failed ensure part is not in reset during first 2ms. |
| */ |
| gpio_set_value(pdata->rst_gpio, 1); |
| } |
| } |
| |
| #ifdef PT_REGULATOR_EN |
| rc = pt_setup_power_by_regulator(pdata, on, dev, &sleep_time); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Failed setup power by regulator rc=%d\n", |
| __func__, rc); |
| } |
| #else /* PT_REGULATOR_EN */ |
| rc = pt_setup_power_by_gpio(pdata, on, dev, &sleep_time); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Failed setup power by gpio rc=%d\n", |
| __func__, rc); |
| } |
| #endif /* PT_REGULATOR_EN */ |
| |
| /* |
| * TP_XRES must be held low for a min of 10ms, however, TDDI panels |
| * will have already slept for 8ms so only add 2ms, whereas TT7K/Gen6 |
| * panels will not have slept yet so do full 10ms sleep. |
| */ |
| if (sleep_time < 8) |
| usleep_range(10000, 11000); |
| else |
| usleep_range(2000, 3000); |
| |
| /* Force part out of RESET by releasing XRES#(TP_XRES) */ |
| if (pdata->rst_gpio) |
| gpio_set_value(pdata->rst_gpio, 1); |
| |
| /* Force part out of RESET by releasing DDI XRES */ |
| if (pdata->ddi_rst_gpio) |
| gpio_set_value(pdata->ddi_rst_gpio, 1); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_wakeup |
| * |
| * SUMMARY: Resume power for "power on/off" sleep strategy which against to |
| * "deepsleep" strategy. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = fail |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * *dev - pointer to Device structure |
| * *ignore_irq - pointer to atomic structure to allow the host ignoring false |
| * IRQ during power up |
| ******************************************************************************/ |
| static int pt_wakeup(struct pt_core_platform_data *pdata, |
| struct device *dev, atomic_t *ignore_irq) |
| { |
| int rc = 0; |
| |
| #ifdef PT_PINCTRL_EN |
| pt_pinctrl_select_normal(pdata, dev); |
| #endif |
| rc = pt_setup_power(pdata, PT_MT_POWER_ON, dev); |
| if (rc) |
| pt_debug(dev, DL_ERROR, "%s: Failed setup power\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_sleep |
| * |
| * SUMMARY: Suspend power for "power on/off" sleep strategy which against to |
| * "deepsleep" strategy. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = fail |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * *dev - pointer to Device structure |
| * *ignore_irq - pointer to atomic structure to allow the host ignoring false |
| * IRQ during power down |
| ******************************************************************************/ |
| static int pt_sleep(struct pt_core_platform_data *pdata, |
| struct device *dev, atomic_t *ignore_irq) |
| { |
| int rc = 0; |
| |
| rc = pt_setup_power(pdata, PT_MT_POWER_OFF, dev); |
| if (rc) |
| pt_debug(dev, DL_ERROR, "%s: Failed setup power\n", __func__); |
| |
| #ifdef PT_PINCTRL_EN |
| pt_pinctrl_select_suspend(pdata, dev); |
| #endif |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_power |
| * |
| * SUMMARY: Wrapper function to resume/suspend power with function |
| * pt_wakeup()/pt_sleep(). |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = fail |
| * |
| * PARAMETERS: |
| * *pdata - pointer to the platform data structure |
| * on - flag to remsume/suspend power(0:resume; 1:suspend) |
| * *dev - pointer to Device structure |
| * *ignore_irq - pointer to atomic structure to allow the host ignoring false |
| * IRQ during power up/down |
| ******************************************************************************/ |
| int pt_power(struct pt_core_platform_data *pdata, |
| int on, struct device *dev, atomic_t *ignore_irq) |
| { |
| if (on) |
| return pt_wakeup(pdata, dev, ignore_irq); |
| |
| return pt_sleep(pdata, dev, ignore_irq); |
| } |
| |
| #ifdef PT_PTSBC_SUPPORT |
| |
| static struct workqueue_struct *parade_wq; |
| static u32 int_handle; |
| |
| /******************************************************************************* |
| * FUNCTION: pt_irq_work_function |
| * |
| * SUMMARY: Work function for queued IRQ activity |
| * |
| * RETURN: Void |
| * |
| * PARAMETERS: |
| * *work - pointer to work structure |
| ******************************************************************************/ |
| static void pt_irq_work_function(struct work_struct *work) |
| { |
| struct pt_core_data *cd = container_of(work, |
| struct pt_core_data, irq_work); |
| |
| pt_irq(cd->irq, (void *)cd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_irq_wrapper |
| * |
| * SUMMARY: Wrapper function for IRQ to queue the irq_work function |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *handle - void pointer to contain the core_data pointer |
| ******************************************************************************/ |
| peint_handle *pt_irq_wrapper(void *handle) |
| { |
| struct pt_core_data *cd = (struct pt_core_data *)handle; |
| |
| queue_work(parade_wq, &cd->irq_work); |
| return 0; |
| } |
| #endif /* PT_PTSBC_SUPPORT */ |
| |
| /******************************************************************************* |
| * FUNCTION: pt_setup_irq |
| * |
| * SUMMARY: Configure the IRQ GPIO used by the TT DUT |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *pdata - pointer to core platform data |
| * on - flag to setup interrupt process work(PT_MT_IRQ_FREE/) |
| * *dev - pointer to device |
| ******************************************************************************/ |
| int pt_setup_irq(struct pt_core_platform_data *pdata, int on, |
| struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| unsigned long irq_flags; |
| int rc = 0; |
| |
| if (on == PT_MT_IRQ_REG) { |
| /* |
| * When TTDL has direct access to the GPIO the irq_stat function |
| * will be defined and the gpio_to_irq conversion must be |
| * performed. e.g. For CHROMEOS this is not the case, the irq is |
| * passed in directly. |
| */ |
| if (pdata->irq_stat) { |
| /* Initialize IRQ */ |
| dev_vdbg(dev, "%s: Value Passed to gpio_to_irq =%d\n", |
| __func__, pdata->irq_gpio); |
| cd->irq = gpio_to_irq(pdata->irq_gpio); |
| dev_vdbg(dev, |
| "%s: Value Returned from gpio_to_irq =%d\n", |
| __func__, cd->irq); |
| } |
| if (cd->irq < 0) |
| return -EINVAL; |
| |
| cd->irq_enabled = true; |
| |
| pt_debug(dev, DL_INFO, "%s: initialize threaded irq=%d\n", |
| __func__, cd->irq); |
| |
| if (pdata->level_irq_udelay > 0) |
| #ifdef PT_PTSBC_SUPPORT |
| /* use level triggered interrupts */ |
| irq_flags = TRIG_LEVL_LOW; |
| else |
| /* use edge triggered interrupts */ |
| irq_flags = TRIG_EDGE_NEGATIVE; |
| #else |
| /* use level triggered interrupts */ |
| irq_flags = IRQF_TRIGGER_LOW | IRQF_ONESHOT; |
| else |
| /* use edge triggered interrupts */ |
| irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; |
| #endif /* PT_PTSBC_SUPPORT */ |
| |
| #ifdef PT_PTSBC_SUPPORT |
| /* Adding new work queue to cd struct */ |
| INIT_WORK(&cd->irq_work, pt_irq_work_function); |
| |
| parade_wq = create_singlethread_workqueue("parade_wq"); |
| if (!parade_wq) |
| pt_debug(dev, DL_ERROR, "%s Create workqueue failed.\n", |
| __func__); |
| |
| int_handle = sw_gpio_irq_request(pdata->irq_gpio, irq_flags, |
| (peint_handle)pt_irq_wrapper, cd); |
| if (!int_handle) { |
| pt_debug(dev, DL_ERROR, |
| "%s: PARADE could not request irq\n", __func__); |
| rc = -1; |
| } else { |
| rc = 0; |
| /* clk=0: 32Khz; clk=1: 24Mhz*/ |
| ctp_set_int_port_rate(pdata->irq_gpio, 1); |
| /* |
| * Debounce INT Line by clock divider: 2^n. E.g. The |
| * para:0x03 means the period of interrupt controller is |
| * 0.33 us = (2^3)/24. It has ability to measure the |
| * high/low width of the pulse bigger than 1 us. |
| */ |
| ctp_set_int_port_deb(pdata->irq_gpio, 0x03); |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Parade sw_gpio_irq_request SUCCESS\n", |
| __func__); |
| } |
| #else |
| rc = request_threaded_irq(cd->irq, pt_top_irq, pt_bottom_irq, irq_flags, |
| dev_name(dev), cd); |
| if (rc < 0) |
| pt_debug(dev, DL_ERROR, |
| "%s: Error, could not request irq\n", __func__); |
| #endif /* PT_PTSBC_SUPPORT */ |
| } else { |
| disable_irq_nosync(cd->irq); |
| #ifndef PT_PTSBC_SUPPORT |
| free_irq(cd->irq, cd); |
| #else |
| sw_gpio_irq_free(int_handle); |
| cancel_work_sync(&cd->irq_work); |
| destroy_workqueue(parade_wq); |
| #endif /* PT_PTSBC_SUPPORT */ |
| } |
| return rc; |
| } |