diff options
| -rw-r--r-- | drivers/usb/otg/tegra-otg.c | 155 | 
1 files changed, 140 insertions, 15 deletions
| diff --git a/drivers/usb/otg/tegra-otg.c b/drivers/usb/otg/tegra-otg.c index bd7d6e11501b..df4806a5c6b5 100644 --- a/drivers/usb/otg/tegra-otg.c +++ b/drivers/usb/otg/tegra-otg.c @@ -3,7 +3,7 @@   *   * OTG transceiver driver for Tegra UTMI phy   * - * Copyright (C) 2010-2012 NVIDIA CORPORATION. All rights reserved. + * Copyright (C) 2010-2013 NVIDIA CORPORATION. All rights reserved.   * Copyright (C) 2010 Google, Inc.   *   * This program is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@  #include <linux/err.h>  #include <linux/export.h>  #include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h>  #define USB_PHY_WAKEUP		0x408  #define  USB_ID_INT_EN		(1 << 0) @@ -51,6 +52,8 @@  #define DBG(stuff...)	do {} while (0)  #endif +#define YCABLE_CHARGING_CURRENT_UA 500000u +  struct tegra_otg_data {  	struct platform_device *pdev;  	struct tegra_usb_otg_data *pdata; @@ -62,7 +65,12 @@ struct tegra_otg_data {  	struct clk *clk;  	int irq;  	struct work_struct work; +	struct regulator *vbus_reg; +	struct regulator *vbus_bat_reg;  	unsigned int intr_reg_data; +	bool support_y_cable; +	bool y_cable_conn; +	bool turn_off_vbus_on_lp0;  	bool clk_enabled;  	bool interrupt_mode;  	bool builtin_host; @@ -190,6 +198,74 @@ static void tegra_otg_notify_event(struct tegra_otg_data *tegra, int event)  	atomic_notifier_call_chain(&tegra->phy.notifier, event, tegra->phy.otg->gadget);  } +static void tegra_otg_set_current(struct regulator *vbus_bat_reg, int max_uA) +{ +	if (vbus_bat_reg == NULL) +		return ; + +	regulator_set_current_limit(vbus_bat_reg, 0, max_uA); +} + +static void tegra_otg_vbus_enable(struct regulator *vbus_reg, int on) +{ +	static int vbus_enable = 1; + +	if (vbus_reg == NULL) +		return ; +	DBG("%s(%d) vbus_enable = %d on = %d\n", __func__, __LINE__, +				vbus_enable, on); + +	if (on && vbus_enable) { +		regulator_enable(vbus_reg); +		vbus_enable = 0; +	} else if (!on && !vbus_enable) { +		regulator_disable(vbus_reg); +		vbus_enable = 1; +	} +} + +static int tegra_otg_start_host(struct tegra_otg_data *tegra, int on) +{ +	if (on) { +		if (tegra->support_y_cable && +				(tegra->int_status & USB_VBUS_STATUS)) { +			DBG("%s(%d) set current %dmA\n", __func__, __LINE__, +					YCABLE_CHARGING_CURRENT_UA/1000); +			tegra_otg_set_current(tegra->vbus_bat_reg, +					YCABLE_CHARGING_CURRENT_UA); +			tegra->y_cable_conn = true; +		} else { +			tegra_otg_vbus_enable(tegra->vbus_reg, 1); +		} +		tegra_start_host(tegra); +		tegra_otg_notify_event(tegra, USB_EVENT_ID); +	} else { +		tegra_stop_host(tegra); +		tegra_otg_notify_event(tegra, USB_EVENT_NONE); +		tegra_otg_vbus_enable(tegra->vbus_reg, 0); +		if (tegra->support_y_cable && tegra->y_cable_conn) { +			tegra_otg_set_current(tegra->vbus_bat_reg, 0); +			tegra->y_cable_conn = false; +		} +	} +	return 0; +} + +static int tegra_otg_start_gadget(struct tegra_otg_data *tegra, int on) +{ +	struct usb_otg *otg = tegra->phy.otg; + +	if (on) { +		usb_gadget_vbus_connect(otg->gadget); +		tegra_otg_notify_event(tegra, USB_EVENT_VBUS); +	} else { +		usb_gadget_vbus_disconnect(otg->gadget); +		tegra_otg_notify_event(tegra, USB_EVENT_NONE); +	} + +	return 0; +} +  static void tegra_change_otg_state(struct tegra_otg_data *tegra,  				enum usb_otg_state to)  { @@ -209,20 +285,23 @@ static void tegra_change_otg_state(struct tegra_otg_data *tegra,  		pr_info("otg state changed: %s --> %s\n", tegra_state_name(from), tegra_state_name(to));  		if (from == OTG_STATE_A_SUSPEND) { -			if (to == OTG_STATE_B_PERIPHERAL && otg->gadget) { -				usb_gadget_vbus_connect(otg->gadget); -				tegra_otg_notify_event(tegra, USB_EVENT_VBUS); -			} -			else if (to == OTG_STATE_A_HOST) { -				tegra_start_host(tegra); -				tegra_otg_notify_event(tegra, USB_EVENT_ID); -			} +			if (to == OTG_STATE_B_PERIPHERAL && otg->gadget) +				tegra_otg_start_gadget(tegra, 1); +			else if (to == OTG_STATE_A_HOST) +				tegra_otg_start_host(tegra, 1);  		} else if (from == OTG_STATE_A_HOST && to == OTG_STATE_A_SUSPEND) { -			tegra_stop_host(tegra); -			tegra_otg_notify_event(tegra, USB_EVENT_NONE); +			tegra_otg_start_host(tegra, 0);  		} else if (from == OTG_STATE_B_PERIPHERAL && otg->gadget && to == OTG_STATE_A_SUSPEND) { -			usb_gadget_vbus_disconnect(otg->gadget); -			tegra_otg_notify_event(tegra, USB_EVENT_NONE); +			tegra_otg_start_gadget(tegra, 0); +		} +	} + +	if (tegra->support_y_cable && tegra->y_cable_conn && +			from == to && from == OTG_STATE_A_HOST) { +		if (!(tegra->int_status & USB_VBUS_STATUS)) { +			DBG("%s(%d) Charger disconnect\n", __func__, __LINE__); +			tegra_otg_set_current(tegra->vbus_bat_reg, 0); +			tegra_otg_vbus_enable(tegra->vbus_reg, 1);  		}  	}  } @@ -421,6 +500,9 @@ static int tegra_otg_probe(struct platform_device *pdev)  	tegra_clone = tegra;  	tegra->interrupt_mode = true;  	tegra->suspended = false; +	tegra->turn_off_vbus_on_lp0 = true; +	tegra->support_y_cable = true; +	tegra->y_cable_conn = false;  	tegra->clk = clk_get(&pdev->dev, NULL);  	if (IS_ERR(tegra->clk)) { @@ -465,6 +547,22 @@ static int tegra_otg_probe(struct platform_device *pdev)  		err = 0;  	} +	tegra->vbus_reg = regulator_get(&pdev->dev, "usb_vbus"); +	if (IS_ERR_OR_NULL(tegra->vbus_reg)) { +		pr_err("failed to get regulator usb_vbus: %ld\n", +				PTR_ERR(tegra->vbus_reg)); +		tegra->vbus_reg = NULL; +	} + +	if (tegra->support_y_cable) { +		tegra->vbus_bat_reg = regulator_get(&pdev->dev, "usb_bat_chg"); +		if (IS_ERR_OR_NULL(tegra->vbus_bat_reg)) { +			pr_err("failed to get regulator usb_bat_chg: %ld\n", +					PTR_ERR(tegra->vbus_bat_reg)); +			tegra->vbus_bat_reg = NULL; +		} +	} +  	INIT_WORK(&tegra->work, irq_work);  	tegra->phy.dev = &pdev->dev; @@ -502,6 +600,11 @@ err_irq:  err_io:  	clk_put(tegra->clk);  err_clk: +	if (tegra->vbus_reg) +		regulator_put(tegra->vbus_reg); +	if (tegra->support_y_cable && tegra->vbus_bat_reg) +		regulator_put(tegra->vbus_bat_reg); +  	return err;  } @@ -509,6 +612,12 @@ static int __exit tegra_otg_remove(struct platform_device *pdev)  {  	struct tegra_otg_data *tegra = platform_get_drvdata(pdev); +	if (tegra->vbus_reg) +		regulator_put(tegra->vbus_reg); + +	if (tegra->support_y_cable && tegra->vbus_bat_reg) +		regulator_put(tegra->vbus_bat_reg); +  	pm_runtime_disable(tegra->phy.dev);  	usb_set_transceiver(NULL);  	iounmap(tegra->regs); @@ -544,6 +653,9 @@ static int tegra_otg_suspend(struct device *dev)  	if (from == OTG_STATE_B_PERIPHERAL)  		tegra_change_otg_state(tegra, OTG_STATE_A_SUSPEND); +	if (from == OTG_STATE_A_HOST && tegra->turn_off_vbus_on_lp0) +		tegra_otg_vbus_enable(tegra->vbus_reg, 0); +  	tegra->suspended = true;  	DBG("%s(%d) END\n", __func__, __LINE__); @@ -583,8 +695,21 @@ static void tegra_otg_resume(struct device *dev)  	else  		tegra->int_status = val | USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN |  			USB_ID_PIN_WAKEUP_EN; -  	spin_unlock_irqrestore(&tegra->lock, flags); + +	if (tegra->turn_off_vbus_on_lp0 && +		!(tegra->int_status & USB_ID_STATUS)) { +		/* Handle Y-cable */ +		if (tegra->support_y_cable && +				(tegra->int_status & USB_VBUS_STATUS)) { +			tegra_otg_set_current(tegra->vbus_bat_reg, +				YCABLE_CHARGING_CURRENT_UA); +			tegra->y_cable_conn = true; +		} else { +			tegra_otg_vbus_enable(tegra->vbus_reg, 1); +		} +	} +  	schedule_work(&tegra->work);  	enable_interrupt(tegra, true); @@ -616,7 +741,7 @@ static int __init tegra_otg_init(void)  {  	return platform_driver_register(&tegra_otg_driver);  } -subsys_initcall(tegra_otg_init); +fs_initcall(tegra_otg_init);  static void __exit tegra_otg_exit(void)  { | 
