blob: 51f66b9788fbfd4628128583b1febdd3e8cf8a85 [file] [log] [blame]
/*
* Copyright (C) 2010 Google, Inc.
* Copyright (c) 2010-2013, NVIDIA CORPORATION. All rights reserved.
*
* Author:
* Erik Gilling <konkers@google.com>
* Benoit Goby <benoit@android.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/resource.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/regulator/consumer.h>
#include <linux/platform_data/tegra_usb.h>
#include <linux/usb/otg.h>
#include <linux/tegra-soc.h>
/* HACK -- need to pass this through DT */
#include "../../../arch/arm/mach-tegra/iomap.h"
#include "../../../arch/arm/mach-tegra/clock.h"
#include "tegra_usb_phy.h"
#include "../../../arch/arm/mach-tegra/fuse.h"
#include "../../../arch/arm/mach-tegra/common.h"
/* HACK! This needs to come from DT */
#include "../../../arch/arm/mach-tegra/iomap.h"
#define ERR(stuff...) pr_err("usb_phy: " stuff)
#define WARNING(stuff...) pr_warning("usb_phy: " stuff)
#define INFO(stuff...) pr_info("usb_phy: " stuff)
#define AHB_MEM_PREFETCH_CFG3 0xe0
#define AHB_MEM_PREFETCH_CFG4 0xe4
#define AHB_MEM_PREFETCH_CFG1 0xec
#define AHB_MEM_PREFETCH_CFG2 0xf0
#define PREFETCH_ENB (1 << 31)
#ifdef CONFIG_ARCH_TEGRA_12x_SOC
#define USB_PLL_REG "avdd_pll_utmip"
#else
#define USB_PLL_REG "avdd_usb_pll"
#endif
#ifdef DEBUG
#define DBG(stuff...) pr_info("usb_phy: " stuff)
#else
#define DBG(stuff...) do {} while (0)
#endif
static void print_usb_plat_data_info(struct tegra_usb_phy *phy)
{
struct tegra_usb_platform_data *pdata = phy->pdata;
char op_mode[][50] = {
"TEGRA_USB_OPMODE_DEVICE",
"TEGRA_USB_OPMODE_HOST"
};
char phy_intf[][50] = {
"USB_PHY_INTF_UTMI",
"USB_PHY_INTF_ULPI_LINK",
"USB_PHY_INTF_ULPI_NULL",
"USB_PHY_INTF_HSIC",
"USB_PHY_INTF_ICUSB"
};
pr_info("tegra USB phy - inst[%d] platform info:\n", phy->inst);
pr_info("port_otg: %s\n", pdata->port_otg ? "yes" : "no");
pr_info("has_hostpc: %s\n", pdata->has_hostpc ? "yes" : "no");
pr_info("phy_interface: %s\n", phy_intf[pdata->phy_intf]);
pr_info("op_mode: %s\n", op_mode[pdata->op_mode]);
if (pdata->op_mode == TEGRA_USB_OPMODE_DEVICE) {
pr_info("vbus_pmu_irq: %d\n", pdata->u_data.dev.vbus_pmu_irq);
pr_info("vbus_gpio: %d\n", pdata->u_data.dev.vbus_gpio);
pr_info("charging: %s\n", pdata->u_data.dev.charging_supported ?
"enabled" : "disabled");
pr_info("remote_wakeup: %s\n", pdata->u_data.dev.remote_wakeup_supported
? "enabled" : "disabled");
} else {
pr_info("vbus_gpio: %d\n", pdata->u_data.host.vbus_gpio);
pr_info("hot_plug: %s\n", pdata->u_data.host.hot_plug ?
"enabled" : "disabled");
pr_info("remote_wakeup: %s\n", pdata->u_data.host.remote_wakeup_supported
? "enabled" : "disabled");
}
}
struct tegra_usb_phy *get_tegra_phy(struct usb_phy *x)
{
return (struct tegra_usb_phy *)x;
}
static void usb_host_vbus_enable(struct tegra_usb_phy *phy, bool enable)
{
/* OTG driver will take care for OTG port */
if (phy->pdata->port_otg)
return;
if (phy->vbus_reg) {
if (enable)
regulator_enable(phy->vbus_reg);
else
regulator_disable(phy->vbus_reg);
} else {
int gpio = phy->pdata->u_data.host.vbus_gpio;
if (gpio == -1)
return;
gpio_set_value_cansleep(gpio, enable ? 1 : 0);
}
}
void tegra_usb_enable_vbus(struct tegra_usb_phy *phy, bool enable)
{
usb_host_vbus_enable(phy, enable);
}
EXPORT_SYMBOL_GPL(tegra_usb_enable_vbus);
int usb_phy_reg_status_wait(void __iomem *reg, u32 mask,
u32 result, u32 timeout)
{
do {
if ((readl(reg) & mask) == result)
return 0;
udelay(1);
timeout--;
} while (timeout);
return -1;
}
static int tegra_usb_phy_init_ops(struct tegra_usb_phy *phy)
{
int err = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->pdata->has_hostpc)
#if defined(CONFIG_ARCH_TEGRA_3x_SOC)
err = tegra3_usb_phy_init_ops(phy);
#else
err = tegra11x_usb_phy_init_ops(phy);
#endif
#if defined (CONFIG_ARCH_TEGRA_2x_SOC)
else
err = tegra2_usb_phy_init_ops(phy);
#endif
return err;
}
static irqreturn_t usb_phy_dev_vbus_pmu_irq_thr(int irq, void *pdata)
{
struct tegra_usb_phy *phy = pdata;
if (phy->vdd_reg && !phy->vdd_reg_on) {
regulator_enable(phy->vdd_reg);
phy->vdd_reg_on = true;
/*
* Optimal time to get the regulator turned on
* before detecting vbus interrupt.
*/
mdelay(15);
}
/* clk is disabled during phy power off and not here*/
if (!phy->ctrl_clk_on) {
tegra_clk_prepare_enable(phy->ctrlr_clk);
phy->ctrl_clk_on = true;
}
return IRQ_HANDLED;
}
static void tegra_usb_phy_release_clocks(struct tegra_usb_phy *phy)
{
clk_put(phy->emc_clk);
clk_put(phy->sys_clk);
if (phy->pdata->op_mode == TEGRA_USB_OPMODE_HOST)
if (phy->pdata->u_data.host.hot_plug ||
phy->pdata->u_data.host.remote_wakeup_supported)
tegra_clk_disable_unprepare(phy->ctrlr_clk);
clk_put(phy->ctrlr_clk);
if (tegra_platform_is_silicon()) {
tegra_clk_disable_unprepare(phy->pllu_clk);
clk_put(phy->pllu_clk);
}
}
static int tegra_usb_phy_get_clocks(struct tegra_usb_phy *phy)
{
int err = 0;
if (tegra_platform_is_silicon()) {
phy->pllu_reg = regulator_get(&phy->pdev->dev, USB_PLL_REG);
if (IS_ERR_OR_NULL(phy->pllu_reg)) {
ERR("Couldn't get regulator %s: %ld\n", USB_PLL_REG,
PTR_ERR(phy->pllu_reg));
phy->pllu_reg = NULL;
return PTR_ERR(phy->pllu_reg);
}
regulator_enable(phy->pllu_reg);
phy->pllu_clk = clk_get_sys(NULL, "pll_u");
if (IS_ERR(phy->pllu_clk)) {
ERR("inst:[%d] Can't get pllu_clk clock\n", phy->inst);
err = PTR_ERR(phy->pllu_clk);
goto fail_pll;
}
tegra_clk_prepare_enable(phy->pllu_clk);
}
phy->ctrlr_clk = clk_get(&phy->pdev->dev, NULL);
if (IS_ERR(phy->ctrlr_clk)) {
dev_err(&phy->pdev->dev, "Can't get controller clock\n");
err = PTR_ERR(phy->ctrlr_clk);
goto fail_ctrlr_clk;
}
if (phy->pdata->op_mode == TEGRA_USB_OPMODE_HOST)
if (phy->pdata->u_data.host.hot_plug ||
phy->pdata->u_data.host.remote_wakeup_supported)
tegra_clk_prepare_enable(phy->ctrlr_clk);
phy->sys_clk = clk_get(&phy->pdev->dev, "sclk");
if (IS_ERR(phy->sys_clk)) {
dev_err(&phy->pdev->dev, "Can't get sclk clock\n");
err = PTR_ERR(phy->sys_clk);
goto fail_sclk;
}
clk_set_rate(phy->sys_clk, 80000000);
phy->emc_clk = clk_get(&phy->pdev->dev, "emc");
if (IS_ERR(phy->emc_clk)) {
dev_err(&phy->pdev->dev, "Can't get emc clock\n");
err = PTR_ERR(phy->emc_clk);
goto fail_emc;
}
if(phy->pdata->has_hostpc)
clk_set_rate(phy->emc_clk, 100000000);
else
clk_set_rate(phy->emc_clk, 300000000);
return err;
fail_emc:
clk_put(phy->sys_clk);
fail_sclk:
clk_put(phy->ctrlr_clk);
fail_ctrlr_clk:
if (tegra_platform_is_silicon()) {
tegra_clk_disable_unprepare(phy->pllu_clk);
clk_put(phy->pllu_clk);
}
fail_pll:
if (tegra_platform_is_silicon()) {
regulator_disable(phy->pllu_reg);
regulator_put(phy->pllu_reg);
}
return err;
}
void tegra_usb_phy_close(struct usb_phy *x)
{
struct tegra_usb_phy *phy = get_tegra_phy(x);
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->ops && phy->ops->close)
phy->ops->close(phy);
if (phy->pdata->ops && phy->pdata->ops->close)
phy->pdata->ops->close();
if (phy->pdata->op_mode == TEGRA_USB_OPMODE_DEVICE) {
if (phy->pdata->u_data.dev.vbus_pmu_irq)
free_irq(phy->pdata->u_data.dev.vbus_pmu_irq, phy);
else
tegra_clk_disable_unprepare(phy->ctrlr_clk);
} else {
usb_host_vbus_enable(phy, false);
if (phy->vbus_reg)
regulator_put(phy->vbus_reg);
else {
int gpio = phy->pdata->u_data.host.vbus_gpio;
if (gpio != -1) {
gpio_set_value_cansleep(gpio, 0);
gpio_free(gpio);
}
}
}
if (phy->vdd_reg) {
if (phy->vdd_reg_on)
regulator_disable(phy->vdd_reg);
regulator_put(phy->vdd_reg);
}
tegra_usb_phy_release_clocks(phy);
if (tegra_platform_is_silicon() && phy->pllu_reg) {
regulator_disable(phy->pllu_reg);
regulator_put(phy->pllu_reg);
}
}
irqreturn_t tegra_usb_phy_irq(struct tegra_usb_phy *phy)
{
irqreturn_t status = IRQ_HANDLED;
if (phy->ops && phy->ops->irq)
status = phy->ops->irq(phy);
return status;
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_irq);
int tegra_usb_phy_init(struct usb_phy *x)
{
int status = 0;
struct tegra_usb_phy *phy = get_tegra_phy(x);
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->pdata->ops && phy->pdata->ops->init)
phy->pdata->ops->init();
if (phy->ops && phy->ops->init)
status = phy->ops->init(phy);
return status;
}
int tegra_usb_phy_power_off(struct tegra_usb_phy *phy)
{
int err = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (!phy->phy_power_on)
return err;
if (phy->ops && phy->ops->power_off) {
if (phy->pdata->ops && phy->pdata->ops->pre_phy_off)
phy->pdata->ops->pre_phy_off();
err = phy->ops->power_off(phy);
if (phy->pdata->ops && phy->pdata->ops->post_phy_off)
phy->pdata->ops->post_phy_off();
}
tegra_clk_disable_unprepare(phy->emc_clk);
tegra_clk_disable_unprepare(phy->sys_clk);
if (phy->pdata->op_mode == TEGRA_USB_OPMODE_HOST) {
if (!phy->pdata->u_data.host.hot_plug &&
!phy->pdata->u_data.host.remote_wakeup_supported) {
tegra_clk_disable_unprepare(phy->ctrlr_clk);
phy->ctrl_clk_on = false;
if (phy->vdd_reg && phy->vdd_reg_on) {
regulator_disable(phy->vdd_reg);
phy->vdd_reg_on = false;
}
}
} else {
/* In device mode clock regulator/clocks will be turned off
* only if pmu interrupt is present on the board and host mode
* support through OTG is supported on the board.
*/
if (phy->pdata->u_data.dev.vbus_pmu_irq &&
phy->pdata->id_det_type == TEGRA_USB_VIRTUAL_ID) {
tegra_clk_disable_unprepare(phy->ctrlr_clk);
phy->ctrl_clk_on = false;
if (phy->vdd_reg && phy->vdd_reg_on) {
regulator_disable(phy->vdd_reg);
phy->vdd_reg_on = false;
}
}
}
phy->phy_power_on = false;
return err;
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_power_off);
int tegra_usb_phy_power_on(struct tegra_usb_phy *phy)
{
int status = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->phy_power_on)
return status;
#ifndef CONFIG_ARCH_TEGRA_2x_SOC
if (phy->vdd_reg && !phy->vdd_reg_on) {
regulator_enable(phy->vdd_reg);
phy->vdd_reg_on = true;
}
#endif
/* In device mode clock is turned on by pmu irq handler
* if pmu irq is not available clocks will not be turned off/on
*/
if (phy->pdata->op_mode == TEGRA_USB_OPMODE_HOST) {
if (!phy->pdata->u_data.host.hot_plug &&
!phy->pdata->u_data.host.remote_wakeup_supported)
tegra_clk_prepare_enable(phy->ctrlr_clk);
} else {
if (phy->pdata->u_data.dev.vbus_pmu_irq &&
!phy->ctrl_clk_on) {
tegra_clk_prepare_enable(phy->ctrlr_clk);
phy->ctrl_clk_on = true;
}
}
tegra_clk_prepare_enable(phy->sys_clk);
tegra_clk_prepare_enable(phy->emc_clk);
if (phy->ops && phy->ops->power_on) {
if (phy->pdata->ops && phy->pdata->ops->pre_phy_on)
phy->pdata->ops->pre_phy_on();
status = phy->ops->power_on(phy);
if (phy->pdata->ops && phy->pdata->ops->post_phy_on)
phy->pdata->ops->post_phy_on();
}
phy->phy_power_on = true;
return status;
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_power_on);
int tegra_usb_phy_reset(struct tegra_usb_phy *phy)
{
int status = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->ops && phy->ops->reset)
status = phy->ops->reset(phy);
return status;
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_reset);
int tegra_usb_phy_pre_suspend(struct tegra_usb_phy *phy)
{
int status = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->pdata->ops && phy->pdata->ops->pre_suspend)
phy->pdata->ops->pre_suspend();
if (phy->ops && phy->ops->pre_suspend)
status = phy->ops->pre_suspend(phy);
return status;
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_pre_suspend);
int tegra_usb_phy_suspend(struct tegra_usb_phy *phy)
{
int err = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->ops && phy->ops->suspend)
err = phy->ops->suspend(phy);
if (!err && phy->pdata->u_data.host.power_off_on_suspend)
tegra_usb_phy_power_off(phy);
return err;
}
int tegra_usb_phy_post_suspend(struct tegra_usb_phy *phy)
{
int status = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->ops && phy->ops->post_suspend)
status = phy->ops->post_suspend(phy);
if (phy->pdata->ops && phy->pdata->ops->post_suspend)
phy->pdata->ops->post_suspend();
return status;
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_post_suspend);
int tegra_usb_phy_pre_resume(struct tegra_usb_phy *phy, bool remote_wakeup)
{
int status = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->pdata->ops && phy->pdata->ops->pre_resume)
phy->pdata->ops->pre_resume();
if (phy->ops && phy->ops->pre_resume)
status = phy->ops->pre_resume(phy, remote_wakeup);
return status;
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_pre_resume);
int tegra_usb_phy_resume(struct tegra_usb_phy *phy)
{
int err = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->pdata->u_data.host.power_off_on_suspend) {
err = tegra_usb_phy_power_on(phy);
}
if (!err && phy->ops && phy->ops->resume)
err = phy->ops->resume(phy);
return err;
}
int tegra_usb_phy_post_resume(struct tegra_usb_phy *phy)
{
int status = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->ops && phy->ops->post_resume)
status = phy->ops->post_resume(phy);
if (phy->pdata->ops && phy->pdata->ops->post_resume)
phy->pdata->ops->post_resume();
return status;
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_post_resume);
int tegra_usb_phy_port_power(struct tegra_usb_phy *phy)
{
int status = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->ops && phy->ops->port_power)
status = phy->ops->port_power(phy);
return status;
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_port_power);
int tegra_usb_phy_bus_reset(struct tegra_usb_phy *phy)
{
int status = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->ops && phy->ops->bus_reset)
status = phy->ops->bus_reset(phy);
return status;
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_bus_reset);
bool tegra_usb_phy_charger_detected(struct tegra_usb_phy *phy)
{
bool status = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->ops && phy->ops->charger_detect)
status = phy->ops->charger_detect(phy);
return status;
}
bool tegra_usb_phy_nv_charger_detected(struct tegra_usb_phy *phy)
{
bool status = 0;
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
if (phy->ops && phy->ops->nv_charger_detect)
status = phy->ops->nv_charger_detect(phy);
return status;
}
bool tegra_usb_phy_hw_accessible(struct tegra_usb_phy *phy)
{
if (!phy->hw_accessible)
DBG("%s(%d) inst:[%d] Not Accessible\n", __func__,
__LINE__, phy->inst);
return phy->hw_accessible;
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_hw_accessible);
bool tegra_usb_phy_pmc_wakeup(struct tegra_usb_phy *phy)
{
return phy->pmc_remote_wakeup || phy->pmc_hotplug_wakeup;
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_pmc_wakeup);
void tegra_usb_phy_memory_prefetch_on(struct tegra_usb_phy *phy)
{
void __iomem *ahb_gizmo = IO_ADDRESS(TEGRA_AHB_GIZMO_BASE);
unsigned long val;
if (phy->inst == 0 && phy->pdata->op_mode == TEGRA_USB_OPMODE_DEVICE) {
val = readl(ahb_gizmo + AHB_MEM_PREFETCH_CFG1);
val |= PREFETCH_ENB;
ahb_gizmo_writel(val, ahb_gizmo + AHB_MEM_PREFETCH_CFG1);
val = readl(ahb_gizmo + AHB_MEM_PREFETCH_CFG2);
val |= PREFETCH_ENB;
ahb_gizmo_writel(val, ahb_gizmo + AHB_MEM_PREFETCH_CFG2);
}
}
void tegra_usb_phy_memory_prefetch_off(struct tegra_usb_phy *phy)
{
void __iomem *ahb_gizmo = IO_ADDRESS(TEGRA_AHB_GIZMO_BASE);
unsigned long val;
if (phy->inst == 0 && phy->pdata->op_mode == TEGRA_USB_OPMODE_DEVICE) {
val = readl(ahb_gizmo + AHB_MEM_PREFETCH_CFG1);
val &= ~(PREFETCH_ENB);
ahb_gizmo_writel(val, ahb_gizmo + AHB_MEM_PREFETCH_CFG1);
val = readl(ahb_gizmo + AHB_MEM_PREFETCH_CFG2);
val &= ~(PREFETCH_ENB);
ahb_gizmo_writel(val, ahb_gizmo + AHB_MEM_PREFETCH_CFG2);
}
}
int tegra_usb_phy_set_suspend(struct usb_phy *x, int suspend)
{
struct tegra_usb_phy *phy = get_tegra_phy(x);
if (suspend)
return tegra_usb_phy_suspend(phy);
else
return tegra_usb_phy_resume(phy);
}
struct tegra_usb_phy *tegra_usb_phy_open(struct platform_device *pdev)
{
struct tegra_usb_phy *phy;
struct tegra_usb_platform_data *pdata;
struct resource *res;
int err;
int plat_data_size = sizeof(struct tegra_usb_platform_data);
DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, pdev->id);
pdata = dev_get_platdata(&pdev->dev);
if (!pdata) {
dev_err(&pdev->dev, "inst:[%d] Platform data missing\n",
pdev->id);
err = -EINVAL;
goto fail_inval;
}
phy = devm_kzalloc(&pdev->dev, sizeof(struct tegra_usb_phy), GFP_KERNEL);
if (!phy) {
ERR("inst:[%d] malloc usb phy failed\n", pdev->id);
err = -ENOMEM;
goto fail_nomem;
}
phy->pdata = devm_kzalloc(&pdev->dev, plat_data_size, GFP_KERNEL);
if (!phy->pdata) {
ERR("inst:[%d] malloc usb phy pdata failed\n", pdev->id);
err = -ENOMEM;
goto fail_nomem;
}
memcpy(phy->pdata, pdata, plat_data_size);
phy->pdev = pdev;
phy->inst = pdev->id;
if (phy->pdata->op_mode == TEGRA_USB_OPMODE_HOST)
phy->hot_plug = phy->pdata->u_data.host.hot_plug;
print_usb_plat_data_info(phy);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
ERR("inst:[%d] failed to get I/O memory\n", phy->inst);
err = -ENXIO;
goto fail_io;
}
phy->regs = ioremap(res->start, resource_size(res));
if (!phy->regs) {
ERR("inst:[%d] Failed to remap I/O memory\n", phy->inst);
err = -ENOMEM;
goto fail_io;
}
phy->vdd_reg = regulator_get(&pdev->dev, "avdd_usb");
if (IS_ERR_OR_NULL(phy->vdd_reg)) {
ERR("inst:[%d] couldn't get regulator avdd_usb: %ld\n",
phy->inst, PTR_ERR(phy->vdd_reg));
phy->vdd_reg = NULL;
}
err = tegra_usb_phy_get_clocks(phy);
if (err) {
ERR("inst:[%d] Failed to init clocks\n", phy->inst);
goto fail_clk;
}
if (phy->pdata->op_mode == TEGRA_USB_OPMODE_DEVICE) {
if (phy->pdata->u_data.dev.vbus_pmu_irq) {
err = request_threaded_irq(
phy->pdata->u_data.dev.vbus_pmu_irq,
NULL, usb_phy_dev_vbus_pmu_irq_thr,
IRQF_SHARED, "usb_pmu_vbus_irq", phy);
if (err) {
ERR("inst:[%d] Failed to register IRQ\n",
phy->inst);
goto fail_init;
}
} else {
tegra_clk_prepare_enable(phy->ctrlr_clk);
}
} else {
int gpio = phy->pdata->u_data.host.vbus_gpio;
if (gpio != -1) {
if (gpio_request(gpio, "usb_host_vbus") < 0) {
ERR("inst:[%d] host vbus gpio req failed\n",
phy->inst);
goto fail_init;
}
if (gpio_direction_output(gpio, 1) < 0) {
ERR("inst:[%d] host vbus gpio dir failed\n",
phy->inst);
goto fail_init;
}
} else {
phy->vbus_reg = regulator_get(&pdev->dev, "usb_vbus");
if (IS_ERR_OR_NULL(phy->vbus_reg)) {
ERR("failed to get regulator vdd_vbus_usb:" \
"%ld,instance : %d\n", PTR_ERR(phy->vbus_reg),
phy->inst);
phy->vbus_reg = NULL;
}
}
usb_host_vbus_enable(phy, true);
/* Fixme: Need delay to stablize the vbus on USB1
this must be fixed properly */
if (phy->inst == 0)
msleep(1000);
}
err = tegra_usb_phy_init_ops(phy);
if (err) {
ERR("inst:[%d] Failed to init ops\n", phy->inst);
goto fail_init;
}
if (phy->pdata->ops && phy->pdata->ops->open)
phy->pdata->ops->open();
if (phy->ops && phy->ops->open) {
err = phy->ops->open(phy);
if (err) {
ERR("inst:[%d] Failed to open hw ops\n", phy->inst);
goto fail_init;
}
}
/* Set usb_phy func pointers */
phy->phy.init = tegra_usb_phy_init;
phy->phy.shutdown = tegra_usb_phy_close;
phy->phy.set_suspend = tegra_usb_phy_set_suspend;
return phy;
fail_init:
tegra_usb_phy_release_clocks(phy);
if (phy->pdata->op_mode == TEGRA_USB_OPMODE_DEVICE) {
if (phy->pdata->u_data.dev.vbus_pmu_irq)
free_irq(phy->pdata->u_data.dev.vbus_pmu_irq, phy);
} else {
usb_host_vbus_enable(phy, false);
if (phy->vbus_reg)
regulator_put(phy->vbus_reg);
else {
int gpio = phy->pdata->u_data.host.vbus_gpio;
if (gpio != -1) {
gpio_set_value_cansleep(gpio, 0);
gpio_free(gpio);
}
}
}
fail_clk:
regulator_put(phy->vdd_reg);
iounmap(phy->regs);
fail_io:
fail_nomem:
fail_inval:
return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_open);
void tegra_usb_phy_pmc_disable(struct tegra_usb_phy *phy)
{
if (phy->ops && phy->ops->pmc_disable)
phy->ops->pmc_disable(phy);
}
EXPORT_SYMBOL_GPL(tegra_usb_phy_pmc_disable);