Skip to content
Navigation Menu
Toggle navigation
Sign in
In this repository
All GitHub Enterprise
↵
Jump to
↵
No suggested jump to results
In this repository
All GitHub Enterprise
↵
Jump to
↵
In this organization
All GitHub Enterprise
↵
Jump to
↵
In this repository
All GitHub Enterprise
↵
Jump to
↵
Sign in
Reseting focus
You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
Dismiss alert
{{ message }}
mariux64
/
linux
Public
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Issues
2
Pull requests
0
Actions
Projects
0
Wiki
Security
Insights
Additional navigation options
Code
Issues
Pull requests
Actions
Projects
Wiki
Security
Insights
Files
f5ef237
Documentation
arch
block
crypto
drivers
accessibility
acpi
amba
ata
atm
auxdisplay
base
bcma
block
bluetooth
bus
cdrom
char
clk
clocksource
connector
cpufreq
cpuidle
crypto
dca
devfreq
dio
dma
edac
eisa
extcon
firewire
firmware
fmc
gpio
gpu
hid
hsi
hv
hwmon
hwspinlock
i2c
ide
idle
iio
infiniband
input
iommu
ipack
irqchip
isdn
leds
lguest
macintosh
mailbox
mcb
md
media
memory
memstick
message
mfd
misc
mmc
mtd
net
nfc
ntb
nubus
of
oprofile
parisc
parport
pci
pcmcia
phy
pinctrl
platform
pnp
power
powercap
pps
ps3
ptp
pwm
rapidio
regulator
remoteproc
reset
rpmsg
rtc
s390
sbus
scsi
sfi
sh
sn
spi
spmi
ssb
staging
target
tc
thermal
tty
uio
usb
atm
c67x00
chipidea
class
core
dwc2
dwc3
early
gadget
host
image
misc
mon
musb
phy
Kconfig
Makefile
am35x-phy-control.h
of.c
phy-ab8500-usb.c
phy-am335x-control.c
phy-am335x.c
phy-fsl-usb.c
phy-fsl-usb.h
phy-fsm-usb.c
phy-generic.c
phy-generic.h
phy-gpio-vbus-usb.c
phy-isp1301-omap.c
phy-isp1301.c
phy-keystone.c
phy-msm-usb.c
phy-mv-u3d-usb.c
phy-mv-u3d-usb.h
phy-mv-usb.c
phy-mv-usb.h
phy-mxs-usb.c
phy-omap-otg.c
phy-rcar-gen2-usb.c
phy-rcar-usb.c
phy-samsung-usb.c
phy-samsung-usb.h
phy-samsung-usb2.c
phy-samsung-usb3.c
phy-tahvo.c
phy-tegra-usb.c
phy-twl6030-usb.c
phy-ulpi-viewport.c
phy-ulpi.c
phy.c
renesas_usbhs
serial
storage
wusbcore
Kconfig
Makefile
README
usb-common.c
usb-skeleton.c
uwb
vfio
vhost
video
virt
virtio
vlynq
vme
w1
watchdog
xen
zorro
Kconfig
Makefile
firmware
fs
include
init
ipc
kernel
lib
mm
net
samples
scripts
security
sound
tools
usr
virt
.gitignore
.mailmap
COPYING
CREDITS
Kbuild
Kconfig
MAINTAINERS
Makefile
README
REPORTING-BUGS
Breadcrumbs
linux
/
drivers
/
usb
/
phy
/
phy-msm-usb.c
Blame
Blame
Latest commit
History
History
1648 lines (1420 loc) · 39.3 KB
Breadcrumbs
linux
/
drivers
/
usb
/
phy
/
phy-msm-usb.c
Top
File metadata and controls
Code
Blame
1648 lines (1420 loc) · 39.3 KB
Raw
/* Copyright (c) 2009-2011, Code Aurora Forum. 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * */ #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/uaccess.h> #include <linux/debugfs.h> #include <linux/seq_file.h> #include <linux/pm_runtime.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> #define MSM_USB_BASE (motg->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_PHY_VDD_DIG_VOL_MIN 1000000 /* uV */ #define USB_PHY_VDD_DIG_VOL_MAX 1320000 /* uV */ static int msm_hsusb_init_vddcx(struct msm_otg *motg, int init) { int ret = 0; if (init) { ret = regulator_set_voltage(motg->vddcx, USB_PHY_VDD_DIG_VOL_MIN, USB_PHY_VDD_DIG_VOL_MAX); if (ret) { dev_err(motg->phy.dev, "Cannot set vddcx voltage\n"); return ret; } ret = regulator_enable(motg->vddcx); if (ret) dev_err(motg->phy.dev, "unable to enable hsusb vddcx\n"); } else { ret = regulator_set_voltage(motg->vddcx, 0, USB_PHY_VDD_DIG_VOL_MAX); if (ret) dev_err(motg->phy.dev, "Cannot set vddcx voltage\n"); ret = regulator_disable(motg->vddcx); if (ret) dev_err(motg->phy.dev, "unable to disable hsusb vddcx\n"); } return ret; } static int msm_hsusb_ldo_init(struct msm_otg *motg, int init) { int rc = 0; if (init) { rc = regulator_set_voltage(motg->v3p3, USB_PHY_3P3_VOL_MIN, USB_PHY_3P3_VOL_MAX); if (rc) { dev_err(motg->phy.dev, "Cannot set v3p3 voltage\n"); goto exit; } rc = regulator_enable(motg->v3p3); if (rc) { dev_err(motg->phy.dev, "unable to enable the hsusb 3p3\n"); goto exit; } rc = regulator_set_voltage(motg->v1p8, USB_PHY_1P8_VOL_MIN, USB_PHY_1P8_VOL_MAX); if (rc) { dev_err(motg->phy.dev, "Cannot set v1p8 voltage\n"); goto disable_3p3; } rc = regulator_enable(motg->v1p8); if (rc) { dev_err(motg->phy.dev, "unable to enable the hsusb 1p8\n"); goto disable_3p3; } return 0; } regulator_disable(motg->v1p8); disable_3p3: regulator_disable(motg->v3p3); exit: return rc; } static int msm_hsusb_ldo_set_mode(struct msm_otg *motg, int on) { int ret = 0; if (on) { ret = regulator_set_optimum_mode(motg->v1p8, USB_PHY_1P8_HPM_LOAD); if (ret < 0) { pr_err("Could not set HPM for v1p8\n"); return ret; } ret = regulator_set_optimum_mode(motg->v3p3, USB_PHY_3P3_HPM_LOAD); if (ret < 0) { pr_err("Could not set HPM for v3p3\n"); regulator_set_optimum_mode(motg->v1p8, USB_PHY_1P8_LPM_LOAD); return ret; } } else { ret = regulator_set_optimum_mode(motg->v1p8, USB_PHY_1P8_LPM_LOAD); if (ret < 0) pr_err("Could not set LPM for v1p8\n"); ret = regulator_set_optimum_mode(motg->v3p3, USB_PHY_3P3_LPM_LOAD); if (ret < 0) pr_err("Could not set LPM for v3p3\n"); } pr_debug("reg (%s)\n", on ? "HPM" : "LPM"); 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; /* initiate read operation */ writel(ULPI_RUN | ULPI_READ | ULPI_ADDR(reg), USB_ULPI_VIEWPORT); /* wait for completion */ while (cnt < ULPI_IO_TIMEOUT_USEC) { if (!(readl(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(USB_ULPI_VIEWPORT)); return -ETIMEDOUT; } return ULPI_DATA_READ(readl(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; /* initiate write operation */ writel(ULPI_RUN | ULPI_WRITE | ULPI_ADDR(reg) | ULPI_DATA(val), USB_ULPI_VIEWPORT); /* wait for completion */ while (cnt < ULPI_IO_TIMEOUT_USEC) { if (!(readl(USB_ULPI_VIEWPORT) & ULPI_RUN)) break; udelay(1); cnt++; } if (cnt >= ULPI_IO_TIMEOUT_USEC) { dev_err(phy->dev, "ulpi_write: timeout\n"); 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 *seq = pdata->phy_init_seq; if (!seq) return; while (seq[0] >= 0) { dev_vdbg(motg->phy.dev, "ulpi: write 0x%02x to 0x%02x\n", seq[0], seq[1]); ulpi_write(&motg->phy, seq[0], seq[1]); seq += 2; } } static int msm_otg_link_clk_reset(struct msm_otg *motg, bool assert) { int ret = 0; if (!motg->pdata->link_clk_reset) return ret; ret = motg->pdata->link_clk_reset(motg->clk, assert); if (ret) dev_err(motg->phy.dev, "usb link clk reset %s failed\n", assert ? "assert" : "deassert"); return ret; } static int msm_otg_phy_clk_reset(struct msm_otg *motg) { int ret = 0; if (!motg->pdata->phy_clk_reset) return ret; ret = motg->pdata->phy_clk_reset(motg->phy_reset_clk); if (ret) dev_err(motg->phy.dev, "usb phy clk reset failed\n"); return ret; } static int msm_otg_phy_reset(struct msm_otg *motg) { u32 val; int ret; int retries; ret = msm_otg_link_clk_reset(motg, 1); if (ret) return ret; ret = msm_otg_phy_clk_reset(motg); if (ret) return ret; ret = msm_otg_link_clk_reset(motg, 0); if (ret) return ret; val = readl(USB_PORTSC) & ~PORTSC_PTS_MASK; writel(val | PORTSC_PTS_ULPI, USB_PORTSC); for (retries = 3; retries > 0; retries--) { ret = ulpi_write(&motg->phy, ULPI_FUNC_CTRL_SUSPENDM, ULPI_CLR(ULPI_FUNC_CTRL)); if (!ret) break; ret = msm_otg_phy_clk_reset(motg); if (ret) return ret; } if (!retries) return -ETIMEDOUT; /* This reset calibrates the phy, if the above write succeeded */ ret = msm_otg_phy_clk_reset(motg); if (ret) return ret; for (retries = 3; retries > 0; retries--) { ret = ulpi_read(&motg->phy, ULPI_DEBUG); if (ret != -ETIMEDOUT) break; ret = msm_otg_phy_clk_reset(motg); if (ret) return ret; } if (!retries) return -ETIMEDOUT; dev_info(motg->phy.dev, "phy_reset: success\n"); return 0; } #define LINK_RESET_TIMEOUT_USEC (250 * 1000) 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 cnt = 0; int ret; u32 val = 0; u32 ulpi_val = 0; ret = msm_otg_phy_reset(motg); if (ret) { dev_err(phy->dev, "phy_reset failed\n"); return ret; } ulpi_init(motg); writel(USBCMD_RESET, USB_USBCMD); while (cnt < LINK_RESET_TIMEOUT_USEC) { if (!(readl(USB_USBCMD) & USBCMD_RESET)) break; udelay(1); cnt++; } if (cnt >= LINK_RESET_TIMEOUT_USEC) return -ETIMEDOUT; /* select ULPI phy */ writel(0x80000000, USB_PORTSC); msleep(100); writel(0x0, USB_AHBBURST); writel(0x00, USB_AHBMODE); if (pdata->otg_control == OTG_PHY_CONTROL) { val = readl(USB_OTGSC); if (pdata->mode == USB_DR_MODE_OTG) { ulpi_val = ULPI_INT_IDGRD | ULPI_INT_SESS_VALID; val |= OTGSC_IDIE | OTGSC_BSVIE; } else if (pdata->mode == USB_DR_MODE_PERIPHERAL) { ulpi_val = ULPI_INT_SESS_VALID; val |= OTGSC_BSVIE; } writel(val, USB_OTGSC); ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_RISE); ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_FALL); } return 0; } #define PHY_SUSPEND_TIMEOUT_USEC (500 * 1000) #define PHY_RESUME_TIMEOUT_USEC (100 * 1000) #ifdef CONFIG_PM #define USB_PHY_SUSP_DIG_VOL 500000 static int msm_hsusb_config_vddcx(struct msm_otg *motg, int high) { int max_vol = USB_PHY_VDD_DIG_VOL_MAX; int min_vol; int ret; if (high) min_vol = USB_PHY_VDD_DIG_VOL_MIN; else min_vol = USB_PHY_SUSP_DIG_VOL; ret = regulator_set_voltage(motg->vddcx, min_vol, max_vol); if (ret) { pr_err("Cannot set vddcx voltage\n"); return ret; } pr_debug("%s: min_vol:%d max_vol:%d\n", __func__, min_vol, max_vol); return ret; } 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 = 0; if (atomic_read(&motg->in_lpm)) return 0; disable_irq(motg->irq); /* * Chipidea 45-nm PHY suspend sequence: * * Interrupt Latch Register auto-clear feature is not present * in all PHY versions. Latch register is clear on read type. * Clear latch register to avoid spurious wakeup from * low power mode (LPM). * * PHY comparators are disabled when PHY enters into low power * mode (LPM). Keep PHY comparators ON in LPM only when we expect * VBUS/Id notifications from USB PHY. Otherwise turn off USB * PHY comparators. This save significant amount of power. * * PLL is not turned off when PHY enters into low power mode (LPM). * Disable PLL for maximum power savings. */ if (motg->pdata->phy_type == CI_45NM_INTEGRATED_PHY) { ulpi_read(phy, 0x14); if (pdata->otg_control == OTG_PHY_CONTROL) ulpi_write(phy, 0x01, 0x30); ulpi_write(phy, 0x08, 0x09); } /* * PHY may take some time or even fail to enter into low power * mode (LPM). Hence poll for 500 msec and reset the PHY and link * in failure case. */ writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { if (readl(USB_PORTSC) & PORTSC_PHCD) break; udelay(1); cnt++; } if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) { dev_err(phy->dev, "Unable to suspend PHY\n"); msm_otg_reset(phy); enable_irq(motg->irq); return -ETIMEDOUT; } /* * 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. */ writel(readl(USB_USBCMD) | ASYNC_INTR_CTRL | ULPI_STP_CTRL, USB_USBCMD); if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && motg->pdata->otg_control == OTG_PMIC_CONTROL) writel(readl(USB_PHY_CTRL) | PHY_RETEN, USB_PHY_CTRL); clk_disable_unprepare(motg->pclk); clk_disable_unprepare(motg->clk); if (!IS_ERR(motg->core_clk)) clk_disable_unprepare(motg->core_clk); if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && motg->pdata->otg_control == OTG_PMIC_CONTROL) { msm_hsusb_ldo_set_mode(motg, 0); msm_hsusb_config_vddcx(motg, 0); } if (device_may_wakeup(phy->dev)) enable_irq_wake(motg->irq); if (bus) clear_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); atomic_set(&motg->in_lpm, 1); enable_irq(motg->irq); dev_info(phy->dev, "USB in low power mode\n"); return 0; } static int msm_otg_resume(struct msm_otg *motg) { struct usb_phy *phy = &motg->phy; struct usb_bus *bus = phy->otg->host; int cnt = 0; unsigned temp; if (!atomic_read(&motg->in_lpm)) return 0; clk_prepare_enable(motg->pclk); clk_prepare_enable(motg->clk); if (!IS_ERR(motg->core_clk)) clk_prepare_enable(motg->core_clk); if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && motg->pdata->otg_control == OTG_PMIC_CONTROL) { msm_hsusb_ldo_set_mode(motg, 1); msm_hsusb_config_vddcx(motg, 1); writel(readl(USB_PHY_CTRL) & ~PHY_RETEN, USB_PHY_CTRL); } temp = readl(USB_USBCMD); temp &= ~ASYNC_INTR_CTRL; temp &= ~ULPI_STP_CTRL; writel(temp, USB_USBCMD); /* * PHY comes out of low power mode (LPM) in case of wakeup * from asynchronous interrupt. */ if (!(readl(USB_PORTSC) & PORTSC_PHCD)) goto skip_phy_resume; writel(readl(USB_PORTSC) & ~PORTSC_PHCD, USB_PORTSC); while (cnt < PHY_RESUME_TIMEOUT_USEC) { if (!(readl(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 (device_may_wakeup(phy->dev)) disable_irq_wake(motg->irq); if (bus) set_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); atomic_set(&motg->in_lpm, 0); if (motg->async_int) { motg->async_int = 0; pm_runtime_put(phy->dev); enable_irq(motg->irq); } dev_info(phy->dev, "USB exited from low power mode\n"); return 0; } #endif static void msm_otg_notify_charger(struct msm_otg *motg, unsigned mA) { if (motg->cur_power == mA) return; /* TODO: Notify PMIC about available current */ dev_info(motg->phy.dev, "Avail curr from USB = %u\n", mA); motg->cur_power = mA; } static int msm_otg_set_power(struct usb_phy *phy, unsigned mA) { struct msm_otg *motg = container_of(phy, struct msm_otg, phy); /* * Gadget driver uses set_power method to notify about the * available current based on suspend/configured states. * * IDEV_CHG can be drawn irrespective of suspend/un-configured * states when CDP/ACA is connected. */ if (motg->chg_type == USB_SDP_CHARGER) msm_otg_notify_charger(motg, mA); return 0; } static void msm_otg_start_host(struct usb_phy *phy, int on) { struct msm_otg *motg = container_of(phy, struct msm_otg, phy); struct msm_otg_platform_data *pdata = motg->pdata; struct usb_hcd *hcd; if (!phy->otg->host) return; hcd = bus_to_hcd(phy->otg->host); if (on) { dev_dbg(phy->dev, "host on\n"); if (pdata->vbus_power) pdata->vbus_power(1); /* * Some boards have a switch cotrolled by gpio * to enable/disable internal HUB. Enable internal * HUB before kicking the host. */ if (pdata->setup_gpio) pdata->setup_gpio(OTG_STATE_A_HOST); #ifdef CONFIG_USB usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); device_wakeup_enable(hcd->self.controller); #endif } else { dev_dbg(phy->dev, "host off\n"); #ifdef CONFIG_USB usb_remove_hcd(hcd); #endif if (pdata->setup_gpio) pdata->setup_gpio(OTG_STATE_UNDEFINED); if (pdata->vbus_power) pdata->vbus_power(0); } } static int msm_otg_set_host(struct usb_otg *otg, struct usb_bus *host) { struct msm_otg *motg = container_of(otg->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_DR_MODE_PERIPHERAL) { dev_info(otg->phy->dev, "Host mode is not supported\n"); return -ENODEV; } if (!host) { if (otg->phy->state == OTG_STATE_A_HOST) { pm_runtime_get_sync(otg->phy->dev); msm_otg_start_host(otg->phy, 0); otg->host = NULL; otg->phy->state = OTG_STATE_UNDEFINED; schedule_work(&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->phy->dev, "host driver registered w/ tranceiver\n"); /* * Kick the state machine work, if peripheral is not supported * or peripheral is already registered with us. */ if (motg->pdata->mode == USB_DR_MODE_HOST || otg->gadget) { pm_runtime_get_sync(otg->phy->dev); schedule_work(&motg->sm_work); } return 0; } static void msm_otg_start_peripheral(struct usb_phy *phy, int on) { struct msm_otg *motg = container_of(phy, struct msm_otg, phy); struct msm_otg_platform_data *pdata = motg->pdata; if (!phy->otg->gadget) return; if (on) { dev_dbg(phy->dev, "gadget on\n"); /* * Some boards have a switch cotrolled by gpio * to enable/disable internal HUB. Disable internal * HUB before kicking the gadget. */ if (pdata->setup_gpio) pdata->setup_gpio(OTG_STATE_B_PERIPHERAL); usb_gadget_vbus_connect(phy->otg->gadget); } else { dev_dbg(phy->dev, "gadget off\n"); usb_gadget_vbus_disconnect(phy->otg->gadget); if (pdata->setup_gpio) pdata->setup_gpio(OTG_STATE_UNDEFINED); } } static int msm_otg_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget) { struct msm_otg *motg = container_of(otg->phy, struct msm_otg, phy); /* * Fail peripheral registration if this board can support * only host configuration. */ if (motg->pdata->mode == USB_DR_MODE_HOST) { dev_info(otg->phy->dev, "Peripheral mode is not supported\n"); return -ENODEV; } if (!gadget) { if (otg->phy->state == OTG_STATE_B_PERIPHERAL) { pm_runtime_get_sync(otg->phy->dev); msm_otg_start_peripheral(otg->phy, 0); otg->gadget = NULL; otg->phy->state = OTG_STATE_UNDEFINED; schedule_work(&motg->sm_work); } else { otg->gadget = NULL; } return 0; } otg->gadget = gadget; dev_dbg(otg->phy->dev, "peripheral driver registered w/ tranceiver\n"); /* * Kick the state machine work, if host is not supported * or host is already registered with us. */ if (motg->pdata->mode == USB_DR_MODE_PERIPHERAL || otg->host) { pm_runtime_get_sync(otg->phy->dev); schedule_work(&motg->sm_work); } return 0; } static bool msm_chg_check_secondary_det(struct msm_otg *motg) { struct usb_phy *phy = &motg->phy; u32 chg_det; bool ret = false; switch (motg->pdata->phy_type) { case CI_45NM_INTEGRATED_PHY: chg_det = ulpi_read(phy, 0x34); ret = chg_det & (1 << 4); break; case SNPS_28NM_INTEGRATED_PHY: chg_det = ulpi_read(phy, 0x87); ret = chg_det & 1; break; default: break; } return ret; } static void msm_chg_enable_secondary_det(struct msm_otg *motg) { struct usb_phy *phy = &motg->phy; u32 chg_det; switch (motg->pdata->phy_type) { case CI_45NM_INTEGRATED_PHY: chg_det = ulpi_read(phy, 0x34); /* Turn off charger block */ chg_det |= ~(1 << 1); ulpi_write(phy, chg_det, 0x34); udelay(20); /* control chg block via ULPI */ chg_det &= ~(1 << 3); ulpi_write(phy, chg_det, 0x34); /* put it in host mode for enabling D- source */ chg_det &= ~(1 << 2); ulpi_write(phy, chg_det, 0x34); /* Turn on chg detect block */ chg_det &= ~(1 << 1); ulpi_write(phy, chg_det, 0x34); udelay(20); /* enable chg detection */ chg_det &= ~(1 << 0); ulpi_write(phy, chg_det, 0x34); break; case SNPS_28NM_INTEGRATED_PHY: /* * Configure DM as current source, DP as current sink * and enable battery charging comparators. */ ulpi_write(phy, 0x8, 0x85); ulpi_write(phy, 0x2, 0x85); ulpi_write(phy, 0x1, 0x85); break; default: break; } } static bool msm_chg_check_primary_det(struct msm_otg *motg) { struct usb_phy *phy = &motg->phy; u32 chg_det; bool ret = false; switch (motg->pdata->phy_type) { case CI_45NM_INTEGRATED_PHY: chg_det = ulpi_read(phy, 0x34); ret = chg_det & (1 << 4); break; case SNPS_28NM_INTEGRATED_PHY: chg_det = ulpi_read(phy, 0x87); ret = chg_det & 1; break; default: break; } return ret; } static void msm_chg_enable_primary_det(struct msm_otg *motg) { struct usb_phy *phy = &motg->phy; u32 chg_det; switch (motg->pdata->phy_type) { case CI_45NM_INTEGRATED_PHY: chg_det = ulpi_read(phy, 0x34); /* enable chg detection */ chg_det &= ~(1 << 0); ulpi_write(phy, chg_det, 0x34); break; case SNPS_28NM_INTEGRATED_PHY: /* * Configure DP as current source, DM as current sink * and enable battery charging comparators. */ ulpi_write(phy, 0x2, 0x85); ulpi_write(phy, 0x1, 0x85); break; default: break; } } static bool msm_chg_check_dcd(struct msm_otg *motg) { struct usb_phy *phy = &motg->phy; u32 line_state; bool ret = false; switch (motg->pdata->phy_type) { case CI_45NM_INTEGRATED_PHY: line_state = ulpi_read(phy, 0x15); ret = !(line_state & 1); break; case SNPS_28NM_INTEGRATED_PHY: line_state = ulpi_read(phy, 0x87); ret = line_state & 2; break; default: break; } return ret; } static void msm_chg_disable_dcd(struct msm_otg *motg) { struct usb_phy *phy = &motg->phy; u32 chg_det; switch (motg->pdata->phy_type) { case CI_45NM_INTEGRATED_PHY: chg_det = ulpi_read(phy, 0x34); chg_det &= ~(1 << 5); ulpi_write(phy, chg_det, 0x34); break; case SNPS_28NM_INTEGRATED_PHY: ulpi_write(phy, 0x10, 0x86); break; default: break; } } static void msm_chg_enable_dcd(struct msm_otg *motg) { struct usb_phy *phy = &motg->phy; u32 chg_det; switch (motg->pdata->phy_type) { case CI_45NM_INTEGRATED_PHY: chg_det = ulpi_read(phy, 0x34); /* Turn on D+ current source */ chg_det |= (1 << 5); ulpi_write(phy, chg_det, 0x34); break; case SNPS_28NM_INTEGRATED_PHY: /* Data contact detection enable */ ulpi_write(phy, 0x10, 0x85); break; default: break; } } static void msm_chg_block_on(struct msm_otg *motg) { struct usb_phy *phy = &motg->phy; u32 func_ctrl, chg_det; /* 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); switch (motg->pdata->phy_type) { case CI_45NM_INTEGRATED_PHY: chg_det = ulpi_read(phy, 0x34); /* control chg block via ULPI */ chg_det &= ~(1 << 3); ulpi_write(phy, chg_det, 0x34); /* Turn on chg detect block */ chg_det &= ~(1 << 1); ulpi_write(phy, chg_det, 0x34); udelay(20); break; case SNPS_28NM_INTEGRATED_PHY: /* Clear charger detecting control bits */ ulpi_write(phy, 0x3F, 0x86); /* Clear alt interrupt latch and enable bits */ ulpi_write(phy, 0x1F, 0x92); ulpi_write(phy, 0x1F, 0x95); udelay(100); break; default: break; } } static void msm_chg_block_off(struct msm_otg *motg) { struct usb_phy *phy = &motg->phy; u32 func_ctrl, chg_det; switch (motg->pdata->phy_type) { case CI_45NM_INTEGRATED_PHY: chg_det = ulpi_read(phy, 0x34); /* Turn off charger block */ chg_det |= ~(1 << 1); ulpi_write(phy, chg_det, 0x34); break; case SNPS_28NM_INTEGRATED_PHY: /* Clear charger detecting control bits */ ulpi_write(phy, 0x3F, 0x86); /* Clear alt interrupt latch and enable bits */ ulpi_write(phy, 0x1F, 0x92); ulpi_write(phy, 0x1F, 0x95); break; default: break; } /* 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); } #define MSM_CHG_DCD_POLL_TIME (100 * HZ/1000) /* 100 msec */ #define MSM_CHG_DCD_MAX_RETRIES 6 /* Tdcd_tmout = 6 * 100 msec */ #define MSM_CHG_PRIMARY_DET_TIME (40 * HZ/1000) /* TVDPSRC_ON */ #define MSM_CHG_SECONDARY_DET_TIME (40 * HZ/1000) /* TVDMSRC_ON */ static void msm_chg_detect_work(struct work_struct *w) { struct msm_otg *motg = container_of(w, struct msm_otg, chg_work.work); struct usb_phy *phy = &motg->phy; bool is_dcd, tmout, vout; unsigned long delay; dev_dbg(phy->dev, "chg detection work\n"); switch (motg->chg_state) { case USB_CHG_STATE_UNDEFINED: pm_runtime_get_sync(phy->dev); msm_chg_block_on(motg); msm_chg_enable_dcd(motg); motg->chg_state = USB_CHG_STATE_WAIT_FOR_DCD; motg->dcd_retries = 0; delay = MSM_CHG_DCD_POLL_TIME; break; case USB_CHG_STATE_WAIT_FOR_DCD: is_dcd = msm_chg_check_dcd(motg); tmout = ++motg->dcd_retries == MSM_CHG_DCD_MAX_RETRIES; if (is_dcd || tmout) { msm_chg_disable_dcd(motg); msm_chg_enable_primary_det(motg); delay = MSM_CHG_PRIMARY_DET_TIME; motg->chg_state = USB_CHG_STATE_DCD_DONE; } else { delay = MSM_CHG_DCD_POLL_TIME; } break; case USB_CHG_STATE_DCD_DONE: vout = msm_chg_check_primary_det(motg); if (vout) { msm_chg_enable_secondary_det(motg); delay = MSM_CHG_SECONDARY_DET_TIME; motg->chg_state = USB_CHG_STATE_PRIMARY_DONE; } else { motg->chg_type = USB_SDP_CHARGER; motg->chg_state = USB_CHG_STATE_DETECTED; delay = 0; } break; case USB_CHG_STATE_PRIMARY_DONE: vout = msm_chg_check_secondary_det(motg); if (vout) motg->chg_type = USB_DCP_CHARGER; else motg->chg_type = USB_CDP_CHARGER; motg->chg_state = USB_CHG_STATE_SECONDARY_DONE; /* fall through */ case USB_CHG_STATE_SECONDARY_DONE: motg->chg_state = USB_CHG_STATE_DETECTED; case USB_CHG_STATE_DETECTED: msm_chg_block_off(motg); dev_dbg(phy->dev, "charger = %d\n", motg->chg_type); schedule_work(&motg->sm_work); return; default: return; } schedule_delayed_work(&motg->chg_work, delay); } /* * 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(USB_OTGSC); switch (pdata->mode) { case USB_DR_MODE_OTG: 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_USER_CONTROL) { set_bit(ID, &motg->inputs); clear_bit(B_SESS_VLD, &motg->inputs); } break; case USB_DR_MODE_HOST: clear_bit(ID, &motg->inputs); break; case USB_DR_MODE_PERIPHERAL: set_bit(ID, &motg->inputs); if (otgsc & OTGSC_BSV) set_bit(B_SESS_VLD, &motg->inputs); else clear_bit(B_SESS_VLD, &motg->inputs); break; default: break; } } 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; switch (otg->phy->state) { case OTG_STATE_UNDEFINED: dev_dbg(otg->phy->dev, "OTG_STATE_UNDEFINED state\n"); msm_otg_reset(otg->phy); msm_otg_init_sm(motg); otg->phy->state = OTG_STATE_B_IDLE; /* FALL THROUGH */ case OTG_STATE_B_IDLE: dev_dbg(otg->phy->dev, "OTG_STATE_B_IDLE state\n"); if (!test_bit(ID, &motg->inputs) && otg->host) { /* disable BSV bit */ writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); msm_otg_start_host(otg->phy, 1); otg->phy->state = OTG_STATE_A_HOST; } else if (test_bit(B_SESS_VLD, &motg->inputs)) { switch (motg->chg_state) { case USB_CHG_STATE_UNDEFINED: msm_chg_detect_work(&motg->chg_work.work); break; case USB_CHG_STATE_DETECTED: switch (motg->chg_type) { case USB_DCP_CHARGER: msm_otg_notify_charger(motg, IDEV_CHG_MAX); break; case USB_CDP_CHARGER: msm_otg_notify_charger(motg, IDEV_CHG_MAX); msm_otg_start_peripheral(otg->phy, 1); otg->phy->state = OTG_STATE_B_PERIPHERAL; break; case USB_SDP_CHARGER: msm_otg_notify_charger(motg, IUNIT); msm_otg_start_peripheral(otg->phy, 1); otg->phy->state = OTG_STATE_B_PERIPHERAL; break; default: break; } break; default: break; } } else { /* * If charger detection work is pending, decrement * the pm usage counter to balance with the one that * is incremented in charger detection work. */ if (cancel_delayed_work_sync(&motg->chg_work)) { pm_runtime_put_sync(otg->phy->dev); msm_otg_reset(otg->phy); } msm_otg_notify_charger(motg, 0); motg->chg_state = USB_CHG_STATE_UNDEFINED; motg->chg_type = USB_INVALID_CHARGER; } pm_runtime_put_sync(otg->phy->dev); break; case OTG_STATE_B_PERIPHERAL: dev_dbg(otg->phy->dev, "OTG_STATE_B_PERIPHERAL state\n"); if (!test_bit(B_SESS_VLD, &motg->inputs) || !test_bit(ID, &motg->inputs)) { msm_otg_notify_charger(motg, 0); msm_otg_start_peripheral(otg->phy, 0); motg->chg_state = USB_CHG_STATE_UNDEFINED; motg->chg_type = USB_INVALID_CHARGER; otg->phy->state = OTG_STATE_B_IDLE; msm_otg_reset(otg->phy); schedule_work(w); } break; case OTG_STATE_A_HOST: dev_dbg(otg->phy->dev, "OTG_STATE_A_HOST state\n"); if (test_bit(ID, &motg->inputs)) { msm_otg_start_host(otg->phy, 0); otg->phy->state = OTG_STATE_B_IDLE; msm_otg_reset(otg->phy); schedule_work(w); } break; default: break; } } static irqreturn_t msm_otg_irq(int irq, void *data) { struct msm_otg *motg = data; struct usb_phy *phy = &motg->phy; u32 otgsc = 0; if (atomic_read(&motg->in_lpm)) { disable_irq_nosync(irq); motg->async_int = 1; pm_runtime_get(phy->dev); return IRQ_HANDLED; } otgsc = readl(USB_OTGSC); if (!(otgsc & (OTGSC_IDIS | OTGSC_BSVIS))) return IRQ_NONE; if ((otgsc & OTGSC_IDIS) && (otgsc & OTGSC_IDIE)) { if (otgsc & OTGSC_ID) set_bit(ID, &motg->inputs); else clear_bit(ID, &motg->inputs); dev_dbg(phy->dev, "ID set/clear\n"); pm_runtime_get_noresume(phy->dev); } else if ((otgsc & OTGSC_BSVIS) && (otgsc & OTGSC_BSVIE)) { if (otgsc & OTGSC_BSV) set_bit(B_SESS_VLD, &motg->inputs); else clear_bit(B_SESS_VLD, &motg->inputs); dev_dbg(phy->dev, "BSV set/clear\n"); pm_runtime_get_noresume(phy->dev); } writel(otgsc, USB_OTGSC); schedule_work(&motg->sm_work); return IRQ_HANDLED; } 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->phy->state) { case OTG_STATE_A_HOST: seq_puts(s, "host\n"); break; case OTG_STATE_B_PERIPHERAL: 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_otg *otg = motg->phy.otg; int status = count; enum usb_dr_mode 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 (!strncmp(buf, "host", 4)) { req_mode = USB_DR_MODE_HOST; } else if (!strncmp(buf, "peripheral", 10)) { req_mode = USB_DR_MODE_PERIPHERAL; } else if (!strncmp(buf, "none", 4)) { req_mode = USB_DR_MODE_UNKNOWN; } else { status = -EINVAL; goto out; } switch (req_mode) { case USB_DR_MODE_UNKNOWN: switch (otg->phy->state) { case OTG_STATE_A_HOST: case OTG_STATE_B_PERIPHERAL: set_bit(ID, &motg->inputs); clear_bit(B_SESS_VLD, &motg->inputs); break; default: goto out; } break; case USB_DR_MODE_PERIPHERAL: switch (otg->phy->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_DR_MODE_HOST: switch (otg->phy->state) { case OTG_STATE_B_IDLE: case OTG_STATE_B_PERIPHERAL: clear_bit(ID, &motg->inputs); break; default: goto out; } break; default: goto out; } pm_runtime_get_sync(otg->phy->dev); schedule_work(&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 struct dentry *msm_otg_dbg_root; static struct dentry *msm_otg_dbg_mode; static int msm_otg_debugfs_init(struct msm_otg *motg) { msm_otg_dbg_root = debugfs_create_dir("msm_otg", NULL); if (!msm_otg_dbg_root || IS_ERR(msm_otg_dbg_root)) return -ENODEV; msm_otg_dbg_mode = debugfs_create_file("mode", S_IRUGO | S_IWUSR, msm_otg_dbg_root, motg, &msm_otg_mode_fops); if (!msm_otg_dbg_mode) { debugfs_remove(msm_otg_dbg_root); msm_otg_dbg_root = NULL; return -ENODEV; } return 0; } static void msm_otg_debugfs_cleanup(void) { debugfs_remove(msm_otg_dbg_mode); debugfs_remove(msm_otg_dbg_root); } static int msm_otg_probe(struct platform_device *pdev) { struct regulator_bulk_data regs[3]; int ret = 0; struct resource *res; struct msm_otg *motg; struct usb_phy *phy; dev_info(&pdev->dev, "msm_otg probe\n"); if (!dev_get_platdata(&pdev->dev)) { dev_err(&pdev->dev, "No platform data given. Bailing out\n"); return -ENODEV; } motg = devm_kzalloc(&pdev->dev, sizeof(struct msm_otg), GFP_KERNEL); if (!motg) { dev_err(&pdev->dev, "unable to allocate msm_otg\n"); return -ENOMEM; } motg->phy.otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg), GFP_KERNEL); if (!motg->phy.otg) { dev_err(&pdev->dev, "unable to allocate msm_otg\n"); return -ENOMEM; } motg->pdata = dev_get_platdata(&pdev->dev); phy = &motg->phy; phy->dev = &pdev->dev; motg->phy_reset_clk = devm_clk_get(&pdev->dev, "usb_phy_clk"); if (IS_ERR(motg->phy_reset_clk)) { dev_err(&pdev->dev, "failed to get usb_phy_clk\n"); return PTR_ERR(motg->phy_reset_clk); } motg->clk = devm_clk_get(&pdev->dev, "usb_hs_clk"); if (IS_ERR(motg->clk)) { dev_err(&pdev->dev, "failed to get usb_hs_clk\n"); return PTR_ERR(motg->clk); } /* * If 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. */ motg->pclk = devm_clk_get(&pdev->dev, "usb_hs_pclk"); if (IS_ERR(motg->pclk)) { dev_err(&pdev->dev, "failed to get usb_hs_pclk\n"); return PTR_ERR(motg->pclk); } /* * USB core clock is not present on all MSM chips. This * clock is introduced to remove the dependency on AXI * bus frequency. */ motg->core_clk = devm_clk_get(&pdev->dev, "usb_hs_core_clk"); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); motg->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (IS_ERR(motg->regs)) return PTR_ERR(motg->regs); dev_info(&pdev->dev, "OTG regs = %p\n", motg->regs); motg->irq = platform_get_irq(pdev, 0); if (!motg->irq) { dev_err(&pdev->dev, "platform_get_irq failed\n"); return motg->irq; } regs[0].supply = "vddcx"; regs[1].supply = "v3p3"; regs[2].supply = "v1p8"; ret = devm_regulator_bulk_get(motg->phy.dev, ARRAY_SIZE(regs), regs); if (ret) return ret; motg->vddcx = regs[0].consumer; motg->v3p3 = regs[1].consumer; motg->v1p8 = regs[2].consumer; clk_set_rate(motg->clk, 60000000); clk_prepare_enable(motg->clk); clk_prepare_enable(motg->pclk); if (!IS_ERR(motg->core_clk)) clk_prepare_enable(motg->core_clk); ret = msm_hsusb_init_vddcx(motg, 1); if (ret) { dev_err(&pdev->dev, "hsusb vddcx configuration failed\n"); goto disable_clks; } ret = msm_hsusb_ldo_init(motg, 1); if (ret) { dev_err(&pdev->dev, "hsusb vreg configuration failed\n"); goto disable_vddcx; } ret = msm_hsusb_ldo_set_mode(motg, 1); if (ret) { dev_err(&pdev->dev, "hsusb vreg enable failed\n"); goto disable_ldo; } writel(0, USB_USBINTR); writel(0, USB_OTGSC); INIT_WORK(&motg->sm_work, msm_otg_sm_work); INIT_DELAYED_WORK(&motg->chg_work, msm_chg_detect_work); 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 disable_ldo; } phy->init = msm_otg_reset; phy->set_power = msm_otg_set_power; phy->io_ops = &msm_otg_io_ops; phy->otg->phy = &motg->phy; phy->otg->set_host = msm_otg_set_host; phy->otg->set_peripheral = msm_otg_set_peripheral; ret = usb_add_phy(&motg->phy, USB_PHY_TYPE_USB2); if (ret) { dev_err(&pdev->dev, "usb_add_phy failed\n"); goto disable_ldo; } platform_set_drvdata(pdev, motg); device_init_wakeup(&pdev->dev, 1); if (motg->pdata->mode == USB_DR_MODE_OTG && motg->pdata->otg_control == OTG_USER_CONTROL) { ret = msm_otg_debugfs_init(motg); if (ret) dev_dbg(&pdev->dev, "Can not create mode change file\n"); } pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); return 0; disable_ldo: msm_hsusb_ldo_init(motg, 0); disable_vddcx: msm_hsusb_init_vddcx(motg, 0); disable_clks: clk_disable_unprepare(motg->pclk); clk_disable_unprepare(motg->clk); if (!IS_ERR(motg->core_clk)) clk_disable_unprepare(motg->core_clk); 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; msm_otg_debugfs_cleanup(); cancel_delayed_work_sync(&motg->chg_work); cancel_work_sync(&motg->sm_work); pm_runtime_resume(&pdev->dev); device_init_wakeup(&pdev->dev, 0); pm_runtime_disable(&pdev->dev); usb_remove_phy(phy); disable_irq(motg->irq); /* * Put PHY in low power mode. */ ulpi_read(phy, 0x14); ulpi_write(phy, 0x08, 0x09); writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { if (readl(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->clk); if (!IS_ERR(motg->core_clk)) clk_disable_unprepare(motg->core_clk); msm_hsusb_ldo_init(motg, 0); pm_runtime_set_suspended(&pdev->dev); return 0; } #ifdef CONFIG_PM_RUNTIME static int msm_otg_runtime_idle(struct device *dev) { struct msm_otg *motg = dev_get_drvdata(dev); struct usb_otg *otg = motg->phy.otg; dev_dbg(dev, "OTG runtime idle\n"); /* * It is observed some times that a spurious interrupt * comes when PHY is put into LPM immediately after PHY reset. * This 1 sec delay also prevents entering into LPM immediately * after asynchronous interrupt. */ if (otg->phy->state != OTG_STATE_UNDEFINED) pm_schedule_suspend(dev, 1000); return -EAGAIN; } static int msm_otg_runtime_suspend(struct device *dev) { struct msm_otg *motg = dev_get_drvdata(dev); dev_dbg(dev, "OTG runtime suspend\n"); 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"); 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"); return msm_otg_suspend(motg); } static int msm_otg_pm_resume(struct device *dev) { struct msm_otg *motg = dev_get_drvdata(dev); int ret; dev_dbg(dev, "OTG PM resume\n"); ret = msm_otg_resume(motg); if (ret) return ret; /* * Runtime PM Documentation recommends bringing the * device to full powered state upon resume. */ pm_runtime_disable(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); return 0; } #endif 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) }; static struct platform_driver msm_otg_driver = { .probe = msm_otg_probe, .remove = msm_otg_remove, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .pm = &msm_otg_dev_pm_ops, }, }; module_platform_driver(msm_otg_driver); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("MSM USB transceiver driver");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
You can’t perform that action at this time.