| /* Copyright (c) 2009-2018, Linux Foundation. All rights reserved. |
| * |
| * 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. |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/device.h> |
| #include <linux/platform_device.h> |
| #include <linux/clk.h> |
| #include <linux/slab.h> |
| #include <linux/interrupt.h> |
| #include <linux/err.h> |
| #include <linux/delay.h> |
| #include <linux/io.h> |
| #include <linux/ioport.h> |
| #include <linux/gpio.h> |
| #include <linux/of_gpio.h> |
| #include <linux/of_platform.h> |
| #include <linux/uaccess.h> |
| #include <linux/debugfs.h> |
| #include <linux/seq_file.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/suspend.h> |
| #include <linux/of.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/pinctrl/consumer.h> |
| #include <linux/pm_wakeup.h> |
| #include <linux/reset.h> |
| #include <linux/extcon.h> |
| #include <soc/qcom/scm.h> |
| |
| #include <linux/usb.h> |
| #include <linux/usb/otg.h> |
| #include <linux/usb/ulpi.h> |
| #include <linux/usb/gadget.h> |
| #include <linux/usb/hcd.h> |
| #include <linux/usb/msm_hsusb.h> |
| #include <linux/usb/msm_hsusb_hw.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/regulator/driver.h> |
| #include <linux/regulator/machine.h> |
| #include <linux/qpnp/qpnp-adc.h> |
| |
| #include <linux/msm-bus.h> |
| |
| /** |
| * Requested USB votes for BUS bandwidth |
| * |
| * USB_NO_PERF_VOTE BUS Vote for inactive USB session or disconnect |
| * USB_MAX_PERF_VOTE Maximum BUS bandwidth vote |
| * USB_MIN_PERF_VOTE Minimum BUS bandwidth vote (for some hw same as NO_PERF) |
| * |
| */ |
| enum usb_bus_vote { |
| USB_NO_PERF_VOTE = 0, |
| USB_MAX_PERF_VOTE, |
| USB_MIN_PERF_VOTE, |
| }; |
| |
| /** |
| * Supported USB modes |
| * |
| * USB_PERIPHERAL Only peripheral mode is supported. |
| * USB_HOST Only host mode is supported. |
| * USB_OTG OTG mode is supported. |
| * |
| */ |
| enum usb_mode_type { |
| USB_NONE = 0, |
| USB_PERIPHERAL, |
| USB_HOST, |
| USB_OTG, |
| }; |
| |
| /** |
| * OTG control |
| * |
| * OTG_NO_CONTROL Id/VBUS notifications not required. Useful in host |
| * only configuration. |
| * OTG_PHY_CONTROL Id/VBUS notifications comes form USB PHY. |
| * OTG_PMIC_CONTROL Id/VBUS notifications comes from PMIC hardware. |
| * OTG_USER_CONTROL Id/VBUS notifcations comes from User via sysfs. |
| * |
| */ |
| enum otg_control_type { |
| OTG_NO_CONTROL = 0, |
| OTG_PHY_CONTROL, |
| OTG_PMIC_CONTROL, |
| OTG_USER_CONTROL, |
| }; |
| |
| /** |
| * PHY used in |
| * |
| * INVALID_PHY Unsupported PHY |
| * CI_PHY Chipidea PHY |
| * SNPS_PICO_PHY Synopsis Pico PHY |
| * SNPS_FEMTO_PHY Synopsis Femto PHY |
| * QUSB_ULPI_PHY |
| * |
| */ |
| enum msm_usb_phy_type { |
| INVALID_PHY = 0, |
| CI_PHY, /* not supported */ |
| SNPS_PICO_PHY, |
| SNPS_FEMTO_PHY, |
| QUSB_ULPI_PHY, |
| }; |
| |
| #define IDEV_CHG_MAX 1500 |
| #define IUNIT 100 |
| #define IDEV_HVDCP_CHG_MAX 1800 |
| |
| /** |
| * Used different VDDCX voltage values |
| */ |
| enum usb_vdd_value { |
| VDD_NONE = 0, |
| VDD_MIN, |
| VDD_MAX, |
| VDD_VAL_MAX, |
| }; |
| |
| /** |
| * struct msm_otg_platform_data - platform device data |
| * for msm_otg driver. |
| * @phy_init_seq: PHY configuration sequence values. Value of -1 is reserved as |
| * "do not overwrite default value at this address". |
| * @power_budget: VBUS power budget in mA (0 will be treated as 500mA). |
| * @mode: Supported mode (OTG/peripheral/host). |
| * @otg_control: OTG switch controlled by user/Id pin |
| * @default_mode: Default operational mode. Applicable only if |
| * OTG switch is controller by user. |
| * @pmic_id_irq: IRQ number assigned for PMIC USB ID line. |
| * @disable_reset_on_disconnect: perform USB PHY and LINK reset |
| * on USB cable disconnection. |
| * @pnoc_errata_fix: workaround needed for PNOC hardware bug that |
| * affects USB performance. |
| * @enable_lpm_on_suspend: Enable the USB core to go into Low |
| * Power Mode, when USB bus is suspended but cable |
| * is connected. |
| * @core_clk_always_on_workaround: Don't disable core_clk when |
| * USB enters LPM. |
| * @delay_lpm_on_disconnect: Use a delay before entering LPM |
| * upon USB cable disconnection. |
| * @enable_sec_phy: Use second HSPHY with USB2 core |
| * @bus_scale_table: parameters for bus bandwidth requirements |
| * @log2_itc: value of 2^(log2_itc-1) will be used as the |
| * interrupt threshold (ITC), when log2_itc is |
| * between 1 to 7. |
| * @l1_supported: enable link power management support. |
| * @dpdm_pulldown_added: Indicates whether pull down resistors are |
| * connected on data lines or not. |
| * @vddmin_gpio: dedictaed gpio in the platform that is used for |
| * pullup the D+ line in case of bus suspend with |
| * phy retention. |
| * @enable_ahb2ahb_bypass: Indicates whether enable AHB2AHB BYPASS |
| * mode with controller in device mode. |
| * @bool disable_retention_with_vdd_min: Indicates whether to enable |
| allowing VDDmin without putting PHY into retention. |
| * @bool enable_phy_id_pullup: Indicates whether phy id pullup is |
| enabled or not. |
| * @usb_id_gpio: Gpio used for USB ID detection. |
| * @hub_reset_gpio: Gpio used for hub reset. |
| * @switch_sel_gpio: Gpio used for controlling switch that |
| routing D+/D- from the USB HUB to the USB jack type B |
| for peripheral mode. |
| * @bool phy_dvdd_always_on: PHY DVDD is supplied by always on PMIC LDO. |
| * @bool emulation: Indicates whether we are running on emulation platform. |
| * @bool enable_streaming: Indicates whether streaming to be enabled by default. |
| * @bool enable_axi_prefetch: Indicates whether AXI Prefetch interface is used |
| for improving data performance. |
| * @usbeth_reset_gpio: Gpio used for external usb-to-eth reset. |
| */ |
| struct msm_otg_platform_data { |
| int *phy_init_seq; |
| int phy_init_sz; |
| unsigned int power_budget; |
| enum usb_mode_type mode; |
| enum otg_control_type otg_control; |
| enum usb_mode_type default_mode; |
| enum msm_usb_phy_type phy_type; |
| int pmic_id_irq; |
| bool disable_reset_on_disconnect; |
| bool pnoc_errata_fix; |
| bool enable_lpm_on_dev_suspend; |
| bool core_clk_always_on_workaround; |
| bool delay_lpm_on_disconnect; |
| bool dp_manual_pullup; |
| bool enable_sec_phy; |
| struct msm_bus_scale_pdata *bus_scale_table; |
| int log2_itc; |
| bool l1_supported; |
| bool dpdm_pulldown_added; |
| int vddmin_gpio; |
| bool enable_ahb2ahb_bypass; |
| bool disable_retention_with_vdd_min; |
| bool enable_phy_id_pullup; |
| int usb_id_gpio; |
| int hub_reset_gpio; |
| int usbeth_reset_gpio; |
| int switch_sel_gpio; |
| bool phy_dvdd_always_on; |
| bool emulation; |
| bool enable_streaming; |
| bool enable_axi_prefetch; |
| bool vbus_low_as_hostmode; |
| }; |
| |
| #define SDP_CHECK_DELAY_MS 10000 /* in ms */ |
| |
| #define MSM_USB_BASE (motg->regs) |
| #define MSM_USB_PHY_CSR_BASE (motg->phy_csr_regs) |
| |
| #define DRIVER_NAME "msm_otg" |
| |
| #define ULPI_IO_TIMEOUT_USEC (10 * 1000) |
| #define USB_PHY_3P3_VOL_MIN 3050000 /* uV */ |
| #define USB_PHY_3P3_VOL_MAX 3300000 /* uV */ |
| #define USB_PHY_3P3_HPM_LOAD 50000 /* uA */ |
| #define USB_PHY_3P3_LPM_LOAD 4000 /* uA */ |
| |
| #define USB_PHY_1P8_VOL_MIN 1800000 /* uV */ |
| #define USB_PHY_1P8_VOL_MAX 1800000 /* uV */ |
| #define USB_PHY_1P8_HPM_LOAD 50000 /* uA */ |
| #define USB_PHY_1P8_LPM_LOAD 4000 /* uA */ |
| |
| #define USB_DEFAULT_SYSTEM_CLOCK 80000000 /* 80 MHz */ |
| |
| #define PM_QOS_SAMPLE_SEC 2 |
| #define PM_QOS_THRESHOLD 400 |
| |
| enum msm_otg_phy_reg_mode { |
| USB_PHY_REG_OFF, |
| USB_PHY_REG_ON, |
| USB_PHY_REG_LPM_ON, |
| USB_PHY_REG_LPM_OFF, |
| USB_PHY_REG_3P3_ON, |
| USB_PHY_REG_3P3_OFF, |
| }; |
| |
| static char *override_phy_init; |
| module_param(override_phy_init, charp, 0644); |
| MODULE_PARM_DESC(override_phy_init, |
| "Override HSUSB PHY Init Settings"); |
| |
| unsigned int lpm_disconnect_thresh = 1000; |
| module_param(lpm_disconnect_thresh, uint, 0644); |
| MODULE_PARM_DESC(lpm_disconnect_thresh, |
| "Delay before entering LPM on USB disconnect"); |
| |
| /* by default debugging is enabled */ |
| static unsigned int enable_dbg_log = 1; |
| module_param(enable_dbg_log, uint, 0644); |
| MODULE_PARM_DESC(enable_dbg_log, "Debug buffer events"); |
| |
| static struct msm_otg *the_msm_otg; |
| static bool debug_bus_voting_enabled; |
| |
| static struct regulator *hsusb_3p3; |
| static struct regulator *hsusb_1p8; |
| static struct regulator *hsusb_vdd; |
| static struct regulator *vbus_otg; |
| static struct power_supply *psy; |
| |
| static int vdd_val[VDD_VAL_MAX]; |
| static u32 bus_freqs[USB_NOC_NUM_VOTE][USB_NUM_BUS_CLOCKS] /*bimc,snoc,pcnoc*/; |
| static char bus_clkname[USB_NUM_BUS_CLOCKS][20] = {"bimc_clk", "snoc_clk", |
| "pcnoc_clk"}; |
| static bool bus_clk_rate_set; |
| |
| static void dbg_inc(unsigned int *idx) |
| { |
| *idx = (*idx + 1) & (DEBUG_MAX_MSG-1); |
| } |
| |
| static void |
| msm_otg_dbg_log_event(struct usb_phy *phy, char *event, int d1, int d2) |
| { |
| struct msm_otg *motg = container_of(phy, struct msm_otg, phy); |
| unsigned long flags; |
| unsigned long long t; |
| unsigned long nanosec; |
| |
| if (!enable_dbg_log) |
| return; |
| |
| write_lock_irqsave(&motg->dbg_lock, flags); |
| t = cpu_clock(smp_processor_id()); |
| nanosec = do_div(t, 1000000000)/1000; |
| scnprintf(motg->buf[motg->dbg_idx], DEBUG_MSG_LEN, |
| "[%5lu.%06lu]: %s :%d:%d", |
| (unsigned long)t, nanosec, event, d1, d2); |
| |
| motg->dbg_idx++; |
| motg->dbg_idx = motg->dbg_idx % DEBUG_MAX_MSG; |
| write_unlock_irqrestore(&motg->dbg_lock, flags); |
| } |
| |
| static int msm_hsusb_ldo_init(struct msm_otg *motg, int init) |
| { |
| int rc = 0; |
| |
| if (init) { |
| hsusb_3p3 = devm_regulator_get(motg->phy.dev, "HSUSB_3p3"); |
| if (IS_ERR(hsusb_3p3)) { |
| dev_err(motg->phy.dev, "unable to get hsusb 3p3\n"); |
| return PTR_ERR(hsusb_3p3); |
| } |
| |
| rc = regulator_set_voltage(hsusb_3p3, USB_PHY_3P3_VOL_MIN, |
| USB_PHY_3P3_VOL_MAX); |
| if (rc) { |
| dev_err(motg->phy.dev, "unable to set voltage level for hsusb 3p3\n" |
| ); |
| return rc; |
| } |
| hsusb_1p8 = devm_regulator_get(motg->phy.dev, "HSUSB_1p8"); |
| if (IS_ERR(hsusb_1p8)) { |
| dev_err(motg->phy.dev, "unable to get hsusb 1p8\n"); |
| rc = PTR_ERR(hsusb_1p8); |
| goto put_3p3_lpm; |
| } |
| rc = regulator_set_voltage(hsusb_1p8, USB_PHY_1P8_VOL_MIN, |
| USB_PHY_1P8_VOL_MAX); |
| if (rc) { |
| dev_err(motg->phy.dev, "unable to set voltage level for hsusb 1p8\n" |
| ); |
| goto put_1p8; |
| } |
| |
| return 0; |
| } |
| |
| put_1p8: |
| regulator_set_voltage(hsusb_1p8, 0, USB_PHY_1P8_VOL_MAX); |
| put_3p3_lpm: |
| regulator_set_voltage(hsusb_3p3, 0, USB_PHY_3P3_VOL_MAX); |
| return rc; |
| } |
| |
| static int msm_hsusb_config_vddcx(int high) |
| { |
| struct msm_otg *motg = the_msm_otg; |
| int max_vol = vdd_val[VDD_MAX]; |
| int min_vol; |
| int ret; |
| |
| min_vol = vdd_val[!!high]; |
| ret = regulator_set_voltage(hsusb_vdd, min_vol, max_vol); |
| if (ret) { |
| pr_err("%s: unable to set the voltage for regulator HSUSB_VDDCX\n", |
| __func__); |
| return ret; |
| } |
| |
| pr_debug("%s: min_vol:%d max_vol:%d\n", __func__, min_vol, max_vol); |
| msm_otg_dbg_log_event(&motg->phy, "CONFIG VDDCX", min_vol, max_vol); |
| |
| return ret; |
| } |
| |
| static int msm_hsusb_ldo_enable(struct msm_otg *motg, |
| enum msm_otg_phy_reg_mode mode) |
| { |
| int ret = 0; |
| |
| if (IS_ERR(hsusb_1p8)) { |
| pr_err("%s: HSUSB_1p8 is not initialized\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (IS_ERR(hsusb_3p3)) { |
| pr_err("%s: HSUSB_3p3 is not initialized\n", __func__); |
| return -ENODEV; |
| } |
| |
| switch (mode) { |
| case USB_PHY_REG_ON: |
| ret = regulator_set_load(hsusb_1p8, USB_PHY_1P8_HPM_LOAD); |
| if (ret < 0) { |
| pr_err("%s: Unable to set HPM of the regulator HSUSB_1p8\n", |
| __func__); |
| return ret; |
| } |
| |
| ret = regulator_enable(hsusb_1p8); |
| if (ret) { |
| dev_err(motg->phy.dev, "%s: unable to enable the hsusb 1p8\n", |
| __func__); |
| regulator_set_load(hsusb_1p8, 0); |
| return ret; |
| } |
| |
| /* fall through */ |
| case USB_PHY_REG_3P3_ON: |
| ret = regulator_set_load(hsusb_3p3, USB_PHY_3P3_HPM_LOAD); |
| if (ret < 0) { |
| pr_err("%s: Unable to set HPM of the regulator HSUSB_3p3\n", |
| __func__); |
| if (mode == USB_PHY_REG_ON) { |
| regulator_set_load(hsusb_1p8, 0); |
| regulator_disable(hsusb_1p8); |
| } |
| return ret; |
| } |
| |
| ret = regulator_enable(hsusb_3p3); |
| if (ret) { |
| dev_err(motg->phy.dev, "%s: unable to enable the hsusb 3p3\n", |
| __func__); |
| regulator_set_load(hsusb_3p3, 0); |
| if (mode == USB_PHY_REG_ON) { |
| regulator_set_load(hsusb_1p8, 0); |
| regulator_disable(hsusb_1p8); |
| } |
| return ret; |
| } |
| |
| break; |
| |
| case USB_PHY_REG_OFF: |
| ret = regulator_disable(hsusb_1p8); |
| if (ret) { |
| dev_err(motg->phy.dev, "%s: unable to disable the hsusb 1p8\n", |
| __func__); |
| return ret; |
| } |
| |
| ret = regulator_set_load(hsusb_1p8, 0); |
| if (ret < 0) |
| pr_err("%s: Unable to set LPM of the regulator HSUSB_1p8\n", |
| __func__); |
| |
| /* fall through */ |
| case USB_PHY_REG_3P3_OFF: |
| ret = regulator_disable(hsusb_3p3); |
| if (ret) { |
| dev_err(motg->phy.dev, "%s: unable to disable the hsusb 3p3\n", |
| __func__); |
| return ret; |
| } |
| ret = regulator_set_load(hsusb_3p3, 0); |
| if (ret < 0) |
| pr_err("%s: Unable to set LPM of the regulator HSUSB_3p3\n", |
| __func__); |
| |
| break; |
| |
| case USB_PHY_REG_LPM_ON: |
| ret = regulator_set_load(hsusb_1p8, USB_PHY_1P8_LPM_LOAD); |
| if (ret < 0) { |
| pr_err("%s: Unable to set LPM of the regulator: HSUSB_1p8\n", |
| __func__); |
| return ret; |
| } |
| |
| ret = regulator_set_load(hsusb_3p3, USB_PHY_3P3_LPM_LOAD); |
| if (ret < 0) { |
| pr_err("%s: Unable to set LPM of the regulator: HSUSB_3p3\n", |
| __func__); |
| regulator_set_load(hsusb_1p8, USB_PHY_REG_ON); |
| return ret; |
| } |
| |
| break; |
| |
| case USB_PHY_REG_LPM_OFF: |
| ret = regulator_set_load(hsusb_1p8, USB_PHY_1P8_HPM_LOAD); |
| if (ret < 0) { |
| pr_err("%s: Unable to set HPM of the regulator: HSUSB_1p8\n", |
| __func__); |
| return ret; |
| } |
| |
| ret = regulator_set_load(hsusb_3p3, USB_PHY_3P3_HPM_LOAD); |
| if (ret < 0) { |
| pr_err("%s: Unable to set HPM of the regulator: HSUSB_3p3\n", |
| __func__); |
| regulator_set_load(hsusb_1p8, USB_PHY_REG_ON); |
| return ret; |
| } |
| |
| break; |
| |
| default: |
| pr_err("%s: Unsupported mode (%d).", __func__, mode); |
| return -ENOTSUPP; |
| } |
| |
| pr_debug("%s: USB reg mode (%d) (OFF/HPM/LPM)\n", __func__, mode); |
| msm_otg_dbg_log_event(&motg->phy, "USB REG MODE", mode, ret); |
| return ret < 0 ? ret : 0; |
| } |
| |
| static int ulpi_read(struct usb_phy *phy, u32 reg) |
| { |
| struct msm_otg *motg = container_of(phy, struct msm_otg, phy); |
| int cnt = 0; |
| |
| if (motg->pdata->emulation) |
| return 0; |
| |
| if (motg->pdata->phy_type == QUSB_ULPI_PHY && reg > 0x3F) { |
| pr_debug("%s: ULPI vendor-specific reg 0x%02x not supported\n", |
| __func__, reg); |
| return 0; |
| } |
| |
| /* initiate read operation */ |
| writel_relaxed(ULPI_RUN | ULPI_READ | ULPI_ADDR(reg), |
| USB_ULPI_VIEWPORT); |
| |
| /* wait for completion */ |
| while (cnt < ULPI_IO_TIMEOUT_USEC) { |
| if (!(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_RUN)) |
| break; |
| udelay(1); |
| cnt++; |
| } |
| |
| if (cnt >= ULPI_IO_TIMEOUT_USEC) { |
| dev_err(phy->dev, "ulpi_read: timeout %08x\n", |
| readl_relaxed(USB_ULPI_VIEWPORT)); |
| dev_err(phy->dev, "PORTSC: %08x USBCMD: %08x\n", |
| readl_relaxed(USB_PORTSC), readl_relaxed(USB_USBCMD)); |
| return -ETIMEDOUT; |
| } |
| return ULPI_DATA_READ(readl_relaxed(USB_ULPI_VIEWPORT)); |
| } |
| |
| static int ulpi_write(struct usb_phy *phy, u32 val, u32 reg) |
| { |
| struct msm_otg *motg = container_of(phy, struct msm_otg, phy); |
| int cnt = 0; |
| |
| if (motg->pdata->emulation) |
| return 0; |
| |
| if (motg->pdata->phy_type == QUSB_ULPI_PHY && reg > 0x3F) { |
| pr_debug("%s: ULPI vendor-specific reg 0x%02x not supported\n", |
| __func__, reg); |
| return 0; |
| } |
| |
| /* initiate write operation */ |
| writel_relaxed(ULPI_RUN | ULPI_WRITE | |
| ULPI_ADDR(reg) | ULPI_DATA(val), |
| USB_ULPI_VIEWPORT); |
| |
| /* wait for completion */ |
| while (cnt < ULPI_IO_TIMEOUT_USEC) { |
| if (!(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_RUN)) |
| break; |
| udelay(1); |
| cnt++; |
| } |
| |
| if (cnt >= ULPI_IO_TIMEOUT_USEC) { |
| dev_err(phy->dev, "ulpi_write: timeout\n"); |
| dev_err(phy->dev, "PORTSC: %08x USBCMD: %08x\n", |
| readl_relaxed(USB_PORTSC), readl_relaxed(USB_USBCMD)); |
| return -ETIMEDOUT; |
| } |
| return 0; |
| } |
| |
| static struct usb_phy_io_ops msm_otg_io_ops = { |
| .read = ulpi_read, |
| .write = ulpi_write, |
| }; |
| |
| static void ulpi_init(struct msm_otg *motg) |
| { |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| int aseq[10]; |
| int *seq = NULL; |
| |
| if (override_phy_init) { |
| pr_debug("%s(): HUSB PHY Init:%s\n", __func__, |
| override_phy_init); |
| get_options(override_phy_init, ARRAY_SIZE(aseq), aseq); |
| seq = &aseq[1]; |
| } else { |
| seq = pdata->phy_init_seq; |
| } |
| |
| if (!seq) |
| return; |
| |
| while (seq[0] >= 0) { |
| if (override_phy_init) |
| pr_debug("ulpi: write 0x%02x to 0x%02x\n", |
| seq[0], seq[1]); |
| |
| dev_vdbg(motg->phy.dev, "ulpi: write 0x%02x to 0x%02x\n", |
| seq[0], seq[1]); |
| msm_otg_dbg_log_event(&motg->phy, "ULPI WRITE", seq[0], seq[1]); |
| ulpi_write(&motg->phy, seq[0], seq[1]); |
| seq += 2; |
| } |
| } |
| |
| static int msm_otg_phy_clk_reset(struct msm_otg *motg) |
| { |
| int ret; |
| |
| if (!motg->phy_reset_clk) |
| return 0; |
| |
| if (motg->sleep_clk) |
| clk_disable_unprepare(motg->sleep_clk); |
| if (motg->phy_csr_clk) |
| clk_disable_unprepare(motg->phy_csr_clk); |
| |
| ret = reset_control_assert(motg->phy_reset); |
| if (ret) { |
| pr_err("phy_reset_clk assert failed %d\n", ret); |
| return ret; |
| } |
| /* |
| * As per databook, 10 usec delay is required between |
| * PHY POR assert and de-assert. |
| */ |
| usleep_range(10, 15); |
| ret = reset_control_deassert(motg->phy_reset); |
| if (ret) { |
| pr_err("phy_reset_clk de-assert failed %d\n", ret); |
| return ret; |
| } |
| /* |
| * As per databook, it takes 75 usec for PHY to stabilize |
| * after the reset. |
| */ |
| usleep_range(80, 100); |
| |
| if (motg->phy_csr_clk) |
| clk_prepare_enable(motg->phy_csr_clk); |
| if (motg->sleep_clk) |
| clk_prepare_enable(motg->sleep_clk); |
| |
| return 0; |
| } |
| |
| static int msm_otg_link_clk_reset(struct msm_otg *motg, bool assert) |
| { |
| int ret; |
| |
| if (assert) { |
| /* Using asynchronous block reset to the hardware */ |
| dev_dbg(motg->phy.dev, "block_reset ASSERT\n"); |
| clk_disable_unprepare(motg->pclk); |
| clk_disable_unprepare(motg->core_clk); |
| ret = reset_control_assert(motg->core_reset); |
| if (ret) |
| dev_err(motg->phy.dev, "usb hs_clk assert failed\n"); |
| } else { |
| dev_dbg(motg->phy.dev, "block_reset DEASSERT\n"); |
| ret = reset_control_deassert(motg->core_reset); |
| ndelay(200); |
| ret = clk_prepare_enable(motg->core_clk); |
| WARN(ret, "USB core_clk enable failed\n"); |
| ret = clk_prepare_enable(motg->pclk); |
| WARN(ret, "USB pclk enable failed\n"); |
| if (ret) |
| dev_err(motg->phy.dev, "usb hs_clk deassert failed\n"); |
| } |
| return ret; |
| } |
| |
| static int msm_otg_phy_reset(struct msm_otg *motg) |
| { |
| u32 val; |
| int ret; |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| |
| /* |
| * AHB2AHB Bypass mode shouldn't be enable before doing |
| * async clock reset. If it is enable, disable the same. |
| */ |
| val = readl_relaxed(USB_AHBMODE); |
| if (val & AHB2AHB_BYPASS) { |
| pr_err("%s(): AHB2AHB_BYPASS SET: AHBMODE:%x\n", |
| __func__, val); |
| val &= ~AHB2AHB_BYPASS_BIT_MASK; |
| writel_relaxed(val | AHB2AHB_BYPASS_CLEAR, USB_AHBMODE); |
| pr_err("%s(): AHBMODE: %x\n", __func__, |
| readl_relaxed(USB_AHBMODE)); |
| } |
| |
| ret = msm_otg_link_clk_reset(motg, 1); |
| if (ret) |
| return ret; |
| |
| msm_otg_phy_clk_reset(motg); |
| |
| /* wait for 1ms delay as suggested in HPG. */ |
| usleep_range(1000, 1200); |
| |
| ret = msm_otg_link_clk_reset(motg, 0); |
| if (ret) |
| return ret; |
| |
| if (pdata && pdata->enable_sec_phy) |
| writel_relaxed(readl_relaxed(USB_PHY_CTRL2) | (1<<16), |
| USB_PHY_CTRL2); |
| val = readl_relaxed(USB_PORTSC) & ~PORTSC_PTS_MASK; |
| writel_relaxed(val | PORTSC_PTS_ULPI, USB_PORTSC); |
| |
| dev_info(motg->phy.dev, "phy_reset: success\n"); |
| msm_otg_dbg_log_event(&motg->phy, "PHY RESET SUCCESS", |
| motg->inputs, motg->phy.otg->state); |
| return 0; |
| } |
| |
| #define LINK_RESET_TIMEOUT_USEC (250 * 1000) |
| static int msm_otg_link_reset(struct msm_otg *motg) |
| { |
| int cnt = 0; |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| |
| writel_relaxed(USBCMD_RESET, USB_USBCMD); |
| while (cnt < LINK_RESET_TIMEOUT_USEC) { |
| if (!(readl_relaxed(USB_USBCMD) & USBCMD_RESET)) |
| break; |
| udelay(1); |
| cnt++; |
| } |
| if (cnt >= LINK_RESET_TIMEOUT_USEC) |
| return -ETIMEDOUT; |
| |
| /* select ULPI phy */ |
| writel_relaxed(0x80000000, USB_PORTSC); |
| writel_relaxed(0x0, USB_AHBBURST); |
| writel_relaxed(0x08, USB_AHBMODE); |
| |
| if (pdata && pdata->enable_sec_phy) |
| writel_relaxed(readl_relaxed(USB_PHY_CTRL2) | (1<<16), |
| USB_PHY_CTRL2); |
| return 0; |
| } |
| |
| #define QUSB2PHY_PORT_POWERDOWN 0xB4 |
| #define QUSB2PHY_PORT_UTMI_CTRL2 0xC4 |
| |
| static void msm_usb_phy_reset(struct msm_otg *motg) |
| { |
| u32 val; |
| int ret, *seq; |
| |
| switch (motg->pdata->phy_type) { |
| case SNPS_PICO_PHY: |
| /* Assert USB PHY_PON */ |
| val = readl_relaxed(motg->usb_phy_ctrl_reg); |
| val &= ~PHY_POR_BIT_MASK; |
| val |= PHY_POR_ASSERT; |
| writel_relaxed(val, motg->usb_phy_ctrl_reg); |
| |
| /* wait for minimum 10 microseconds as |
| * suggested in HPG. |
| */ |
| usleep_range(10, 15); |
| |
| /* Deassert USB PHY_PON */ |
| val = readl_relaxed(motg->usb_phy_ctrl_reg); |
| val &= ~PHY_POR_BIT_MASK; |
| val |= PHY_POR_DEASSERT; |
| writel_relaxed(val, motg->usb_phy_ctrl_reg); |
| break; |
| case QUSB_ULPI_PHY: |
| ret = reset_control_assert(motg->phy_reset); |
| if (ret) { |
| pr_err("phy_reset_clk assert failed %d\n", ret); |
| break; |
| } |
| |
| /* need to delay 10us for PHY to reset */ |
| usleep_range(10, 20); |
| |
| ret = reset_control_deassert(motg->phy_reset); |
| if (ret) { |
| pr_err("phy_reset_clk de-assert failed %d\n", ret); |
| break; |
| } |
| |
| /* Ensure that RESET operation is completed. */ |
| mb(); |
| |
| writel_relaxed(0x23, |
| motg->phy_csr_regs + QUSB2PHY_PORT_POWERDOWN); |
| writel_relaxed(0x0, |
| motg->phy_csr_regs + QUSB2PHY_PORT_UTMI_CTRL2); |
| |
| /* Program tuning parameters for PHY */ |
| seq = motg->pdata->phy_init_seq; |
| if (seq) { |
| while (seq[0] >= 0) { |
| writel_relaxed(seq[1], |
| motg->phy_csr_regs + seq[0]); |
| seq += 2; |
| } |
| } |
| |
| /* ensure above writes are completed before re-enabling PHY */ |
| wmb(); |
| writel_relaxed(0x22, |
| motg->phy_csr_regs + QUSB2PHY_PORT_POWERDOWN); |
| break; |
| case SNPS_FEMTO_PHY: |
| if (!motg->phy_por_clk) { |
| pr_err("phy_por_clk missing\n"); |
| break; |
| } |
| ret = reset_control_assert(motg->phy_por_reset); |
| if (ret) { |
| pr_err("phy_por_clk assert failed %d\n", ret); |
| break; |
| } |
| /* |
| * The Femto PHY is POR reset in the following scenarios. |
| * |
| * 1. After overriding the parameter registers. |
| * 2. Low power mode exit from PHY retention. |
| * |
| * Ensure that SIDDQ is cleared before bringing the PHY |
| * out of reset. |
| * |
| */ |
| |
| val = readb_relaxed(USB_PHY_CSR_PHY_CTRL_COMMON0); |
| val &= ~SIDDQ; |
| writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL_COMMON0); |
| |
| /* |
| * As per databook, 10 usec delay is required between |
| * PHY POR assert and de-assert. |
| */ |
| usleep_range(10, 20); |
| ret = reset_control_deassert(motg->phy_por_reset); |
| if (ret) { |
| pr_err("phy_por_clk de-assert failed %d\n", ret); |
| break; |
| } |
| /* |
| * As per databook, it takes 75 usec for PHY to stabilize |
| * after the reset. |
| */ |
| usleep_range(80, 100); |
| break; |
| default: |
| break; |
| } |
| /* Ensure that RESET operation is completed. */ |
| mb(); |
| } |
| |
| static int msm_otg_reset(struct usb_phy *phy) |
| { |
| struct msm_otg *motg = container_of(phy, struct msm_otg, phy); |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| int ret; |
| u32 val = 0; |
| u32 ulpi_val = 0; |
| |
| msm_otg_dbg_log_event(&motg->phy, "USB RESET", phy->otg->state, |
| get_pm_runtime_counter(phy->dev)); |
| /* |
| * USB PHY and Link reset also reset the USB BAM. |
| * Thus perform reset operation only once to avoid |
| * USB BAM reset on other cases e.g. USB cable disconnections. |
| * If hardware reported error then it must be reset for recovery. |
| */ |
| if (motg->err_event_seen) |
| dev_info(phy->dev, "performing USB h/w reset for recovery\n"); |
| else if (pdata->disable_reset_on_disconnect && motg->reset_counter) |
| return 0; |
| |
| motg->reset_counter++; |
| |
| disable_irq(motg->irq); |
| if (motg->phy_irq) |
| disable_irq(motg->phy_irq); |
| |
| ret = msm_otg_phy_reset(motg); |
| if (ret) { |
| dev_err(phy->dev, "phy_reset failed\n"); |
| if (motg->phy_irq) |
| enable_irq(motg->phy_irq); |
| |
| enable_irq(motg->irq); |
| return ret; |
| } |
| |
| if (motg->phy_irq) |
| enable_irq(motg->phy_irq); |
| |
| enable_irq(motg->irq); |
| ret = msm_otg_link_reset(motg); |
| if (ret) { |
| dev_err(phy->dev, "link reset failed\n"); |
| return ret; |
| } |
| |
| msleep(100); |
| |
| /* Reset USB PHY after performing USB Link RESET */ |
| msm_usb_phy_reset(motg); |
| |
| /* Program USB PHY Override registers. */ |
| ulpi_init(motg); |
| |
| /* |
| * It is required to reset USB PHY after programming |
| * the USB PHY Override registers to get the new |
| * values into effect. |
| */ |
| msm_usb_phy_reset(motg); |
| |
| if (pdata->otg_control == OTG_PHY_CONTROL) { |
| val = readl_relaxed(USB_OTGSC); |
| if (pdata->mode == USB_OTG) { |
| ulpi_val = ULPI_INT_IDGRD | ULPI_INT_SESS_VALID; |
| val |= OTGSC_IDIE | OTGSC_BSVIE; |
| } else if (pdata->mode == USB_PERIPHERAL) { |
| ulpi_val = ULPI_INT_SESS_VALID; |
| val |= OTGSC_BSVIE; |
| } |
| writel_relaxed(val, USB_OTGSC); |
| ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_RISE); |
| ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_FALL); |
| } else if (pdata->otg_control == OTG_PMIC_CONTROL) { |
| ulpi_write(phy, OTG_COMP_DISABLE, |
| ULPI_SET(ULPI_PWR_CLK_MNG_REG)); |
| if (motg->phy_irq) |
| writeb_relaxed(USB_PHY_ID_MASK, |
| USB2_PHY_USB_PHY_INTERRUPT_MASK1); |
| } |
| |
| if (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED) |
| writel_relaxed(readl_relaxed(USB_OTGSC) & ~(OTGSC_IDPU), |
| USB_OTGSC); |
| |
| msm_otg_dbg_log_event(&motg->phy, "USB RESET DONE", phy->otg->state, |
| get_pm_runtime_counter(phy->dev)); |
| |
| if (pdata->enable_axi_prefetch) |
| writel_relaxed(readl_relaxed(USB_HS_APF_CTRL) | (APF_CTRL_EN), |
| USB_HS_APF_CTRL); |
| |
| /* |
| * Disable USB BAM as block reset resets USB BAM registers. |
| */ |
| msm_usb_bam_enable(CI_CTRL, false); |
| |
| return 0; |
| } |
| |
| static void msm_otg_kick_sm_work(struct msm_otg *motg) |
| { |
| if (atomic_read(&motg->in_lpm)) |
| motg->resume_pending = true; |
| |
| /* For device mode, resume now. Let pm_resume handle other cases */ |
| if (atomic_read(&motg->pm_suspended) && |
| motg->phy.otg->state != OTG_STATE_B_SUSPEND) { |
| motg->sm_work_pending = true; |
| } else if (!motg->sm_work_pending) { |
| /* process event only if previous one is not pending */ |
| queue_work(motg->otg_wq, &motg->sm_work); |
| } |
| } |
| |
| /* |
| * UDC calls usb_phy_set_suspend() to notify during bus suspend/resume. |
| * Update relevant state-machine inputs and queue sm_work. |
| * LPM enter/exit doesn't happen directly from this routine. |
| */ |
| |
| static int msm_otg_set_suspend(struct usb_phy *phy, int suspend) |
| { |
| struct msm_otg *motg = container_of(phy, struct msm_otg, phy); |
| |
| pr_debug("%s(%d) in %s state\n", __func__, suspend, |
| usb_otg_state_string(phy->otg->state)); |
| msm_otg_dbg_log_event(phy, "SET SUSPEND", suspend, phy->otg->state); |
| |
| if (!(motg->caps & ALLOW_LPM_ON_DEV_SUSPEND)) |
| return 0; |
| |
| if (suspend) { |
| /* called in suspend interrupt context */ |
| pr_debug("peripheral bus suspend\n"); |
| msm_otg_dbg_log_event(phy, "PERIPHERAL BUS SUSPEND", |
| motg->inputs, phy->otg->state); |
| |
| set_bit(A_BUS_SUSPEND, &motg->inputs); |
| } else { |
| /* host resume or remote-wakeup */ |
| pr_debug("peripheral bus resume\n"); |
| msm_otg_dbg_log_event(phy, "PERIPHERAL BUS RESUME", |
| motg->inputs, phy->otg->state); |
| |
| clear_bit(A_BUS_SUSPEND, &motg->inputs); |
| } |
| /* use kick_sm_work to handle race with pm_resume */ |
| msm_otg_kick_sm_work(motg); |
| |
| return 0; |
| } |
| |
| static int msm_otg_bus_freq_set(struct msm_otg *motg, enum usb_noc_mode mode) |
| { |
| int i, ret; |
| long rate; |
| |
| for (i = 0; i < USB_NUM_BUS_CLOCKS; i++) { |
| rate = bus_freqs[mode][i]; |
| if (!rate) { |
| pr_debug("%s rate not available\n", bus_clkname[i]); |
| continue; |
| } |
| |
| ret = clk_set_rate(motg->bus_clks[i], rate); |
| if (ret) { |
| pr_err("%s set rate failed: %d\n", bus_clkname[i], ret); |
| return ret; |
| } |
| pr_debug("%s set to %lu Hz\n", bus_clkname[i], |
| clk_get_rate(motg->bus_clks[i])); |
| msm_otg_dbg_log_event(&motg->phy, "OTG BUS FREQ SET", i, rate); |
| } |
| |
| bus_clk_rate_set = true; |
| |
| return 0; |
| } |
| |
| static int msm_otg_bus_freq_get(struct msm_otg *motg) |
| { |
| struct device *dev = motg->phy.dev; |
| struct device_node *np = dev->of_node; |
| int len = 0, i, count = USB_NUM_BUS_CLOCKS; |
| |
| if (!np) |
| return -EINVAL; |
| |
| /* SVS requires extra set of frequencies for perf_mode sysfs node */ |
| if (motg->default_noc_mode == USB_NOC_SVS_VOTE) |
| count *= 2; |
| |
| len = of_property_count_elems_of_size(np, "qcom,bus-clk-rate", |
| sizeof(len)); |
| if (!len || (len != count)) { |
| pr_err("Invalid bus rate:%d %u\n", len, motg->default_noc_mode); |
| return -EINVAL; |
| } |
| of_property_read_u32_array(np, "qcom,bus-clk-rate", bus_freqs[0], |
| count); |
| for (i = 0; i < USB_NUM_BUS_CLOCKS; i++) { |
| if (bus_freqs[0][i] == 0) { |
| motg->bus_clks[i] = NULL; |
| pr_debug("%s not available\n", bus_clkname[i]); |
| continue; |
| } |
| |
| motg->bus_clks[i] = devm_clk_get(dev, bus_clkname[i]); |
| if (IS_ERR(motg->bus_clks[i])) { |
| pr_err("%s get failed\n", bus_clkname[i]); |
| return PTR_ERR(motg->bus_clks[i]); |
| } |
| } |
| return 0; |
| } |
| |
| static void msm_otg_bus_clks_enable(struct msm_otg *motg) |
| { |
| int i; |
| int ret; |
| |
| if (!bus_clk_rate_set || motg->bus_clks_enabled) |
| return; |
| |
| for (i = 0; i < USB_NUM_BUS_CLOCKS; i++) { |
| if (motg->bus_clks[i] == NULL) |
| continue; |
| ret = clk_prepare_enable(motg->bus_clks[i]); |
| if (ret) { |
| pr_err("%s enable rate failed: %d\n", bus_clkname[i], |
| ret); |
| goto err_clk_en; |
| } |
| } |
| motg->bus_clks_enabled = true; |
| return; |
| err_clk_en: |
| for (--i; i >= 0; --i) { |
| if (motg->bus_clks[i] != NULL) |
| clk_disable_unprepare(motg->bus_clks[i]); |
| } |
| } |
| |
| static void msm_otg_bus_clks_disable(struct msm_otg *motg) |
| { |
| int i; |
| |
| if (!bus_clk_rate_set || !motg->bus_clks_enabled) |
| return; |
| |
| for (i = 0; i < USB_NUM_BUS_CLOCKS; i++) { |
| if (motg->bus_clks[i] != NULL) |
| clk_disable_unprepare(motg->bus_clks[i]); |
| } |
| motg->bus_clks_enabled = false; |
| } |
| |
| static void msm_otg_bus_vote(struct msm_otg *motg, enum usb_bus_vote vote) |
| { |
| int ret; |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| |
| msm_otg_dbg_log_event(&motg->phy, "BUS VOTE", vote, |
| motg->phy.otg->state); |
| /* Check if target allows min_vote to be same as no_vote */ |
| if (pdata->bus_scale_table && |
| vote >= pdata->bus_scale_table->num_usecases) |
| vote = USB_NO_PERF_VOTE; |
| |
| if (motg->bus_perf_client) { |
| ret = msm_bus_scale_client_update_request( |
| motg->bus_perf_client, vote); |
| if (ret) |
| dev_err(motg->phy.dev, "%s: Failed to vote (%d)\n" |
| "for bus bw %d\n", __func__, vote, ret); |
| } |
| |
| if (vote == USB_MAX_PERF_VOTE) |
| msm_otg_bus_clks_enable(motg); |
| else |
| msm_otg_bus_clks_disable(motg); |
| } |
| |
| static void msm_otg_enable_phy_hv_int(struct msm_otg *motg) |
| { |
| bool bsv_id_hv_int = false; |
| bool dp_dm_hv_int = false; |
| u32 val; |
| |
| if (motg->pdata->otg_control == OTG_PHY_CONTROL || |
| motg->phy_irq) |
| bsv_id_hv_int = true; |
| if (motg->host_bus_suspend || motg->device_bus_suspend) |
| dp_dm_hv_int = true; |
| |
| if (!bsv_id_hv_int && !dp_dm_hv_int) |
| return; |
| |
| switch (motg->pdata->phy_type) { |
| case SNPS_PICO_PHY: |
| val = readl_relaxed(motg->usb_phy_ctrl_reg); |
| if (bsv_id_hv_int) |
| val |= (PHY_IDHV_INTEN | PHY_OTGSESSVLDHV_INTEN); |
| if (dp_dm_hv_int) |
| val |= PHY_CLAMP_DPDMSE_EN; |
| writel_relaxed(val, motg->usb_phy_ctrl_reg); |
| break; |
| case SNPS_FEMTO_PHY: |
| if (bsv_id_hv_int) { |
| val = readb_relaxed(USB_PHY_CSR_PHY_CTRL1); |
| val |= ID_HV_CLAMP_EN_N; |
| writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL1); |
| } |
| |
| if (dp_dm_hv_int) { |
| val = readb_relaxed(USB_PHY_CSR_PHY_CTRL3); |
| val |= CLAMP_MPM_DPSE_DMSE_EN_N; |
| writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL3); |
| } |
| break; |
| default: |
| break; |
| } |
| pr_debug("%s: bsv_id_hv = %d dp_dm_hv_int = %d\n", |
| __func__, bsv_id_hv_int, dp_dm_hv_int); |
| msm_otg_dbg_log_event(&motg->phy, "PHY HV INTR ENABLED", |
| bsv_id_hv_int, dp_dm_hv_int); |
| } |
| |
| static void msm_otg_disable_phy_hv_int(struct msm_otg *motg) |
| { |
| bool bsv_id_hv_int = false; |
| bool dp_dm_hv_int = false; |
| u32 val; |
| |
| if (motg->pdata->otg_control == OTG_PHY_CONTROL || |
| motg->phy_irq) |
| bsv_id_hv_int = true; |
| if (motg->host_bus_suspend || motg->device_bus_suspend) |
| dp_dm_hv_int = true; |
| |
| if (!bsv_id_hv_int && !dp_dm_hv_int) |
| return; |
| |
| switch (motg->pdata->phy_type) { |
| case SNPS_PICO_PHY: |
| val = readl_relaxed(motg->usb_phy_ctrl_reg); |
| if (bsv_id_hv_int) |
| val &= ~(PHY_IDHV_INTEN | PHY_OTGSESSVLDHV_INTEN); |
| if (dp_dm_hv_int) |
| val &= ~PHY_CLAMP_DPDMSE_EN; |
| writel_relaxed(val, motg->usb_phy_ctrl_reg); |
| break; |
| case SNPS_FEMTO_PHY: |
| if (bsv_id_hv_int) { |
| val = readb_relaxed(USB_PHY_CSR_PHY_CTRL1); |
| val &= ~ID_HV_CLAMP_EN_N; |
| writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL1); |
| } |
| |
| if (dp_dm_hv_int) { |
| val = readb_relaxed(USB_PHY_CSR_PHY_CTRL3); |
| val &= ~CLAMP_MPM_DPSE_DMSE_EN_N; |
| writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL3); |
| } |
| break; |
| default: |
| break; |
| } |
| pr_debug("%s: bsv_id_hv = %d dp_dm_hv_int = %d\n", |
| __func__, bsv_id_hv_int, dp_dm_hv_int); |
| msm_otg_dbg_log_event(&motg->phy, "PHY HV INTR DISABLED", |
| bsv_id_hv_int, dp_dm_hv_int); |
| } |
| |
| static void msm_otg_enter_phy_retention(struct msm_otg *motg) |
| { |
| u32 val; |
| |
| switch (motg->pdata->phy_type) { |
| case SNPS_PICO_PHY: |
| val = readl_relaxed(motg->usb_phy_ctrl_reg); |
| val &= ~PHY_RETEN; |
| writel_relaxed(val, motg->usb_phy_ctrl_reg); |
| break; |
| case SNPS_FEMTO_PHY: |
| /* Retention is supported via SIDDQ */ |
| val = readb_relaxed(USB_PHY_CSR_PHY_CTRL_COMMON0); |
| val |= SIDDQ; |
| writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL_COMMON0); |
| break; |
| default: |
| break; |
| } |
| pr_debug("USB PHY is in retention\n"); |
| msm_otg_dbg_log_event(&motg->phy, "USB PHY ENTER RETENTION", |
| motg->pdata->phy_type, 0); |
| } |
| |
| static void msm_otg_exit_phy_retention(struct msm_otg *motg) |
| { |
| int val; |
| |
| switch (motg->pdata->phy_type) { |
| case SNPS_PICO_PHY: |
| val = readl_relaxed(motg->usb_phy_ctrl_reg); |
| val |= PHY_RETEN; |
| writel_relaxed(val, motg->usb_phy_ctrl_reg); |
| break; |
| case SNPS_FEMTO_PHY: |
| /* |
| * It is required to do USB block reset to bring Femto PHY out |
| * of retention. |
| */ |
| msm_otg_reset(&motg->phy); |
| break; |
| default: |
| break; |
| } |
| pr_debug("USB PHY is exited from retention\n"); |
| msm_otg_dbg_log_event(&motg->phy, "USB PHY EXIT RETENTION", |
| motg->pdata->phy_type, 0); |
| } |
| |
| static void msm_id_status_w(struct work_struct *w); |
| static irqreturn_t msm_otg_phy_irq_handler(int irq, void *data) |
| { |
| struct msm_otg *motg = data; |
| |
| msm_otg_dbg_log_event(&motg->phy, "PHY ID IRQ", |
| atomic_read(&motg->in_lpm), motg->phy.otg->state); |
| if (atomic_read(&motg->in_lpm)) { |
| pr_debug("PHY ID IRQ in LPM\n"); |
| motg->phy_irq_pending = true; |
| msm_otg_kick_sm_work(motg); |
| } else { |
| pr_debug("PHY ID IRQ outside LPM\n"); |
| msm_id_status_w(&motg->id_status_work.work); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| #define PHY_SUSPEND_TIMEOUT_USEC (5 * 1000) |
| #define PHY_DEVICE_BUS_SUSPEND_TIMEOUT_USEC 100 |
| #define PHY_RESUME_TIMEOUT_USEC (100 * 1000) |
| |
| #define PHY_SUSPEND_RETRIES_MAX 3 |
| |
| static void msm_otg_set_vbus_state(int online); |
| static void msm_otg_perf_vote_update(struct msm_otg *motg, bool perf_mode); |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int msm_otg_suspend(struct msm_otg *motg) |
| { |
| struct usb_phy *phy = &motg->phy; |
| struct usb_bus *bus = phy->otg->host; |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| int cnt; |
| bool host_bus_suspend, device_bus_suspend, sm_work_busy; |
| u32 cmd_val; |
| u32 portsc, config2; |
| u32 func_ctrl; |
| int phcd_retry_cnt = 0, ret; |
| unsigned int phy_suspend_timeout; |
| |
| cnt = 0; |
| msm_otg_dbg_log_event(phy, "LPM ENTER START", |
| motg->inputs, phy->otg->state); |
| |
| if (atomic_read(&motg->in_lpm)) |
| return 0; |
| |
| cancel_delayed_work_sync(&motg->perf_vote_work); |
| |
| disable_irq(motg->irq); |
| if (motg->phy_irq) |
| disable_irq(motg->phy_irq); |
| lpm_start: |
| host_bus_suspend = phy->otg->host && !test_bit(ID, &motg->inputs); |
| device_bus_suspend = phy->otg->gadget && test_bit(ID, &motg->inputs) && |
| test_bit(A_BUS_SUSPEND, &motg->inputs) && |
| motg->caps & ALLOW_LPM_ON_DEV_SUSPEND; |
| |
| if (host_bus_suspend) |
| msm_otg_perf_vote_update(motg, false); |
| |
| /* !BSV, but its handling is in progress by otg sm_work */ |
| sm_work_busy = !test_bit(B_SESS_VLD, &motg->inputs) && |
| phy->otg->state == OTG_STATE_B_PERIPHERAL; |
| |
| /* Perform block reset to recover from UDC error events on disconnect */ |
| if (motg->err_event_seen) |
| msm_otg_reset(phy); |
| |
| /* Enable line state difference wakeup fix for only device and host |
| * bus suspend scenarios. Otherwise PHY can not be suspended when |
| * a charger that pulls DP/DM high is connected. |
| */ |
| config2 = readl_relaxed(USB_GENCONFIG_2); |
| if (device_bus_suspend) |
| config2 |= GENCONFIG_2_LINESTATE_DIFF_WAKEUP_EN; |
| else |
| config2 &= ~GENCONFIG_2_LINESTATE_DIFF_WAKEUP_EN; |
| writel_relaxed(config2, USB_GENCONFIG_2); |
| |
| /* |
| * Abort suspend when, |
| * 1. host mode activation in progress due to Micro-A cable insertion |
| * 2. !BSV, but its handling is in progress by otg sm_work |
| * Don't abort suspend in case of dcp detected by PMIC |
| */ |
| |
| if ((test_bit(B_SESS_VLD, &motg->inputs) && !device_bus_suspend) || |
| sm_work_busy) { |
| msm_otg_dbg_log_event(phy, "LPM ENTER ABORTED", |
| motg->inputs, 0); |
| enable_irq(motg->irq); |
| if (motg->phy_irq) |
| enable_irq(motg->phy_irq); |
| return -EBUSY; |
| } |
| |
| if (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED) { |
| /* put the controller in non-driving mode */ |
| func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL); |
| func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; |
| func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; |
| ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL); |
| ulpi_write(phy, ULPI_IFC_CTRL_AUTORESUME, |
| ULPI_CLR(ULPI_IFC_CTRL)); |
| } |
| |
| /* |
| * PHY suspend sequence as mentioned in the databook. |
| * |
| * Device bus suspend: The controller may abort PHY suspend if |
| * there is an incoming reset or resume from the host. If PHCD |
| * is not set within 100 usec. Abort the LPM sequence. |
| * |
| * Host bus suspend: If the peripheral is attached, PHY is already |
| * put into suspend along with the peripheral bus suspend. poll for |
| * PHCD upto 5 msec. If the peripheral is not attached i.e entering |
| * LPM with Micro-A cable, set the PHCD and poll for it for 5 msec. |
| * |
| * No cable connected: Set the PHCD to suspend the PHY. Poll for PHCD |
| * upto 5 msec. |
| * |
| * The controller aborts PHY suspend only in device bus suspend case. |
| * In other cases, it is observed that PHCD may not get set within |
| * the timeout. If so, set the PHCD again and poll for it before |
| * reset recovery. |
| */ |
| |
| phcd_retry: |
| if (device_bus_suspend) |
| phy_suspend_timeout = PHY_DEVICE_BUS_SUSPEND_TIMEOUT_USEC; |
| else |
| phy_suspend_timeout = PHY_SUSPEND_TIMEOUT_USEC; |
| |
| cnt = 0; |
| portsc = readl_relaxed(USB_PORTSC); |
| if (!(portsc & PORTSC_PHCD)) { |
| writel_relaxed(portsc | PORTSC_PHCD, |
| USB_PORTSC); |
| while (cnt < phy_suspend_timeout) { |
| if (readl_relaxed(USB_PORTSC) & PORTSC_PHCD) |
| break; |
| udelay(1); |
| cnt++; |
| } |
| } |
| |
| if (cnt >= phy_suspend_timeout) { |
| if (phcd_retry_cnt > PHY_SUSPEND_RETRIES_MAX) { |
| msm_otg_dbg_log_event(phy, "PHY SUSPEND FAILED", |
| phcd_retry_cnt, phy->otg->state); |
| dev_err(phy->dev, "PHY suspend failed\n"); |
| ret = -EBUSY; |
| goto phy_suspend_fail; |
| } |
| |
| if (device_bus_suspend) { |
| dev_dbg(phy->dev, "PHY suspend aborted\n"); |
| ret = -EBUSY; |
| goto phy_suspend_fail; |
| } else { |
| if (phcd_retry_cnt++ < PHY_SUSPEND_RETRIES_MAX) { |
| dev_dbg(phy->dev, "PHY suspend retry\n"); |
| goto phcd_retry; |
| } else { |
| dev_err(phy->dev, "reset attempt during PHY suspend\n"); |
| phcd_retry_cnt++; |
| motg->reset_counter = 0; |
| msm_otg_reset(phy); |
| goto lpm_start; |
| } |
| } |
| } |
| |
| /* |
| * PHY has capability to generate interrupt asynchronously in low |
| * power mode (LPM). This interrupt is level triggered. So USB IRQ |
| * line must be disabled till async interrupt enable bit is cleared |
| * in USBCMD register. Assert STP (ULPI interface STOP signal) to |
| * block data communication from PHY. |
| * |
| * PHY retention mode is disallowed while entering to LPM with wall |
| * charger connected. But PHY is put into suspend mode. Hence |
| * enable asynchronous interrupt to detect charger disconnection when |
| * PMIC notifications are unavailable. |
| */ |
| cmd_val = readl_relaxed(USB_USBCMD); |
| if (host_bus_suspend || device_bus_suspend || |
| (motg->pdata->otg_control == OTG_PHY_CONTROL)) |
| cmd_val |= ASYNC_INTR_CTRL | ULPI_STP_CTRL; |
| else |
| cmd_val |= ULPI_STP_CTRL; |
| writel_relaxed(cmd_val, USB_USBCMD); |
| |
| /* |
| * BC1.2 spec mandates PD to enable VDP_SRC when charging from DCP. |
| * PHY retention and collapse can not happen with VDP_SRC enabled. |
| */ |
| |
| |
| /* |
| * We come here in 3 scenarios. |
| * |
| * (1) No cable connected (out of session): |
| * - BSV/ID HV interrupts are enabled for PHY based detection. |
| * - PHY is put in retention. |
| * - If allowed (PMIC based detection), PHY is power collapsed. |
| * - DVDD (CX/MX) minimization and XO shutdown are allowed. |
| * - The wakeup is through VBUS/ID interrupt from PHY/PMIC/user. |
| * (2) USB wall charger: |
| * - BSV/ID HV interrupts are enabled for PHY based detection. |
| * - For BC1.2 compliant charger, retention is not allowed to |
| * keep VDP_SRC on. XO shutdown is allowed. |
| * - The wakeup is through VBUS/ID interrupt from PHY/PMIC/user. |
| * (3) Device/Host Bus suspend (if LPM is enabled): |
| * - BSV/ID HV interrupts are enabled for PHY based detection. |
| * - D+/D- MPM pin are configured to wakeup from line state |
| * change through PHY HV interrupts. PHY HV interrupts are |
| * also enabled. If MPM pins are not available, retention and |
| * XO is not allowed. |
| * - PHY is put into retention only if a gpio is used to keep |
| * the D+ pull-up. ALLOW_BUS_SUSPEND_WITHOUT_REWORK capability |
| * is set means, PHY can enable D+ pull-up or D+/D- pull-down |
| * without any re-work and PHY should not be put into retention. |
| * - DVDD (CX/MX) minimization and XO shutdown is allowed if |
| * ALLOW_BUS_SUSPEND_WITHOUT_REWORK is set (PHY DVDD is supplied |
| * via PMIC LDO) or board level re-work is present. |
| * - The wakeup is through VBUS/ID interrupt from PHY/PMIC/user |
| * or USB link asynchronous interrupt for line state change. |
| * |
| */ |
| motg->host_bus_suspend = host_bus_suspend; |
| motg->device_bus_suspend = device_bus_suspend; |
| |
| if (motg->caps & ALLOW_PHY_RETENTION && !device_bus_suspend && |
| (!host_bus_suspend || (motg->caps & |
| ALLOW_BUS_SUSPEND_WITHOUT_REWORK) || |
| ((motg->caps & ALLOW_HOST_PHY_RETENTION) |
| && (pdata->dpdm_pulldown_added || !(portsc & PORTSC_CCS))))) { |
| msm_otg_enable_phy_hv_int(motg); |
| if ((!host_bus_suspend || !(motg->caps & |
| ALLOW_BUS_SUSPEND_WITHOUT_REWORK)) && |
| !(motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED)) { |
| msm_otg_enter_phy_retention(motg); |
| motg->lpm_flags |= PHY_RETENTIONED; |
| } |
| } else if (device_bus_suspend) { |
| /* DP DM HV interrupts are used for bus resume from XO off */ |
| msm_otg_enable_phy_hv_int(motg); |
| if (motg->caps & ALLOW_PHY_RETENTION && pdata->vddmin_gpio) { |
| |
| /* |
| * This is HW WA needed when PHY_CLAMP_DPDMSE_EN is |
| * enabled and we put the phy in retention mode. |
| * Without this WA, the async_irq will be fired right |
| * after suspending whithout any bus resume. |
| */ |
| config2 = readl_relaxed(USB_GENCONFIG_2); |
| config2 &= ~GENCONFIG_2_DPSE_DMSE_HV_INTR_EN; |
| writel_relaxed(config2, USB_GENCONFIG_2); |
| |
| msm_otg_enter_phy_retention(motg); |
| motg->lpm_flags |= PHY_RETENTIONED; |
| gpio_direction_output(pdata->vddmin_gpio, 1); |
| } |
| } |
| |
| /* Ensure that above operation is completed before turning off clocks */ |
| mb(); |
| /* Consider clocks on workaround flag only in case of bus suspend */ |
| if (!(phy->otg->state == OTG_STATE_B_PERIPHERAL && |
| test_bit(A_BUS_SUSPEND, &motg->inputs)) || |
| !motg->pdata->core_clk_always_on_workaround) { |
| clk_disable_unprepare(motg->pclk); |
| clk_disable_unprepare(motg->core_clk); |
| if (motg->phy_csr_clk) |
| clk_disable_unprepare(motg->phy_csr_clk); |
| motg->lpm_flags |= CLOCKS_DOWN; |
| } |
| |
| /* usb phy no more require TCXO clock, hence vote for TCXO disable */ |
| if (!host_bus_suspend || (motg->caps & |
| ALLOW_BUS_SUSPEND_WITHOUT_REWORK) || |
| ((motg->caps & ALLOW_HOST_PHY_RETENTION) && |
| (pdata->dpdm_pulldown_added || !(portsc & PORTSC_CCS)))) { |
| if (motg->xo_clk) { |
| clk_disable_unprepare(motg->xo_clk); |
| motg->lpm_flags |= XO_SHUTDOWN; |
| } |
| } |
| |
| if (motg->caps & ALLOW_PHY_POWER_COLLAPSE && |
| !host_bus_suspend && !device_bus_suspend) { |
| msm_hsusb_ldo_enable(motg, USB_PHY_REG_OFF); |
| motg->lpm_flags |= PHY_PWR_COLLAPSED; |
| } else if (motg->caps & ALLOW_PHY_REGULATORS_LPM && |
| !host_bus_suspend && !device_bus_suspend) { |
| msm_hsusb_ldo_enable(motg, USB_PHY_REG_LPM_ON); |
| motg->lpm_flags |= PHY_REGULATORS_LPM; |
| } |
| |
| if (motg->lpm_flags & PHY_RETENTIONED || |
| (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED)) { |
| regulator_disable(hsusb_vdd); |
| msm_hsusb_config_vddcx(0); |
| } |
| |
| if (device_may_wakeup(phy->dev)) { |
| if (host_bus_suspend || device_bus_suspend) { |
| enable_irq_wake(motg->async_irq); |
| enable_irq_wake(motg->irq); |
| } |
| |
| if (motg->phy_irq) |
| enable_irq_wake(motg->phy_irq); |
| if (motg->pdata->pmic_id_irq) |
| enable_irq_wake(motg->pdata->pmic_id_irq); |
| if (motg->ext_id_irq) |
| enable_irq_wake(motg->ext_id_irq); |
| } |
| if (bus) |
| clear_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); |
| |
| msm_otg_bus_vote(motg, USB_NO_PERF_VOTE); |
| |
| atomic_set(&motg->in_lpm, 1); |
| |
| /* Enable ASYNC IRQ during LPM */ |
| enable_irq(motg->async_irq); |
| if (motg->phy_irq) |
| enable_irq(motg->phy_irq); |
| |
| enable_irq(motg->irq); |
| pm_relax(&motg->pdev->dev); |
| |
| dev_dbg(phy->dev, "LPM caps = %lu flags = %lu\n", |
| motg->caps, motg->lpm_flags); |
| dev_info(phy->dev, "USB in low power mode\n"); |
| msm_otg_dbg_log_event(phy, "LPM ENTER DONE", |
| motg->caps, motg->lpm_flags); |
| |
| if (motg->err_event_seen) { |
| motg->err_event_seen = false; |
| if (motg->vbus_state != test_bit(B_SESS_VLD, &motg->inputs)) |
| msm_otg_set_vbus_state(motg->vbus_state); |
| if (motg->id_state != test_bit(ID, &motg->inputs)) |
| msm_id_status_w(&motg->id_status_work.work); |
| } |
| |
| return 0; |
| |
| phy_suspend_fail: |
| enable_irq(motg->irq); |
| if (motg->phy_irq) |
| enable_irq(motg->phy_irq); |
| return ret; |
| } |
| |
| static int msm_otg_resume(struct msm_otg *motg) |
| { |
| struct usb_phy *phy = &motg->phy; |
| struct usb_bus *bus = phy->otg->host; |
| struct usb_hcd *hcd = bus_to_hcd(phy->otg->host); |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| int cnt = 0; |
| unsigned int temp; |
| unsigned int ret; |
| u32 func_ctrl; |
| |
| msm_otg_dbg_log_event(phy, "LPM EXIT START", motg->inputs, |
| phy->otg->state); |
| if (!atomic_read(&motg->in_lpm)) { |
| msm_otg_dbg_log_event(phy, "USB NOT IN LPM", |
| atomic_read(&motg->in_lpm), phy->otg->state); |
| return 0; |
| } |
| |
| disable_irq(motg->irq); |
| pm_stay_awake(&motg->pdev->dev); |
| |
| /* |
| * If we are resuming from the device bus suspend, restore |
| * the max performance bus vote. Otherwise put a minimum |
| * bus vote to satisfy the requirement for enabling clocks. |
| */ |
| |
| if (motg->device_bus_suspend && debug_bus_voting_enabled) |
| msm_otg_bus_vote(motg, USB_MAX_PERF_VOTE); |
| else |
| msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE); |
| |
| /* Vote for TCXO when waking up the phy */ |
| if (motg->lpm_flags & XO_SHUTDOWN) { |
| if (motg->xo_clk) |
| clk_prepare_enable(motg->xo_clk); |
| motg->lpm_flags &= ~XO_SHUTDOWN; |
| } |
| |
| if (motg->lpm_flags & CLOCKS_DOWN) { |
| if (motg->phy_csr_clk) { |
| ret = clk_prepare_enable(motg->phy_csr_clk); |
| WARN(ret, "USB phy_csr_clk enable failed\n"); |
| } |
| ret = clk_prepare_enable(motg->core_clk); |
| WARN(ret, "USB core_clk enable failed\n"); |
| ret = clk_prepare_enable(motg->pclk); |
| WARN(ret, "USB pclk enable failed\n"); |
| motg->lpm_flags &= ~CLOCKS_DOWN; |
| } |
| |
| if (motg->lpm_flags & PHY_PWR_COLLAPSED) { |
| msm_hsusb_ldo_enable(motg, USB_PHY_REG_ON); |
| motg->lpm_flags &= ~PHY_PWR_COLLAPSED; |
| } else if (motg->lpm_flags & PHY_REGULATORS_LPM) { |
| msm_hsusb_ldo_enable(motg, USB_PHY_REG_LPM_OFF); |
| motg->lpm_flags &= ~PHY_REGULATORS_LPM; |
| } |
| |
| if (motg->lpm_flags & PHY_RETENTIONED || |
| (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED)) { |
| msm_hsusb_config_vddcx(1); |
| ret = regulator_enable(hsusb_vdd); |
| WARN(ret, "hsusb_vdd LDO enable failed\n"); |
| msm_otg_disable_phy_hv_int(motg); |
| msm_otg_exit_phy_retention(motg); |
| motg->lpm_flags &= ~PHY_RETENTIONED; |
| if (pdata->vddmin_gpio && motg->device_bus_suspend) |
| gpio_direction_input(pdata->vddmin_gpio); |
| } else if (motg->device_bus_suspend) { |
| msm_otg_disable_phy_hv_int(motg); |
| } |
| |
| temp = readl_relaxed(USB_USBCMD); |
| temp &= ~ASYNC_INTR_CTRL; |
| temp &= ~ULPI_STP_CTRL; |
| writel_relaxed(temp, USB_USBCMD); |
| |
| /* |
| * PHY comes out of low power mode (LPM) in case of wakeup |
| * from asynchronous interrupt. |
| */ |
| if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD)) |
| goto skip_phy_resume; |
| |
| writel_relaxed(readl_relaxed(USB_PORTSC) & ~PORTSC_PHCD, USB_PORTSC); |
| |
| while (cnt < PHY_RESUME_TIMEOUT_USEC) { |
| if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD)) |
| break; |
| udelay(1); |
| cnt++; |
| } |
| |
| if (cnt >= PHY_RESUME_TIMEOUT_USEC) { |
| /* |
| * This is a fatal error. Reset the link and |
| * PHY. USB state can not be restored. Re-insertion |
| * of USB cable is the only way to get USB working. |
| */ |
| dev_err(phy->dev, "Unable to resume USB. Re-plugin the cable\n" |
| ); |
| msm_otg_reset(phy); |
| } |
| |
| skip_phy_resume: |
| if (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED) { |
| /* put the controller in normal mode */ |
| func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL); |
| func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; |
| func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NORMAL; |
| ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL); |
| } |
| |
| if (device_may_wakeup(phy->dev)) { |
| if (motg->host_bus_suspend || motg->device_bus_suspend) { |
| disable_irq_wake(motg->async_irq); |
| disable_irq_wake(motg->irq); |
| } |
| |
| if (motg->phy_irq) |
| disable_irq_wake(motg->phy_irq); |
| if (motg->pdata->pmic_id_irq) |
| disable_irq_wake(motg->pdata->pmic_id_irq); |
| if (motg->ext_id_irq) |
| disable_irq_wake(motg->ext_id_irq); |
| } |
| if (bus) |
| set_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); |
| |
| atomic_set(&motg->in_lpm, 0); |
| |
| if (motg->async_int) { |
| /* Match the disable_irq call from ISR */ |
| enable_irq(motg->async_int); |
| motg->async_int = 0; |
| } |
| enable_irq(motg->irq); |
| |
| /* Enable ASYNC_IRQ only during LPM */ |
| disable_irq(motg->async_irq); |
| |
| if (motg->phy_irq_pending) { |
| motg->phy_irq_pending = false; |
| msm_id_status_w(&motg->id_status_work.work); |
| } |
| |
| if (motg->host_bus_suspend) { |
| usb_hcd_resume_root_hub(hcd); |
| schedule_delayed_work(&motg->perf_vote_work, |
| msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); |
| } |
| |
| dev_info(phy->dev, "USB exited from low power mode\n"); |
| msm_otg_dbg_log_event(phy, "LPM EXIT DONE", |
| motg->caps, motg->lpm_flags); |
| |
| return 0; |
| } |
| #endif |
| |
| static int get_psy_type(struct msm_otg *motg) |
| { |
| union power_supply_propval pval = {0}; |
| |
| if (!psy) { |
| dev_err(motg->phy.dev, "no usb power supply registered\n"); |
| return -ENODEV; |
| } |
| |
| power_supply_get_property(psy, POWER_SUPPLY_PROP_REAL_TYPE, &pval); |
| |
| return pval.intval; |
| } |
| |
| static void msm_otg_notify_chg_current(struct msm_otg *motg, unsigned int mA) |
| { |
| struct usb_gadget *g = motg->phy.otg->gadget; |
| union power_supply_propval pval = {0}; |
| int psy_type; |
| |
| if (g && g->is_a_peripheral) |
| return; |
| |
| psy_type = get_psy_type(motg); |
| if (psy_type == POWER_SUPPLY_TYPE_USB_FLOAT) { |
| if (!mA) |
| pval.intval = -ETIMEDOUT; |
| else |
| pval.intval = 1000 * mA; |
| goto set_prop; |
| } |
| |
| dev_dbg(motg->phy.dev, "Requested curr from USB = %u\n", mA); |
| |
| if (motg->cur_power == mA || psy_type != POWER_SUPPLY_TYPE_USB) |
| return; |
| |
| dev_info(motg->phy.dev, "Avail curr from USB = %u\n", mA); |
| msm_otg_dbg_log_event(&motg->phy, "AVAIL CURR FROM USB", mA, 0); |
| |
| /* Set max current limit in uA */ |
| pval.intval = 1000 * mA; |
| |
| set_prop: |
| if (power_supply_set_property(psy, POWER_SUPPLY_PROP_SDP_CURRENT_MAX, |
| &pval)) { |
| dev_dbg(motg->phy.dev, "power supply error when setting property\n"); |
| return; |
| } |
| |
| motg->cur_power = mA; |
| } |
| |
| static void msm_otg_notify_chg_current_work(struct work_struct *w) |
| { |
| struct msm_otg *motg = container_of(w, |
| struct msm_otg, notify_chg_current_work); |
| /* |
| * Gadget driver uses set_power method to notify about the |
| * available current based on suspend/configured states. |
| */ |
| msm_otg_notify_chg_current(motg, motg->notify_current_mA); |
| } |
| |
| static int msm_otg_set_power(struct usb_phy *phy, unsigned int mA) |
| { |
| struct msm_otg *motg = container_of(phy, struct msm_otg, phy); |
| |
| motg->notify_current_mA = mA; |
| schedule_work(&motg->notify_chg_current_work); |
| |
| return 0; |
| } |
| |
| static void msm_hsusb_vbus_power(struct msm_otg *motg, bool on); |
| |
| static void msm_otg_perf_vote_update(struct msm_otg *motg, bool perf_mode) |
| { |
| static bool curr_perf_mode; |
| int ret, latency = motg->pm_qos_latency; |
| long clk_rate; |
| |
| if (curr_perf_mode == perf_mode) |
| return; |
| |
| if (perf_mode) { |
| if (latency) |
| pm_qos_update_request(&motg->pm_qos_req_dma, latency); |
| msm_otg_bus_vote(motg, USB_MAX_PERF_VOTE); |
| clk_rate = motg->core_clk_rate; |
| } else { |
| if (latency) |
| pm_qos_update_request(&motg->pm_qos_req_dma, |
| PM_QOS_DEFAULT_VALUE); |
| msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE); |
| clk_rate = motg->core_clk_svs_rate; |
| } |
| |
| if (clk_rate) { |
| ret = clk_set_rate(motg->core_clk, clk_rate); |
| if (ret) |
| dev_err(motg->phy.dev, "sys_clk set_rate fail:%d %ld\n", |
| ret, clk_rate); |
| } |
| curr_perf_mode = perf_mode; |
| pr_debug("%s: latency updated to: %d, core_freq to: %ld\n", __func__, |
| latency, clk_rate); |
| } |
| |
| static void msm_otg_perf_vote_work(struct work_struct *w) |
| { |
| struct msm_otg *motg = container_of(w, struct msm_otg, |
| perf_vote_work.work); |
| unsigned int curr_sample_int_count; |
| bool in_perf_mode = false; |
| |
| curr_sample_int_count = motg->usb_irq_count; |
| motg->usb_irq_count = 0; |
| |
| if (curr_sample_int_count >= PM_QOS_THRESHOLD) |
| in_perf_mode = true; |
| |
| msm_otg_perf_vote_update(motg, in_perf_mode); |
| pr_debug("%s: in_perf_mode:%u, interrupts in last sample:%u\n", |
| __func__, in_perf_mode, curr_sample_int_count); |
| |
| schedule_delayed_work(&motg->perf_vote_work, |
| msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); |
| } |
| |
| static void msm_otg_start_host(struct usb_otg *otg, int on) |
| { |
| struct msm_otg *motg = container_of(otg->usb_phy, struct msm_otg, phy); |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| struct usb_hcd *hcd; |
| u32 val; |
| |
| if (!otg->host) |
| return; |
| |
| hcd = bus_to_hcd(otg->host); |
| |
| msm_otg_dbg_log_event(&motg->phy, "PM RT: StartHost GET", |
| get_pm_runtime_counter(motg->phy.dev), 0); |
| pm_runtime_get_sync(otg->usb_phy->dev); |
| if (on) { |
| dev_dbg(otg->usb_phy->dev, "host on\n"); |
| msm_otg_dbg_log_event(&motg->phy, "HOST ON", |
| motg->inputs, otg->state); |
| msm_hsusb_vbus_power(motg, 1); |
| msm_otg_reset(&motg->phy); |
| |
| if (pdata->otg_control == OTG_PHY_CONTROL) |
| ulpi_write(otg->usb_phy, OTG_COMP_DISABLE, |
| ULPI_SET(ULPI_PWR_CLK_MNG_REG)); |
| |
| if (pdata->enable_axi_prefetch) { |
| val = readl_relaxed(USB_HS_APF_CTRL); |
| val &= ~APF_CTRL_EN; |
| writel_relaxed(val, USB_HS_APF_CTRL); |
| } |
| usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); |
| #ifdef CONFIG_SMP |
| motg->pm_qos_req_dma.type = PM_QOS_REQ_AFFINE_IRQ; |
| motg->pm_qos_req_dma.irq = motg->irq; |
| #endif |
| pm_qos_add_request(&motg->pm_qos_req_dma, |
| PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); |
| /* start in perf mode for better performance initially */ |
| msm_otg_perf_vote_update(motg, true); |
| schedule_delayed_work(&motg->perf_vote_work, |
| msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); |
| } else { |
| dev_dbg(otg->usb_phy->dev, "host off\n"); |
| msm_otg_dbg_log_event(&motg->phy, "HOST OFF", |
| motg->inputs, otg->state); |
| msm_hsusb_vbus_power(motg, 0); |
| |
| cancel_delayed_work_sync(&motg->perf_vote_work); |
| msm_otg_perf_vote_update(motg, false); |
| pm_qos_remove_request(&motg->pm_qos_req_dma); |
| |
| pm_runtime_disable(&hcd->self.root_hub->dev); |
| pm_runtime_barrier(&hcd->self.root_hub->dev); |
| usb_remove_hcd(hcd); |
| msm_otg_reset(&motg->phy); |
| |
| if (pdata->enable_axi_prefetch) |
| writel_relaxed(readl_relaxed(USB_HS_APF_CTRL) |
| | (APF_CTRL_EN), USB_HS_APF_CTRL); |
| |
| /* HCD core reset all bits of PORTSC. select ULPI phy */ |
| writel_relaxed(0x80000000, USB_PORTSC); |
| |
| if (pdata->otg_control == OTG_PHY_CONTROL) |
| ulpi_write(otg->usb_phy, OTG_COMP_DISABLE, |
| ULPI_CLR(ULPI_PWR_CLK_MNG_REG)); |
| } |
| msm_otg_dbg_log_event(&motg->phy, "PM RT: StartHost PUT", |
| get_pm_runtime_counter(motg->phy.dev), 0); |
| |
| pm_runtime_mark_last_busy(otg->usb_phy->dev); |
| pm_runtime_put_autosuspend(otg->usb_phy->dev); |
| } |
| |
| static void msm_hsusb_vbus_power(struct msm_otg *motg, bool on) |
| { |
| int ret; |
| static bool vbus_is_on; |
| |
| msm_otg_dbg_log_event(&motg->phy, "VBUS POWER", on, vbus_is_on); |
| if (vbus_is_on == on) |
| return; |
| |
| if (!vbus_otg) { |
| pr_err("vbus_otg is NULL."); |
| return; |
| } |
| |
| /* |
| * if entering host mode tell the charger to not draw any current |
| * from usb before turning on the boost. |
| * if exiting host mode disable the boost before enabling to draw |
| * current from the source. |
| */ |
| if (on) { |
| ret = regulator_enable(vbus_otg); |
| if (ret) { |
| pr_err("unable to enable vbus_otg\n"); |
| return; |
| } |
| vbus_is_on = true; |
| } else { |
| ret = regulator_disable(vbus_otg); |
| if (ret) { |
| pr_err("unable to disable vbus_otg\n"); |
| return; |
| } |
| vbus_is_on = false; |
| } |
| } |
| |
| static int msm_otg_set_host(struct usb_otg *otg, struct usb_bus *host) |
| { |
| struct msm_otg *motg = container_of(otg->usb_phy, struct msm_otg, phy); |
| struct usb_hcd *hcd; |
| |
| /* |
| * Fail host registration if this board can support |
| * only peripheral configuration. |
| */ |
| if (motg->pdata->mode == USB_PERIPHERAL) { |
| dev_info(otg->usb_phy->dev, "Host mode is not supported\n"); |
| return -ENODEV; |
| } |
| |
| if (host) { |
| vbus_otg = devm_regulator_get(motg->phy.dev, "vbus_otg"); |
| if (IS_ERR(vbus_otg)) { |
| msm_otg_dbg_log_event(&motg->phy, |
| "UNABLE TO GET VBUS_OTG", |
| otg->state, 0); |
| pr_err("Unable to get vbus_otg\n"); |
| return PTR_ERR(vbus_otg); |
| } |
| } else { |
| if (otg->state == OTG_STATE_A_HOST) { |
| msm_otg_start_host(otg, 0); |
| otg->host = NULL; |
| otg->state = OTG_STATE_UNDEFINED; |
| queue_work(motg->otg_wq, &motg->sm_work); |
| } else { |
| otg->host = NULL; |
| } |
| |
| return 0; |
| } |
| |
| hcd = bus_to_hcd(host); |
| hcd->power_budget = motg->pdata->power_budget; |
| |
| otg->host = host; |
| dev_dbg(otg->usb_phy->dev, "host driver registered w/ tranceiver\n"); |
| msm_otg_dbg_log_event(&motg->phy, "HOST DRIVER REGISTERED", |
| hcd->power_budget, motg->pdata->mode); |
| |
| /* |
| * Kick the state machine work, if peripheral is not supported |
| * or peripheral is already registered with us. |
| */ |
| if (motg->pdata->mode == USB_HOST || otg->gadget) |
| queue_work(motg->otg_wq, &motg->sm_work); |
| |
| return 0; |
| } |
| |
| static void msm_otg_start_peripheral(struct usb_otg *otg, int on) |
| { |
| struct msm_otg *motg = container_of(otg->usb_phy, struct msm_otg, phy); |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| struct pinctrl_state *set_state; |
| int ret; |
| |
| if (!otg->gadget) |
| return; |
| |
| msm_otg_dbg_log_event(&motg->phy, "PM RT: StartPeri GET", |
| get_pm_runtime_counter(motg->phy.dev), 0); |
| pm_runtime_get_sync(otg->usb_phy->dev); |
| if (on) { |
| dev_dbg(otg->usb_phy->dev, "gadget on\n"); |
| msm_otg_dbg_log_event(&motg->phy, "GADGET ON", |
| motg->inputs, otg->state); |
| |
| /* Configure BUS performance parameters for MAX bandwidth */ |
| if (debug_bus_voting_enabled) |
| msm_otg_bus_vote(motg, USB_MAX_PERF_VOTE); |
| /* bump up usb core_clk to default */ |
| clk_set_rate(motg->core_clk, motg->core_clk_rate); |
| |
| usb_gadget_vbus_connect(otg->gadget); |
| |
| /* |
| * Request VDD min gpio, if need to support VDD |
| * minimazation during peripheral bus suspend. |
| */ |
| if (pdata->vddmin_gpio) { |
| if (motg->phy_pinctrl) { |
| set_state = |
| pinctrl_lookup_state(motg->phy_pinctrl, |
| "hsusb_active"); |
| if (IS_ERR(set_state)) { |
| pr_err("cannot get phy pinctrl active state\n"); |
| } else { |
| pinctrl_select_state(motg->phy_pinctrl, |
| set_state); |
| } |
| } |
| |
| ret = gpio_request(pdata->vddmin_gpio, |
| "MSM_OTG_VDD_MIN_GPIO"); |
| if (ret < 0) { |
| dev_err(otg->usb_phy->dev, "gpio req failed for vdd min:%d\n", |
| ret); |
| pdata->vddmin_gpio = 0; |
| } |
| } |
| } else { |
| dev_dbg(otg->usb_phy->dev, "gadget off\n"); |
| msm_otg_dbg_log_event(&motg->phy, "GADGET OFF", |
| motg->inputs, otg->state); |
| usb_gadget_vbus_disconnect(otg->gadget); |
| clear_bit(A_BUS_SUSPEND, &motg->inputs); |
| /* Configure BUS performance parameters to default */ |
| msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE); |
| |
| if (pdata->vddmin_gpio) { |
| gpio_free(pdata->vddmin_gpio); |
| if (motg->phy_pinctrl) { |
| set_state = |
| pinctrl_lookup_state(motg->phy_pinctrl, |
| "hsusb_sleep"); |
| if (IS_ERR(set_state)) |
| pr_err("cannot get phy pinctrl sleep state\n"); |
| else |
| pinctrl_select_state(motg->phy_pinctrl, |
| set_state); |
| } |
| } |
| } |
| msm_otg_dbg_log_event(&motg->phy, "PM RT: StartPeri PUT", |
| get_pm_runtime_counter(motg->phy.dev), 0); |
| pm_runtime_mark_last_busy(otg->usb_phy->dev); |
| pm_runtime_put_autosuspend(otg->usb_phy->dev); |
| } |
| |
| static int msm_otg_set_peripheral(struct usb_otg *otg, |
| struct usb_gadget *gadget) |
| { |
| struct msm_otg *motg = container_of(otg->usb_phy, struct msm_otg, phy); |
| |
| /* |
| * Fail peripheral registration if this board can support |
| * only host configuration. |
| */ |
| if (motg->pdata->mode == USB_HOST) { |
| dev_info(otg->usb_phy->dev, "Peripheral mode is not supported\n"); |
| return -ENODEV; |
| } |
| |
| if (!gadget) { |
| if (otg->state == OTG_STATE_B_PERIPHERAL) { |
| msm_otg_dbg_log_event(&motg->phy, |
| "PM RUNTIME: PERIPHERAL GET1", |
| get_pm_runtime_counter(otg->usb_phy->dev), 0); |
| msm_otg_start_peripheral(otg, 0); |
| otg->gadget = NULL; |
| otg->state = OTG_STATE_UNDEFINED; |
| queue_work(motg->otg_wq, &motg->sm_work); |
| } else { |
| otg->gadget = NULL; |
| } |
| |
| return 0; |
| } |
| otg->gadget = gadget; |
| dev_dbg(otg->usb_phy->dev, "peripheral driver registered w/ tranceiver\n"); |
| msm_otg_dbg_log_event(&motg->phy, "PERIPHERAL DRIVER REGISTERED", |
| otg->state, motg->pdata->mode); |
| |
| /* |
| * Kick the state machine work, if host is not supported |
| * or host is already registered with us. |
| */ |
| if (motg->pdata->mode == USB_PERIPHERAL || otg->host) |
| queue_work(motg->otg_wq, &motg->sm_work); |
| |
| return 0; |
| } |
| |
| static bool msm_otg_read_pmic_id_state(struct msm_otg *motg) |
| { |
| unsigned long flags; |
| bool id; |
| int ret; |
| |
| if (!motg->pdata->pmic_id_irq) |
| return -ENODEV; |
| |
| local_irq_save(flags); |
| ret = irq_get_irqchip_state(motg->pdata->pmic_id_irq, |
| IRQCHIP_STATE_LINE_LEVEL, &id); |
| local_irq_restore(flags); |
| |
| /* |
| * If we can not read ID line state for some reason, treat |
| * it as float. This would prevent MHL discovery and kicking |
| * host mode unnecessarily. |
| */ |
| if (ret < 0) |
| return true; |
| |
| return !!id; |
| } |
| |
| static bool msm_otg_read_phy_id_state(struct msm_otg *motg) |
| { |
| u8 val; |
| |
| /* |
| * clear the pending/outstanding interrupts and |
| * read the ID status from the SRC_STATUS register. |
| */ |
| writeb_relaxed(USB_PHY_ID_MASK, USB2_PHY_USB_PHY_INTERRUPT_CLEAR1); |
| |
| writeb_relaxed(0x1, USB2_PHY_USB_PHY_IRQ_CMD); |
| /* |
| * Databook says 200 usec delay is required for |
| * clearing the interrupts. |
| */ |
| udelay(200); |
| writeb_relaxed(0x0, USB2_PHY_USB_PHY_IRQ_CMD); |
| |
| val = readb_relaxed(USB2_PHY_USB_PHY_INTERRUPT_SRC_STATUS); |
| if (val & USB_PHY_IDDIG_1_0) |
| return false; /* ID is grounded */ |
| else |
| return true; |
| } |
| |
| /* |
| * We support OTG, Peripheral only and Host only configurations. In case |
| * of OTG, mode switch (host-->peripheral/peripheral-->host) can happen |
| * via Id pin status or user request (debugfs). Id/BSV interrupts are not |
| * enabled when switch is controlled by user and default mode is supplied |
| * by board file, which can be changed by userspace later. |
| */ |
| static void msm_otg_init_sm(struct msm_otg *motg) |
| { |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| u32 otgsc = readl_relaxed(USB_OTGSC); |
| |
| switch (pdata->mode) { |
| case USB_OTG: |
| if (pdata->otg_control == OTG_USER_CONTROL) { |
| if (pdata->default_mode == USB_HOST) { |
| clear_bit(ID, &motg->inputs); |
| } else if (pdata->default_mode == USB_PERIPHERAL) { |
| set_bit(ID, &motg->inputs); |
| set_bit(B_SESS_VLD, &motg->inputs); |
| } else { |
| set_bit(ID, &motg->inputs); |
| clear_bit(B_SESS_VLD, &motg->inputs); |
| } |
| } else if (pdata->otg_control == OTG_PHY_CONTROL) { |
| if (otgsc & OTGSC_ID) |
| set_bit(ID, &motg->inputs); |
| else |
| clear_bit(ID, &motg->inputs); |
| if (otgsc & OTGSC_BSV) |
| set_bit(B_SESS_VLD, &motg->inputs); |
| else |
| clear_bit(B_SESS_VLD, &motg->inputs); |
| } else if (pdata->otg_control == OTG_PMIC_CONTROL) { |
| if (pdata->pmic_id_irq) { |
| if (msm_otg_read_pmic_id_state(motg)) |
| set_bit(ID, &motg->inputs); |
| else |
| clear_bit(ID, &motg->inputs); |
| } else if (motg->ext_id_irq) { |
| if (gpio_get_value(pdata->usb_id_gpio)) |
| set_bit(ID, &motg->inputs); |
| else |
| clear_bit(ID, &motg->inputs); |
| } else if (motg->phy_irq) { |
| if (msm_otg_read_phy_id_state(motg)) |
| set_bit(ID, &motg->inputs); |
| else |
| clear_bit(ID, &motg->inputs); |
| } |
| } |
| break; |
| case USB_HOST: |
| clear_bit(ID, &motg->inputs); |
| break; |
| case USB_PERIPHERAL: |
| set_bit(ID, &motg->inputs); |
| if (pdata->otg_control == OTG_PHY_CONTROL) { |
| if (otgsc & OTGSC_BSV) |
| set_bit(B_SESS_VLD, &motg->inputs); |
| else |
| clear_bit(B_SESS_VLD, &motg->inputs); |
| } else if (pdata->otg_control == OTG_USER_CONTROL) { |
| set_bit(ID, &motg->inputs); |
| set_bit(B_SESS_VLD, &motg->inputs); |
| } |
| break; |
| default: |
| break; |
| } |
| msm_otg_dbg_log_event(&motg->phy, "SM INIT", pdata->mode, motg->inputs); |
| if (motg->id_state != USB_ID_GROUND) |
| motg->id_state = (test_bit(ID, &motg->inputs)) ? USB_ID_FLOAT : |
| USB_ID_GROUND; |
| } |
| |
| static void check_for_sdp_connection(struct work_struct *w) |
| { |
| struct msm_otg *motg = container_of(w, struct msm_otg, sdp_check.work); |
| |
| /* Cable disconnected or device enumerated as SDP */ |
| if (!motg->vbus_state || motg->phy.otg->gadget->state >= |
| USB_STATE_DEFAULT) |
| return; |
| |
| /* floating D+/D- lines detected */ |
| motg->vbus_state = 0; |
| msm_otg_dbg_log_event(&motg->phy, "Q RW SDP CHK", motg->vbus_state, 0); |
| msm_otg_set_vbus_state(motg->vbus_state); |
| } |
| |
| static void msm_otg_sm_work(struct work_struct *w) |
| { |
| struct msm_otg *motg = container_of(w, struct msm_otg, sm_work); |
| struct usb_otg *otg = motg->phy.otg; |
| struct device *dev = otg->usb_phy->dev; |
| bool work = 0; |
| int ret; |
| |
| pr_debug("%s work\n", usb_otg_state_string(otg->state)); |
| msm_otg_dbg_log_event(&motg->phy, "SM WORK:", |
| otg->state, motg->inputs); |
| |
| /* Just resume h/w if reqd, pm_count is handled based on state/inputs */ |
| if (motg->resume_pending) { |
| pm_runtime_get_sync(otg->usb_phy->dev); |
| if (atomic_read(&motg->in_lpm)) { |
| dev_err(dev, "SM WORK: USB is in LPM\n"); |
| msm_otg_dbg_log_event(&motg->phy, |
| "SM WORK: USB IS IN LPM", |
| otg->state, motg->inputs); |
| msm_otg_resume(motg); |
| } |
| motg->resume_pending = false; |
| pm_runtime_put_noidle(otg->usb_phy->dev); |
| } |
| |
| switch (otg->state) { |
| case OTG_STATE_UNDEFINED: |
| pm_runtime_get_sync(otg->usb_phy->dev); |
| msm_otg_reset(otg->usb_phy); |
| /* Add child device only after block reset */ |
| ret = of_platform_populate(motg->pdev->dev.of_node, NULL, NULL, |
| &motg->pdev->dev); |
| if (ret) |
| dev_dbg(&motg->pdev->dev, "failed to add BAM core\n"); |
| |
| msm_otg_init_sm(motg); |
| otg->state = OTG_STATE_B_IDLE; |
| if (!test_bit(B_SESS_VLD, &motg->inputs) && |
| test_bit(ID, &motg->inputs)) { |
| msm_otg_dbg_log_event(&motg->phy, |
| "PM RUNTIME: UNDEF PUT", |
| get_pm_runtime_counter(otg->usb_phy->dev), 0); |
| pm_runtime_put_sync(otg->usb_phy->dev); |
| break; |
| } |
| pm_runtime_put(otg->usb_phy->dev); |
| /* FALL THROUGH */ |
| case OTG_STATE_B_IDLE: |
| if (!test_bit(ID, &motg->inputs) && otg->host) { |
| pr_debug("!id\n"); |
| msm_otg_dbg_log_event(&motg->phy, "!ID", |
| motg->inputs, otg->state); |
| if (!otg->host) { |
| msm_otg_dbg_log_event(&motg->phy, |
| "SM WORK: Host Not Set", |
| otg->state, motg->inputs); |
| break; |
| } |
| |
| msm_otg_start_host(otg, 1); |
| otg->state = OTG_STATE_A_HOST; |
| } else if (test_bit(B_SESS_VLD, &motg->inputs)) { |
| pr_debug("b_sess_vld\n"); |
| msm_otg_dbg_log_event(&motg->phy, "B_SESS_VLD", |
| motg->inputs, otg->state); |
| if (!otg->gadget) { |
| msm_otg_dbg_log_event(&motg->phy, |
| "SM WORK: Gadget Not Set", |
| otg->state, motg->inputs); |
| break; |
| } |
| |
| if (get_psy_type(motg) == POWER_SUPPLY_TYPE_USB_FLOAT) |
| queue_delayed_work(motg->otg_wq, |
| &motg->sdp_check, |
| msecs_to_jiffies(SDP_CHECK_DELAY_MS)); |
| |
| pm_runtime_get_sync(otg->usb_phy->dev); |
| msm_otg_start_peripheral(otg, 1); |
| otg->state = OTG_STATE_B_PERIPHERAL; |
| } else { |
| pr_debug("Cable disconnected\n"); |
| msm_otg_dbg_log_event(&motg->phy, "RT: Cable DISC", |
| get_pm_runtime_counter(otg->usb_phy->dev), 0); |
| |
| msm_otg_notify_chg_current(motg, 0); |
| } |
| break; |
| case OTG_STATE_B_PERIPHERAL: |
| if (!test_bit(B_SESS_VLD, &motg->inputs)) { |
| cancel_delayed_work_sync(&motg->sdp_check); |
| msm_otg_start_peripheral(otg, 0); |
| msm_otg_dbg_log_event(&motg->phy, "RT PM: B_PERI A PUT", |
| get_pm_runtime_counter(dev), 0); |
| /* _put for _get done on cable connect in B_IDLE */ |
| pm_runtime_mark_last_busy(dev); |
| pm_runtime_put_autosuspend(dev); |
| /* Schedule work to finish cable disconnect processing*/ |
| otg->state = OTG_STATE_B_IDLE; |
| work = 1; |
| } else if (test_bit(A_BUS_SUSPEND, &motg->inputs)) { |
| pr_debug("a_bus_suspend\n"); |
| msm_otg_dbg_log_event(&motg->phy, |
| "BUS_SUSPEND: PM RT PUT", |
| get_pm_runtime_counter(dev), 0); |
| otg->state = OTG_STATE_B_SUSPEND; |
| /* _get on connect in B_IDLE or host resume in B_SUSP */ |
| pm_runtime_mark_last_busy(dev); |
| pm_runtime_put_autosuspend(dev); |
| } |
| break; |
| case OTG_STATE_B_SUSPEND: |
| if (!test_bit(B_SESS_VLD, &motg->inputs)) { |
| cancel_delayed_work_sync(&motg->sdp_check); |
| msm_otg_start_peripheral(otg, 0); |
| otg->state = OTG_STATE_B_IDLE; |
| /* Schedule work to finish cable disconnect processing*/ |
| work = 1; |
| } else if (!test_bit(A_BUS_SUSPEND, &motg->inputs)) { |
| pr_debug("!a_bus_suspend\n"); |
| otg->state = OTG_STATE_B_PERIPHERAL; |
| msm_otg_dbg_log_event(&motg->phy, |
| "BUS_RESUME: PM RT GET", |
| get_pm_runtime_counter(dev), 0); |
| pm_runtime_get_sync(dev); |
| } |
| break; |
| case OTG_STATE_A_HOST: |
| if (test_bit(ID, &motg->inputs)) { |
| msm_otg_start_host(otg, 0); |
| otg->state = OTG_STATE_B_IDLE; |
| work = 1; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| if (work) |
| queue_work(motg->otg_wq, &motg->sm_work); |
| } |
| |
| static irqreturn_t msm_otg_irq(int irq, void *data) |
| { |
| struct msm_otg *motg = data; |
| struct usb_otg *otg = motg->phy.otg; |
| u32 otgsc = 0; |
| bool work = 0; |
| |
| if (atomic_read(&motg->in_lpm)) { |
| pr_debug("OTG IRQ: %d in LPM\n", irq); |
| msm_otg_dbg_log_event(&motg->phy, "OTG IRQ IS IN LPM", |
| irq, otg->state); |
| /*Ignore interrupt if one interrupt already seen in LPM*/ |
| if (motg->async_int) |
| return IRQ_HANDLED; |
| |
| disable_irq_nosync(irq); |
| motg->async_int = irq; |
| msm_otg_kick_sm_work(motg); |
| |
| return IRQ_HANDLED; |
| } |
| motg->usb_irq_count++; |
| |
| otgsc = readl_relaxed(USB_OTGSC); |
| if (!(otgsc & (OTGSC_IDIS | OTGSC_BSVIS))) |
| return IRQ_NONE; |
| |
| if ((otgsc & OTGSC_IDIS) && (otgsc & OTGSC_IDIE)) { |
| if (otgsc & OTGSC_ID) { |
| dev_dbg(otg->usb_phy->dev, "ID set\n"); |
| msm_otg_dbg_log_event(&motg->phy, "ID SET", |
| motg->inputs, otg->state); |
| set_bit(ID, &motg->inputs); |
| } else { |
| dev_dbg(otg->usb_phy->dev, "ID clear\n"); |
| msm_otg_dbg_log_event(&motg->phy, "ID CLEAR", |
| motg->inputs, otg->state); |
| clear_bit(ID, &motg->inputs); |
| } |
| work = 1; |
| } else if ((otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) { |
| if (otgsc & OTGSC_BSV) { |
| dev_dbg(otg->usb_phy->dev, "BSV set\n"); |
| msm_otg_dbg_log_event(&motg->phy, "BSV SET", |
| motg->inputs, otg->state); |
| set_bit(B_SESS_VLD, &motg->inputs); |
| } else { |
| dev_dbg(otg->usb_phy->dev, "BSV clear\n"); |
| msm_otg_dbg_log_event(&motg->phy, "BSV CLEAR", |
| motg->inputs, otg->state); |
| clear_bit(B_SESS_VLD, &motg->inputs); |
| clear_bit(A_BUS_SUSPEND, &motg->inputs); |
| } |
| work = 1; |
| } |
| if (work) |
| queue_work(motg->otg_wq, &motg->sm_work); |
| |
| writel_relaxed(otgsc, USB_OTGSC); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void msm_otg_set_vbus_state(int online) |
| { |
| struct msm_otg *motg = the_msm_otg; |
| |
| motg->vbus_state = online; |
| |
| if (motg->err_event_seen) |
| return; |
| |
| if (online) { |
| pr_debug("EXTCON: BSV set\n"); |
| msm_otg_dbg_log_event(&motg->phy, "EXTCON: BSV SET", |
| motg->inputs, 0); |
| if (test_and_set_bit(B_SESS_VLD, &motg->inputs)) |
| return; |
| } else { |
| pr_debug("EXTCON: BSV clear\n"); |
| msm_otg_dbg_log_event(&motg->phy, "EXTCON: BSV CLEAR", |
| motg->inputs, 0); |
| if (!test_and_clear_bit(B_SESS_VLD, &motg->inputs)) |
| return; |
| } |
| |
| msm_otg_dbg_log_event(&motg->phy, "CHECK VBUS EVENT DURING SUSPEND", |
| atomic_read(&motg->pm_suspended), |
| motg->sm_work_pending); |
| |
| /* Move to host mode on vbus low if required */ |
| if (motg->pdata->vbus_low_as_hostmode) { |
| if (!test_bit(B_SESS_VLD, &motg->inputs)) |
| clear_bit(ID, &motg->inputs); |
| else |
| set_bit(ID, &motg->inputs); |
| } |
| msm_otg_kick_sm_work(motg); |
| } |
| |
| static void msm_id_status_w(struct work_struct *w) |
| { |
| struct msm_otg *motg = container_of(w, struct msm_otg, |
| id_status_work.work); |
| int work = 0; |
| |
| dev_dbg(motg->phy.dev, "ID status_w\n"); |
| |
| if (motg->pdata->pmic_id_irq) |
| motg->id_state = msm_otg_read_pmic_id_state(motg); |
| else if (motg->ext_id_irq) |
| motg->id_state = gpio_get_value(motg->pdata->usb_id_gpio); |
| else if (motg->phy_irq) |
| motg->id_state = msm_otg_read_phy_id_state(motg); |
| |
| if (motg->err_event_seen) |
| return; |
| |
| if (motg->id_state) { |
| if (gpio_is_valid(motg->pdata->switch_sel_gpio)) |
| gpio_direction_input(motg->pdata->switch_sel_gpio); |
| if (!test_and_set_bit(ID, &motg->inputs)) { |
| pr_debug("ID set\n"); |
| msm_otg_dbg_log_event(&motg->phy, "ID SET", |
| motg->inputs, motg->phy.otg->state); |
| work = 1; |
| } |
| } else { |
| if (gpio_is_valid(motg->pdata->switch_sel_gpio)) |
| gpio_direction_output(motg->pdata->switch_sel_gpio, 1); |
| if (test_and_clear_bit(ID, &motg->inputs)) { |
| pr_debug("ID clear\n"); |
| msm_otg_dbg_log_event(&motg->phy, "ID CLEAR", |
| motg->inputs, motg->phy.otg->state); |
| work = 1; |
| } |
| } |
| |
| if (work && (motg->phy.otg->state != OTG_STATE_UNDEFINED)) { |
| msm_otg_dbg_log_event(&motg->phy, |
| "CHECK ID EVENT DURING SUSPEND", |
| atomic_read(&motg->pm_suspended), |
| motg->sm_work_pending); |
| msm_otg_kick_sm_work(motg); |
| } |
| } |
| |
| #define MSM_ID_STATUS_DELAY 5 /* 5msec */ |
| static irqreturn_t msm_id_irq(int irq, void *data) |
| { |
| struct msm_otg *motg = data; |
| |
| /*schedule delayed work for 5msec for ID line state to settle*/ |
| queue_delayed_work(motg->otg_wq, &motg->id_status_work, |
| msecs_to_jiffies(MSM_ID_STATUS_DELAY)); |
| |
| return IRQ_HANDLED; |
| } |
| |
| int msm_otg_pm_notify(struct notifier_block *notify_block, |
| unsigned long mode, void *unused) |
| { |
| struct msm_otg *motg = container_of( |
| notify_block, struct msm_otg, pm_notify); |
| |
| dev_dbg(motg->phy.dev, "OTG PM notify:%lx, sm_pending:%u\n", mode, |
| motg->sm_work_pending); |
| msm_otg_dbg_log_event(&motg->phy, "PM NOTIFY", |
| mode, motg->sm_work_pending); |
| |
| switch (mode) { |
| case PM_POST_SUSPEND: |
| /* OTG sm_work can be armed now */ |
| atomic_set(&motg->pm_suspended, 0); |
| |
| /* Handle any deferred wakeup events from USB during suspend */ |
| if (motg->sm_work_pending) { |
| motg->sm_work_pending = false; |
| queue_work(motg->otg_wq, &motg->sm_work); |
| } |
| break; |
| |
| default: |
| break; |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| static int msm_otg_mode_show(struct seq_file *s, void *unused) |
| { |
| struct msm_otg *motg = s->private; |
| struct usb_otg *otg = motg->phy.otg; |
| |
| switch (otg->state) { |
| case OTG_STATE_A_HOST: |
| seq_puts(s, "host\n"); |
| break; |
| case OTG_STATE_B_IDLE: |
| case OTG_STATE_B_PERIPHERAL: |
| case OTG_STATE_B_SUSPEND: |
| seq_puts(s, "peripheral\n"); |
| break; |
| default: |
| seq_puts(s, "none\n"); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int msm_otg_mode_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, msm_otg_mode_show, inode->i_private); |
| } |
| |
| static ssize_t msm_otg_mode_write(struct file *file, const char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| struct seq_file *s = file->private_data; |
| struct msm_otg *motg = s->private; |
| char buf[16]; |
| struct usb_phy *phy = &motg->phy; |
| int status = count; |
| enum usb_mode_type req_mode; |
| |
| memset(buf, 0x00, sizeof(buf)); |
| |
| if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) { |
| status = -EFAULT; |
| goto out; |
| } |
| |
| if (!strcmp(buf, "host")) { |
| req_mode = USB_HOST; |
| } else if (!strcmp(buf, "peripheral")) { |
| req_mode = USB_PERIPHERAL; |
| } else if (!strcmp(buf, "none")) { |
| req_mode = USB_NONE; |
| } else { |
| status = -EINVAL; |
| goto out; |
| } |
| |
| switch (req_mode) { |
| case USB_NONE: |
| switch (phy->otg->state) { |
| case OTG_STATE_A_HOST: |
| case OTG_STATE_B_PERIPHERAL: |
| case OTG_STATE_B_SUSPEND: |
| set_bit(ID, &motg->inputs); |
| clear_bit(B_SESS_VLD, &motg->inputs); |
| break; |
| default: |
| goto out; |
| } |
| break; |
| case USB_PERIPHERAL: |
| switch (phy->otg->state) { |
| case OTG_STATE_B_IDLE: |
| case OTG_STATE_A_HOST: |
| set_bit(ID, &motg->inputs); |
| set_bit(B_SESS_VLD, &motg->inputs); |
| break; |
| default: |
| goto out; |
| } |
| break; |
| case USB_HOST: |
| switch (phy->otg->state) { |
| case OTG_STATE_B_IDLE: |
| case OTG_STATE_B_PERIPHERAL: |
| case OTG_STATE_B_SUSPEND: |
| clear_bit(ID, &motg->inputs); |
| break; |
| default: |
| goto out; |
| } |
| break; |
| default: |
| goto out; |
| } |
| |
| motg->id_state = (test_bit(ID, &motg->inputs)) ? USB_ID_FLOAT : |
| USB_ID_GROUND; |
| queue_work(motg->otg_wq, &motg->sm_work); |
| out: |
| return status; |
| } |
| |
| const struct file_operations msm_otg_mode_fops = { |
| .open = msm_otg_mode_open, |
| .read = seq_read, |
| .write = msm_otg_mode_write, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static int msm_otg_show_otg_state(struct seq_file *s, void *unused) |
| { |
| struct msm_otg *motg = s->private; |
| struct usb_phy *phy = &motg->phy; |
| |
| seq_printf(s, "%s\n", usb_otg_state_string(phy->otg->state)); |
| return 0; |
| } |
| |
| static int msm_otg_otg_state_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, msm_otg_show_otg_state, inode->i_private); |
| } |
| |
| const struct file_operations msm_otg_state_fops = { |
| .open = msm_otg_otg_state_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static int msm_otg_bus_show(struct seq_file *s, void *unused) |
| { |
| if (debug_bus_voting_enabled) |
| seq_puts(s, "enabled\n"); |
| else |
| seq_puts(s, "disabled\n"); |
| |
| return 0; |
| } |
| |
| static int msm_otg_bus_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, msm_otg_bus_show, inode->i_private); |
| } |
| |
| static ssize_t msm_otg_bus_write(struct file *file, const char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| char buf[8]; |
| struct seq_file *s = file->private_data; |
| struct msm_otg *motg = s->private; |
| |
| memset(buf, 0x00, sizeof(buf)); |
| |
| if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) |
| return -EFAULT; |
| |
| if (!strcmp(buf, "enable")) { |
| /* Do not vote here. Let OTG statemachine decide when to vote */ |
| debug_bus_voting_enabled = true; |
| } else { |
| debug_bus_voting_enabled = false; |
| msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE); |
| } |
| |
| return count; |
| } |
| |
| static int msm_otg_dbg_buff_show(struct seq_file *s, void *unused) |
| { |
| struct msm_otg *motg = s->private; |
| unsigned long flags; |
| unsigned int i; |
| |
| read_lock_irqsave(&motg->dbg_lock, flags); |
| |
| i = motg->dbg_idx; |
| if (strnlen(motg->buf[i], DEBUG_MSG_LEN)) |
| seq_printf(s, "%s\n", motg->buf[i]); |
| for (dbg_inc(&i); i != motg->dbg_idx; dbg_inc(&i)) { |
| if (!strnlen(motg->buf[i], DEBUG_MSG_LEN)) |
| continue; |
| seq_printf(s, "%s\n", motg->buf[i]); |
| } |
| read_unlock_irqrestore(&motg->dbg_lock, flags); |
| |
| return 0; |
| } |
| |
| static int msm_otg_dbg_buff_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, msm_otg_dbg_buff_show, inode->i_private); |
| } |
| |
| const struct file_operations msm_otg_dbg_buff_fops = { |
| .open = msm_otg_dbg_buff_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static int msm_otg_dpdm_regulator_enable(struct regulator_dev *rdev) |
| { |
| int ret = 0; |
| struct msm_otg *motg = rdev_get_drvdata(rdev); |
| |
| if (!motg->rm_pulldown) { |
| ret = msm_hsusb_ldo_enable(motg, USB_PHY_REG_3P3_ON); |
| if (!ret) { |
| motg->rm_pulldown = true; |
| msm_otg_dbg_log_event(&motg->phy, "RM Pulldown", |
| motg->rm_pulldown, 0); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int msm_otg_dpdm_regulator_disable(struct regulator_dev *rdev) |
| { |
| int ret = 0; |
| struct msm_otg *motg = rdev_get_drvdata(rdev); |
| |
| if (motg->rm_pulldown) { |
| ret = msm_hsusb_ldo_enable(motg, USB_PHY_REG_3P3_OFF); |
| if (!ret) { |
| motg->rm_pulldown = false; |
| msm_otg_dbg_log_event(&motg->phy, "RM Pulldown", |
| motg->rm_pulldown, 0); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int msm_otg_dpdm_regulator_is_enabled(struct regulator_dev *rdev) |
| { |
| struct msm_otg *motg = rdev_get_drvdata(rdev); |
| |
| return motg->rm_pulldown; |
| } |
| |
| static struct regulator_ops msm_otg_dpdm_regulator_ops = { |
| .enable = msm_otg_dpdm_regulator_enable, |
| .disable = msm_otg_dpdm_regulator_disable, |
| .is_enabled = msm_otg_dpdm_regulator_is_enabled, |
| }; |
| |
| static int usb_phy_regulator_init(struct msm_otg *motg) |
| { |
| struct device *dev = motg->phy.dev; |
| struct regulator_config cfg = {}; |
| struct regulator_init_data *init_data; |
| |
| init_data = devm_kzalloc(dev, sizeof(*init_data), GFP_KERNEL); |
| if (!init_data) |
| return -ENOMEM; |
| |
| init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_STATUS; |
| motg->dpdm_rdesc.owner = THIS_MODULE; |
| motg->dpdm_rdesc.type = REGULATOR_VOLTAGE; |
| motg->dpdm_rdesc.ops = &msm_otg_dpdm_regulator_ops; |
| motg->dpdm_rdesc.name = kbasename(dev->of_node->full_name); |
| |
| cfg.dev = dev; |
| cfg.init_data = init_data; |
| cfg.driver_data = motg; |
| cfg.of_node = dev->of_node; |
| |
| motg->dpdm_rdev = devm_regulator_register(dev, &motg->dpdm_rdesc, &cfg); |
| if (IS_ERR(motg->dpdm_rdev)) |
| return PTR_ERR(motg->dpdm_rdev); |
| |
| return 0; |
| } |
| |
| const struct file_operations msm_otg_bus_fops = { |
| .open = msm_otg_bus_open, |
| .read = seq_read, |
| .write = msm_otg_bus_write, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static struct dentry *msm_otg_dbg_root; |
| |
| static int msm_otg_debugfs_init(struct msm_otg *motg) |
| { |
| struct dentry *msm_otg_dentry; |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| |
| msm_otg_dbg_root = debugfs_create_dir("msm_otg", NULL); |
| |
| if (!msm_otg_dbg_root || IS_ERR(msm_otg_dbg_root)) |
| return -ENODEV; |
| |
| if ((pdata->mode == USB_OTG || pdata->mode == USB_PERIPHERAL) && |
| pdata->otg_control == OTG_USER_CONTROL) { |
| |
| msm_otg_dentry = debugfs_create_file("mode", 0644, |
| msm_otg_dbg_root, motg, &msm_otg_mode_fops); |
| |
| if (!msm_otg_dentry) { |
| debugfs_remove(msm_otg_dbg_root); |
| msm_otg_dbg_root = NULL; |
| return -ENODEV; |
| } |
| } |
| |
| msm_otg_dentry = debugfs_create_file("bus_voting", 0644, |
| msm_otg_dbg_root, motg, &msm_otg_bus_fops); |
| |
| if (!msm_otg_dentry) { |
| debugfs_remove_recursive(msm_otg_dbg_root); |
| return -ENODEV; |
| } |
| |
| msm_otg_dentry = debugfs_create_file("otg_state", 0444, |
| msm_otg_dbg_root, motg, &msm_otg_state_fops); |
| |
| if (!msm_otg_dentry) { |
| debugfs_remove_recursive(msm_otg_dbg_root); |
| return -ENODEV; |
| } |
| |
| msm_otg_dentry = debugfs_create_file("dbg_buff", 0444, |
| msm_otg_dbg_root, motg, &msm_otg_dbg_buff_fops); |
| |
| if (!msm_otg_dentry) { |
| debugfs_remove_recursive(msm_otg_dbg_root); |
| return -ENODEV; |
| } |
| return 0; |
| } |
| |
| static void msm_otg_debugfs_cleanup(void) |
| { |
| debugfs_remove_recursive(msm_otg_dbg_root); |
| } |
| |
| static ssize_t |
| set_msm_otg_perf_mode(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct msm_otg *motg = the_msm_otg; |
| int ret; |
| long clk_rate; |
| |
| pr_debug("%s: enable:%d\n", __func__, !strncasecmp(buf, "enable", 6)); |
| |
| if (!strncasecmp(buf, "enable", 6)) { |
| clk_rate = motg->core_clk_nominal_rate; |
| msm_otg_bus_freq_set(motg, USB_NOC_NOM_VOTE); |
| } else { |
| clk_rate = motg->core_clk_svs_rate; |
| msm_otg_bus_freq_set(motg, USB_NOC_SVS_VOTE); |
| } |
| |
| if (clk_rate) { |
| pr_debug("Set usb sys_clk rate:%ld\n", clk_rate); |
| ret = clk_set_rate(motg->core_clk, clk_rate); |
| if (ret) |
| pr_err("sys_clk set_rate fail:%d %ld\n", ret, clk_rate); |
| msm_otg_dbg_log_event(&motg->phy, "OTG PERF SET", |
| clk_rate, ret); |
| } else { |
| pr_err("usb sys_clk rate is undefined\n"); |
| } |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(perf_mode, 0200, NULL, set_msm_otg_perf_mode); |
| |
| #define MSM_OTG_CMD_ID 0x09 |
| #define MSM_OTG_DEVICE_ID 0x04 |
| #define MSM_OTG_VMID_IDX 0xFF |
| #define MSM_OTG_MEM_TYPE 0x02 |
| struct msm_otg_scm_cmd_buf { |
| unsigned int device_id; |
| unsigned int vmid_idx; |
| unsigned int mem_type; |
| } __attribute__ ((__packed__)); |
| |
| static void msm_otg_pnoc_errata_fix(struct msm_otg *motg) |
| { |
| int ret; |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| struct msm_otg_scm_cmd_buf cmd_buf; |
| |
| if (!pdata->pnoc_errata_fix) |
| return; |
| |
| dev_dbg(motg->phy.dev, "applying fix for pnoc h/w issue\n"); |
| |
| cmd_buf.device_id = MSM_OTG_DEVICE_ID; |
| cmd_buf.vmid_idx = MSM_OTG_VMID_IDX; |
| cmd_buf.mem_type = MSM_OTG_MEM_TYPE; |
| |
| ret = scm_call(SCM_SVC_MP, MSM_OTG_CMD_ID, &cmd_buf, |
| sizeof(cmd_buf), NULL, 0); |
| |
| if (ret) |
| dev_err(motg->phy.dev, "scm command failed to update VMIDMT\n"); |
| } |
| |
| static u64 msm_otg_dma_mask = DMA_BIT_MASK(32); |
| static struct platform_device *msm_otg_add_pdev( |
| struct platform_device *ofdev, const char *name) |
| { |
| struct platform_device *pdev; |
| const struct resource *res = ofdev->resource; |
| unsigned int num = ofdev->num_resources; |
| int retval; |
| struct ci13xxx_platform_data ci_pdata; |
| struct msm_otg_platform_data *otg_pdata; |
| struct msm_otg *motg; |
| |
| pdev = platform_device_alloc(name, -1); |
| if (!pdev) { |
| retval = -ENOMEM; |
| goto error; |
| } |
| |
| pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); |
| pdev->dev.dma_mask = &msm_otg_dma_mask; |
| pdev->dev.parent = &ofdev->dev; |
| |
| if (num) { |
| retval = platform_device_add_resources(pdev, res, num); |
| if (retval) |
| goto error; |
| } |
| |
| if (!strcmp(name, "msm_hsusb")) { |
| otg_pdata = |
| (struct msm_otg_platform_data *) |
| ofdev->dev.platform_data; |
| motg = platform_get_drvdata(ofdev); |
| ci_pdata.log2_itc = otg_pdata->log2_itc; |
| ci_pdata.usb_core_id = 0; |
| ci_pdata.l1_supported = otg_pdata->l1_supported; |
| ci_pdata.enable_ahb2ahb_bypass = |
| otg_pdata->enable_ahb2ahb_bypass; |
| ci_pdata.enable_streaming = otg_pdata->enable_streaming; |
| ci_pdata.enable_axi_prefetch = otg_pdata->enable_axi_prefetch; |
| retval = platform_device_add_data(pdev, &ci_pdata, |
| sizeof(ci_pdata)); |
| if (retval) |
| goto error; |
| } |
| |
| arch_setup_dma_ops(&pdev->dev, 0, DMA_BIT_MASK(32), NULL, false); |
| retval = platform_device_add(pdev); |
| if (retval) |
| goto error; |
| |
| return pdev; |
| |
| error: |
| platform_device_put(pdev); |
| return ERR_PTR(retval); |
| } |
| |
| static int msm_otg_setup_devices(struct platform_device *ofdev, |
| enum usb_mode_type mode, bool init) |
| { |
| const char *gadget_name = "msm_hsusb"; |
| const char *host_name = "msm_hsusb_host"; |
| static struct platform_device *gadget_pdev; |
| static struct platform_device *host_pdev; |
| int retval = 0; |
| |
| if (!init) { |
| if (gadget_pdev) { |
| device_remove_file(&gadget_pdev->dev, |
| &dev_attr_perf_mode); |
| platform_device_unregister(gadget_pdev); |
| } |
| if (host_pdev) |
| platform_device_unregister(host_pdev); |
| return 0; |
| } |
| |
| switch (mode) { |
| case USB_OTG: |
| /* fall through */ |
| case USB_PERIPHERAL: |
| gadget_pdev = msm_otg_add_pdev(ofdev, gadget_name); |
| if (IS_ERR(gadget_pdev)) { |
| retval = PTR_ERR(gadget_pdev); |
| break; |
| } |
| if (device_create_file(&gadget_pdev->dev, &dev_attr_perf_mode)) |
| dev_err(&gadget_pdev->dev, "perf_mode file failed\n"); |
| if (mode == USB_PERIPHERAL) |
| break; |
| /* fall through */ |
| case USB_HOST: |
| host_pdev = msm_otg_add_pdev(ofdev, host_name); |
| if (IS_ERR(host_pdev)) { |
| retval = PTR_ERR(host_pdev); |
| if (mode == USB_OTG) { |
| platform_device_unregister(gadget_pdev); |
| device_remove_file(&gadget_pdev->dev, |
| &dev_attr_perf_mode); |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return retval; |
| } |
| |
| static ssize_t dpdm_pulldown_enable_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct msm_otg *motg = the_msm_otg; |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| |
| return snprintf(buf, PAGE_SIZE, "%s\n", pdata->dpdm_pulldown_added ? |
| "enabled" : "disabled"); |
| } |
| |
| static ssize_t dpdm_pulldown_enable_store(struct device *dev, |
| struct device_attribute *attr, const char |
| *buf, size_t size) |
| { |
| struct msm_otg *motg = the_msm_otg; |
| struct msm_otg_platform_data *pdata = motg->pdata; |
| |
| if (!strncasecmp(buf, "enable", 6)) { |
| pdata->dpdm_pulldown_added = true; |
| return size; |
| } else if (!strncasecmp(buf, "disable", 7)) { |
| pdata->dpdm_pulldown_added = false; |
| return size; |
| } |
| |
| return -EINVAL; |
| } |
| |
| static DEVICE_ATTR(dpdm_pulldown_enable, 0644, |
| dpdm_pulldown_enable_show, dpdm_pulldown_enable_store); |
| |
| static int msm_otg_vbus_notifier(struct notifier_block *nb, unsigned long event, |
| void *ptr) |
| { |
| msm_otg_set_vbus_state(!!event); |
| |
| return NOTIFY_DONE; |
| } |
| |
| static int msm_otg_id_notifier(struct notifier_block *nb, unsigned long event, |
| void *ptr) |
| { |
| struct msm_otg *motg = container_of(nb, struct msm_otg, id_nb); |
| |
| if (event) |
| motg->id_state = USB_ID_GROUND; |
| else |
| motg->id_state = USB_ID_FLOAT; |
| |
| msm_id_status_w(&motg->id_status_work.work); |
| |
| return NOTIFY_DONE; |
| } |
| |
| static int msm_otg_extcon_register(struct msm_otg *motg) |
| { |
| struct device_node *node = motg->pdev->dev.of_node; |
| struct extcon_dev *edev; |
| int ret = 0; |
| |
| if (!of_property_read_bool(node, "extcon")) |
| return 0; |
| |
| edev = extcon_get_edev_by_phandle(&motg->pdev->dev, 0); |
| if (IS_ERR(edev) && PTR_ERR(edev) != -ENODEV) |
| return PTR_ERR(edev); |
| |
| if (!IS_ERR(edev)) { |
| motg->extcon_vbus = edev; |
| motg->vbus_nb.notifier_call = msm_otg_vbus_notifier; |
| ret = extcon_register_notifier(edev, EXTCON_USB, |
| &motg->vbus_nb); |
| if (ret < 0) { |
| dev_err(&motg->pdev->dev, "failed to register notifier for USB\n"); |
| return ret; |
| } |
| } |
| |
| if (of_count_phandle_with_args(node, "extcon", NULL) > 1) { |
| edev = extcon_get_edev_by_phandle(&motg->pdev->dev, 1); |
| if (IS_ERR(edev) && PTR_ERR(edev) != -ENODEV) { |
| ret = PTR_ERR(edev); |
| goto err; |
| } |
| } |
| |
| if (!IS_ERR(edev)) { |
| motg->extcon_id = edev; |
| motg->id_nb.notifier_call = msm_otg_id_notifier; |
| ret = extcon_register_notifier(edev, EXTCON_USB_HOST, |
| &motg->id_nb); |
| if (ret < 0) { |
| dev_err(&motg->pdev->dev, "failed to register notifier for USB-HOST\n"); |
| goto err; |
| } |
| } |
| |
| return 0; |
| err: |
| if (motg->extcon_vbus) |
| extcon_unregister_notifier(motg->extcon_vbus, EXTCON_USB, |
| &motg->vbus_nb); |
| |
| return ret; |
| } |
| |
| struct msm_otg_platform_data *msm_otg_dt_to_pdata(struct platform_device *pdev) |
| { |
| struct device_node *node = pdev->dev.of_node; |
| struct msm_otg_platform_data *pdata; |
| int len = 0; |
| int res_gpio; |
| |
| pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); |
| if (!pdata) |
| return NULL; |
| |
| len = of_property_count_elems_of_size(node, |
| "qcom,hsusb-otg-phy-init-seq", sizeof(len)); |
| if (len > 0) { |
| pdata->phy_init_seq = devm_kzalloc(&pdev->dev, |
| len * sizeof(len), GFP_KERNEL); |
| if (!pdata->phy_init_seq) |
| return NULL; |
| of_property_read_u32_array(node, "qcom,hsusb-otg-phy-init-seq", |
| pdata->phy_init_seq, len); |
| } |
| of_property_read_u32(node, "qcom,hsusb-otg-power-budget", |
| &pdata->power_budget); |
| of_property_read_u32(node, "qcom,hsusb-otg-mode", |
| &pdata->mode); |
| of_property_read_u32(node, "qcom,hsusb-otg-otg-control", |
| &pdata->otg_control); |
| of_property_read_u32(node, "qcom,hsusb-otg-default-mode", |
| &pdata->default_mode); |
| of_property_read_u32(node, "qcom,hsusb-otg-phy-type", |
| &pdata->phy_type); |
| pdata->disable_reset_on_disconnect = of_property_read_bool(node, |
| "qcom,hsusb-otg-disable-reset"); |
| pdata->pnoc_errata_fix = of_property_read_bool(node, |
| "qcom,hsusb-otg-pnoc-errata-fix"); |
| pdata->enable_lpm_on_dev_suspend = of_property_read_bool(node, |
| "qcom,hsusb-otg-lpm-on-dev-suspend"); |
| pdata->core_clk_always_on_workaround = of_property_read_bool(node, |
| "qcom,hsusb-otg-clk-always-on-workaround"); |
| pdata->delay_lpm_on_disconnect = of_property_read_bool(node, |
| "qcom,hsusb-otg-delay-lpm"); |
| pdata->dp_manual_pullup = of_property_read_bool(node, |
| "qcom,dp-manual-pullup"); |
| pdata->enable_sec_phy = of_property_read_bool(node, |
| "qcom,usb2-enable-hsphy2"); |
| of_property_read_u32(node, "qcom,hsusb-log2-itc", |
| &pdata->log2_itc); |
| |
| pdata->pmic_id_irq = platform_get_irq_byname(pdev, "pmic_id_irq"); |
| if (pdata->pmic_id_irq < 0) |
| pdata->pmic_id_irq = 0; |
| |
| pdata->hub_reset_gpio = of_get_named_gpio( |
| node, "qcom,hub-reset-gpio", 0); |
| if (!gpio_is_valid(pdata->hub_reset_gpio)) |
| pr_debug("hub_reset_gpio is not available\n"); |
| |
| pdata->usbeth_reset_gpio = of_get_named_gpio( |
| node, "qcom,usbeth-reset-gpio", 0); |
| if (!gpio_is_valid(pdata->usbeth_reset_gpio)) |
| pr_debug("usbeth_reset_gpio is not available\n"); |
| |
| pdata->switch_sel_gpio = |
| of_get_named_gpio(node, "qcom,sw-sel-gpio", 0); |
| if (!gpio_is_valid(pdata->switch_sel_gpio)) |
| pr_debug("switch_sel_gpio is not available\n"); |
| |
| pdata->usb_id_gpio = |
| of_get_named_gpio(node, "qcom,usbid-gpio", 0); |
| if (!gpio_is_valid(pdata->usb_id_gpio)) |
| pr_debug("usb_id_gpio is not available\n"); |
| |
| pdata->l1_supported = of_property_read_bool(node, |
| "qcom,hsusb-l1-supported"); |
| pdata->enable_ahb2ahb_bypass = of_property_read_bool(node, |
| "qcom,ahb-async-bridge-bypass"); |
| pdata->disable_retention_with_vdd_min = of_property_read_bool(node, |
| "qcom,disable-retention-with-vdd-min"); |
| pdata->enable_phy_id_pullup = of_property_read_bool(node, |
| "qcom,enable-phy-id-pullup"); |
| pdata->phy_dvdd_always_on = of_property_read_bool(node, |
| "qcom,phy-dvdd-always-on"); |
| |
| res_gpio = of_get_named_gpio(node, "qcom,hsusb-otg-vddmin-gpio", 0); |
| if (!gpio_is_valid(res_gpio)) |
| res_gpio = 0; |
| pdata->vddmin_gpio = res_gpio; |
| |
| pdata->emulation = of_property_read_bool(node, |
| "qcom,emulation"); |
| |
| pdata->enable_streaming = of_property_read_bool(node, |
| "qcom,boost-sysclk-with-streaming"); |
| |
| pdata->enable_axi_prefetch = of_property_read_bool(node, |
| "qcom,axi-prefetch-enable"); |
| |
| pdata->vbus_low_as_hostmode = of_property_read_bool(node, |
| "qcom,vbus-low-as-hostmode"); |
| return pdata; |
| } |
| |
| static int msm_otg_probe(struct platform_device *pdev) |
| { |
| int ret = 0; |
| int len = 0; |
| u32 tmp[3]; |
| struct resource *res; |
| struct msm_otg *motg; |
| struct usb_phy *phy; |
| struct msm_otg_platform_data *pdata; |
| void __iomem *tcsr; |
| int id_irq = 0; |
| |
| dev_info(&pdev->dev, "msm_otg probe\n"); |
| |
| motg = kzalloc(sizeof(struct msm_otg), GFP_KERNEL); |
| if (!motg) { |
| ret = -ENOMEM; |
| return ret; |
| } |
| |
| /* |
| * USB Core is running its protocol engine based on CORE CLK, |
| * CORE CLK must be running at >55Mhz for correct HSUSB |
| * operation and USB core cannot tolerate frequency changes on |
| * CORE CLK. For such USB cores, vote for maximum clk frequency |
| * on pclk source |
| */ |
| motg->core_clk = clk_get(&pdev->dev, "core_clk"); |
| if (IS_ERR(motg->core_clk)) { |
| ret = PTR_ERR(motg->core_clk); |
| motg->core_clk = NULL; |
| if (ret != -EPROBE_DEFER) |
| dev_err(&pdev->dev, "failed to get core_clk\n"); |
| goto free_motg; |
| } |
| |
| motg->core_reset = devm_reset_control_get(&pdev->dev, "core_reset"); |
| if (IS_ERR(motg->core_reset)) { |
| dev_err(&pdev->dev, "failed to get core_reset\n"); |
| ret = PTR_ERR(motg->core_reset); |
| goto put_core_clk; |
| } |
| |
| /* |
| * USB Core CLK can run at max freq if streaming is enabled. Hence, |
| * get Max supported clk frequency for USB Core CLK and request to set |
| * the same. Otherwise set USB Core CLK to defined default value. |
| */ |
| if (of_property_read_u32(pdev->dev.of_node, |
| "qcom,max-nominal-sysclk-rate", &ret)) { |
| ret = -EINVAL; |
| goto put_core_clk; |
| } else { |
| motg->core_clk_nominal_rate = clk_round_rate(motg->core_clk, |
| ret); |
| } |
| |
| if (of_property_read_u32(pdev->dev.of_node, |
| "qcom,max-svs-sysclk-rate", &ret)) { |
| dev_dbg(&pdev->dev, "core_clk svs freq not specified\n"); |
| } else { |
| motg->core_clk_svs_rate = clk_round_rate(motg->core_clk, ret); |
| } |
| |
| motg->default_noc_mode = USB_NOC_NOM_VOTE; |
| if (of_property_read_bool(pdev->dev.of_node, "qcom,default-mode-svs")) { |
| motg->core_clk_rate = motg->core_clk_svs_rate; |
| motg->default_noc_mode = USB_NOC_SVS_VOTE; |
| } else if (of_property_read_bool(pdev->dev.of_node, |
| "qcom,boost-sysclk-with-streaming")) { |
| motg->core_clk_rate = motg->core_clk_nominal_rate; |
| } else { |
| motg->core_clk_rate = clk_round_rate(motg->core_clk, |
| USB_DEFAULT_SYSTEM_CLOCK); |
| } |
| |
| if (IS_ERR_VALUE(motg->core_clk_rate)) { |
| dev_err(&pdev->dev, "fail to get core clk max freq.\n"); |
| } else { |
| ret = clk_set_rate(motg->core_clk, motg->core_clk_rate); |
| if (ret) |
| dev_err(&pdev->dev, "fail to set core_clk freq:%d\n", |
| ret); |
| } |
| |
| motg->pclk = clk_get(&pdev->dev, "iface_clk"); |
| if (IS_ERR(motg->pclk)) { |
| ret = PTR_ERR(motg->pclk); |
| motg->pclk = NULL; |
| if (ret != -EPROBE_DEFER) |
| dev_err(&pdev->dev, "failed to get iface_clk\n"); |
| goto put_core_clk; |
| } |
| |
| motg->xo_clk = clk_get(&pdev->dev, "xo"); |
| if (IS_ERR(motg->xo_clk)) { |
| ret = PTR_ERR(motg->xo_clk); |
| motg->xo_clk = NULL; |
| if (ret == -EPROBE_DEFER) |
| goto put_pclk; |
| } |
| |
| /* |
| * On few platforms USB PHY is fed with sleep clk. |
| * Hence don't fail probe. |
| */ |
| motg->sleep_clk = devm_clk_get(&pdev->dev, "sleep_clk"); |
| if (IS_ERR(motg->sleep_clk)) { |
| ret = PTR_ERR(motg->sleep_clk); |
| motg->sleep_clk = NULL; |
| if (ret == -EPROBE_DEFER) |
| goto put_xo_clk; |
| else |
| dev_dbg(&pdev->dev, "failed to get sleep_clk\n"); |
| } else { |
| ret = clk_prepare_enable(motg->sleep_clk); |
| if (ret) { |
| dev_err(&pdev->dev, "%s failed to vote sleep_clk%d\n", |
| __func__, ret); |
| goto put_xo_clk; |
| } |
| } |
| |
| /* |
| * If present, phy_reset_clk is used to reset the PHY, ULPI bridge |
| * and CSR Wrapper. This is a reset only clock. |
| */ |
| |
| if (of_property_match_string(pdev->dev.of_node, |
| "clock-names", "phy_reset_clk") >= 0) { |
| motg->phy_reset_clk = devm_clk_get(&pdev->dev, "phy_reset_clk"); |
| if (IS_ERR(motg->phy_reset_clk)) { |
| ret = PTR_ERR(motg->phy_reset_clk); |
| goto disable_sleep_clk; |
| } |
| |
| motg->phy_reset = devm_reset_control_get(&pdev->dev, |
| "phy_reset"); |
| if (IS_ERR(motg->phy_reset)) { |
| dev_err(&pdev->dev, "failed to get phy_reset\n"); |
| ret = PTR_ERR(motg->phy_reset); |
| goto disable_sleep_clk; |
| } |
| } |
| |
| /* |
| * If present, phy_por_clk is used to assert/de-assert phy POR |
| * input. This is a reset only clock. phy POR must be asserted |
| * after overriding the parameter registers via CSR wrapper or |
| * ULPI bridge. |
| */ |
| if (of_property_match_string(pdev->dev.of_node, |
| "clock-names", "phy_por_clk") >= 0) { |
| motg->phy_por_clk = devm_clk_get(&pdev->dev, "phy_por_clk"); |
| if (IS_ERR(motg->phy_por_clk)) { |
| ret = PTR_ERR(motg->phy_por_clk); |
| goto disable_sleep_clk; |
| } |
| |
| motg->phy_por_reset = devm_reset_control_get(&pdev->dev, |
| "phy_por_reset"); |
| if (IS_ERR(motg->phy_por_reset)) { |
| dev_err(&pdev->dev, "failed to get phy_por_reset\n"); |
| ret = PTR_ERR(motg->phy_por_reset); |
| goto disable_sleep_clk; |
| } |
| } |
| |
| /* |
| * If present, phy_csr_clk is required for accessing PHY |
| * CSR registers via AHB2PHY interface. |
| */ |
| if (of_property_match_string(pdev->dev.of_node, |
| "clock-names", "phy_csr_clk") >= 0) { |
| motg->phy_csr_clk = devm_clk_get(&pdev->dev, "phy_csr_clk"); |
| if (IS_ERR(motg->phy_csr_clk)) { |
| ret = PTR_ERR(motg->phy_csr_clk); |
| goto disable_sleep_clk; |
| } else { |
| ret = clk_prepare_enable(motg->phy_csr_clk); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "fail to enable phy csr clk %d\n", ret); |
| goto disable_sleep_clk; |
| } |
| } |
| } |
| |
| of_property_read_u32(pdev->dev.of_node, "qcom,pm-qos-latency", |
| &motg->pm_qos_latency); |
| |
| pdata = msm_otg_dt_to_pdata(pdev); |
| if (!pdata) { |
| ret = -ENOMEM; |
| goto disable_phy_csr_clk; |
| } |
| pdev->dev.platform_data = pdata; |
| |
| pdata->bus_scale_table = msm_bus_cl_get_pdata(pdev); |
| if (!pdata->bus_scale_table) |
| dev_dbg(&pdev->dev, "bus scaling is disabled\n"); |
| |
| if (pdata->phy_type == QUSB_ULPI_PHY) { |
| if (of_property_match_string(pdev->dev.of_node, |
| "clock-names", "phy_ref_clk") >= 0) { |
| motg->phy_ref_clk = devm_clk_get(&pdev->dev, |
| "phy_ref_clk"); |
| if (IS_ERR(motg->phy_ref_clk)) { |
| ret = PTR_ERR(motg->phy_ref_clk); |
| goto disable_phy_csr_clk; |
| } else { |
| ret = clk_prepare_enable(motg->phy_ref_clk); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "fail to enable phy ref clk %d\n", |
| ret); |
| goto disable_phy_csr_clk; |
| } |
| } |
| } |
| } |
| |
| motg->phy.otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg), |
| GFP_KERNEL); |
| if (!motg->phy.otg) { |
| ret = -ENOMEM; |
| goto disable_phy_csr_clk; |
| } |
| |
| the_msm_otg = motg; |
| motg->pdata = pdata; |
| phy = &motg->phy; |
| phy->dev = &pdev->dev; |
| motg->pdev = pdev; |
| motg->dbg_idx = 0; |
| motg->dbg_lock = __RW_LOCK_UNLOCKED(lck); |
| |
| if (motg->pdata->bus_scale_table) { |
| motg->bus_perf_client = |
| msm_bus_scale_register_client(motg->pdata->bus_scale_table); |
| if (!motg->bus_perf_client) { |
| dev_err(motg->phy.dev, "%s: Failed to register BUS\n" |
| "scaling client!!\n", __func__); |
| } else { |
| debug_bus_voting_enabled = true; |
| /* Some platforms require BUS vote to control clocks */ |
| msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE); |
| } |
| } |
| |
| ret = msm_otg_bus_freq_get(motg); |
| if (ret) { |
| pr_err("failed to get noc clocks: %d\n", ret); |
| } else { |
| ret = msm_otg_bus_freq_set(motg, motg->default_noc_mode); |
| if (ret) |
| pr_err("failed to vote explicit noc rates: %d\n", ret); |
| } |
| |
| /* initialize reset counter */ |
| motg->reset_counter = 0; |
| |
| res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); |
| if (!res) { |
| dev_err(&pdev->dev, "failed to get core iomem resource\n"); |
| ret = -ENODEV; |
| goto devote_bus_bw; |
| } |
| |
| motg->io_res = res; |
| motg->regs = ioremap(res->start, resource_size(res)); |
| if (!motg->regs) { |
| dev_err(&pdev->dev, "core iomem ioremap failed\n"); |
| ret = -ENOMEM; |
| goto devote_bus_bw; |
| } |
| dev_info(&pdev->dev, "OTG regs = %pK\n", motg->regs); |
| |
| if (pdata->enable_sec_phy) { |
| res = platform_get_resource_byname(pdev, |
| IORESOURCE_MEM, "tcsr"); |
| if (!res) { |
| dev_dbg(&pdev->dev, "missing TCSR memory resource\n"); |
| } else { |
| tcsr = devm_ioremap_nocache(&pdev->dev, res->start, |
| resource_size(res)); |
| if (!tcsr) { |
| dev_dbg(&pdev->dev, "tcsr ioremap failed\n"); |
| } else { |
| /* Enable USB2 on secondary HSPHY. */ |
| writel_relaxed(0x1, tcsr); |
| /* |
| * Ensure that TCSR write is completed before |
| * USB registers initialization. |
| */ |
| mb(); |
| } |
| } |
| } |
| |
| if (pdata->enable_sec_phy) |
| motg->usb_phy_ctrl_reg = USB_PHY_CTRL2; |
| else |
| motg->usb_phy_ctrl_reg = USB_PHY_CTRL; |
| |
| /* |
| * The USB PHY wrapper provides a register interface |
| * through AHB2PHY for performing PHY related operations |
| * like retention, HV interrupts and overriding parameter |
| * registers etc. The registers start at 4 byte boundary |
| * but only the first byte is valid and remaining are not |
| * used. Relaxed versions of readl/writel should be used. |
| * |
| * The link does not have any PHY specific registers. |
| * Hence set motg->usb_phy_ctrl_reg to. |
| */ |
| if (motg->pdata->phy_type == SNPS_FEMTO_PHY || |
| pdata->phy_type == QUSB_ULPI_PHY) { |
| res = platform_get_resource_byname(pdev, |
| IORESOURCE_MEM, "phy_csr"); |
| if (!res) { |
| dev_err(&pdev->dev, "PHY CSR IOMEM missing!\n"); |
| ret = -ENODEV; |
| goto free_regs; |
| } |
| motg->phy_csr_regs = devm_ioremap_resource(&pdev->dev, res); |
| if (IS_ERR(motg->phy_csr_regs)) { |
| ret = PTR_ERR(motg->phy_csr_regs); |
| dev_err(&pdev->dev, "PHY CSR ioremap failed!\n"); |
| goto free_regs; |
| } |
| motg->usb_phy_ctrl_reg = 0; |
| } |
| |
| motg->irq = platform_get_irq(pdev, 0); |
| if (!motg->irq) { |
| dev_err(&pdev->dev, "platform_get_irq failed\n"); |
| ret = -ENODEV; |
| goto free_regs; |
| } |
| |
| motg->async_irq = platform_get_irq_byname(pdev, "async_irq"); |
| if (motg->async_irq < 0) { |
| dev_err(&pdev->dev, "platform_get_irq for async_int failed\n"); |
| motg->async_irq = 0; |
| goto free_regs; |
| } |
| |
| if (motg->xo_clk) { |
| ret = clk_prepare_enable(motg->xo_clk); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "%s failed to vote for TCXO %d\n", |
| __func__, ret); |
| goto free_xo_handle; |
| } |
| } |
| |
| |
| clk_prepare_enable(motg->pclk); |
| |
| hsusb_vdd = devm_regulator_get(motg->phy.dev, "hsusb_vdd_dig"); |
| if (IS_ERR(hsusb_vdd)) { |
| hsusb_vdd = devm_regulator_get(motg->phy.dev, "HSUSB_VDDCX"); |
| if (IS_ERR(hsusb_vdd)) { |
| dev_err(motg->phy.dev, "unable to get hsusb vddcx\n"); |
| ret = PTR_ERR(hsusb_vdd); |
| goto devote_xo_handle; |
| } |
| } |
| |
| len = of_property_count_elems_of_size(pdev->dev.of_node, |
| "qcom,vdd-voltage-level", sizeof(len)); |
| if (len > 0) { |
| if (len == sizeof(tmp) / sizeof(len)) { |
| of_property_read_u32_array(pdev->dev.of_node, |
| "qcom,vdd-voltage-level", |
| tmp, len); |
| vdd_val[0] = tmp[0]; |
| vdd_val[1] = tmp[1]; |
| vdd_val[2] = tmp[2]; |
| } else { |
| dev_dbg(&pdev->dev, |
| "Using default hsusb vdd config.\n"); |
| goto devote_xo_handle; |
| } |
| } else { |
| goto devote_xo_handle; |
| } |
| |
| ret = msm_hsusb_config_vddcx(1); |
| if (ret) { |
| dev_err(&pdev->dev, "hsusb vddcx configuration failed\n"); |
| goto devote_xo_handle; |
| } |
| |
| ret = regulator_enable(hsusb_vdd); |
| if (ret) { |
| dev_err(&pdev->dev, "unable to enable the hsusb vddcx\n"); |
| goto free_config_vddcx; |
| } |
| |
| ret = msm_hsusb_ldo_init(motg, 1); |
| if (ret) { |
| dev_err(&pdev->dev, "hsusb vreg configuration failed\n"); |
| goto free_hsusb_vdd; |
| } |
| |
| /* Get pinctrl if target uses pinctrl */ |
| motg->phy_pinctrl = devm_pinctrl_get(&pdev->dev); |
| if (IS_ERR(motg->phy_pinctrl)) { |
| if (of_property_read_bool(pdev->dev.of_node, "pinctrl-names")) { |
| dev_err(&pdev->dev, "Error encountered while getting pinctrl"); |
| ret = PTR_ERR(motg->phy_pinctrl); |
| goto free_ldo_init; |
| } |
| dev_dbg(&pdev->dev, "Target does not use pinctrl\n"); |
| motg->phy_pinctrl = NULL; |
| } |
| |
| ret = msm_hsusb_ldo_enable(motg, USB_PHY_REG_ON); |
| if (ret) { |
| dev_err(&pdev->dev, "hsusb vreg enable failed\n"); |
| goto free_ldo_init; |
| } |
| clk_prepare_enable(motg->core_clk); |
| |
| /* Check if USB mem_type change is needed to workaround PNOC hw issue */ |
| msm_otg_pnoc_errata_fix(motg); |
| |
| writel_relaxed(0, USB_USBINTR); |
| writel_relaxed(0, USB_OTGSC); |
| /* Ensure that above STOREs are completed before enabling interrupts */ |
| mb(); |
| |
| motg->id_state = USB_ID_FLOAT; |
| set_bit(ID, &motg->inputs); |
| INIT_WORK(&motg->sm_work, msm_otg_sm_work); |
| INIT_DELAYED_WORK(&motg->id_status_work, msm_id_status_w); |
| INIT_DELAYED_WORK(&motg->perf_vote_work, msm_otg_perf_vote_work); |
| INIT_DELAYED_WORK(&motg->sdp_check, check_for_sdp_connection); |
| INIT_WORK(&motg->notify_chg_current_work, |
| msm_otg_notify_chg_current_work); |
| motg->otg_wq = alloc_ordered_workqueue("k_otg", 0); |
| if (!motg->otg_wq) { |
| pr_err("%s: Unable to create workqueue otg_wq\n", |
| __func__); |
| goto disable_core_clk; |
| } |
| |
| ret = devm_request_irq(&pdev->dev, motg->irq, msm_otg_irq, IRQF_SHARED, |
| "msm_otg", motg); |
| if (ret) { |
| dev_err(&pdev->dev, "request irq failed\n"); |
| goto destroy_wq; |
| } |
| |
| motg->phy_irq = platform_get_irq_byname(pdev, "phy_irq"); |
| if (motg->phy_irq < 0) { |
| dev_dbg(&pdev->dev, "phy_irq is not present\n"); |
| motg->phy_irq = 0; |
| } else { |
| |
| /* clear all interrupts before enabling the IRQ */ |
| writeb_relaxed(0xFF, USB2_PHY_USB_PHY_INTERRUPT_CLEAR0); |
| writeb_relaxed(0xFF, USB2_PHY_USB_PHY_INTERRUPT_CLEAR1); |
| |
| writeb_relaxed(0x1, USB2_PHY_USB_PHY_IRQ_CMD); |
| /* |
| * Databook says 200 usec delay is required for |
| * clearing the interrupts. |
| */ |
| udelay(200); |
| writeb_relaxed(0x0, USB2_PHY_USB_PHY_IRQ_CMD); |
| |
| ret = devm_request_irq(&pdev->dev, motg->phy_irq, |
| msm_otg_phy_irq_handler, IRQF_TRIGGER_RISING, |
| "msm_otg_phy_irq", motg); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "phy_irq request fail %d\n", ret); |
| goto destroy_wq; |
| } |
| } |
| |
| ret = devm_request_irq(&pdev->dev, motg->async_irq, msm_otg_irq, |
| IRQF_TRIGGER_RISING, "msm_otg", motg); |
| if (ret) { |
| dev_err(&pdev->dev, "request irq failed (ASYNC INT)\n"); |
| goto destroy_wq; |
| } |
| disable_irq(motg->async_irq); |
| |
| phy->init = msm_otg_reset; |
| phy->set_power = msm_otg_set_power; |
| phy->set_suspend = msm_otg_set_suspend; |
| phy->dbg_event = msm_otg_dbg_log_event; |
| |
| phy->io_ops = &msm_otg_io_ops; |
| |
| phy->otg->usb_phy = &motg->phy; |
| phy->otg->set_host = msm_otg_set_host; |
| phy->otg->set_peripheral = msm_otg_set_peripheral; |
| if (pdata->dp_manual_pullup) |
| phy->flags |= ENABLE_DP_MANUAL_PULLUP; |
| |
| if (pdata->enable_sec_phy) |
| phy->flags |= ENABLE_SECONDARY_PHY; |
| |
| ret = usb_add_phy(&motg->phy, USB_PHY_TYPE_USB2); |
| if (ret) { |
| dev_err(&pdev->dev, "usb_add_phy failed\n"); |
| goto destroy_wq; |
| } |
| |
| ret = usb_phy_regulator_init(motg); |
| if (ret) { |
| dev_err(&pdev->dev, "usb_phy_regulator_init failed\n"); |
| goto remove_phy; |
| } |
| |
| if (motg->pdata->mode == USB_OTG && |
| motg->pdata->otg_control == OTG_PMIC_CONTROL && |
| !motg->phy_irq) { |
| |
| if (gpio_is_valid(motg->pdata->usb_id_gpio)) { |
| /* usb_id_gpio request */ |
| ret = devm_gpio_request(&pdev->dev, |
| motg->pdata->usb_id_gpio, |
| "USB_ID_GPIO"); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "gpio req failed for id\n"); |
| goto phy_reg_deinit; |
| } |
| |
| /* |
| * The following code implements switch between the HOST |
| * mode to device mode when used different HW components |
| * on the same port: USB HUB and the usb jack type B |
| * for device mode In this case HUB should be gone |
| * only once out of reset at the boot time and after |
| * that always stay on |
| */ |
| if (gpio_is_valid(motg->pdata->hub_reset_gpio)) { |
| ret = devm_gpio_request(&pdev->dev, |
| motg->pdata->hub_reset_gpio, |
| "qcom,hub-reset-gpio"); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "gpio req failed for hub reset\n"); |
| goto phy_reg_deinit; |
| } |
| gpio_direction_output( |
| motg->pdata->hub_reset_gpio, 1); |
| } |
| |
| if (gpio_is_valid(motg->pdata->switch_sel_gpio)) { |
| ret = devm_gpio_request(&pdev->dev, |
| motg->pdata->switch_sel_gpio, |
| "qcom,sw-sel-gpio"); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "gpio req failed for switch sel\n"); |
| goto phy_reg_deinit; |
| } |
| if (gpio_get_value(motg->pdata->usb_id_gpio)) |
| gpio_direction_input( |
| motg->pdata->switch_sel_gpio); |
| |
| else |
| gpio_direction_output( |
| motg->pdata->switch_sel_gpio, |
| 1); |
| } |
| |
| /* usb_id_gpio to irq */ |
| id_irq = gpio_to_irq(motg->pdata->usb_id_gpio); |
| motg->ext_id_irq = id_irq; |
| } else if (motg->pdata->pmic_id_irq) { |
| id_irq = motg->pdata->pmic_id_irq; |
| } |
| |
| if (id_irq) { |
| ret = devm_request_irq(&pdev->dev, id_irq, |
| msm_id_irq, |
| IRQF_TRIGGER_RISING | |
| IRQF_TRIGGER_FALLING, |
| "msm_otg", motg); |
| if (ret) { |
| dev_err(&pdev->dev, "request irq failed for ID\n"); |
| goto phy_reg_deinit; |
| } |
| } else { |
| /* PMIC does USB ID detection and notifies through |
| * USB_OTG property of USB powersupply. |
| */ |
| dev_dbg(&pdev->dev, "PMIC does ID detection\n"); |
| } |
| } |
| |
| platform_set_drvdata(pdev, motg); |
| device_init_wakeup(&pdev->dev, 1); |
| |
| ret = msm_otg_debugfs_init(motg); |
| if (ret) |
| dev_dbg(&pdev->dev, "mode debugfs file is not available\n"); |
| |
| if (motg->pdata->otg_control == OTG_PMIC_CONTROL && |
| (!(motg->pdata->mode == USB_OTG) || |
| motg->pdata->pmic_id_irq || motg->ext_id_irq || |
| !motg->phy_irq)) |
| motg->caps = ALLOW_PHY_POWER_COLLAPSE | ALLOW_PHY_RETENTION; |
| |
| if (motg->pdata->otg_control == OTG_PHY_CONTROL || motg->phy_irq || |
| motg->pdata->enable_phy_id_pullup) |
| motg->caps = ALLOW_PHY_RETENTION | ALLOW_PHY_REGULATORS_LPM; |
| |
| motg->caps |= ALLOW_HOST_PHY_RETENTION; |
| |
| device_create_file(&pdev->dev, &dev_attr_dpdm_pulldown_enable); |
| |
| if (motg->pdata->enable_lpm_on_dev_suspend) |
| motg->caps |= ALLOW_LPM_ON_DEV_SUSPEND; |
| |
| if (motg->pdata->disable_retention_with_vdd_min) |
| motg->caps |= ALLOW_VDD_MIN_WITH_RETENTION_DISABLED; |
| |
| /* |
| * PHY DVDD is supplied by a always on PMIC LDO (unlike |
| * vddcx/vddmx). PHY can keep D+ pull-up and D+/D- |
| * pull-down during suspend without any additional |
| * hardware re-work. |
| */ |
| if (motg->pdata->phy_type == SNPS_FEMTO_PHY) |
| motg->caps |= ALLOW_BUS_SUSPEND_WITHOUT_REWORK; |
| |
| pm_stay_awake(&pdev->dev); |
| pm_runtime_set_active(&pdev->dev); |
| pm_runtime_enable(&pdev->dev); |
| |
| if (motg->pdata->delay_lpm_on_disconnect) { |
| pm_runtime_set_autosuspend_delay(&pdev->dev, |
| lpm_disconnect_thresh); |
| pm_runtime_use_autosuspend(&pdev->dev); |
| } |
| |
| if (pdev->dev.of_node) { |
| ret = msm_otg_setup_devices(pdev, pdata->mode, true); |
| if (ret) { |
| dev_err(&pdev->dev, "devices setup failed\n"); |
| goto remove_cdev; |
| } |
| } |
| |
| psy = power_supply_get_by_name("usb"); |
| if (!psy) { |
| dev_dbg(&pdev->dev, "Could not get usb power_supply\n"); |
| ret = -EPROBE_DEFER; |
| goto otg_remove_devices; |
| } |
| |
| |
| ret = msm_otg_extcon_register(motg); |
| if (ret) |
| goto put_psy; |
| |
| if (motg->extcon_vbus && extcon_get_cable_state_(motg->extcon_vbus, |
| EXTCON_USB)) |
| msm_otg_vbus_notifier(&motg->vbus_nb, true, motg->extcon_vbus); |
| |
| if (motg->extcon_id && extcon_get_cable_state_(motg->extcon_id, |
| EXTCON_USB_HOST)) |
| msm_otg_id_notifier(&motg->id_nb, true, motg->extcon_id); |
| |
| if (gpio_is_valid(motg->pdata->hub_reset_gpio)) { |
| ret = devm_gpio_request(&pdev->dev, |
| motg->pdata->hub_reset_gpio, |
| "HUB_RESET"); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "gpio req failed for hub_reset\n"); |
| } else { |
| gpio_direction_output( |
| motg->pdata->hub_reset_gpio, 0); |
| /* 5 microsecs reset signaling to usb hub */ |
| usleep_range(5, 10); |
| gpio_direction_output( |
| motg->pdata->hub_reset_gpio, 1); |
| } |
| } |
| |
| if (gpio_is_valid(motg->pdata->usbeth_reset_gpio)) { |
| ret = devm_gpio_request(&pdev->dev, |
| motg->pdata->usbeth_reset_gpio, |
| "ETH_RESET"); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "gpio req failed for usbeth_reset\n"); |
| } else { |
| gpio_direction_output( |
| motg->pdata->usbeth_reset_gpio, 0); |
| /* 100 microsecs reset signaling to usb-to-eth */ |
| usleep_range(100, 110); |
| gpio_direction_output( |
| motg->pdata->usbeth_reset_gpio, 1); |
| } |
| } |
| |
| motg->pm_notify.notifier_call = msm_otg_pm_notify; |
| register_pm_notifier(&motg->pm_notify); |
| msm_otg_dbg_log_event(phy, "OTG PROBE", motg->caps, motg->lpm_flags); |
| |
| return 0; |
| |
| put_psy: |
| if (psy) |
| power_supply_put(psy); |
| otg_remove_devices: |
| if (pdev->dev.of_node) |
| msm_otg_setup_devices(pdev, motg->pdata->mode, false); |
| remove_cdev: |
| pm_runtime_disable(&pdev->dev); |
| device_remove_file(&pdev->dev, &dev_attr_dpdm_pulldown_enable); |
| msm_otg_debugfs_cleanup(); |
| phy_reg_deinit: |
| devm_regulator_unregister(motg->phy.dev, motg->dpdm_rdev); |
| remove_phy: |
| usb_remove_phy(&motg->phy); |
| destroy_wq: |
| destroy_workqueue(motg->otg_wq); |
| disable_core_clk: |
| clk_disable_unprepare(motg->core_clk); |
| msm_hsusb_ldo_enable(motg, USB_PHY_REG_OFF); |
| free_ldo_init: |
| msm_hsusb_ldo_init(motg, 0); |
| free_hsusb_vdd: |
| regulator_disable(hsusb_vdd); |
| free_config_vddcx: |
| regulator_set_voltage(hsusb_vdd, |
| vdd_val[VDD_NONE], |
| vdd_val[VDD_MAX]); |
| devote_xo_handle: |
| clk_disable_unprepare(motg->pclk); |
| if (motg->xo_clk) |
| clk_disable_unprepare(motg->xo_clk); |
| free_xo_handle: |
| if (motg->xo_clk) { |
| clk_put(motg->xo_clk); |
| motg->xo_clk = NULL; |
| } |
| free_regs: |
| iounmap(motg->regs); |
| devote_bus_bw: |
| if (motg->bus_perf_client) { |
| msm_otg_bus_vote(motg, USB_NO_PERF_VOTE); |
| msm_bus_scale_unregister_client(motg->bus_perf_client); |
| } |
| disable_phy_csr_clk: |
| if (motg->phy_csr_clk) |
| clk_disable_unprepare(motg->phy_csr_clk); |
| disable_sleep_clk: |
| if (motg->sleep_clk) |
| clk_disable_unprepare(motg->sleep_clk); |
| put_xo_clk: |
| if (motg->xo_clk) |
| clk_put(motg->xo_clk); |
| put_pclk: |
| if (motg->pclk) |
| clk_put(motg->pclk); |
| put_core_clk: |
| if (motg->core_clk) |
| clk_put(motg->core_clk); |
| free_motg: |
| kfree(motg); |
| return ret; |
| } |
| |
| static int msm_otg_remove(struct platform_device *pdev) |
| { |
| struct msm_otg *motg = platform_get_drvdata(pdev); |
| struct usb_phy *phy = &motg->phy; |
| int cnt = 0; |
| |
| if (phy->otg->host || phy->otg->gadget) |
| return -EBUSY; |
| |
| unregister_pm_notifier(&motg->pm_notify); |
| |
| extcon_unregister_notifier(motg->extcon_id, EXTCON_USB_HOST, |
| &motg->id_nb); |
| extcon_unregister_notifier(motg->extcon_vbus, EXTCON_USB, |
| &motg->vbus_nb); |
| |
| if (pdev->dev.of_node) |
| msm_otg_setup_devices(pdev, motg->pdata->mode, false); |
| if (psy) |
| power_supply_put(psy); |
| msm_otg_debugfs_cleanup(); |
| cancel_delayed_work_sync(&motg->sdp_check); |
| cancel_delayed_work_sync(&motg->id_status_work); |
| cancel_delayed_work_sync(&motg->perf_vote_work); |
| msm_otg_perf_vote_update(motg, false); |
| cancel_work_sync(&motg->sm_work); |
| cancel_work_sync(&motg->notify_chg_current_work); |
| destroy_workqueue(motg->otg_wq); |
| |
| pm_runtime_resume(&pdev->dev); |
| |
| device_init_wakeup(&pdev->dev, 0); |
| pm_runtime_disable(&pdev->dev); |
| |
| usb_remove_phy(phy); |
| |
| device_remove_file(&pdev->dev, &dev_attr_dpdm_pulldown_enable); |
| |
| /* |
| * Put PHY in low power mode. |
| */ |
| ulpi_read(phy, 0x14); |
| ulpi_write(phy, 0x08, 0x09); |
| |
| writel_relaxed(readl_relaxed(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); |
| while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { |
| if (readl_relaxed(USB_PORTSC) & PORTSC_PHCD) |
| break; |
| udelay(1); |
| cnt++; |
| } |
| if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) |
| dev_err(phy->dev, "Unable to suspend PHY\n"); |
| |
| clk_disable_unprepare(motg->pclk); |
| clk_disable_unprepare(motg->core_clk); |
| if (motg->phy_csr_clk) |
| clk_disable_unprepare(motg->phy_csr_clk); |
| if (motg->xo_clk) { |
| clk_disable_unprepare(motg->xo_clk); |
| clk_put(motg->xo_clk); |
| } |
| |
| if (!IS_ERR(motg->sleep_clk)) |
| clk_disable_unprepare(motg->sleep_clk); |
| |
| msm_hsusb_ldo_enable(motg, USB_PHY_REG_OFF); |
| msm_hsusb_ldo_init(motg, 0); |
| regulator_disable(hsusb_vdd); |
| regulator_set_voltage(hsusb_vdd, |
| vdd_val[VDD_NONE], |
| vdd_val[VDD_MAX]); |
| |
| iounmap(motg->regs); |
| pm_runtime_set_suspended(&pdev->dev); |
| |
| clk_put(motg->pclk); |
| clk_put(motg->core_clk); |
| |
| if (motg->bus_perf_client) { |
| msm_otg_bus_vote(motg, USB_NO_PERF_VOTE); |
| msm_bus_scale_unregister_client(motg->bus_perf_client); |
| } |
| |
| return 0; |
| } |
| |
| static void msm_otg_shutdown(struct platform_device *pdev) |
| { |
| struct msm_otg *motg = platform_get_drvdata(pdev); |
| |
| dev_dbg(&pdev->dev, "OTG shutdown\n"); |
| msm_hsusb_vbus_power(motg, 0); |
| } |
| |
| #ifdef CONFIG_PM |
| static int msm_otg_runtime_idle(struct device *dev) |
| { |
| struct msm_otg *motg = dev_get_drvdata(dev); |
| struct usb_phy *phy = &motg->phy; |
| |
| dev_dbg(dev, "OTG runtime idle\n"); |
| msm_otg_dbg_log_event(phy, "RUNTIME IDLE", phy->otg->state, 0); |
| |
| if (phy->otg->state == OTG_STATE_UNDEFINED) |
| return -EAGAIN; |
| |
| return 0; |
| } |
| |
| static int msm_otg_runtime_suspend(struct device *dev) |
| { |
| struct msm_otg *motg = dev_get_drvdata(dev); |
| |
| dev_dbg(dev, "OTG runtime suspend\n"); |
| msm_otg_dbg_log_event(&motg->phy, "RUNTIME SUSPEND", |
| get_pm_runtime_counter(dev), 0); |
| return msm_otg_suspend(motg); |
| } |
| |
| static int msm_otg_runtime_resume(struct device *dev) |
| { |
| struct msm_otg *motg = dev_get_drvdata(dev); |
| |
| dev_dbg(dev, "OTG runtime resume\n"); |
| msm_otg_dbg_log_event(&motg->phy, "RUNTIME RESUME", |
| get_pm_runtime_counter(dev), 0); |
| |
| return msm_otg_resume(motg); |
| } |
| #endif |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int msm_otg_pm_suspend(struct device *dev) |
| { |
| struct msm_otg *motg = dev_get_drvdata(dev); |
| |
| dev_dbg(dev, "OTG PM suspend\n"); |
| msm_otg_dbg_log_event(&motg->phy, "PM SUSPEND START", |
| get_pm_runtime_counter(dev), |
| atomic_read(&motg->pm_suspended)); |
| |
| /* flush any pending sm_work first */ |
| flush_work(&motg->sm_work); |
| if (!atomic_read(&motg->in_lpm)) { |
| dev_err(dev, "Abort PM suspend!! (USB is outside LPM)\n"); |
| return -EBUSY; |
| } |
| atomic_set(&motg->pm_suspended, 1); |
| |
| return 0; |
| } |
| |
| static int msm_otg_pm_resume(struct device *dev) |
| { |
| int ret = 0; |
| struct msm_otg *motg = dev_get_drvdata(dev); |
| |
| dev_dbg(dev, "OTG PM resume\n"); |
| msm_otg_dbg_log_event(&motg->phy, "PM RESUME START", |
| get_pm_runtime_counter(dev), pm_runtime_suspended(dev)); |
| |
| if (motg->resume_pending || motg->phy_irq_pending) { |
| msm_otg_dbg_log_event(&motg->phy, "PM RESUME BY USB", |
| motg->async_int, motg->resume_pending); |
| /* sm work if pending will start in pm notify to exit LPM */ |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| #ifdef CONFIG_PM |
| static const struct dev_pm_ops msm_otg_dev_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(msm_otg_pm_suspend, msm_otg_pm_resume) |
| SET_RUNTIME_PM_OPS(msm_otg_runtime_suspend, msm_otg_runtime_resume, |
| msm_otg_runtime_idle) |
| }; |
| #endif |
| |
| static const struct of_device_id msm_otg_dt_match[] = { |
| { .compatible = "qcom,hsusb-otg", |
| }, |
| {} |
| }; |
| |
| static struct platform_driver msm_otg_driver = { |
| .probe = msm_otg_probe, |
| .remove = msm_otg_remove, |
| .shutdown = msm_otg_shutdown, |
| .driver = { |
| .name = DRIVER_NAME, |
| .owner = THIS_MODULE, |
| #ifdef CONFIG_PM |
| .pm = &msm_otg_dev_pm_ops, |
| #endif |
| .of_match_table = msm_otg_dt_match, |
| }, |
| }; |
| |
| module_platform_driver(msm_otg_driver); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("MSM USB transceiver driver"); |