| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/pci.h> |
| #include <linux/platform_device.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/usb/otg.h> |
| #include <linux/slab.h> |
| #include <linux/sched.h> |
| #include <linux/freezer.h> |
| #include <linux/delay.h> |
| #include <linux/kthread.h> |
| #include <linux/version.h> |
| |
| #include <linux/usb.h> |
| #include <linux/usb/hcd.h> |
| #include <linux/usb/gadget.h> |
| #include <linux/usb/dwc3-intel-mid.h> |
| #include <asm/intel_scu_pmic.h> |
| #include "otg.h" |
| |
| #define VERSION "2.10a" |
| |
| static int otg_id = -1; |
| static int reboot_flag = 1; |
| static int enable_usb_phy(struct dwc_otg2 *otg, bool on_off); |
| static int dwc3_intel_notify_charger_type(struct dwc_otg2 *otg, |
| enum power_supply_charger_event event); |
| static struct power_supply_cable_props cap_record; |
| |
| static int charger_detect_enable(struct dwc_otg2 *otg) |
| { |
| struct intel_dwc_otg_pdata *data; |
| |
| if (!otg || !otg->otg_data) |
| return 0; |
| |
| data = (struct intel_dwc_otg_pdata *)otg->otg_data; |
| |
| return data->charger_detect_enable; |
| } |
| |
| static int is_basin_cove(struct dwc_otg2 *otg) |
| { |
| struct intel_dwc_otg_pdata *data; |
| if (!otg || !otg->otg_data) |
| return -EINVAL; |
| |
| data = (struct intel_dwc_otg_pdata *)otg->otg_data; |
| |
| return data->pmic_type == BASIN_COVE; |
| } |
| |
| static int is_hybridvp(struct dwc_otg2 *otg) |
| { |
| struct intel_dwc_otg_pdata *data; |
| if (!otg || !otg->otg_data) |
| return -EINVAL; |
| |
| data = (struct intel_dwc_otg_pdata *)otg->otg_data; |
| |
| return data->is_hvp; |
| } |
| |
| static void usb2phy_eye_optimization(struct dwc_otg2 *otg) |
| { |
| struct usb_phy *phy; |
| |
| phy = usb_get_phy(USB_PHY_TYPE_USB2); |
| if (!phy) |
| return; |
| |
| /* Set 0x7f for better quality in eye diagram |
| * It means ZHSDRV = 0b11 and IHSTX = 0b1111*/ |
| usb_phy_io_write(phy, 0x7f, TUSB1211_VENDOR_SPECIFIC1_SET); |
| |
| usb_put_phy(phy); |
| } |
| |
| |
| /* As we use SW mode to do charger detection, need to notify HW |
| * the result SW get, charging port or not */ |
| static int dwc_otg_charger_hwdet(bool enable) |
| { |
| int retval; |
| struct usb_phy *phy; |
| struct dwc_otg2 *otg = dwc3_get_otg(); |
| |
| /* Just return if charger detection is not enabled */ |
| if (!charger_detect_enable(otg)) |
| return 0; |
| |
| phy = usb_get_phy(USB_PHY_TYPE_USB2); |
| if (!phy) |
| return -ENODEV; |
| |
| if (enable) { |
| retval = usb_phy_io_write(phy, PWCTRL_HWDETECT, |
| TUSB1211_POWER_CONTROL_SET); |
| if (retval) |
| return retval; |
| otg_dbg(otg, "set HWDETECT\n"); |
| } else { |
| retval = usb_phy_io_write(phy, PWCTRL_HWDETECT, |
| TUSB1211_POWER_CONTROL_CLR); |
| if (retval) |
| return retval; |
| otg_dbg(otg, "clear HWDETECT\n"); |
| } |
| usb_put_phy(phy); |
| |
| return 0; |
| } |
| |
| static enum power_supply_charger_cable_type |
| basin_cove_aca_check(struct dwc_otg2 *otg) |
| { |
| u8 rarbrc; |
| int ret; |
| enum power_supply_charger_cable_type type = |
| POWER_SUPPLY_CHARGER_TYPE_NONE; |
| |
| ret = intel_scu_ipc_update_register(PMIC_USBIDCTRL, |
| USBIDCTRL_ACA_DETEN_D1, |
| USBIDCTRL_ACA_DETEN_D1); |
| if (ret) |
| otg_err(otg, "Fail to enable ACA&ID detection logic\n"); |
| |
| /* Wait >66.1ms (for TCHGD_SERX_DEB) */ |
| msleep(66); |
| |
| /* Read decoded RID value */ |
| ret = intel_scu_ipc_ioread8(PMIC_USBIDSTS, &rarbrc); |
| if (ret) |
| otg_err(otg, "Fail to read decoded RID value\n"); |
| rarbrc &= USBIDSTS_ID_RARBRC_STS(3); |
| rarbrc >>= 1; |
| |
| /* If ID_RARBRC_STS==01: ACA-Dock detected |
| * If ID_RARBRC_STS==00: MHL detected |
| */ |
| if (rarbrc == 1) { |
| /* ACA-Dock */ |
| type = POWER_SUPPLY_CHARGER_TYPE_ACA_DOCK; |
| } else if (!rarbrc) { |
| /* MHL */ |
| type = POWER_SUPPLY_CHARGER_TYPE_MHL; |
| } |
| |
| ret = intel_scu_ipc_update_register(PMIC_USBIDCTRL, |
| USBIDCTRL_ACA_DETEN_D1, |
| 0); |
| if (ret) |
| otg_err(otg, "Fail to enable ACA&ID detection logic\n"); |
| |
| return type; |
| } |
| |
| static enum power_supply_charger_cable_type |
| dwc3_intel_aca_check(struct dwc_otg2 *otg) |
| { |
| return basin_cove_aca_check(otg); |
| } |
| |
| static ssize_t store_otg_id(struct device *_dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| unsigned long flags; |
| struct dwc_otg2 *otg = dwc3_get_otg(); |
| |
| if (!otg) |
| return 0; |
| if (count != 2) { |
| otg_err(otg, "return EINVAL\n"); |
| return -EINVAL; |
| } |
| |
| if (count > 0 && buf[count-1] == '\n') |
| ((char *) buf)[count-1] = 0; |
| |
| switch (buf[0]) { |
| case 'a': |
| case 'A': |
| otg_dbg(otg, "Change ID to A\n"); |
| otg->user_events |= USER_ID_A_CHANGE_EVENT; |
| spin_lock_irqsave(&otg->lock, flags); |
| dwc3_wakeup_otg_thread(otg); |
| otg_id = 0; |
| spin_unlock_irqrestore(&otg->lock, flags); |
| return count; |
| case 'b': |
| case 'B': |
| otg_dbg(otg, "Change ID to B\n"); |
| otg->user_events |= USER_ID_B_CHANGE_EVENT; |
| spin_lock_irqsave(&otg->lock, flags); |
| dwc3_wakeup_otg_thread(otg); |
| otg_id = 1; |
| spin_unlock_irqrestore(&otg->lock, flags); |
| return count; |
| default: |
| otg_err(otg, "Just support change ID to A!\n"); |
| return -EINVAL; |
| } |
| |
| return count; |
| } |
| |
| static ssize_t |
| show_otg_id(struct device *_dev, struct device_attribute *attr, char *buf) |
| { |
| char *next; |
| unsigned size, t; |
| |
| next = buf; |
| size = PAGE_SIZE; |
| |
| t = scnprintf(next, size, |
| "USB OTG ID: %s\n", |
| (otg_id ? "B" : "A") |
| ); |
| size -= t; |
| next += t; |
| |
| return PAGE_SIZE - size; |
| } |
| |
| static DEVICE_ATTR(otg_id, S_IRUGO|S_IWUSR|S_IWGRP, |
| show_otg_id, store_otg_id); |
| |
| static void set_sus_phy(struct dwc_otg2 *otg, int bit) |
| { |
| u32 data = 0; |
| |
| data = otg_read(otg, GUSB2PHYCFG0); |
| if (bit) |
| data |= GUSB2PHYCFG_SUS_PHY; |
| else |
| data &= ~GUSB2PHYCFG_SUS_PHY; |
| |
| otg_write(otg, GUSB2PHYCFG0, data); |
| |
| data = otg_read(otg, GUSB3PIPECTL0); |
| if (bit) |
| data |= GUSB3PIPECTL_SUS_EN; |
| else |
| data &= ~GUSB3PIPECTL_SUS_EN; |
| otg_write(otg, GUSB3PIPECTL0, data); |
| } |
| |
| int dwc3_intel_platform_init(struct dwc_otg2 *otg) |
| { |
| u32 gctl; |
| int retval; |
| |
| otg_info(otg, "De-assert USBRST# to enable PHY\n"); |
| retval = intel_scu_ipc_iowrite8(PMIC_USBPHYCTRL, |
| PMIC_USBPHYCTRL_D0); |
| if (retval) |
| otg_err(otg, "Fail to de-assert USBRST#\n"); |
| |
| /* Don't let phy go to suspend mode, which |
| * will cause FS/LS devices enum failed in host mode. |
| */ |
| set_sus_phy(otg, 0); |
| |
| retval = device_create_file(otg->dev, &dev_attr_otg_id); |
| if (retval < 0) { |
| otg_dbg(otg, |
| "Can't register sysfs attribute: %d\n", retval); |
| return -ENOMEM; |
| } |
| |
| otg_dbg(otg, "\n"); |
| otg_write(otg, OEVTEN, 0); |
| otg_write(otg, OCTL, 0); |
| gctl = otg_read(otg, GCTL); |
| gctl |= GCTL_PRT_CAP_DIR_OTG << GCTL_PRT_CAP_DIR_SHIFT; |
| otg_write(otg, GCTL, gctl); |
| |
| return 0; |
| } |
| |
| /* Disable auto-resume feature for USB2 PHY. This is one |
| * silicon workaround. It will cause fabric timeout error |
| * for LS case after resume from hibernation */ |
| static void disable_phy_auto_resume(struct dwc_otg2 *otg) |
| { |
| u32 data = 0; |
| |
| data = otg_read(otg, GUSB2PHYCFG0); |
| data &= ~GUSB2PHYCFG_ULPI_AUTO_RESUME; |
| otg_write(otg, GUSB2PHYCFG0, data); |
| } |
| |
| /* This function will control VUSBPHY to power gate/ungate USBPHY */ |
| static int enable_usb_phy(struct dwc_otg2 *otg, bool on_off) |
| { |
| int ret; |
| |
| if (on_off) { |
| ret = intel_scu_ipc_update_register(PMIC_VLDOCNT, |
| 0xff, PMIC_VLDOCNT_VUSBPHYEN); |
| if (ret) |
| otg_err(otg, "Fail to enable VBUSPHY\n"); |
| |
| msleep(20); |
| } else { |
| ret = intel_scu_ipc_update_register(PMIC_VLDOCNT, |
| 0x00, PMIC_VLDOCNT_VUSBPHYEN); |
| if (ret) |
| otg_err(otg, "Fail to disable VBUSPHY\n"); |
| } |
| |
| return 0; |
| } |
| |
| int basin_cove_get_id(struct dwc_otg2 *otg) |
| { |
| int ret, id = RID_UNKNOWN; |
| u8 idsts, pmic_id; |
| |
| ret = intel_scu_ipc_update_register(PMIC_USBIDCTRL, |
| USBIDCTRL_ACA_DETEN_D1 | PMIC_USBPHYCTRL_D0, |
| USBIDCTRL_ACA_DETEN_D1 | PMIC_USBPHYCTRL_D0); |
| if (ret) |
| otg_err(otg, "Fail to enable ACA&ID detection logic\n"); |
| |
| mdelay(50); |
| |
| ret = intel_scu_ipc_ioread8(PMIC_USBIDSTS, &idsts); |
| if (ret) { |
| otg_err(otg, "Fail to read id\n"); |
| return id; |
| } |
| |
| if (idsts & USBIDSTS_ID_FLOAT_STS) |
| id = RID_FLOAT; |
| else if (idsts & USBIDSTS_ID_RARBRC_STS(1)) |
| id = RID_A; |
| else if (idsts & USBIDSTS_ID_RARBRC_STS(2)) |
| id = RID_B; |
| else if (idsts & USBIDSTS_ID_RARBRC_STS(3)) |
| id = RID_C; |
| else { |
| /* PMIC A0 reports ID_GND = 0 for RID_GND but PMIC B0 reports |
| * ID_GND = 1 for RID_GND |
| */ |
| ret = intel_scu_ipc_ioread8(0x00, &pmic_id); |
| if (ret) { |
| otg_err(otg, "Fail to read PMIC ID register\n"); |
| } else if (((pmic_id & VENDOR_ID_MASK) == BASIN_COVE_PMIC_ID) && |
| ((pmic_id & PMIC_MAJOR_REV) == PMIC_A0_MAJOR_REV)) { |
| if (idsts & USBIDSTS_ID_GND) |
| id = RID_GND; |
| } else { |
| if (!(idsts & USBIDSTS_ID_GND)) |
| id = RID_GND; |
| } |
| } |
| |
| ret = intel_scu_ipc_update_register(PMIC_USBIDCTRL, |
| USBIDCTRL_ACA_DETEN_D1 | PMIC_USBPHYCTRL_D0, |
| 0); |
| if (ret) |
| otg_err(otg, "Fail to enable ACA&ID detection logic\n"); |
| |
| return id; |
| } |
| |
| int dwc3_intel_get_id(struct dwc_otg2 *otg) |
| { |
| return basin_cove_get_id(otg); |
| } |
| |
| int dwc3_intel_b_idle(struct dwc_otg2 *otg) |
| { |
| u32 gctl, tmp; |
| |
| /* Disable hibernation mode by default */ |
| gctl = otg_read(otg, GCTL); |
| gctl &= ~GCTL_GBL_HIBERNATION_EN; |
| otg_write(otg, GCTL, gctl); |
| |
| /* Reset ADP related registers */ |
| otg_write(otg, ADPCFG, 0); |
| otg_write(otg, ADPCTL, 0); |
| otg_write(otg, ADPEVTEN, 0); |
| tmp = otg_read(otg, ADPEVT); |
| otg_write(otg, ADPEVT, tmp); |
| |
| otg_write(otg, OCFG, 0); |
| otg_write(otg, OEVTEN, 0); |
| tmp = otg_read(otg, OEVT); |
| otg_write(otg, OEVT, tmp); |
| otg_write(otg, OCTL, OCTL_PERI_MODE); |
| |
| /* Force config to device mode as default */ |
| gctl = otg_read(otg, GCTL); |
| gctl &= ~GCTL_PRT_CAP_DIR; |
| gctl |= GCTL_PRT_CAP_DIR_DEV << GCTL_PRT_CAP_DIR_SHIFT; |
| otg_write(otg, GCTL, gctl); |
| |
| if (!is_hybridvp(otg)) { |
| dwc_otg_charger_hwdet(false); |
| enable_usb_phy(otg, false); |
| } |
| |
| mdelay(100); |
| |
| return 0; |
| } |
| |
| static int dwc3_intel_set_power(struct usb_phy *_otg, |
| unsigned ma) |
| { |
| unsigned long flags; |
| struct dwc_otg2 *otg = dwc3_get_otg(); |
| struct power_supply_cable_props cap; |
| struct intel_dwc_otg_pdata *data; |
| |
| data = (struct intel_dwc_otg_pdata *)otg->otg_data; |
| |
| if (otg->charging_cap.chrg_type == |
| POWER_SUPPLY_CHARGER_TYPE_USB_CDP) |
| return 0; |
| else if (otg->charging_cap.chrg_type != |
| POWER_SUPPLY_CHARGER_TYPE_USB_SDP) { |
| otg_err(otg, "%s: currently, chrg type is not SDP!\n", |
| __func__); |
| return -EINVAL; |
| } |
| |
| if (ma == OTG_DEVICE_SUSPEND) { |
| spin_lock_irqsave(&otg->lock, flags); |
| cap.chrg_type = otg->charging_cap.chrg_type; |
| cap.ma = otg->charging_cap.ma; |
| cap.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_SUSPEND; |
| spin_unlock_irqrestore(&otg->lock, flags); |
| |
| /* mA is zero mean D+/D- opened cable. |
| * If SMIP set, then notify 500mA. |
| * Otherwise, notify 0mA. |
| */ |
| if (!cap.ma) { |
| if (data->charging_compliance) { |
| cap.ma = 500; |
| cap.chrg_evt = |
| POWER_SUPPLY_CHARGER_EVENT_CONNECT; |
| } |
| /* For standard SDP, if SMIP set, then ignore suspend */ |
| } else if (data->charging_compliance) |
| return 0; |
| /* Stander SDP(cap.mA != 0) and SMIP not set. |
| * Should send 0mA with SUSPEND event |
| */ |
| else |
| cap.ma = 0; |
| |
| atomic_notifier_call_chain(&otg->usb2_phy.notifier, |
| USB_EVENT_CHARGER, &cap); |
| otg_dbg(otg, "Notify EM"); |
| otg_dbg(otg, "POWER_SUPPLY_CHARGER_EVENT_SUSPEND\n"); |
| |
| return 0; |
| } else if (ma == OTG_DEVICE_RESUME) { |
| otg_dbg(otg, "Notify EM"); |
| otg_dbg(otg, "POWER_SUPPLY_CHARGER_EVENT_CONNECT\n"); |
| dwc3_intel_notify_charger_type(otg, |
| POWER_SUPPLY_CHARGER_EVENT_CONNECT); |
| |
| return 0; |
| } |
| |
| /* For SMIP set case, only need to report 500/900mA */ |
| if (data->charging_compliance) { |
| if ((ma != OTG_USB2_500MA) && |
| (ma != OTG_USB3_900MA)) |
| return 0; |
| } |
| |
| /* Covert macro to integer number*/ |
| switch (ma) { |
| case OTG_USB2_100MA: |
| ma = 100; |
| break; |
| case OTG_USB3_150MA: |
| ma = 150; |
| break; |
| case OTG_USB2_500MA: |
| ma = 500; |
| break; |
| case OTG_USB3_900MA: |
| ma = 900; |
| break; |
| default: |
| otg_err(otg, "Device driver set invalid SDP current value!\n"); |
| return -EINVAL; |
| } |
| |
| spin_lock_irqsave(&otg->lock, flags); |
| otg->charging_cap.ma = ma; |
| spin_unlock_irqrestore(&otg->lock, flags); |
| |
| dwc3_intel_notify_charger_type(otg, |
| POWER_SUPPLY_CHARGER_EVENT_CONNECT); |
| |
| return 0; |
| } |
| |
| int dwc3_intel_enable_vbus(struct dwc_otg2 *otg, int enable) |
| { |
| if (reboot_flag) { |
| atomic_notifier_call_chain(&otg->usb2_phy.notifier, |
| USB_EVENT_VBUS, &enable); |
| reboot_flag = 0; |
| } |
| return 0; |
| } |
| |
| static int dwc3_intel_notify_charger_type(struct dwc_otg2 *otg, |
| enum power_supply_charger_event event) |
| { |
| struct power_supply_cable_props cap; |
| int ret = 0; |
| unsigned long flags; |
| |
| if (!charger_detect_enable(otg) && |
| (otg->charging_cap.chrg_type != |
| POWER_SUPPLY_CHARGER_TYPE_USB_SDP)) |
| return 0; |
| |
| if (event > POWER_SUPPLY_CHARGER_EVENT_DISCONNECT) { |
| otg_err(otg, |
| "%s: Invalid power_supply_charger_event!\n", __func__); |
| return -EINVAL; |
| } |
| |
| if ((otg->charging_cap.chrg_type == |
| POWER_SUPPLY_CHARGER_TYPE_USB_SDP) && |
| ((otg->charging_cap.ma != 0) && |
| (otg->charging_cap.ma != 100) && |
| (otg->charging_cap.ma != 150) && |
| (otg->charging_cap.ma != 500) && |
| (otg->charging_cap.ma != 900))) { |
| otg_err(otg, "%s: invalid SDP current!\n", __func__); |
| return -EINVAL; |
| } |
| |
| spin_lock_irqsave(&otg->lock, flags); |
| cap.chrg_type = otg->charging_cap.chrg_type; |
| cap.ma = otg->charging_cap.ma; |
| cap.chrg_evt = event; |
| spin_unlock_irqrestore(&otg->lock, flags); |
| |
| atomic_notifier_call_chain(&otg->usb2_phy.notifier, USB_EVENT_CHARGER, |
| &cap); |
| |
| return ret; |
| } |
| |
| static void dwc3_phy_soft_reset(struct dwc_otg2 *otg) |
| { |
| u32 val; |
| |
| val = otg_read(otg, GCTL); |
| val |= GCTL_CORESOFTRESET; |
| otg_write(otg, GCTL, val); |
| |
| val = otg_read(otg, GUSB3PIPECTL0); |
| val |= GUSB3PIPECTL_PHYSOFTRST; |
| otg_write(otg, GUSB3PIPECTL0, val); |
| |
| val = otg_read(otg, GUSB2PHYCFG0); |
| val |= GUSB2PHYCFG_PHYSOFTRST; |
| otg_write(otg, GUSB2PHYCFG0, val); |
| |
| msleep(50); |
| |
| val = otg_read(otg, GUSB3PIPECTL0); |
| val &= ~GUSB3PIPECTL_PHYSOFTRST; |
| otg_write(otg, GUSB3PIPECTL0, val); |
| |
| val = otg_read(otg, GUSB2PHYCFG0); |
| val &= ~GUSB2PHYCFG_PHYSOFTRST; |
| otg_write(otg, GUSB2PHYCFG0, val); |
| |
| msleep(100); |
| |
| val = otg_read(otg, GCTL); |
| val &= ~GCTL_CORESOFTRESET; |
| otg_write(otg, GCTL, val); |
| } |
| |
| static enum power_supply_charger_cable_type |
| dwc3_intel_get_charger_type(struct dwc_otg2 *otg) |
| { |
| int ret; |
| struct usb_phy *phy; |
| u8 val, vdat_det, chgd_serx_dm; |
| unsigned long timeout, interval; |
| enum power_supply_charger_cable_type type = |
| POWER_SUPPLY_CHARGER_TYPE_NONE; |
| |
| if (!charger_detect_enable(otg)) |
| return cap_record.chrg_type; |
| |
| phy = usb_get_phy(USB_PHY_TYPE_USB2); |
| if (!phy) { |
| otg_err(otg, "Get USB2 PHY failed\n"); |
| return POWER_SUPPLY_CHARGER_TYPE_NONE; |
| } |
| |
| /* PHY Enable: |
| * Power on PHY |
| */ |
| enable_usb_phy(otg, true); |
| dwc3_phy_soft_reset(otg); |
| |
| /* Enable ACA: |
| * Enable ACA & ID detection logic. |
| */ |
| ret = intel_scu_ipc_update_register(PMIC_USBIDCTRL, |
| USBIDCTRL_ACA_DETEN_D1 | PMIC_USBPHYCTRL_D0, |
| USBIDCTRL_ACA_DETEN_D1 | PMIC_USBPHYCTRL_D0); |
| if (ret) |
| otg_err(otg, "Fail to enable ACA&ID detection logic\n"); |
| |
| /* DCD Enable: Change OPMODE to 01 (Non-driving), |
| * TermSel to 0, & |
| * XcvrSel to 01 (enable FS xcvr) |
| */ |
| usb_phy_io_write(phy, FUNCCTRL_OPMODE(1) | FUNCCTRL_XCVRSELECT(1), |
| TUSB1211_FUNC_CTRL_SET); |
| |
| usb_phy_io_write(phy, FUNCCTRL_OPMODE(2) | FUNCCTRL_XCVRSELECT(2) |
| | FUNCCTRL_TERMSELECT, |
| TUSB1211_FUNC_CTRL_CLR); |
| |
| /*Enable SW control*/ |
| usb_phy_io_write(phy, PWCTRL_SW_CONTROL, TUSB1211_POWER_CONTROL_SET); |
| |
| /* Enable IDPSRC */ |
| usb_phy_io_write(phy, VS3_CHGD_IDP_SRC_EN, |
| TUSB1211_VENDOR_SPECIFIC3_SET); |
| |
| /* Check DCD result, use same polling parameter */ |
| timeout = jiffies + msecs_to_jiffies(DATACON_TIMEOUT); |
| interval = DATACON_INTERVAL * 1000; /* us */ |
| |
| /* DCD Check: |
| * Delay 66.5 ms. (Note: |
| * TIDP_SRC_ON + TCHGD_SERX_DEB = |
| * 347.8us + 66.1ms). |
| */ |
| usleep_range(66500, 67000); |
| |
| while (!time_after(jiffies, timeout)) { |
| /* Read DP logic level. */ |
| val = usb_phy_io_read(phy, TUSB1211_VENDOR_SPECIFIC4); |
| if (val < 0) { |
| otg_err(otg, "ULPI read error! try again\n"); |
| continue; |
| } |
| |
| if (!(val & VS4_CHGD_SERX_DP)) { |
| otg_info(otg, "Data contact detected!\n"); |
| break; |
| } |
| |
| /* Polling interval */ |
| usleep_range(interval, interval + 2000); |
| } |
| |
| /* Disable DP pullup (Idp_src) */ |
| usb_phy_io_write(phy, VS3_CHGD_IDP_SRC_EN, |
| TUSB1211_VENDOR_SPECIFIC3_CLR); |
| |
| /* ID Check: |
| * Check ID pin state. |
| */ |
| val = dwc3_intel_get_id(otg); |
| if (val != RID_FLOAT) { |
| type = dwc3_intel_aca_check(otg); |
| goto cleanup; |
| } |
| |
| /* SE1 Det Enable: |
| * Read DP/DM logic level. Note: use DEBUG |
| * because VS4 isn’t enabled in this situation. |
| */ |
| val = usb_phy_io_read(phy, TUSB1211_DEBUG); |
| if (val < 0) |
| otg_err(otg, "ULPI read error!\n"); |
| |
| val &= DEBUG_LINESTATE; |
| |
| /* If '11': SE1 detected; goto 'Cleanup'. |
| * Else: goto 'Pri Det Enable'. |
| */ |
| if (val == 3) { |
| type = POWER_SUPPLY_CHARGER_TYPE_SE1; |
| goto cleanup; |
| } |
| |
| /* Pri Det Enable: |
| * Enable VDPSRC. |
| */ |
| usb_phy_io_write(phy, PWCTRL_DP_VSRC_EN, TUSB1211_POWER_CONTROL_SET); |
| |
| /* Wait >106.1ms (40ms for BC |
| * Tvdpsrc_on, 66.1ms for TI CHGD_SERX_DEB). |
| */ |
| msleep(107); |
| |
| /* Pri Det Check: |
| * Check if DM > VDATREF. |
| */ |
| vdat_det = usb_phy_io_read(phy, TUSB1211_POWER_CONTROL); |
| if (vdat_det < 0) |
| otg_err(otg, "ULPI read error!\n"); |
| |
| vdat_det &= PWCTRL_VDAT_DET; |
| |
| /* Check if DM<VLGC */ |
| chgd_serx_dm = usb_phy_io_read(phy, TUSB1211_VENDOR_SPECIFIC4); |
| if (chgd_serx_dm < 0) |
| otg_err(otg, "ULPI read error!\n"); |
| |
| chgd_serx_dm &= VS4_CHGD_SERX_DM; |
| |
| /* If VDAT_DET==0 || CHGD_SERX_DM==1: SDP detected |
| * If VDAT_DET==1 && CHGD_SERX_DM==0: CDP/DCP |
| */ |
| if (vdat_det == 0 || chgd_serx_dm == 1) |
| type = POWER_SUPPLY_CHARGER_TYPE_USB_SDP; |
| |
| /* Disable VDPSRC. */ |
| usb_phy_io_write(phy, PWCTRL_DP_VSRC_EN, TUSB1211_POWER_CONTROL_CLR); |
| |
| /* If SDP, goto “Cleanup”. |
| * Else, goto “Sec Det Enable” |
| */ |
| if (type == POWER_SUPPLY_CHARGER_TYPE_USB_SDP) |
| goto cleanup; |
| |
| /* Sec Det Enable: |
| * delay 1ms. |
| */ |
| usleep_range(1000, 1500); |
| |
| /* Swap DP & DM */ |
| usb_phy_io_write(phy, VS1_DATAPOLARITY, TUSB1211_VENDOR_SPECIFIC1_CLR); |
| |
| /* Enable 'VDMSRC'. */ |
| usb_phy_io_write(phy, PWCTRL_DP_VSRC_EN, TUSB1211_POWER_CONTROL_SET); |
| |
| /* Wait >73ms (40ms for BC Tvdmsrc_on, 33ms for TI TVDPSRC_DEB) */ |
| msleep(80); |
| |
| /* Sec Det Check: |
| * Check if DP>VDATREF. |
| */ |
| val = usb_phy_io_read(phy, TUSB1211_POWER_CONTROL); |
| if (val < 0) |
| otg_err(otg, "ULPI read error!\n"); |
| |
| val &= PWCTRL_VDAT_DET; |
| |
| /* If VDAT_DET==0: CDP detected. |
| * If VDAT_DET==1: DCP detected. |
| */ |
| if (!val) |
| type = POWER_SUPPLY_CHARGER_TYPE_USB_CDP; |
| else |
| type = POWER_SUPPLY_CHARGER_TYPE_USB_DCP; |
| |
| /* Disable VDMSRC. */ |
| usb_phy_io_write(phy, PWCTRL_DP_VSRC_EN, TUSB1211_POWER_CONTROL_CLR); |
| |
| /* Swap DP & DM. */ |
| usb_phy_io_write(phy, VS1_DATAPOLARITY, TUSB1211_VENDOR_SPECIFIC1_SET); |
| |
| cleanup: |
| |
| /* If DCP detected, assert VDPSRC. */ |
| if (type == POWER_SUPPLY_CHARGER_TYPE_USB_DCP) |
| usb_phy_io_write(phy, PWCTRL_SW_CONTROL | PWCTRL_DP_VSRC_EN, |
| TUSB1211_POWER_CONTROL_SET); |
| |
| usb_put_phy(phy); |
| |
| switch (type) { |
| case POWER_SUPPLY_CHARGER_TYPE_ACA_DOCK: |
| case POWER_SUPPLY_CHARGER_TYPE_ACA_A: |
| case POWER_SUPPLY_CHARGER_TYPE_ACA_B: |
| case POWER_SUPPLY_CHARGER_TYPE_ACA_C: |
| case POWER_SUPPLY_CHARGER_TYPE_USB_DCP: |
| case POWER_SUPPLY_CHARGER_TYPE_USB_CDP: |
| case POWER_SUPPLY_CHARGER_TYPE_SE1: |
| dwc_otg_charger_hwdet(true); |
| break; |
| default: |
| break; |
| }; |
| |
| return type; |
| } |
| |
| static int dwc3_intel_handle_notification(struct notifier_block *nb, |
| unsigned long event, void *data) |
| { |
| int state; |
| unsigned long flags, valid_chrg_type; |
| struct dwc_otg2 *otg = dwc3_get_otg(); |
| struct power_supply_cable_props *cap; |
| |
| if (!otg) |
| return NOTIFY_BAD; |
| |
| valid_chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_SDP | |
| POWER_SUPPLY_CHARGER_TYPE_USB_CDP | |
| POWER_SUPPLY_CHARGER_TYPE_ACA_DOCK; |
| |
| spin_lock_irqsave(&otg->lock, flags); |
| switch (event) { |
| case USB_EVENT_ID: |
| otg->otg_events |= OEVT_CONN_ID_STS_CHNG_EVNT; |
| state = NOTIFY_OK; |
| break; |
| case USB_EVENT_VBUS: |
| if (*(int *)data) { |
| otg->otg_events |= OEVT_B_DEV_SES_VLD_DET_EVNT; |
| otg->otg_events &= ~OEVT_A_DEV_SESS_END_DET_EVNT; |
| } else { |
| otg->otg_events |= OEVT_A_DEV_SESS_END_DET_EVNT; |
| otg->otg_events &= ~OEVT_B_DEV_SES_VLD_DET_EVNT; |
| } |
| state = NOTIFY_OK; |
| break; |
| case USB_EVENT_CHARGER: |
| if (charger_detect_enable(otg)) { |
| state = NOTIFY_DONE; |
| goto done; |
| } |
| cap = (struct power_supply_cable_props *)data; |
| if (!(cap->chrg_type & valid_chrg_type)) { |
| otg_err(otg, "Invalid charger type!\n"); |
| state = NOTIFY_BAD; |
| } |
| if (cap->chrg_evt == POWER_SUPPLY_CHARGER_EVENT_CONNECT) { |
| otg->otg_events |= OEVT_B_DEV_SES_VLD_DET_EVNT; |
| otg->otg_events &= ~OEVT_A_DEV_SESS_END_DET_EVNT; |
| |
| cap_record.chrg_type = cap->chrg_type; |
| cap_record.ma = cap->ma; |
| cap_record.chrg_evt = cap->chrg_evt; |
| } else if (cap->chrg_evt == |
| POWER_SUPPLY_CHARGER_EVENT_DISCONNECT) { |
| otg->otg_events |= OEVT_A_DEV_SESS_END_DET_EVNT; |
| otg->otg_events &= ~OEVT_B_DEV_SES_VLD_DET_EVNT; |
| |
| cap_record.chrg_type = POWER_SUPPLY_CHARGER_TYPE_NONE; |
| cap_record.ma = 0; |
| cap_record.chrg_evt = |
| POWER_SUPPLY_CHARGER_EVENT_DISCONNECT; |
| } |
| state = NOTIFY_OK; |
| break; |
| default: |
| otg_dbg(otg, "DWC OTG Notify unknow notify message\n"); |
| state = NOTIFY_DONE; |
| } |
| |
| done: |
| dwc3_wakeup_otg_thread(otg); |
| spin_unlock_irqrestore(&otg->lock, flags); |
| |
| return state; |
| |
| } |
| |
| int dwc3_intel_prepare_start_host(struct dwc_otg2 *otg) |
| { |
| if (!is_hybridvp(otg)) { |
| enable_usb_phy(otg, true); |
| usb2phy_eye_optimization(otg); |
| disable_phy_auto_resume(otg); |
| } |
| |
| return 0; |
| } |
| |
| int dwc3_intel_prepare_start_peripheral(struct dwc_otg2 *otg) |
| { |
| if (!is_hybridvp(otg)) { |
| enable_usb_phy(otg, true); |
| usb2phy_eye_optimization(otg); |
| disable_phy_auto_resume(otg); |
| } |
| |
| return 0; |
| } |
| |
| int dwc3_intel_suspend(struct dwc_otg2 *otg) |
| { |
| struct pci_dev *pci_dev; |
| pci_power_t state = PCI_D3cold; |
| |
| if (!otg) |
| return 0; |
| |
| pci_dev = to_pci_dev(otg->dev); |
| |
| if (otg->state == DWC_STATE_B_PERIPHERAL || |
| otg->state == DWC_STATE_A_HOST) |
| state = PCI_D3hot; |
| |
| set_sus_phy(otg, 1); |
| |
| if (pci_save_state(pci_dev)) { |
| otg_err(otg, "pci_save_state failed!\n"); |
| return -EIO; |
| } |
| |
| pci_disable_device(pci_dev); |
| pci_set_power_state(pci_dev, state); |
| |
| return 0; |
| } |
| |
| int dwc3_intel_resume(struct dwc_otg2 *otg) |
| { |
| struct pci_dev *pci_dev; |
| |
| if (!otg) |
| return 0; |
| |
| pci_dev = to_pci_dev(otg->dev); |
| |
| /* From synopsys spec 12.2.11. |
| * Software cannot access memory-mapped I/O space |
| * for 10ms. Delay 5 ms here should be enough. Too |
| * long a delay causes hibernation exit failure. |
| */ |
| mdelay(5); |
| |
| pci_restore_state(pci_dev); |
| if (pci_enable_device(pci_dev) < 0) { |
| otg_err(otg, "pci_enable_device failed.\n"); |
| return -EIO; |
| } |
| |
| set_sus_phy(otg, 0); |
| |
| /* Delay 1ms waiting PHY clock debounce. |
| * Without this debounce, will met fabric error randomly. |
| **/ |
| mdelay(1); |
| |
| return 0; |
| } |
| |
| struct dwc3_otg_hw_ops dwc3_intel_otg_pdata = { |
| .mode = DWC3_DRD, |
| .bus = DWC3_PCI, |
| .get_id = dwc3_intel_get_id, |
| .b_idle = dwc3_intel_b_idle, |
| .set_power = dwc3_intel_set_power, |
| .enable_vbus = dwc3_intel_enable_vbus, |
| .platform_init = dwc3_intel_platform_init, |
| .get_charger_type = dwc3_intel_get_charger_type, |
| .otg_notifier_handler = dwc3_intel_handle_notification, |
| .prepare_start_peripheral = dwc3_intel_prepare_start_peripheral, |
| .prepare_start_host = dwc3_intel_prepare_start_host, |
| .notify_charger_type = dwc3_intel_notify_charger_type, |
| |
| .suspend = dwc3_intel_suspend, |
| .resume = dwc3_intel_resume, |
| }; |
| |
| static int __init dwc3_intel_init(void) |
| { |
| return dwc3_otg_register(&dwc3_intel_otg_pdata); |
| } |
| module_init(dwc3_intel_init); |
| |
| static void __exit dwc3_intel_exit(void) |
| { |
| dwc3_otg_unregister(&dwc3_intel_otg_pdata); |
| } |
| module_exit(dwc3_intel_exit); |
| |
| MODULE_AUTHOR("Wang Yu <yu.y.wang@intel.com>"); |
| MODULE_DESCRIPTION("DWC3 Intel OTG Driver"); |
| MODULE_LICENSE("Dual BSD/GPL"); |
| MODULE_VERSION(VERSION); |