Contributors: 29
Author Tokens Token Proportion Commits Commit Proportion
Roger Quadros 375 23.60% 8 13.56%
Robert Jarzmik 352 22.15% 6 10.17%
Ajay Kumar Gupta 327 20.58% 1 1.69%
Sean Anderson 125 7.87% 3 5.08%
Sebastian Andrzej Siewior 67 4.22% 1 1.69%
Felipe Balbi 66 4.15% 7 11.86%
Heikki Krogerus 58 3.65% 2 3.39%
Stefan Eichenberger 46 2.89% 1 1.69%
Fabio Estevam 27 1.70% 3 5.08%
David Brownell 26 1.64% 2 3.39%
Geert Uytterhoeven 23 1.45% 3 5.08%
Peter Chen 15 0.94% 1 1.69%
Maarten ter Huurne 13 0.82% 1 1.69%
Li Jun 10 0.63% 1 1.69%
Sascha Hauer 9 0.57% 1 1.69%
Juha Yrjola 8 0.50% 1 1.69%
Antoine Tenart 7 0.44% 2 3.39%
Uwe Kleine-König 6 0.38% 3 5.08%
Kishon Vijay Abraham I 5 0.31% 1 1.69%
Mark Brown 5 0.31% 1 1.69%
Mike Looijmans 3 0.19% 1 1.69%
Yang Yingliang 3 0.19% 1 1.69%
Michael Grzeschik 3 0.19% 1 1.69%
Linus Torvalds (pre-git) 2 0.13% 1 1.69%
Greg Kroah-Hartman 2 0.13% 2 3.39%
Lucas Stach 2 0.13% 1 1.69%
Paul Zimmerman 2 0.13% 1 1.69%
Linus Walleij 1 0.06% 1 1.69%
Linus Torvalds 1 0.06% 1 1.69%
Total 1589 59


// SPDX-License-Identifier: GPL-2.0+
/*
 * NOP USB transceiver for all USB transceiver which are either built-in
 * into USB IP or which are mostly autonomous.
 *
 * Copyright (C) 2009 Texas Instruments Inc
 * Author: Ajay Kumar Gupta <ajay.gupta@ti.com>
 *
 * Current status:
 *	This provides a "nop" transceiver for PHYs which are
 *	autonomous such as isp1504, isp1707, etc.
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/usb/gadget.h>
#include <linux/usb/otg.h>
#include <linux/usb/usb_phy_generic.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/regulator/consumer.h>
#include <linux/property.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>

#include "phy-generic.h"

#define VBUS_IRQ_FLAGS \
	(IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | \
		IRQF_ONESHOT)

struct platform_device *usb_phy_generic_register(void)
{
	return platform_device_register_simple("usb_phy_generic",
			PLATFORM_DEVID_AUTO, NULL, 0);
}
EXPORT_SYMBOL_GPL(usb_phy_generic_register);

void usb_phy_generic_unregister(struct platform_device *pdev)
{
	platform_device_unregister(pdev);
}
EXPORT_SYMBOL_GPL(usb_phy_generic_unregister);

static int nop_set_suspend(struct usb_phy *x, int suspend)
{
	struct usb_phy_generic *nop = dev_get_drvdata(x->dev);
	int ret = 0;

	if (suspend) {
		clk_disable_unprepare(nop->clk);
		if (!IS_ERR(nop->vcc) && !device_may_wakeup(x->dev))
			ret = regulator_disable(nop->vcc);
	} else {
		if (!IS_ERR(nop->vcc) && !device_may_wakeup(x->dev))
			ret = regulator_enable(nop->vcc);
		clk_prepare_enable(nop->clk);
	}

	return ret;
}

static void nop_reset(struct usb_phy_generic *nop)
{
	if (!nop->gpiod_reset)
		return;

	gpiod_set_value_cansleep(nop->gpiod_reset, 1);
	usleep_range(10000, 20000);
	gpiod_set_value_cansleep(nop->gpiod_reset, 0);
	usleep_range(10000, 30000);
}

/* interface to regulator framework */
static int nop_set_vbus(struct usb_otg *otg, bool enable)
{
	int ret = 0;
	struct usb_phy_generic *nop = dev_get_drvdata(otg->usb_phy->dev);

	if (!nop->vbus_draw)
		return 0;

	if (enable && !nop->vbus_draw_enabled) {
		ret = regulator_enable(nop->vbus_draw);
		if (ret)
			nop->vbus_draw_enabled = false;
		else
			nop->vbus_draw_enabled = true;

	} else if (!enable && nop->vbus_draw_enabled) {
		ret = regulator_disable(nop->vbus_draw);
		nop->vbus_draw_enabled = false;
	}
	return ret;
}


static irqreturn_t nop_gpio_vbus_thread(int irq, void *data)
{
	struct usb_phy_generic *nop = data;
	struct usb_otg *otg = nop->phy.otg;
	int vbus, status;

	vbus = gpiod_get_value(nop->gpiod_vbus);
	if ((vbus ^ nop->vbus) == 0)
		return IRQ_HANDLED;
	nop->vbus = vbus;

	if (vbus) {
		status = USB_EVENT_VBUS;
		otg->state = OTG_STATE_B_PERIPHERAL;
		nop->phy.last_event = status;

		atomic_notifier_call_chain(&nop->phy.notifier, status,
					   otg->gadget);
	} else {
		status = USB_EVENT_NONE;
		otg->state = OTG_STATE_B_IDLE;
		nop->phy.last_event = status;

		atomic_notifier_call_chain(&nop->phy.notifier, status,
					   otg->gadget);
	}
	return IRQ_HANDLED;
}

int usb_gen_phy_init(struct usb_phy *phy)
{
	struct usb_phy_generic *nop = dev_get_drvdata(phy->dev);
	int ret;

	if (!IS_ERR(nop->vcc)) {
		if (regulator_enable(nop->vcc))
			dev_err(phy->dev, "Failed to enable power\n");
	}

	ret = clk_prepare_enable(nop->clk);
	if (ret)
		return ret;

	nop_reset(nop);

	return 0;
}
EXPORT_SYMBOL_GPL(usb_gen_phy_init);

void usb_gen_phy_shutdown(struct usb_phy *phy)
{
	struct usb_phy_generic *nop = dev_get_drvdata(phy->dev);

	gpiod_set_value_cansleep(nop->gpiod_reset, 1);

	clk_disable_unprepare(nop->clk);

	if (!IS_ERR(nop->vcc)) {
		if (regulator_disable(nop->vcc))
			dev_err(phy->dev, "Failed to disable power\n");
	}
}
EXPORT_SYMBOL_GPL(usb_gen_phy_shutdown);

static int nop_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget)
{
	if (!otg)
		return -ENODEV;

	if (!gadget) {
		otg->gadget = NULL;
		return -ENODEV;
	}

	otg->gadget = gadget;
	if (otg->state == OTG_STATE_B_PERIPHERAL)
		atomic_notifier_call_chain(&otg->usb_phy->notifier,
					   USB_EVENT_VBUS, otg->gadget);
	else
		otg->state = OTG_STATE_B_IDLE;
	return 0;
}

static int nop_set_host(struct usb_otg *otg, struct usb_bus *host)
{
	if (!otg)
		return -ENODEV;

	if (!host) {
		otg->host = NULL;
		return -ENODEV;
	}

	otg->host = host;
	return 0;
}

int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop)
{
	enum usb_phy_type type = USB_PHY_TYPE_USB2;
	int err = 0;
	u32 clk_rate = 0;

	device_property_read_u32(dev, "clock-frequency", &clk_rate);
	nop->gpiod_reset = devm_gpiod_get_optional(dev, "reset",
						   GPIOD_ASIS);
	err = PTR_ERR_OR_ZERO(nop->gpiod_reset);
	if (!err) {
		nop->gpiod_vbus = devm_gpiod_get_optional(dev,
						 "vbus-detect",
						 GPIOD_ASIS);
		err = PTR_ERR_OR_ZERO(nop->gpiod_vbus);
	}

	if (err)
		return dev_err_probe(dev, err,
				     "Error requesting RESET or VBUS GPIO\n");
	if (nop->gpiod_reset)
		gpiod_direction_output(nop->gpiod_reset, 1);

	nop->phy.otg = devm_kzalloc(dev, sizeof(*nop->phy.otg),
			GFP_KERNEL);
	if (!nop->phy.otg)
		return -ENOMEM;

	nop->clk = devm_clk_get_optional(dev, "main_clk");
	if (IS_ERR(nop->clk))
		return dev_err_probe(dev, PTR_ERR(nop->clk),
				     "Can't get phy clock\n");

	if (clk_rate) {
		err = clk_set_rate(nop->clk, clk_rate);
		if (err)
			return dev_err_probe(dev, err,
					     "Error setting clock rate\n");
	}

	nop->vcc = devm_regulator_get_optional(dev, "vcc");
	if (IS_ERR(nop->vcc) && PTR_ERR(nop->vcc) != -ENODEV)
		return dev_err_probe(dev, PTR_ERR(nop->vcc),
				     "could not get vcc regulator\n");

	nop->vbus_draw = devm_regulator_get_exclusive(dev, "vbus");
	if (PTR_ERR(nop->vbus_draw) == -ENODEV)
		nop->vbus_draw = NULL;
	if (IS_ERR(nop->vbus_draw))
		return dev_err_probe(dev, PTR_ERR(nop->vbus_draw),
				     "could not get vbus regulator\n");

	nop->dev		= dev;
	nop->phy.dev		= nop->dev;
	nop->phy.label		= "nop-xceiv";
	nop->phy.set_suspend	= nop_set_suspend;
	nop->phy.type		= type;

	nop->phy.otg->state		= OTG_STATE_UNDEFINED;
	nop->phy.otg->usb_phy		= &nop->phy;
	nop->phy.otg->set_host		= nop_set_host;
	nop->phy.otg->set_peripheral	= nop_set_peripheral;
	nop->phy.otg->set_vbus          = nop_set_vbus;

	return 0;
}
EXPORT_SYMBOL_GPL(usb_phy_gen_create_phy);

static int usb_phy_generic_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct usb_phy_generic	*nop;
	int err;

	nop = devm_kzalloc(dev, sizeof(*nop), GFP_KERNEL);
	if (!nop)
		return -ENOMEM;

	err = usb_phy_gen_create_phy(dev, nop);
	if (err)
		return err;

	if (nop->gpiod_vbus) {
		err = devm_request_threaded_irq(dev,
						gpiod_to_irq(nop->gpiod_vbus),
						NULL, nop_gpio_vbus_thread,
						VBUS_IRQ_FLAGS, "vbus_detect",
						nop);
		if (err)
			return dev_err_probe(dev, err, "can't request irq %i\n",
					     gpiod_to_irq(nop->gpiod_vbus));

		nop->phy.otg->state = gpiod_get_value(nop->gpiod_vbus) ?
			OTG_STATE_B_PERIPHERAL : OTG_STATE_B_IDLE;
	}

	nop->phy.init		= usb_gen_phy_init;
	nop->phy.shutdown	= usb_gen_phy_shutdown;

	err = usb_add_phy_dev(&nop->phy);
	if (err)
		return dev_err_probe(dev, err, "can't register transceiver\n");

	platform_set_drvdata(pdev, nop);

	device_set_wakeup_capable(dev,
				  device_property_read_bool(dev, "wakeup-source"));

	return 0;
}

static void usb_phy_generic_remove(struct platform_device *pdev)
{
	struct usb_phy_generic *nop = platform_get_drvdata(pdev);

	usb_remove_phy(&nop->phy);

	if (nop->vbus_draw && nop->vbus_draw_enabled)
		regulator_disable(nop->vbus_draw);
}

static const struct of_device_id nop_xceiv_dt_ids[] = {
	{ .compatible = "usb-nop-xceiv" },
	{ }
};

MODULE_DEVICE_TABLE(of, nop_xceiv_dt_ids);

static struct platform_driver usb_phy_generic_driver = {
	.probe		= usb_phy_generic_probe,
	.remove		= usb_phy_generic_remove,
	.driver		= {
		.name	= "usb_phy_generic",
		.of_match_table = nop_xceiv_dt_ids,
	},
};

static int __init usb_phy_generic_init(void)
{
	return platform_driver_register(&usb_phy_generic_driver);
}
subsys_initcall(usb_phy_generic_init);

static void __exit usb_phy_generic_exit(void)
{
	platform_driver_unregister(&usb_phy_generic_driver);
}
module_exit(usb_phy_generic_exit);

MODULE_ALIAS("platform:usb_phy_generic");
MODULE_AUTHOR("Texas Instruments Inc");
MODULE_DESCRIPTION("NOP USB Transceiver driver");
MODULE_LICENSE("GPL");