Commit b4e19931 authored by Nagarjuna Kristam's avatar Nagarjuna Kristam Committed by Thierry Reding
Browse files

usb: gadget: tegra-xudc: Support multiple device modes



This change supports limited multiple device modes by:
- At most 4 ports contains OTG/Device capability.
- One port run as device mode at a time.

Signed-off-by: default avatarNagarjuna Kristam <nkristam@nvidia.com>
Acked-by: default avatarFelipe Balbi <balbi@kernel.org>
Signed-off-by: default avatarThierry Reding <treding@nvidia.com>
parent b9c9fd4a
Loading
Loading
Loading
Loading
+160 −57
Original line number Diff line number Diff line
@@ -482,14 +482,16 @@ struct tegra_xudc {
	bool device_mode;
	struct work_struct usb_role_sw_work;

	struct phy *usb3_phy;
	struct phy *utmi_phy;
	struct phy **usb3_phy;
	struct phy *curr_usb3_phy;
	struct phy **utmi_phy;
	struct phy *curr_utmi_phy;

	struct tegra_xudc_save_regs saved_regs;
	bool suspended;
	bool powergated;

	struct usb_phy *usbphy;
	struct usb_phy **usbphy;
	struct notifier_block vbus_nb;

	struct completion disconnect_complete;
@@ -521,6 +523,7 @@ struct tegra_xudc_soc {
	unsigned int num_supplies;
	const char * const *clock_names;
	unsigned int num_clks;
	unsigned int num_phys;
	bool u1_enable;
	bool u2_enable;
	bool lpm_enable;
@@ -602,17 +605,18 @@ static void tegra_xudc_device_mode_on(struct tegra_xudc *xudc)

	pm_runtime_get_sync(xudc->dev);

	err = phy_power_on(xudc->utmi_phy);
	err = phy_power_on(xudc->curr_utmi_phy);
	if (err < 0)
		dev_err(xudc->dev, "utmi power on failed %d\n", err);

	err = phy_power_on(xudc->usb3_phy);
	err = phy_power_on(xudc->curr_usb3_phy);
	if (err < 0)
		dev_err(xudc->dev, "usb3 phy power on failed %d\n", err);

	dev_dbg(xudc->dev, "device mode on\n");

	phy_set_mode_ext(xudc->utmi_phy, PHY_MODE_USB_OTG, USB_ROLE_DEVICE);
	phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG,
			 USB_ROLE_DEVICE);
}

static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc)
@@ -627,7 +631,7 @@ static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc)

	reinit_completion(&xudc->disconnect_complete);

	phy_set_mode_ext(xudc->utmi_phy, PHY_MODE_USB_OTG, USB_ROLE_NONE);
	phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG, USB_ROLE_NONE);

	pls = (xudc_readl(xudc, PORTSC) & PORTSC_PLS_MASK) >>
		PORTSC_PLS_SHIFT;
@@ -652,11 +656,11 @@ static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc)
	/* Make sure interrupt handler has completed before powergating. */
	synchronize_irq(xudc->irq);

	err = phy_power_off(xudc->utmi_phy);
	err = phy_power_off(xudc->curr_utmi_phy);
	if (err < 0)
		dev_err(xudc->dev, "utmi_phy power off failed %d\n", err);

	err = phy_power_off(xudc->usb3_phy);
	err = phy_power_off(xudc->curr_usb3_phy);
	if (err < 0)
		dev_err(xudc->dev, "usb3_phy power off failed %d\n", err);

@@ -674,12 +678,27 @@ static void tegra_xudc_usb_role_sw_work(struct work_struct *work)
		tegra_xudc_device_mode_off(xudc);
}

static int tegra_xudc_get_phy_index(struct tegra_xudc *xudc,
					      struct usb_phy *usbphy)
{
	unsigned int i;

	for (i = 0; i < xudc->soc->num_phys; i++) {
		if (xudc->usbphy[i] && usbphy == xudc->usbphy[i])
			return i;
	}

	dev_info(xudc->dev, "phy index could not be found for shared USB PHY");
	return -1;
}

static int tegra_xudc_vbus_notify(struct notifier_block *nb,
					 unsigned long action, void *data)
{
	struct tegra_xudc *xudc = container_of(nb, struct tegra_xudc,
					       vbus_nb);
	struct usb_phy *usbphy = (struct usb_phy *)data;
	int phy_index;

	dev_dbg(xudc->dev, "%s(): event is %d\n", __func__, usbphy->last_event);

@@ -693,8 +712,15 @@ static int tegra_xudc_vbus_notify(struct notifier_block *nb,
	xudc->device_mode = (usbphy->last_event == USB_EVENT_VBUS) ? true :
								     false;

	if (!xudc->suspended)
	phy_index = tegra_xudc_get_phy_index(xudc, usbphy);
	dev_dbg(xudc->dev, "%s(): current phy index is %d\n", __func__,
		phy_index);

	if (!xudc->suspended && phy_index != -1) {
		xudc->curr_utmi_phy = xudc->utmi_phy[phy_index];
		xudc->curr_usb3_phy = xudc->usb3_phy[phy_index];
		schedule_work(&xudc->usb_role_sw_work);
	}

	return NOTIFY_OK;
}
@@ -714,9 +740,9 @@ static void tegra_xudc_plc_reset_work(struct work_struct *work)

		if (pls == PORTSC_PLS_INACTIVE) {
			dev_info(xudc->dev, "PLS = Inactive. Toggle VBUS\n");
			phy_set_mode_ext(xudc->utmi_phy, PHY_MODE_USB_OTG,
			phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG,
					 USB_ROLE_NONE);
			phy_set_mode_ext(xudc->utmi_phy, PHY_MODE_USB_OTG,
			phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG,
					 USB_ROLE_DEVICE);

			xudc->wait_csc = false;
@@ -745,7 +771,8 @@ static void tegra_xudc_port_reset_war_work(struct work_struct *work)
		if (pls == PORTSC_PLS_DISABLED) {
			dev_dbg(xudc->dev, "toggle vbus\n");
			/* PRC doesn't complete in 100ms, toggle the vbus */
			ret = tegra_phy_xusb_utmi_port_reset(xudc->utmi_phy);
			ret = tegra_phy_xusb_utmi_port_reset(
				xudc->curr_utmi_phy);
			if (ret == 1)
				xudc->wait_for_sec_prc = 0;
		}
@@ -1934,6 +1961,7 @@ static int tegra_xudc_gadget_start(struct usb_gadget *gadget,
	unsigned long flags;
	u32 val;
	int ret;
	unsigned int i;

	if (!driver)
		return -EINVAL;
@@ -1969,8 +1997,9 @@ static int tegra_xudc_gadget_start(struct usb_gadget *gadget,
		xudc_writel(xudc, val, CTRL);
	}

	if (xudc->usbphy)
		otg_set_peripheral(xudc->usbphy->otg, gadget);
	for (i = 0; i < xudc->soc->num_phys; i++)
		if (xudc->usbphy[i])
			otg_set_peripheral(xudc->usbphy[i]->otg, gadget);

	xudc->driver = driver;
unlock:
@@ -1987,13 +2016,15 @@ static int tegra_xudc_gadget_stop(struct usb_gadget *gadget)
	struct tegra_xudc *xudc = to_xudc(gadget);
	unsigned long flags;
	u32 val;
	unsigned int i;

	pm_runtime_get_sync(xudc->dev);

	spin_lock_irqsave(&xudc->lock, flags);

	if (xudc->usbphy)
		otg_set_peripheral(xudc->usbphy->otg, NULL);
	for (i = 0; i < xudc->soc->num_phys; i++)
		if (xudc->usbphy[i])
			otg_set_peripheral(xudc->usbphy[i]->otg, NULL);

	val = xudc_readl(xudc, CTRL);
	val &= ~(CTRL_IE | CTRL_ENABLE);
@@ -3327,35 +3358,122 @@ static void tegra_xudc_device_params_init(struct tegra_xudc *xudc)
	xudc_writel(xudc, val, CFG_DEV_SSPI_XFER);
}

static int tegra_xudc_phy_get(struct tegra_xudc *xudc)
{
	int err = 0, usb3;
	unsigned int i;

	xudc->utmi_phy = devm_kcalloc(xudc->dev, xudc->soc->num_phys,
					   sizeof(*xudc->utmi_phy), GFP_KERNEL);
	if (!xudc->utmi_phy)
		return -ENOMEM;

	xudc->usb3_phy = devm_kcalloc(xudc->dev, xudc->soc->num_phys,
					   sizeof(*xudc->usb3_phy), GFP_KERNEL);
	if (!xudc->usb3_phy)
		return -ENOMEM;

	xudc->usbphy = devm_kcalloc(xudc->dev, xudc->soc->num_phys,
					   sizeof(*xudc->usbphy), GFP_KERNEL);
	if (!xudc->usbphy)
		return -ENOMEM;

	xudc->vbus_nb.notifier_call = tegra_xudc_vbus_notify;

	for (i = 0; i < xudc->soc->num_phys; i++) {
		char phy_name[] = "usb.-.";

		/* Get USB2 phy */
		snprintf(phy_name, sizeof(phy_name), "usb2-%d", i);
		xudc->utmi_phy[i] = devm_phy_optional_get(xudc->dev, phy_name);
		if (IS_ERR(xudc->utmi_phy[i])) {
			err = PTR_ERR(xudc->utmi_phy[i]);
			if (err != -EPROBE_DEFER)
				dev_err(xudc->dev, "failed to get usb2-%d phy: %d\n",
					i, err);

			goto clean_up;
		} else if (xudc->utmi_phy[i]) {
			/* Get usb-phy, if utmi phy is available */
			xudc->usbphy[i] = devm_usb_get_phy_by_node(xudc->dev,
						xudc->utmi_phy[i]->dev.of_node,
						&xudc->vbus_nb);
			if (IS_ERR(xudc->usbphy[i])) {
				err = PTR_ERR(xudc->usbphy[i]);
				dev_err(xudc->dev, "failed to get usbphy-%d: %d\n",
					i, err);
				goto clean_up;
			}
		} else if (!xudc->utmi_phy[i]) {
			/* if utmi phy is not available, ignore USB3 phy get */
			continue;
		}

		/* Get USB3 phy */
		usb3 = tegra_xusb_padctl_get_usb3_companion(xudc->padctl, i);
		if (usb3 < 0)
			continue;

		snprintf(phy_name, sizeof(phy_name), "usb3-%d", usb3);
		xudc->usb3_phy[i] = devm_phy_optional_get(xudc->dev, phy_name);
		if (IS_ERR(xudc->usb3_phy[i])) {
			err = PTR_ERR(xudc->usb3_phy[i]);
			if (err != -EPROBE_DEFER)
				dev_err(xudc->dev, "failed to get usb3-%d phy: %d\n",
					usb3, err);

			goto clean_up;
		} else if (xudc->usb3_phy[i])
			dev_dbg(xudc->dev, "usb3_phy-%d registered", usb3);
	}

	return err;

clean_up:
	for (i = 0; i < xudc->soc->num_phys; i++) {
		xudc->usb3_phy[i] = NULL;
		xudc->utmi_phy[i] = NULL;
		xudc->usbphy[i] = NULL;
	}

	return err;
}

static void tegra_xudc_phy_exit(struct tegra_xudc *xudc)
{
	unsigned int i;

	for (i = 0; i < xudc->soc->num_phys; i++) {
		phy_exit(xudc->usb3_phy[i]);
		phy_exit(xudc->utmi_phy[i]);
	}
}

static int tegra_xudc_phy_init(struct tegra_xudc *xudc)
{
	int err;
	unsigned int i;

	err = phy_init(xudc->utmi_phy);
	for (i = 0; i < xudc->soc->num_phys; i++) {
		err = phy_init(xudc->utmi_phy[i]);
		if (err < 0) {
			dev_err(xudc->dev, "utmi phy init failed: %d\n", err);
		return err;
			goto exit_phy;
		}

	err = phy_init(xudc->usb3_phy);
		err = phy_init(xudc->usb3_phy[i]);
		if (err < 0) {
			dev_err(xudc->dev, "usb3 phy init failed: %d\n", err);
		goto exit_utmi_phy;
			goto exit_phy;
		}
	}

	return 0;

exit_utmi_phy:
	phy_exit(xudc->utmi_phy);
exit_phy:
	tegra_xudc_phy_exit(xudc);
	return err;
}

static void tegra_xudc_phy_exit(struct tegra_xudc *xudc)
{
	phy_exit(xudc->usb3_phy);
	phy_exit(xudc->utmi_phy);
}

static const char * const tegra210_xudc_supply_names[] = {
	"hvdd-usb",
	"avddio-usb",
@@ -3381,6 +3499,7 @@ static struct tegra_xudc_soc tegra210_xudc_soc_data = {
	.num_supplies = ARRAY_SIZE(tegra210_xudc_supply_names),
	.clock_names = tegra210_xudc_clock_names,
	.num_clks = ARRAY_SIZE(tegra210_xudc_clock_names),
	.num_phys = 4,
	.u1_enable = false,
	.u2_enable = true,
	.lpm_enable = false,
@@ -3393,6 +3512,7 @@ static struct tegra_xudc_soc tegra210_xudc_soc_data = {
static struct tegra_xudc_soc tegra186_xudc_soc_data = {
	.clock_names = tegra186_xudc_clock_names,
	.num_clks = ARRAY_SIZE(tegra186_xudc_clock_names),
	.num_phys = 4,
	.u1_enable = true,
	.u2_enable = true,
	.lpm_enable = false,
@@ -3555,19 +3675,9 @@ static int tegra_xudc_probe(struct platform_device *pdev)
		goto put_padctl;
	}

	xudc->usb3_phy = devm_phy_optional_get(&pdev->dev, "usb3");
	if (IS_ERR(xudc->usb3_phy)) {
		err = PTR_ERR(xudc->usb3_phy);
		dev_err(xudc->dev, "failed to get usb3 phy: %d\n", err);
		goto disable_regulator;
	}

	xudc->utmi_phy = devm_phy_optional_get(&pdev->dev, "usb2");
	if (IS_ERR(xudc->utmi_phy)) {
		err = PTR_ERR(xudc->utmi_phy);
		dev_err(xudc->dev, "failed to get usb2 phy: %d\n", err);
	err = tegra_xudc_phy_get(xudc);
	if (err)
		goto disable_regulator;
	}

	err = tegra_xudc_powerdomain_init(xudc);
	if (err)
@@ -3596,16 +3706,6 @@ static int tegra_xudc_probe(struct platform_device *pdev)
	INIT_DELAYED_WORK(&xudc->port_reset_war_work,
				tegra_xudc_port_reset_war_work);

	xudc->vbus_nb.notifier_call = tegra_xudc_vbus_notify;
	xudc->usbphy = devm_usb_get_phy_by_node(xudc->dev,
						xudc->utmi_phy->dev.of_node,
						&xudc->vbus_nb);
	if (IS_ERR(xudc->usbphy)) {
		err = PTR_ERR(xudc->usbphy);
		dev_err(xudc->dev, "failed to get USB PHY: %d\n", err);
		goto free_eps;
	}

	pm_runtime_enable(&pdev->dev);

	xudc->gadget.ops = &tegra_xudc_gadget_ops;
@@ -3640,6 +3740,7 @@ static int tegra_xudc_probe(struct platform_device *pdev)
static int tegra_xudc_remove(struct platform_device *pdev)
{
	struct tegra_xudc *xudc = platform_get_drvdata(pdev);
	unsigned int i;

	pm_runtime_get_sync(xudc->dev);

@@ -3655,8 +3756,10 @@ static int tegra_xudc_remove(struct platform_device *pdev)

	regulator_bulk_disable(xudc->soc->num_supplies, xudc->supplies);

	phy_power_off(xudc->utmi_phy);
	phy_power_off(xudc->usb3_phy);
	for (i = 0; i < xudc->soc->num_phys; i++) {
		phy_power_off(xudc->utmi_phy[i]);
		phy_power_off(xudc->usb3_phy[i]);
	}

	tegra_xudc_phy_exit(xudc);