| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2020 MediaTek Inc. |
| */ |
| |
| #include <linux/extcon-provider.h> |
| #include <linux/gpio.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/of_gpio.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <linux/workqueue.h> |
| #include <linux/pinctrl/consumer.h> |
| #include <linux/debugfs.h> |
| #include <linux/seq_file.h> |
| #include <linux/uaccess.h> |
| #include "extcon-kirkwood-usb.h" |
| |
| #define USB_GPIO_DEBOUNCE_MS 20 /* ms */ |
| |
| static const unsigned int usb_cables[] = { |
| EXTCON_USB, |
| EXTCON_USB_HOST, |
| EXTCON_CHG_USB_SDP, |
| EXTCON_CHG_USB_DCP, |
| EXTCON_CHG_USB_CDP, |
| /* must end with EXTCON_NONE */ |
| EXTCON_NONE, |
| }; |
| |
| #define SET_EXTCON_STATE(info, cable, val) {\ |
| (info)->usb_cable = (((cable) & 0x03) << 16) | ((val) & 0x01);\ |
| } |
| |
| #define GET_EXTCON_CABLE(info) ((info)->usb_cable >> 16) |
| #define GET_EXTCON_STATUS(info) ((info)->usb_cable & 0x01) |
| |
| #define SET_EXTCON_BC12_STATE(info, cable, val) {\ |
| (info)->bc12_cable = (((cable) & 0x0F) << 16) | ((val) & 0x01);\ |
| } |
| |
| #define GET_EXTCON_BC12_CABLE(info) ((info)->bc12_cable >> 16) |
| #define GET_EXTCON_BC12_STATUS(info) ((info)->bc12_cable & 0x01) |
| |
| static enum power_supply_property kirkwood_psy_props[] = { |
| POWER_SUPPLY_PROP_USB_TYPE, |
| POWER_SUPPLY_PROP_VOLTAGE_MAX, |
| POWER_SUPPLY_PROP_CURRENT_MAX, |
| }; |
| |
| static enum power_supply_usb_type kirkwood_psy_usb_types[] = { |
| POWER_SUPPLY_USB_TYPE_UNKNOWN, |
| POWER_SUPPLY_USB_TYPE_SDP, |
| POWER_SUPPLY_USB_TYPE_DCP, |
| POWER_SUPPLY_USB_TYPE_CDP, |
| }; |
| |
| static enum power_supply_usb_type kirkwood_bc12_role_to_psy_usb_type(int bc12_role) { |
| switch (bc12_role) { |
| case BC12_DETECTED_DCP: |
| return POWER_SUPPLY_USB_TYPE_DCP; |
| case BC12_DETECTED_CDP: |
| case BC12_DETECTED_CDP_B: |
| return POWER_SUPPLY_USB_TYPE_CDP; |
| case BC12_DETECTED_SDP: |
| case BC12_DETECTED_SDP_B: |
| return POWER_SUPPLY_USB_TYPE_SDP; |
| default: |
| return POWER_SUPPLY_USB_TYPE_UNKNOWN; |
| } |
| } |
| |
| static int kirkwood_psy_get_prop(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| struct extcon_kirkwood *info = power_supply_get_drvdata(psy); |
| int ret = 0; |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_USB_TYPE: |
| val->intval = info->bc12_usb_type; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
| val->intval = 5000000; |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_MAX: |
| mutex_lock(&info->ekw_mutex); |
| if (info->bc12_usb_type == POWER_SUPPLY_USB_TYPE_DCP || |
| info->bc12_usb_type == POWER_SUPPLY_USB_TYPE_CDP) { |
| val->intval = 1500000; |
| } else { |
| val->intval = 500000; |
| } |
| mutex_unlock(&info->ekw_mutex); |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int kirkwood_psy_prop_writeable(struct power_supply *psy, |
| enum power_supply_property psp) |
| { |
| return 0; |
| } |
| |
| static const char *kirkwood_psy_name_prefix = "extcon-kirkwood-bc12-psy-"; |
| |
| static int devm_kirkwood_psy_register(struct extcon_kirkwood *info) |
| { |
| struct power_supply_config psy_cfg = {}; |
| const char *info_dev_name = dev_name(info->dev); |
| size_t psy_name_len = strlen(kirkwood_psy_name_prefix) + |
| strlen(info_dev_name) + 1; |
| char *psy_name; |
| |
| psy_cfg.drv_data = info; |
| psy_cfg.fwnode = dev_fwnode(info->dev); |
| psy_name = devm_kzalloc(info->dev, psy_name_len, GFP_KERNEL); |
| if (!psy_name) |
| return -ENOMEM; |
| |
| snprintf(psy_name, psy_name_len, "%s%s", kirkwood_psy_name_prefix, |
| info_dev_name); |
| info->psy_desc.name = psy_name; |
| info->psy_desc.type = POWER_SUPPLY_TYPE_USB; |
| info->psy_desc.usb_types = kirkwood_psy_usb_types; |
| info->psy_desc.num_usb_types = ARRAY_SIZE(kirkwood_psy_usb_types); |
| info->psy_desc.properties = kirkwood_psy_props; |
| info->psy_desc.num_properties = ARRAY_SIZE(kirkwood_psy_props); |
| info->psy_desc.get_property = kirkwood_psy_get_prop; |
| info->psy_desc.set_property = NULL; |
| info->psy_desc.property_is_writeable = kirkwood_psy_prop_writeable; |
| |
| info->bc12_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; |
| |
| info->psy = devm_power_supply_register(info->dev, &info->psy_desc, |
| &psy_cfg); |
| |
| return PTR_ERR_OR_ZERO(info->psy); |
| } |
| |
| static void kirkwood_switch_role(struct extcon_kirkwood *info, enum usb_role role) |
| { |
| struct extcon_dev *edev = info->edev; |
| |
| switch (role) { |
| case USB_ROLE_HOST: |
| if (info->bc12_role == BC12_DETECTED_CDP || |
| info->bc12_role == BC12_DETECTED_SDP) { |
| SET_EXTCON_STATE(info, EXTCON_USB_HOST, true); |
| extcon_set_state_sync(edev, EXTCON_USB_HOST, true); |
| } else { |
| dev_info(info->dev, |
| "BC12 is disconnections since %i\n", |
| info->bc12_role); |
| } |
| break; |
| case USB_ROLE_DEVICE: |
| if (info->bc12_role == BC12_DETECTED_CDP || |
| info->bc12_role == BC12_DETECTED_SDP) { |
| SET_EXTCON_STATE(info, EXTCON_USB_HOST, false); |
| extcon_set_state_sync(edev, EXTCON_USB_HOST, false); |
| SET_EXTCON_STATE(info, EXTCON_USB, true); |
| extcon_set_state_sync(edev, EXTCON_USB, true); |
| } else { |
| dev_info(info->dev, |
| "BC12 is disconnections since %i\n", |
| info->bc12_role); |
| } |
| break; |
| case USB_ROLE_NONE: |
| default: |
| SET_EXTCON_STATE(info, EXTCON_USB_HOST, false); |
| extcon_set_state_sync(edev, EXTCON_USB_HOST, false); |
| SET_EXTCON_STATE(info, EXTCON_USB, false); |
| extcon_set_state_sync(edev, EXTCON_USB, false); |
| dev_info(info->dev, |
| "BC12 connections status: %i\n", |
| info->bc12_role); |
| break; |
| } |
| } |
| |
| static int kirkwood_usb_set_role(struct usb_role_switch *sw, |
| enum usb_role role) |
| { |
| struct extcon_kirkwood *info = usb_role_switch_get_drvdata(sw); |
| |
| if (info == NULL) |
| return -ENODEV; |
| |
| mutex_lock(&info->ekw_mutex); |
| if (info->usb_hal_ver_en) { |
| if (info->usb_hal_ver != 0x13) { |
| dev_info(info->dev, |
| "ban is ignored since usb_hal_ver: 0x%02X\n", |
| info->usb_hal_ver); |
| goto skip_ban; |
| } |
| |
| if (!info->forced_role_none) { |
| info->ban_usb_data_role = USB_ROLE_NONE; |
| info->usb_data_role = role; |
| kirkwood_switch_role(info, role); |
| } else { |
| /* forced to NONE from sysfs, can't be changed from role_switch |
| * interface |
| */ |
| info->ban_usb_data_role = role; |
| dev_info(info->dev, |
| "%s is banned since usb role NONE\n", |
| role == USB_ROLE_HOST ? "HOST" : |
| role == USB_ROLE_DEVICE ? "DEVICE" : |
| "NONE"); |
| } |
| } else { |
| skip_ban: |
| info->usb_data_role = role; |
| kirkwood_switch_role(info, role); |
| } |
| mutex_unlock(&info->ekw_mutex); |
| |
| return 0; |
| } |
| |
| static enum usb_role kirkwood_usb_get_role(struct usb_role_switch *sw) |
| { |
| struct extcon_kirkwood *info = usb_role_switch_get_drvdata(sw); |
| enum usb_role role; |
| |
| if (info == NULL) |
| return USB_ROLE_NONE; |
| |
| mutex_lock(&info->ekw_mutex); |
| role = info->usb_data_role; |
| mutex_unlock(&info->ekw_mutex); |
| |
| return role; |
| } |
| |
| /* |
| * |
| * State | GOOD_BAT | SW_OPEN | CHG_AL_N|CHG_DET |
| * ---------------------------------------------------- |
| * [1] DCP | X | Hi-Z | L | H |
| * [2] CDP | H | L | L | H |
| * [3] CDP!| L | Hi-Z | L | H |
| * [4] SDP | H | L | L | L |
| * [5] SDP!| L | Hi-Z | L | L |
| * [6] BRK | L | Hi-Z | Hi-Z | L |
| * [7] UKN | X | Hi-Z | Hi-Z | Hi-Z to Low |
| * |
| * more details, please watch FSA831AL10 datasheet. |
| */ |
| static void kirkwood_bc12_detect_cable(struct work_struct *work) |
| { |
| u32 good_bat; |
| u32 sw_open; |
| u32 chg_aln; |
| u32 chg_det; |
| struct extcon_dev *edev; |
| struct extcon_kirkwood *info = container_of(to_delayed_work(work), |
| struct extcon_kirkwood, |
| wq_detcable); |
| int old_bc12_role = info->bc12_role; |
| |
| /* |
| * check good_bat, sw_open, chg_aln and chg_det |
| * and update cable state |
| */ |
| edev = info->edev; |
| good_bat = info->good_bat_gpiod ? |
| gpiod_get_value_cansleep(info->good_bat_gpiod) : |
| info->good_bat_polarity; |
| |
| sw_open = gpiod_get_value_cansleep(info->sw_open_gpiod); |
| chg_aln = gpiod_get_value_cansleep(info->chg_aln_gpiod); |
| chg_det = gpiod_get_value_cansleep(info->chg_det_gpiod); |
| |
| mutex_lock(&info->ekw_mutex); |
| /* at first we clean states which are no longer active */ |
| if (chg_aln != info->chg_aln_polarity) { |
| if (sw_open != info->sw_open_polarity) { |
| if (chg_det != info->chg_det_polarity) { |
| if (good_bat == info->good_bat_polarity) |
| info->bc12_role = BC12_DETECTED_SDP; |
| /* low undefined */ |
| } else { |
| if (good_bat == info->good_bat_polarity) |
| info->bc12_role = BC12_DETECTED_CDP; |
| /* low undefined */ |
| } |
| } else { |
| if (chg_det != info->chg_det_polarity) { |
| if (good_bat != info->good_bat_polarity) |
| info->bc12_role = BC12_DETECTED_SDP_B; |
| /* high undefined */ |
| |
| } else { |
| info->bc12_role = BC12_DETECTED_DCP; |
| if (good_bat != info->good_bat_polarity) |
| info->bc12_role = BC12_DETECTED_CDP_B; |
| } |
| } |
| } else { |
| if (chg_det == info->chg_det_polarity) { |
| if (chg_det != info->chg_det_polarity) { |
| if (good_bat != info->good_bat_polarity) |
| info->bc12_role = BC12_DETECTED_BRK; |
| /* others undefined */ |
| } else { |
| info->bc12_role = BC12_DETECTED_UNK; |
| } |
| } else { |
| info->bc12_role = BC12_DETECTED_NON; |
| } |
| } |
| |
| switch (info->bc12_role) { |
| case BC12_DETECTED_SDP: |
| case BC12_DETECTED_SDP_B: |
| SET_EXTCON_BC12_STATE(info, EXTCON_CHG_USB_SDP, true); |
| extcon_set_state_sync(edev, EXTCON_CHG_USB_SDP, true); |
| extcon_set_state_sync(edev, EXTCON_CHG_USB_DCP, false); |
| extcon_set_state_sync(edev, EXTCON_CHG_USB_CDP, false); |
| break; |
| case BC12_DETECTED_DCP: |
| extcon_set_state_sync(edev, EXTCON_CHG_USB_SDP, false); |
| |
| SET_EXTCON_BC12_STATE(info, EXTCON_CHG_USB_DCP, true); |
| extcon_set_state_sync(edev, EXTCON_CHG_USB_DCP, true); |
| extcon_set_state_sync(edev, EXTCON_CHG_USB_CDP, false); |
| break; |
| case BC12_DETECTED_CDP: |
| case BC12_DETECTED_CDP_B: |
| extcon_set_state_sync(edev, EXTCON_CHG_USB_SDP, false); |
| extcon_set_state_sync(edev, EXTCON_CHG_USB_DCP, false); |
| |
| SET_EXTCON_BC12_STATE(info, EXTCON_CHG_USB_CDP, true); |
| extcon_set_state_sync(edev, EXTCON_CHG_USB_CDP, true); |
| break; |
| default: |
| SET_EXTCON_BC12_STATE(info, EXTCON_NONE, false); |
| extcon_set_state_sync(edev, EXTCON_CHG_USB_SDP, false); |
| extcon_set_state_sync(edev, EXTCON_CHG_USB_DCP, false); |
| extcon_set_state_sync(edev, EXTCON_CHG_USB_CDP, false); |
| break; |
| } |
| |
| if (!(info->bc12_role == BC12_DETECTED_CDP || |
| info->bc12_role == BC12_DETECTED_SDP)) { |
| info->usb_data_role = USB_ROLE_NONE; |
| kirkwood_switch_role(info, USB_ROLE_NONE); |
| } |
| |
| if (info->bc12_role != old_bc12_role) { |
| info->bc12_usb_type = kirkwood_bc12_role_to_psy_usb_type(info->bc12_role); |
| power_supply_changed(info->psy); |
| } |
| mutex_unlock(&info->ekw_mutex); |
| } |
| |
| static irqreturn_t kirkwood_bc12_irq_handler(int irq, void *dev_id) |
| { |
| struct extcon_kirkwood *info = dev_id; |
| |
| queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, |
| info->debounce_jiffies); |
| |
| return IRQ_HANDLED; |
| } |
| |
| #if IS_ENABLED(CONFIG_DEBUG_FS) |
| static int kirkwood_show(struct seq_file *s, void *v) |
| { |
| struct extcon_kirkwood *info = s->private; |
| unsigned int good_bat = 0; |
| unsigned int sw_open = 0; |
| unsigned int chg_aln = 0; |
| unsigned int chg_det = 0; |
| unsigned int cable; |
| unsigned int value; |
| char * const szusbcable[] = { |
| "EXTCON_NONE", |
| "EXTCON_USB", |
| "EXTCON_USB_HOST" |
| }; |
| |
| char * const szusbval[] = { |
| "DETACH", |
| "ATTACH" |
| }; |
| |
| char * const szbc12cable[] = { |
| "BC12_NON", |
| "BC12_DCP", |
| "BC12_CDP", |
| "BC12_CDP_B", |
| "BC12_SDP", |
| "BC12_SDP_B", |
| "BC12_BRK", |
| "BC12_UNK" |
| }; |
| |
| char * const szusbrole[] = { |
| "TYPEC_NONE", |
| "TYPEC_HOST", |
| "TYPEC_DEVICE" |
| }; |
| |
| seq_puts(s, |
| "\n======Mediatek Extcon kirkwood Debug Information======\n"); |
| seq_puts(s, |
| " | BC12 | State | DP_HOST | DM_HOST | DP_CON | DM_CON\n"); |
| seq_puts(s, |
| "--+-------+-------+---------+---------+---------+-------\n"); |
| seq_puts(s, |
| " B| DCP | | Hi-Z | Hi-Z | Vdp_src | Hi-Z\n"); |
| seq_puts(s, |
| " C| CDP | USB | DP_CON | DM_CON | DP_HOST | DM_HOST\n"); |
| seq_puts(s, |
| " 1| CDP_B | | Hi-Z | Hi-Z | Vdp_src | Hi-Z\n"); |
| seq_puts(s, |
| " 2| SDP | USB | DP_CON | DM_CON | DP_HOST | DM_HOST\n"); |
| seq_puts(s, |
| " 2| SDP_B | | Hi-Z | Hi-Z | Vdp_src | Hi-Z\n"); |
| seq_puts(s, |
| " 2| BRK | | Hi-Z | Hi-Z | Hi-Z | Hi-Z\n"); |
| seq_puts(s, |
| " 2| UNK | | Hi-Z | Hi-Z | Hi-Z | Hi-Z\n"); |
| seq_puts(s, |
| "--+-------+-------+---------+---------+---------+--------\n"); |
| |
| mutex_lock(&info->ekw_mutex); |
| /* parameters */ |
| if (!info->good_bat_fixed && info->good_bat_gpiod) { |
| seq_printf(s, "[CFG] good_bat with GPIO%i property\n", |
| info->good_bat_gpio_dts); |
| seq_printf(s, " good_bat_gpiod : %p\n", |
| info->good_bat_gpiod); |
| seq_printf(s, " good_bat_irq : %i\n", |
| info->good_bat_irq); |
| seq_printf(s, " good_bat_polarity : %i\n", |
| info->good_bat_polarity); |
| } else { |
| seq_printf(s, " good_bat is fixed without battery: %i\n", |
| info->good_bat_polarity); |
| } |
| |
| if (info->sw_open_gpiod) { |
| seq_printf(s, "[CFG] sw_open with GPIO%i property\n", |
| info->sw_open_gpio_dts); |
| seq_printf(s, " sw_open_gpiod : %p\n", |
| info->sw_open_gpiod); |
| seq_printf(s, " sw_open_irq : %i\n", |
| info->sw_open_irq); |
| seq_printf(s, " sw_open_polarity : %i\n", |
| info->sw_open_polarity); |
| } |
| |
| if (info->chg_aln_gpiod) { |
| seq_printf(s, "[CFG] chg_aln with GPIO%i property\n", |
| info->chg_aln_gpio_dts); |
| seq_printf(s, " chg_aln_gpiod : %p\n", |
| info->chg_aln_gpiod); |
| seq_printf(s, " chg_aln_irq : %i\n", |
| info->chg_aln_irq); |
| seq_printf(s, " chg_aln_polarity : %i\n", |
| info->chg_aln_polarity); |
| } |
| |
| if (info->chg_det_gpiod) { |
| seq_printf(s, "[CFG] chg_aln with GPIO%i property\n", |
| info->chg_det_gpio_dts); |
| seq_printf(s, " chg_det_gpiod : %p\n", |
| info->chg_det_gpiod); |
| seq_printf(s, " chg_det_irq : %i\n", |
| info->chg_det_irq); |
| seq_printf(s, " chg_det_polarity : %i\n", |
| info->chg_det_polarity); |
| } |
| |
| if (info->debounce_jiffies) |
| seq_printf(s, "[CFG][SW]debounce_jiffies: %u ms)\n", |
| jiffies_to_msecs(info->debounce_jiffies)); |
| else |
| seq_printf(s, "[CFG][HW]debounce_jiffies: %u ms)\n", |
| USB_GPIO_DEBOUNCE_MS); |
| |
| |
| if (!info->good_bat_fixed && info->good_bat_gpiod) { |
| good_bat = gpiod_get_value_cansleep(info->good_bat_gpiod); |
| seq_printf(s, "gpio%u good_bat pin : %i\n", |
| info->good_bat_gpio_dts, good_bat); |
| } |
| |
| if (info->sw_open_gpiod) { |
| sw_open = gpiod_get_value_cansleep(info->sw_open_gpiod); |
| seq_printf(s, "gpio%u sw_open pin : %i\n", |
| info->sw_open_gpio_dts, sw_open); |
| } |
| |
| if (info->chg_aln_gpiod) { |
| chg_aln = gpiod_get_value_cansleep(info->chg_aln_gpiod); |
| seq_printf(s, "gpio%u chg_aln pin : %i\n", |
| info->chg_aln_gpio_dts, chg_aln); |
| } |
| |
| if (info->chg_det_gpiod) { |
| chg_det = gpiod_get_value_cansleep(info->chg_det_gpiod); |
| seq_printf(s, "gpio%u chg_det pin : %i\n", |
| info->chg_det_gpio_dts, chg_det); |
| } |
| |
| if (!info->good_bat_fixed && info->good_bat_gpiod) |
| good_bat = gpiod_get_value_cansleep(info->good_bat_gpiod); |
| else |
| good_bat = info->good_bat_polarity; |
| |
| seq_printf(s, "bc12_cable : 0x%08X\n", info->bc12_cable); |
| seq_printf(s, "usb_cable : 0x%08X\n", info->usb_cable); |
| seq_printf(s, "usb_data_role : %i\n", info->usb_data_role); |
| seq_printf(s, "port_data_role : %i\n", info->port_data_role); |
| seq_printf(s, "usb_hal_ver_en : %i\n", info->usb_hal_ver_en); |
| seq_printf(s, "usb_hal_ver : 0x%02X\n", info->usb_hal_ver); |
| seq_printf(s, "ban_usb_data_role : %i\n", info->ban_usb_data_role); |
| seq_printf(s, "forced_role_none : %i\n", info->forced_role_none); |
| |
| /* current result */ |
| seq_printf(s, "[FINAL ] FSA831A chg_aln: %s, sw_open: %s,", |
| (chg_aln == info->chg_aln_polarity) ? "H" : "L", |
| (sw_open == info->sw_open_polarity) ? "H" : "L"); |
| |
| seq_printf(s, " chg_det: %s, good_bat: %s\n", |
| (chg_det == info->chg_det_polarity) ? "H" : "L", |
| (good_bat == info->good_bat_polarity) ? "H" : "L"); |
| |
| cable = GET_EXTCON_BC12_CABLE(info); |
| value = GET_EXTCON_BC12_STATUS(info); |
| seq_printf(s, "[FINAL ] BC12: %s : %s (%i)\n", |
| (cable == EXTCON_CHG_USB_SDP) ? "SDP" : |
| (cable == EXTCON_CHG_USB_DCP) ? "DCP" : |
| (cable == EXTCON_CHG_USB_CDP) ? "CDP" : "UNDEFINED", |
| szbc12cable[info->bc12_role], info->bc12_role); |
| |
| if (info->usb_hal_ver_en) { |
| seq_printf(s, |
| "[FINAL ] Type-C: usb hal version: 0x%02X\n", |
| info->usb_hal_ver); |
| seq_printf(s, |
| "[FINAL ] Type-C: usb role: %s\n", |
| szusbrole[info->usb_data_role]); |
| seq_printf(s, |
| "[FINAL ] Type-C: ban usb role: %s\n", |
| szusbrole[info->ban_usb_data_role]); |
| } else { |
| seq_printf(s, |
| "[FINAL ] Type-C: usb role: %s\n", |
| szusbrole[info->usb_data_role]); |
| } |
| |
| seq_printf(s, |
| "[FINAL ] Type-C: data role: %s\n", |
| info->port_data_role == TYPEC_PORT_UFP ? "UFP" : |
| info->port_data_role == TYPEC_PORT_DFP ? "DFP" : "DRD"); |
| |
| seq_printf(s, "[CURRENT] %s Port: %s(%i), %s(%i)\n", |
| (GET_EXTCON_CABLE(info) == EXTCON_USB) ? |
| (GET_EXTCON_STATUS(info) ? "USB" : "USB/NONE") |
| : (GET_EXTCON_CABLE(info) == EXTCON_USB_HOST) ? "USB-HOST" |
| : "UNKNOWN", |
| szusbcable[GET_EXTCON_CABLE(info)], |
| GET_EXTCON_CABLE(info), |
| szusbval[GET_EXTCON_STATUS(info)], |
| GET_EXTCON_STATUS(info)); |
| |
| mutex_unlock(&info->ekw_mutex); |
| |
| return 0; |
| } |
| |
| static int kirkwood_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, kirkwood_show, inode->i_private); |
| } |
| |
| static const struct file_operations kirkwood_proc_fops = { |
| .open = kirkwood_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static int kirkwood_role_show(struct seq_file *sf, void *unused) |
| { |
| struct extcon_kirkwood *info = sf->private; |
| |
| mutex_lock(&info->ekw_mutex); |
| seq_printf(sf, "current ssusb: %s, typec role: %s, data role: %s\n", |
| (GET_EXTCON_CABLE(info) == EXTCON_USB) ? |
| (GET_EXTCON_STATUS(info) ? "device" : "none") |
| : "host", |
| (info->usb_data_role == USB_ROLE_HOST) ? "host" : |
| (info->usb_data_role == USB_ROLE_DEVICE) ? "device" : |
| "none", |
| info->port_data_role == TYPEC_PORT_DFP ? "dfp" : |
| info->port_data_role == TYPEC_PORT_UFP ? "ufp" : "drd"); |
| seq_puts(sf, "--online help:\n"); |
| seq_puts(sf, " echo -n <device/host/none,ufp/dfp/drd> > role\n"); |
| seq_puts(sf, " e.x 1: echo -n device > role\n"); |
| seq_puts(sf, " e.x 2: echo -n ufp > role\n"); |
| mutex_unlock(&info->ekw_mutex); |
| |
| return 0; |
| } |
| |
| static int kirkwood_role_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, kirkwood_role_show, inode->i_private); |
| } |
| |
| static ssize_t kirkwood_role_write(struct file *file, const char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| struct seq_file *sf = file->private_data; |
| struct extcon_kirkwood *info = sf->private; |
| char buf[16] = { 0 }; |
| |
| if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) |
| return -EFAULT; |
| |
| mutex_lock(&info->ekw_mutex); |
| if (!strncmp(buf, "host", 4)) { |
| if (info->usb_data_role != USB_ROLE_HOST) { |
| info->usb_data_role = USB_ROLE_HOST; |
| kirkwood_switch_role(info, USB_ROLE_HOST); |
| info->forced_role_none = false; |
| } else { |
| dev_info(info->dev, "HOST role already.\n"); |
| } |
| } else if (!strncmp(buf, "device", 6)) { |
| if (info->usb_data_role != USB_ROLE_DEVICE) { |
| info->usb_data_role = USB_ROLE_DEVICE; |
| kirkwood_switch_role(info, USB_ROLE_DEVICE); |
| info->forced_role_none = false; |
| } else { |
| dev_info(info->dev, "DEVICE role already.\n"); |
| } |
| } else if (!strncmp(buf, "none", 4)) { |
| if (info->usb_data_role != USB_ROLE_NONE) { |
| info->usb_data_role = USB_ROLE_NONE; |
| kirkwood_switch_role(info, USB_ROLE_NONE); |
| info->forced_role_none = true; |
| } else { |
| dev_info(info->dev, "NONE role already.\n"); |
| } |
| } else if (!strncmp(buf, "ufp", 3)) { |
| if (info->port_data_role != TYPEC_PORT_UFP) { |
| info->port_data_role = TYPEC_PORT_UFP; |
| if (info->usb_data_role != USB_ROLE_DEVICE) { |
| kirkwood_switch_role(info, |
| USB_ROLE_DEVICE); |
| info->forced_role_none = false; |
| } |
| } else { |
| dev_info(info->dev, "UFP role already.\n"); |
| } |
| } else if (!strncmp(buf, "dfp", 3)) { |
| if (info->port_data_role != TYPEC_PORT_DFP) { |
| info->port_data_role = TYPEC_PORT_DFP; |
| if (info->usb_data_role != USB_ROLE_HOST) { |
| kirkwood_switch_role(info, |
| USB_ROLE_HOST); |
| info->forced_role_none = false; |
| } |
| } else { |
| dev_info(info->dev, "DFP role already.\n"); |
| } |
| } else if (!strncmp(buf, "drd", 3)) { |
| if (info->port_data_role != TYPEC_PORT_DRD) { |
| info->port_data_role = TYPEC_PORT_DRD; |
| if (info->usb_data_role != USB_ROLE_NONE) { |
| kirkwood_switch_role(info, |
| USB_ROLE_NONE); |
| info->forced_role_none = true; |
| } |
| } else { |
| dev_info(info->dev, "DRD role already.\n"); |
| } |
| } else { |
| dev_info(info->dev, "Wrong parameter: (%s)\n", buf); |
| mutex_unlock(&info->ekw_mutex); |
| return -EINVAL; |
| } |
| mutex_unlock(&info->ekw_mutex); |
| |
| return count; |
| } |
| |
| static const struct file_operations kirkwood_role_fops = { |
| .open = kirkwood_role_open, |
| .write = kirkwood_role_write, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| int kirkwood_init_debugfs(struct extcon_kirkwood *info) |
| { |
| int ret; |
| struct dentry *file; |
| struct dentry *root; |
| |
| root = debugfs_create_dir(dev_driver_string(info->dev), NULL); |
| if (!root) { |
| ret = -ENOMEM; |
| goto err0; |
| } |
| |
| file = debugfs_create_file("kirkwood", 0444, root, |
| info, &kirkwood_proc_fops); |
| if (!file) { |
| ret = -ENOMEM; |
| goto err1; |
| } |
| |
| file = debugfs_create_file("role", 0644, root, |
| info, &kirkwood_role_fops); |
| if (!file) { |
| ret = -ENOMEM; |
| goto err1; |
| } |
| |
| info->debugfs_root = root; |
| |
| return 0; |
| |
| err1: |
| debugfs_remove_recursive(root); |
| |
| err0: |
| return ret; |
| } |
| |
| void kirkwood_exit_debugfs(struct extcon_kirkwood *info) |
| { |
| debugfs_remove_recursive(info->debugfs_root); |
| } |
| #else |
| #define kirkwood_init_debugfs(info) |
| #define kirkwood_exit_debugfs(info) |
| #endif |
| |
| static ssize_t role_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| ssize_t ret; |
| struct extcon_kirkwood *info = dev_get_drvdata(dev); |
| |
| mutex_lock(&info->ekw_mutex); |
| ret = sprintf(buf, "%s\n", |
| (GET_EXTCON_CABLE(info) == EXTCON_USB) ? |
| (GET_EXTCON_STATUS(info) ? "device" : "none") |
| : "host"); |
| mutex_unlock(&info->ekw_mutex); |
| |
| return ret; |
| } |
| |
| static ssize_t role_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct extcon_kirkwood *info = dev_get_drvdata(dev); |
| |
| mutex_lock(&info->ekw_mutex); |
| if (!strncmp(buf, "host", 4)) { |
| if (info->usb_data_role != USB_ROLE_HOST) { |
| info->usb_data_role = USB_ROLE_HOST; |
| kirkwood_switch_role(info, USB_ROLE_HOST); |
| info->forced_role_none = false; |
| } else { |
| dev_info(info->dev, "HOST role already.\n"); |
| } |
| } else if (!strncmp(buf, "device", 6)) { |
| if (info->usb_data_role != USB_ROLE_DEVICE) { |
| info->usb_data_role = USB_ROLE_DEVICE; |
| kirkwood_switch_role(info, USB_ROLE_DEVICE); |
| info->forced_role_none = false; |
| } else { |
| dev_info(info->dev, "DEVICE role already.\n"); |
| } |
| } else if (!strncmp(buf, "none", 4)) { |
| if (info->usb_data_role != USB_ROLE_NONE) { |
| info->usb_data_role = USB_ROLE_NONE; |
| kirkwood_switch_role(info, USB_ROLE_NONE); |
| /* forced NONE from sysfs */ |
| info->forced_role_none = true; |
| } else { |
| dev_info(info->dev, "NONE role already.\n"); |
| } |
| } else { |
| dev_info(info->dev, "Wrong parameter: (%s)\n", buf); |
| return -EINVAL; |
| } |
| mutex_unlock(&info->ekw_mutex); |
| |
| return count; |
| } |
| static DEVICE_ATTR_RW(role); |
| |
| static struct attribute *usb_role_attrs[] = { |
| &dev_attr_role.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group kirkwood_attr_group = { |
| .attrs = usb_role_attrs, |
| }; |
| |
| static int kirkwood_extcon_probe(struct platform_device *pdev) |
| { |
| struct usb_role_switch_desc sw_desc = { 0 }; |
| struct device *dev = &pdev->dev; |
| struct device_node *np = dev->of_node; |
| struct extcon_kirkwood *info; |
| unsigned int val; |
| int ret; |
| |
| if (!np) |
| return -EINVAL; |
| |
| info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); |
| if (!info) |
| return -ENOMEM; |
| |
| ret = sysfs_create_group(&dev->kobj, &kirkwood_attr_group); |
| if (ret) |
| return ret; |
| |
| mutex_init(&info->ekw_mutex); |
| platform_set_drvdata(pdev, info); |
| info->dev = dev; |
| |
| info->usb_data_role = USB_ROLE_NONE; |
| info->port_data_role = TYPEC_PORT_DRD; |
| info->ban_usb_data_role = USB_ROLE_NONE; |
| info->usb_hal_ver_en = |
| device_property_read_bool(dev, "android,usb-hal-ver"); |
| if (info->usb_hal_ver_en) { |
| ret = device_property_read_u32(dev, |
| "android,usb-hal-ver", |
| &info->usb_hal_ver); |
| if (ret) |
| return ret; |
| } |
| info->forced_role_none = false; |
| |
| dev_info(info->dev, |
| "usb role ban algorithm: %sable and usb_hal_ver: 0x%02X\n", |
| info->usb_hal_ver_en ? " En" : "Dis", |
| info->usb_hal_ver); |
| |
| info->edev = devm_extcon_dev_allocate(dev, usb_cables); |
| if (IS_ERR(info->edev)) { |
| dev_info(dev, "failed to allocate extcon device\n"); |
| return -ENOMEM; |
| } |
| |
| ret = devm_extcon_dev_register(dev, info->edev); |
| if (ret < 0) { |
| dev_info(dev, "failed to register extcon device\n"); |
| return ret; |
| } |
| |
| info->good_bat_polarity = 1; |
| info->sw_open_polarity = 1; |
| info->chg_aln_polarity = 1; |
| info->chg_det_polarity = 1; |
| |
| info->good_bat_fixed = |
| device_property_read_bool(dev, "fsa831a,good-bat-fixed"); |
| info->good_bat_gpiod = |
| devm_gpiod_get_optional(&pdev->dev, "good-bat", GPIOD_IN); |
| |
| info->sw_open_gpiod = |
| devm_gpiod_get_optional(&pdev->dev, "sw-open", GPIOD_IN); |
| info->chg_aln_gpiod = |
| devm_gpiod_get_optional(&pdev->dev, "chg-aln", GPIOD_IN); |
| info->chg_det_gpiod = |
| devm_gpiod_get_optional(&pdev->dev, "chg-det", GPIOD_IN); |
| |
| if (!info->good_bat_fixed && info->good_bat_gpiod) { |
| ret = of_property_read_u32(np, "goodbat_gpio_pin", &val); |
| if (ret == 0) { |
| info->good_bat_gpio_dts = val; |
| dev_info(dev, |
| "goodbat_gpio_pin gpio pin: %u\n", val); |
| } |
| |
| ret = of_property_read_u32(np, "goodbat_polarity", &val); |
| if (ret == 0) { |
| info->good_bat_polarity = val ? 1 : 0; |
| dev_info(dev, |
| "goodbat_polarity gpio pin active: %u\n", val); |
| } |
| |
| info->good_bat_irq = gpiod_to_irq(info->good_bat_gpiod); |
| if (info->good_bat_irq < 0) { |
| dev_info(dev, "failed to get goodbat IRQ\n"); |
| return info->good_bat_irq; |
| } |
| } |
| |
| if (info->sw_open_gpiod) { |
| ret = of_property_read_u32(np, "swopen-gpio-pin", &val); |
| if (ret == 0) { |
| info->sw_open_gpio_dts = val; |
| dev_info(dev, "swopen-gpio-pin pin: %u\n", val); |
| } |
| |
| ret = of_property_read_u32(np, "swopen-polarity", &val); |
| if (ret == 0) { |
| info->sw_open_polarity = val ? 1 : 0; |
| dev_info(dev, |
| "swopen-polarity gpio pin active: %u\n", val); |
| } |
| |
| info->sw_open_irq = gpiod_to_irq(info->sw_open_gpiod); |
| if (info->sw_open_irq < 0) { |
| dev_info(dev, "failed to get swopen IRQ\n"); |
| return info->sw_open_irq; |
| } |
| } |
| |
| if (info->chg_aln_gpiod) { |
| ret = of_property_read_u32(np, "chgaln-gpio-pin", &val); |
| if (ret == 0) { |
| info->chg_aln_gpio_dts = val; |
| dev_info(dev, "chgaln-gpio-pin gpio pin: %u\n", val); |
| } |
| |
| ret = of_property_read_u32(np, "chgaln-polarity", &val); |
| if (ret == 0) { |
| info->chg_aln_polarity = val ? 1 : 0; |
| dev_info(dev, |
| "chgaln-polarity gpio pin active: %u\n", val); |
| } |
| |
| info->chg_aln_irq = gpiod_to_irq(info->chg_aln_gpiod); |
| if (info->chg_aln_irq < 0) { |
| dev_info(dev, "failed to get chgaln IRQ\n"); |
| return info->chg_aln_irq; |
| } |
| } |
| |
| if (info->chg_det_gpiod) { |
| ret = of_property_read_u32(np, "chgdet-gpio-pin", &val); |
| if (ret == 0) { |
| info->chg_det_gpio_dts = val; |
| dev_info(dev, "chgdet-gpio-pin pin: %u!\n", val); |
| } |
| |
| ret = of_property_read_u32(np, "chgdet-polarity", &val); |
| if (ret == 0) { |
| info->chg_det_polarity = val ? 1 : 0; |
| dev_info(dev, |
| "chgdet-polarity gpio pin active: %u\n", val); |
| } |
| |
| info->chg_det_irq = gpiod_to_irq(info->chg_det_gpiod); |
| if (info->sw_open_irq < 0) { |
| dev_info(dev, "failed to get chgdet IRQ\n"); |
| return info->sw_open_irq; |
| } |
| } |
| |
| if (info->good_bat_fixed) { |
| if (!info->sw_open_gpiod && |
| !info->chg_aln_gpiod && |
| !info->chg_det_gpiod) { |
| dev_info(dev, "failed to get gpios\n"); |
| return -ENODEV; |
| } |
| |
| } else { |
| if (!info->good_bat_gpiod && |
| !info->sw_open_gpiod && |
| !info->chg_aln_gpiod && |
| !info->chg_det_gpiod) { |
| dev_info(dev, "failed to get gpios\n"); |
| return -ENODEV; |
| } |
| } |
| |
| if (!info->good_bat_fixed && IS_ERR(info->good_bat_gpiod)) |
| return PTR_ERR(info->good_bat_gpiod); |
| |
| if (IS_ERR(info->sw_open_gpiod)) |
| return PTR_ERR(info->sw_open_gpiod); |
| |
| if (IS_ERR(info->chg_aln_gpiod)) |
| return PTR_ERR(info->chg_aln_gpiod); |
| |
| if (IS_ERR(info->chg_det_gpiod)) |
| return PTR_ERR(info->chg_det_gpiod); |
| |
| if (!info->good_bat_fixed && info->good_bat_gpiod) |
| ret = gpiod_set_debounce(info->good_bat_gpiod, |
| USB_GPIO_DEBOUNCE_MS * 1000); |
| |
| if (!ret && info->sw_open_gpiod) |
| ret = gpiod_set_debounce(info->sw_open_gpiod, |
| USB_GPIO_DEBOUNCE_MS * 1000); |
| |
| if (!ret && info->chg_aln_gpiod) |
| ret = gpiod_set_debounce(info->chg_aln_gpiod, |
| USB_GPIO_DEBOUNCE_MS * 1000); |
| |
| if (!ret && info->chg_det_gpiod) |
| ret = gpiod_set_debounce(info->chg_det_gpiod, |
| USB_GPIO_DEBOUNCE_MS * 1000); |
| |
| if (ret < 0) |
| info->debounce_jiffies = |
| msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS); |
| |
| INIT_DELAYED_WORK(&info->wq_detcable, kirkwood_bc12_detect_cable); |
| |
| if (!info->good_bat_fixed && info->good_bat_gpiod) { |
| ret = devm_request_threaded_irq(dev, info->good_bat_irq, NULL, |
| kirkwood_bc12_irq_handler, |
| IRQF_TRIGGER_RISING | |
| IRQF_TRIGGER_FALLING | |
| IRQF_ONESHOT, |
| pdev->name, info); |
| if (ret < 0) { |
| dev_info(dev, |
| "failed to request handler for goodbat IRQ\n"); |
| return ret; |
| } |
| } |
| |
| if (info->sw_open_gpiod) { |
| ret = devm_request_threaded_irq(dev, info->sw_open_irq, NULL, |
| kirkwood_bc12_irq_handler, |
| IRQF_TRIGGER_RISING | |
| IRQF_TRIGGER_FALLING | |
| IRQF_ONESHOT, |
| pdev->name, info); |
| if (ret < 0) { |
| dev_info(dev, |
| "failed to request handler for swopen IRQ\n"); |
| return ret; |
| } |
| } |
| |
| if (info->chg_aln_gpiod) { |
| ret = devm_request_threaded_irq(dev, info->chg_aln_irq, NULL, |
| kirkwood_bc12_irq_handler, |
| IRQF_TRIGGER_RISING | |
| IRQF_TRIGGER_FALLING | |
| IRQF_ONESHOT, |
| pdev->name, info); |
| if (ret < 0) { |
| dev_info(dev, |
| "failed to request handler for chgaln IRQ\n"); |
| return ret; |
| } |
| } |
| |
| if (info->chg_det_gpiod) { |
| ret = devm_request_threaded_irq(dev, info->chg_det_irq, NULL, |
| kirkwood_bc12_irq_handler, |
| IRQF_TRIGGER_RISING | |
| IRQF_TRIGGER_FALLING | |
| IRQF_ONESHOT, |
| pdev->name, info); |
| if (ret < 0) { |
| dev_info(dev, |
| "failed to request handler for chgdet IRQ\n"); |
| return ret; |
| } |
| } |
| |
| sw_desc.driver_data = info; |
| sw_desc.fwnode = dev_fwnode(dev); |
| sw_desc.allow_userspace_control = true, |
| sw_desc.set = kirkwood_usb_set_role, |
| sw_desc.get = kirkwood_usb_get_role, |
| |
| info->usb_role_sw = usb_role_switch_register(dev, &sw_desc); |
| if (IS_ERR(info->usb_role_sw)) { |
| dev_info(dev, "failed to register role switch device\n"); |
| return PTR_ERR(info->usb_role_sw); |
| } |
| |
| ret = devm_kirkwood_psy_register(info); |
| if (ret < 0) { |
| dev_info(dev, "failed to register psy\n"); |
| return ret; |
| } |
| |
| /* Perform initial detection */ |
| kirkwood_bc12_detect_cable(&info->wq_detcable.work); |
| |
| device_set_wakeup_capable(&pdev->dev, true); |
| kirkwood_init_debugfs(info); |
| |
| /* set to DEVICE by default as factory fixture doesn't connect |
| * CC1/CC2 for a valid TypeC connection and role config. |
| */ |
| mutex_lock(&info->ekw_mutex); |
| info->usb_data_role = USB_ROLE_DEVICE; |
| kirkwood_switch_role(info, USB_ROLE_DEVICE); |
| mutex_unlock(&info->ekw_mutex); |
| |
| dev_info(dev, " successfully!\n"); |
| return 0; |
| } |
| |
| static int kirkwood_extcon_remove(struct platform_device *pdev) |
| { |
| struct extcon_kirkwood *info = platform_get_drvdata(pdev); |
| |
| kirkwood_exit_debugfs(info); |
| cancel_delayed_work_sync(&info->wq_detcable); |
| device_init_wakeup(&pdev->dev, false); |
| mutex_destroy(&info->ekw_mutex); |
| |
| return 0; |
| } |
| |
| #if IS_ENABLED(CONFIG_PM_SLEEP) |
| static int kirkwood_extcon_suspend(struct device *dev) |
| { |
| struct extcon_kirkwood *info = dev_get_drvdata(dev); |
| int ret = 0; |
| |
| if (device_may_wakeup(dev)) { |
| if (!info->good_bat_fixed && info->good_bat_gpiod) { |
| ret = enable_irq_wake(info->good_bat_irq); |
| if (ret) |
| return ret; |
| } |
| |
| if (info->sw_open_gpiod) { |
| ret = enable_irq_wake(info->sw_open_irq); |
| if (ret) { |
| if (!info->good_bat_fixed && |
| info->good_bat_gpiod) |
| disable_irq_wake(info->good_bat_irq); |
| |
| return ret; |
| } |
| } |
| |
| if (info->chg_aln_gpiod) { |
| ret = enable_irq_wake(info->chg_aln_irq); |
| if (ret) { |
| if (!info->good_bat_fixed && |
| info->good_bat_gpiod) |
| disable_irq_wake(info->good_bat_irq); |
| |
| if (info->sw_open_gpiod) |
| disable_irq_wake(info->sw_open_irq); |
| |
| return ret; |
| } |
| } |
| |
| if (info->chg_det_gpiod) { |
| ret = enable_irq_wake(info->chg_det_irq); |
| if (ret) { |
| if (!info->good_bat_fixed && |
| info->good_bat_gpiod) |
| disable_irq_wake(info->good_bat_irq); |
| |
| if (info->sw_open_gpiod) |
| disable_irq_wake(info->sw_open_irq); |
| |
| if (info->chg_aln_gpiod) |
| disable_irq_wake(info->chg_aln_irq); |
| |
| return ret; |
| } |
| } |
| } |
| |
| /* |
| * We don't want to process any IRQs after this point |
| * as GPIOs used behind I2C subsystem might not be |
| * accessible until resume completes. So disable IRQ. |
| */ |
| if (!info->good_bat_fixed && info->good_bat_gpiod) |
| disable_irq(info->good_bat_irq); |
| |
| if (info->sw_open_gpiod) |
| disable_irq(info->sw_open_irq); |
| |
| if (info->chg_aln_gpiod) |
| disable_irq(info->chg_aln_irq); |
| |
| if (info->chg_det_gpiod) |
| disable_irq(info->chg_det_irq); |
| |
| if (!device_may_wakeup(dev)) |
| pinctrl_pm_select_sleep_state(dev); |
| |
| return ret; |
| } |
| |
| static int kirkwood_extcon_resume(struct device *dev) |
| { |
| struct extcon_kirkwood *info = dev_get_drvdata(dev); |
| int ret = 0; |
| |
| if (!device_may_wakeup(dev)) |
| pinctrl_pm_select_default_state(dev); |
| |
| if (device_may_wakeup(dev)) { |
| if (!info->good_bat_fixed && info->good_bat_gpiod) { |
| ret = disable_irq_wake(info->good_bat_irq); |
| if (ret) |
| return ret; |
| } |
| |
| if (info->sw_open_gpiod) { |
| ret = disable_irq_wake(info->sw_open_irq); |
| if (ret) { |
| if (!info->good_bat_fixed && |
| info->good_bat_gpiod) |
| enable_irq_wake(info->good_bat_irq); |
| |
| return ret; |
| } |
| } |
| |
| if (info->chg_aln_gpiod) { |
| ret = disable_irq_wake(info->chg_aln_irq); |
| if (ret) { |
| if (!info->good_bat_fixed && |
| info->good_bat_gpiod) |
| enable_irq_wake(info->good_bat_irq); |
| |
| if (info->sw_open_gpiod) |
| enable_irq_wake(info->sw_open_irq); |
| |
| return ret; |
| } |
| } |
| |
| if (info->chg_det_gpiod) { |
| ret = disable_irq_wake(info->chg_det_irq); |
| if (ret) { |
| if (!info->good_bat_fixed && |
| info->good_bat_gpiod) |
| enable_irq_wake(info->good_bat_irq); |
| |
| if (info->sw_open_gpiod) |
| enable_irq_wake(info->sw_open_irq); |
| |
| if (info->chg_aln_gpiod) |
| enable_irq_wake(info->chg_aln_irq); |
| |
| return ret; |
| } |
| } |
| } |
| |
| if (!info->good_bat_fixed && info->good_bat_gpiod) |
| enable_irq(info->good_bat_irq); |
| |
| if (info->sw_open_gpiod) |
| enable_irq(info->sw_open_irq); |
| |
| if (info->chg_aln_gpiod) |
| enable_irq(info->chg_aln_irq); |
| |
| if (info->chg_det_gpiod) |
| enable_irq(info->chg_det_irq); |
| |
| queue_delayed_work(system_power_efficient_wq, |
| &info->wq_detcable, 0); |
| |
| return ret; |
| } |
| #endif |
| |
| static SIMPLE_DEV_PM_OPS(kirkwood_extcon_pm_ops, |
| kirkwood_extcon_suspend, kirkwood_extcon_resume); |
| |
| static const struct of_device_id kirkwood_extcon_dt_match[] = { |
| { .compatible = "linux,extcon-usb-kirkwood", }, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(of, kirkwood_extcon_dt_match); |
| |
| static const struct platform_device_id kirkwood_extcon_platform_ids[] = { |
| { .name = "extcon-usb-kirkwood", }, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(platform, kirkwood_extcon_platform_ids); |
| |
| static struct platform_driver kirkwood_extcon_driver = { |
| .probe = kirkwood_extcon_probe, |
| .remove = kirkwood_extcon_remove, |
| .driver = { |
| .name = "extcon-usb-kirkwood", |
| .pm = &kirkwood_extcon_pm_ops, |
| .of_match_table = kirkwood_extcon_dt_match, |
| }, |
| .id_table = kirkwood_extcon_platform_ids, |
| }; |
| |
| module_platform_driver(kirkwood_extcon_driver); |
| |
| MODULE_AUTHOR("Zhanyong Wang <zhanyong.wang@mediatek.com>"); |
| MODULE_DESCRIPTION("USB kirkwood extcon driver"); |
| MODULE_LICENSE("GPL v2"); |