linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] R-Car Gen2 USB0 Host/Function switching
@ 2015-06-22 14:42 Phil Edworthy
  2015-06-22 14:42 ` [PATCH 1/3] usb: renesas_usbhs: Allow an OTG PHY driver to provide VBUS Phil Edworthy
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Phil Edworthy @ 2015-06-22 14:42 UTC (permalink / raw)
  To: Kishon Vijay Abraham I, Yoshihiro Shimoda, Simon Horman
  Cc: Sergei Shtylyov, linux-kernel, linux-usb, linux-sh, Phil Edworthy

This patch set adds support for USB Host/Function switching using external
gpios to get the vbus and id signals.

I am aware that the dt binding for the USB phy will need updating, but wanted
to get this patch set out first to see whether this is the best way to
implement this.

Phil

Phil Edworthy (3):
  usb: renesas_usbhs: Allow an OTG PHY driver to provide VBUS
  phy: rcar-gen2 usb: Add Host/Function switching for USB0
  arm: koelsch: make USB0 perform Host/Function switching

 arch/arm/boot/dts/r8a7791-koelsch.dts  |   7 +-
 drivers/phy/phy-rcar-gen2.c            | 269 ++++++++++++++++++++++++++++++---
 drivers/usb/renesas_usbhs/common.h     |   2 +
 drivers/usb/renesas_usbhs/mod.c        |   3 +
 drivers/usb/renesas_usbhs/mod_gadget.c |  38 +++++
 5 files changed, 294 insertions(+), 25 deletions(-)

-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
Please read the FAQ at  http://www.tux.org/lkml/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 1/3] usb: renesas_usbhs: Allow an OTG PHY driver to provide VBUS
  2015-06-22 14:42 [PATCH 0/3] R-Car Gen2 USB0 Host/Function switching Phil Edworthy
@ 2015-06-22 14:42 ` Phil Edworthy
  2015-06-22 17:38   ` Sergei Shtylyov
  2015-06-22 14:42 ` [PATCH 2/3] phy: rcar-gen2 usb: Add Host/Function switching for USB0 Phil Edworthy
  2015-06-22 14:42 ` [PATCH 3/3] arm: koelsch: make USB0 perform Host/Function switching Phil Edworthy
  2 siblings, 1 reply; 11+ messages in thread
From: Phil Edworthy @ 2015-06-22 14:42 UTC (permalink / raw)
  To: Kishon Vijay Abraham I, Yoshihiro Shimoda, Simon Horman
  Cc: Sergei Shtylyov, linux-kernel, linux-usb, linux-sh, Phil Edworthy

These changes allow a PHY driver to trigger a VBUS interrupt and
to provide the value of VBUS.

Signed-off-by: Phil Edworthy <phil.edworthy@renesas.com>
---
 drivers/usb/renesas_usbhs/common.h     |  2 ++
 drivers/usb/renesas_usbhs/mod.c        |  3 +++
 drivers/usb/renesas_usbhs/mod_gadget.c | 38 ++++++++++++++++++++++++++++++++++
 3 files changed, 43 insertions(+)

diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h
index 8c5fc12..94a7aeb 100644
--- a/drivers/usb/renesas_usbhs/common.h
+++ b/drivers/usb/renesas_usbhs/common.h
@@ -255,6 +255,8 @@ struct usbhs_priv {
 	struct renesas_usbhs_driver_param	dparam;
 
 	struct delayed_work notify_hotplug_work;
+	int vbus_is_indirect;
+	int vbus_indirect_value;
 	struct platform_device *pdev;
 
 	struct extcon_dev *edev;
diff --git a/drivers/usb/renesas_usbhs/mod.c b/drivers/usb/renesas_usbhs/mod.c
index 9a705b1..8a2d3dd 100644
--- a/drivers/usb/renesas_usbhs/mod.c
+++ b/drivers/usb/renesas_usbhs/mod.c
@@ -42,6 +42,9 @@ static int usbhsm_autonomy_get_vbus(struct platform_device *pdev)
 {
 	struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev);
 
+	if (priv->vbus_is_indirect)
+		return priv->vbus_indirect_value;
+
 	return  VBSTS & usbhs_read(priv, INTSTS0);
 }
 
diff --git a/drivers/usb/renesas_usbhs/mod_gadget.c b/drivers/usb/renesas_usbhs/mod_gadget.c
index dc2aa32..2cfdb50 100644
--- a/drivers/usb/renesas_usbhs/mod_gadget.c
+++ b/drivers/usb/renesas_usbhs/mod_gadget.c
@@ -21,6 +21,7 @@
 #include <linux/platform_device.h>
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
+#include <linux/usb/otg.h>
 #include "common.h"
 
 /*
@@ -50,6 +51,7 @@ struct usbhsg_gpriv {
 	int			 uep_size;
 
 	struct usb_gadget_driver	*driver;
+	struct usb_phy		*transceiver;
 
 	u32	status;
 #define USBHSG_STATUS_STARTED		(1 << 0)
@@ -882,6 +884,8 @@ static int usbhsg_gadget_start(struct usb_gadget *gadget,
 {
 	struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget);
 	struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv);
+	struct device *dev = usbhs_priv_to_dev(priv);
+	int ret;
 
 	if (!driver		||
 	    !driver->setup	||
@@ -891,6 +895,17 @@ static int usbhsg_gadget_start(struct usb_gadget *gadget,
 	/* first hook up the driver ... */
 	gpriv->driver = driver;
 
+	/* connect to bus through transceiver */
+	if (!IS_ERR_OR_NULL(gpriv->transceiver)) {
+		ret = otg_set_peripheral(gpriv->transceiver->otg,
+					&gpriv->gadget);
+		if (ret) {
+			dev_info(dev, "%s: can't bind to transceiver\n",
+				gpriv->gadget.name);
+			return ret;
+		}
+	}
+
 	return usbhsg_try_start(priv, USBHSG_STATUS_REGISTERD);
 }
 
@@ -900,6 +915,10 @@ static int usbhsg_gadget_stop(struct usb_gadget *gadget)
 	struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv);
 
 	usbhsg_try_stop(priv, USBHSG_STATUS_REGISTERD);
+
+	if (!IS_ERR_OR_NULL(gpriv->transceiver))
+		(void) otg_set_peripheral(gpriv->transceiver->otg, NULL);
+
 	gpriv->driver = NULL;
 
 	return 0;
@@ -947,12 +966,27 @@ static int usbhsg_set_selfpowered(struct usb_gadget *gadget, int is_self)
 	return 0;
 }
 
+static int usbhsg_vbus_session(struct usb_gadget *gadget, int is_active)
+{
+	struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget);
+	struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv);
+	struct platform_device *pdev = usbhs_priv_to_pdev(priv);
+
+	priv->vbus_is_indirect = 1;
+	priv->vbus_indirect_value = !!is_active;
+
+	renesas_usbhs_call_notify_hotplug(pdev);
+
+	return 0;
+}
+
 static const struct usb_gadget_ops usbhsg_gadget_ops = {
 	.get_frame		= usbhsg_get_frame,
 	.set_selfpowered	= usbhsg_set_selfpowered,
 	.udc_start		= usbhsg_gadget_start,
 	.udc_stop		= usbhsg_gadget_stop,
 	.pullup			= usbhsg_pullup,
+	.vbus_session		= usbhsg_vbus_session,
 };
 
 static int usbhsg_start(struct usbhs_priv *priv)
@@ -994,6 +1028,10 @@ int usbhs_mod_gadget_probe(struct usbhs_priv *priv)
 		goto usbhs_mod_gadget_probe_err_gpriv;
 	}
 
+	gpriv->transceiver = usb_get_phy(USB_PHY_TYPE_UNDEFINED);
+	dev_info(dev, "%s transceiver found\n",
+		 gpriv->transceiver ? "" : "No");
+
 	/*
 	 * CAUTION
 	 *
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
Please read the FAQ at  http://www.tux.org/lkml/

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 2/3] phy: rcar-gen2 usb: Add Host/Function switching for USB0
  2015-06-22 14:42 [PATCH 0/3] R-Car Gen2 USB0 Host/Function switching Phil Edworthy
  2015-06-22 14:42 ` [PATCH 1/3] usb: renesas_usbhs: Allow an OTG PHY driver to provide VBUS Phil Edworthy
@ 2015-06-22 14:42 ` Phil Edworthy
  2015-06-22 16:13   ` Kishon Vijay Abraham I
  2015-07-02  8:22   ` Kishon Vijay Abraham I
  2015-06-22 14:42 ` [PATCH 3/3] arm: koelsch: make USB0 perform Host/Function switching Phil Edworthy
  2 siblings, 2 replies; 11+ messages in thread
From: Phil Edworthy @ 2015-06-22 14:42 UTC (permalink / raw)
  To: Kishon Vijay Abraham I, Yoshihiro Shimoda, Simon Horman
  Cc: Sergei Shtylyov, linux-kernel, linux-usb, linux-sh, Phil Edworthy

Instead of statically selecting the PHY connection to either the
USBHS (Function) or PCI0 (Host) IP blocks, this change allows the
dts to specifiy gpio pins for the vbus and id signals. Additional
gpio pins are used to control power to an external OTG device and
an override to turn vbus on/off.

Note: the R-Car USB PHY only allows this Host/Function switching
on channel 0.

This has been tested on a r8a7791 based Koelsch board, which uses
a MAX3355 device to supply vbus power when needed.

Signed-off-by: Phil Edworthy <phil.edworthy@renesas.com>
---
 drivers/phy/phy-rcar-gen2.c | 269 ++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 247 insertions(+), 22 deletions(-)

diff --git a/drivers/phy/phy-rcar-gen2.c b/drivers/phy/phy-rcar-gen2.c
index 97d45f4..8564e7d 100644
--- a/drivers/phy/phy-rcar-gen2.c
+++ b/drivers/phy/phy-rcar-gen2.c
@@ -1,5 +1,5 @@
 /*
- * Renesas R-Car Gen2 PHY driver
+ * Renesas R-Car Gen2 USB PHY driver
  *
  * Copyright (C) 2014 Renesas Solutions Corp.
  * Copyright (C) 2014 Cogent Embedded, Inc.
@@ -12,11 +12,16 @@
 #include <linux/clk.h>
 #include <linux/delay.h>
 #include <linux/io.h>
+#include <linux/interrupt.h>
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/of_gpio.h>
 #include <linux/phy/phy.h>
 #include <linux/platform_device.h>
 #include <linux/spinlock.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/otg.h>
+#include <linux/workqueue.h>
 
 #include <asm/cmpxchg.h>
 
@@ -58,6 +63,18 @@ struct rcar_gen2_channel {
 	struct rcar_gen2_phy phys[PHYS_PER_CHANNEL];
 	int selected_phy;
 	u32 select_mask;
+
+	/* external power enable pin */
+	int			gpio_pwr;
+
+	/* Host/Function switching */
+	struct delayed_work	work;
+	int			use_otg;
+	int			gpio_vbus;
+	int			gpio_id;
+	int			gpio_vbus_pwr;
+	struct usb_phy		*usbphy;
+	struct usb_otg		*otg;
 };
 
 struct rcar_gen2_phy_driver {
@@ -68,31 +85,50 @@ struct rcar_gen2_phy_driver {
 	struct rcar_gen2_channel *channels;
 };
 
-static int rcar_gen2_phy_init(struct phy *p)
+static void rcar_gen2_phy_switch(struct rcar_gen2_channel *channel,
+	u32 select_value)
 {
-	struct rcar_gen2_phy *phy = phy_get_drvdata(p);
-	struct rcar_gen2_channel *channel = phy->channel;
 	struct rcar_gen2_phy_driver *drv = channel->drv;
 	unsigned long flags;
 	u32 ugctrl2;
 
-	/*
-	 * Try to acquire exclusive access to PHY.  The first driver calling
-	 * phy_init()  on a given channel wins, and all attempts  to use another
-	 * PHY on this channel will fail until phy_exit() is called by the first
-	 * driver.   Achieving this with cmpxcgh() should be SMP-safe.
-	 */
-	if (cmpxchg(&channel->selected_phy, -1, phy->number) != -1)
-		return -EBUSY;
-
-	clk_prepare_enable(drv->clk);
-
 	spin_lock_irqsave(&drv->lock, flags);
 	ugctrl2 = readl(drv->base + USBHS_UGCTRL2);
 	ugctrl2 &= ~channel->select_mask;
-	ugctrl2 |= phy->select_value;
+	ugctrl2 |= select_value;
 	writel(ugctrl2, drv->base + USBHS_UGCTRL2);
 	spin_unlock_irqrestore(&drv->lock, flags);
+}
+
+static int rcar_gen2_phy_init(struct phy *p)
+{
+	struct rcar_gen2_phy *phy = phy_get_drvdata(p);
+	struct rcar_gen2_channel *channel = phy->channel;
+	struct rcar_gen2_phy_driver *drv = channel->drv;
+
+	if (!channel->use_otg) {
+		/*
+		 * Static Host/Function role.
+		 * Try to acquire exclusive access to PHY. The first driver
+		 * calling phy_init() on a given channel wins, and all attempts
+		 * to use another PHY on this channel will fail until
+		 * phy_exit() is called by the first driver. Achieving this
+		 * with cmpxcgh() should be SMP-safe.
+		 */
+		if (cmpxchg(&channel->selected_phy, -1, phy->number) != -1)
+			return -EBUSY;
+
+		clk_prepare_enable(drv->clk);
+		rcar_gen2_phy_switch(channel, phy->select_value);
+	} else {
+		/*
+		 * Using Host/Function switching, so schedule work to determine
+		 * which role to use.
+		 */
+		clk_prepare_enable(drv->clk);
+		schedule_delayed_work(&channel->work, 100);
+	}
+
 	return 0;
 }
 
@@ -117,9 +153,9 @@ static int rcar_gen2_phy_power_on(struct phy *p)
 	u32 value;
 	int err = 0, i;
 
-	/* Skip if it's not USBHS */
-	if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB)
-		return 0;
+	/* Optional external power pin */
+	if (gpio_is_valid(phy->channel->gpio_pwr))
+		gpio_direction_output(phy->channel->gpio_pwr, 1);
 
 	spin_lock_irqsave(&drv->lock, flags);
 
@@ -160,9 +196,9 @@ static int rcar_gen2_phy_power_off(struct phy *p)
 	unsigned long flags;
 	u32 value;
 
-	/* Skip if it's not USBHS */
-	if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB)
-		return 0;
+	/* External power pin */
+	if (gpio_is_valid(phy->channel->gpio_pwr))
+		gpio_direction_output(phy->channel->gpio_pwr, 0);
 
 	spin_lock_irqsave(&drv->lock, flags);
 
@@ -236,6 +272,132 @@ static const u32 select_value[][PHYS_PER_CHANNEL] = {
 	[2]	= { USBHS_UGCTRL2_USB2SEL_PCI, USBHS_UGCTRL2_USB2SEL_USB30 },
 };
 
+
+#define VBUS_IRQ_FLAGS \
+	(IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
+
+static void gpio_vbus_work(struct work_struct *work)
+{
+	struct rcar_gen2_channel *channel = container_of(work,
+					struct rcar_gen2_channel, work.work);
+	struct usb_phy *usbphy = channel->usbphy;
+	int status, vbus, id;
+
+	vbus = !!gpio_get_value(channel->gpio_vbus);
+	id = !!gpio_get_value(channel->gpio_id);
+
+	/* Switch the PHY over */
+	if (id)
+		rcar_gen2_phy_switch(channel, USBHS_UGCTRL2_USB0SEL_HS_USB);
+	else
+		rcar_gen2_phy_switch(channel, USBHS_UGCTRL2_USB0SEL_PCI);
+
+	/* If VBUS is powered and we are not the initial Host, turn off VBUS */
+	if (gpio_is_valid(channel->gpio_vbus_pwr))
+		gpio_direction_output(channel->gpio_vbus_pwr, !(id && vbus));
+
+	if (!channel->otg->gadget)
+		return;
+
+	/* Function handling: vbus=1 when initially plugged into a Host */
+	if (vbus) {
+		status = USB_EVENT_VBUS;
+		usbphy->otg->state = OTG_STATE_B_PERIPHERAL;
+		usbphy->last_event = status;
+		usb_gadget_vbus_connect(usbphy->otg->gadget);
+
+		atomic_notifier_call_chain(&usbphy->notifier,
+					   status, usbphy->otg->gadget);
+	} else {
+		usb_gadget_vbus_disconnect(usbphy->otg->gadget);
+		status = USB_EVENT_NONE;
+		usbphy->otg->state = OTG_STATE_B_IDLE;
+		usbphy->last_event = status;
+
+		atomic_notifier_call_chain(&usbphy->notifier,
+					   status, usbphy->otg->gadget);
+	}
+}
+
+/* VBUS change IRQ handler */
+static irqreturn_t gpio_vbus_irq(int irq, void *data)
+{
+	struct rcar_gen2_channel *channel = data;
+
+	/* Wait 20ms before doing anything as VBUS needs to settle */
+	schedule_delayed_work(&channel->work, msecs_to_jiffies(20));
+
+	return IRQ_HANDLED;
+}
+
+static int probe_otg(struct platform_device *pdev,
+	struct rcar_gen2_phy_driver *drv)
+{
+	struct device *dev = &pdev->dev;
+	struct rcar_gen2_channel *ch = drv->channels;
+	int irq;
+	int ret;
+
+	/* GPIOs for Host/Fn switching */
+	ch->gpio_id = of_get_named_gpio_flags(dev->of_node, "renesas,id",
+				0, NULL);
+	ch->gpio_vbus = of_get_named_gpio_flags(dev->of_node, "renesas,vbus",
+				0, NULL);
+
+	/* Need valid ID and VBUS gpios for Host/Fn switching */
+	if (gpio_is_valid(ch->gpio_id) && gpio_is_valid(ch->gpio_vbus)) {
+		ch->use_otg = 1;
+
+		/* GPIO for ID input */
+		ret = devm_gpio_request_one(dev, ch->gpio_id, GPIOF_IN, "id");
+		if (ret)
+			return ret;
+
+		/* GPIO for VBUS input */
+		ret = devm_gpio_request_one(dev, ch->gpio_vbus, GPIOF_IN, "vbus");
+		if (ret)
+			return ret;
+
+		irq = gpio_to_irq(ch->gpio_vbus);
+		ret = devm_request_irq(dev, irq, gpio_vbus_irq, VBUS_IRQ_FLAGS,
+				"vbus_detect", ch);
+		if (ret)
+			return ret;
+
+		/* Optional GPIO for VBUS power */
+		ch->gpio_vbus_pwr = of_get_named_gpio_flags(dev->of_node,
+						"renesas,vbus-pwr", 0, NULL);
+		if (gpio_is_valid(ch->gpio_id)) {
+			ret = devm_gpio_request_one(dev, ch->gpio_vbus_pwr,
+					GPIOF_OUT_INIT_LOW, "vbus-pwr");
+			if (ret)
+				return ret;
+		}
+
+	} else if (gpio_is_valid(ch->gpio_id)) {
+		dev_err(dev, "Failed to get VBUS gpio\n");
+		return ch->gpio_vbus;
+	} else if (gpio_is_valid(ch->gpio_vbus)) {
+		dev_err(dev, "Failed to get ID gpio\n");
+		return ch->gpio_id;
+	}
+
+	return 0;
+}
+
+/* bind/unbind the peripheral controller */
+static int rcar_gen2_usb_set_peripheral(struct usb_otg *otg,
+					struct usb_gadget *gadget)
+{
+	otg->gadget = gadget;
+	if (!gadget) {
+		usb_gadget_vbus_disconnect(otg->gadget);
+		otg->state = OTG_STATE_UNDEFINED;
+	}
+
+	return 0;
+}
+
 static int rcar_gen2_phy_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -245,7 +407,9 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
 	struct resource *res;
 	void __iomem *base;
 	struct clk *clk;
+	struct usb_otg *otg;
 	int i = 0;
+	int err;
 
 	if (!dev->of_node) {
 		dev_err(dev,
@@ -280,6 +444,57 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
 	if (!drv->channels)
 		return -ENOMEM;
 
+	/* USB0 optional GPIO power pin for external devices */
+	drv->channels->gpio_pwr = of_get_named_gpio_flags(dev->of_node,
+						"renesas,pwr", 0, NULL);
+	if (drv->channels->gpio_pwr == -EPROBE_DEFER)
+		return -EPROBE_DEFER;
+
+	if (gpio_is_valid(drv->channels->gpio_pwr)) {
+		err = devm_gpio_request(dev, drv->channels->gpio_pwr, "pwr");
+		if (err)
+			return err;
+	}
+
+	/* USB0 Host/Function switching info */
+	err = probe_otg(pdev, drv);
+	if (err)
+		return err;
+
+	/*
+	 * The PHY connected to channel 0 can be used to steer signals to the
+	 * USB Host IP that stils behind a PCI bridge (pci0), or the USB Func
+	 * IP (hsusb). We can dynamically switch this based on VBUS and ID
+	 * signals connected to gpios, to get something approaching OTG.
+	 */
+	if (drv->channels->use_otg) {
+		struct usb_phy *usbphy;
+
+		usbphy = devm_kzalloc(dev, sizeof(*usbphy), GFP_KERNEL);
+		if (!usbphy)
+			return -ENOMEM;
+
+		otg = devm_kzalloc(dev, sizeof(*otg), GFP_KERNEL);
+		if (!otg)
+			return -ENOMEM;
+
+		usbphy->dev		= dev;
+		usbphy->otg		= otg;
+
+		otg->usb_phy		= usbphy;
+		otg->state		= OTG_STATE_UNDEFINED;
+		otg->set_peripheral	= rcar_gen2_usb_set_peripheral;
+
+		drv->channels->otg	= otg;
+		drv->channels->usbphy	= usbphy;
+
+		ATOMIC_INIT_NOTIFIER_HEAD(&usbphy->notifier);
+
+		INIT_DELAYED_WORK(&drv->channels->work, gpio_vbus_work);
+
+		usb_add_phy_dev(usbphy);
+	}
+
 	for_each_child_of_node(dev->of_node, np) {
 		struct rcar_gen2_channel *channel = drv->channels + i;
 		u32 channel_num;
@@ -288,6 +503,8 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
 		channel->of_node = np;
 		channel->drv = drv;
 		channel->selected_phy = -1;
+		if (i != 0)
+			channel->gpio_pwr = -ENOENT;
 
 		error = of_property_read_u32(np, "reg", &channel_num);
 		if (error || channel_num > 2) {
@@ -323,6 +540,14 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
 
 	dev_set_drvdata(dev, drv);
 
+	/*
+	 * If we already have something plugged into USB0, we won't get an edge
+	 * on VBUS, so we have to manually schedule work to look at the VBUS
+	 * and ID signals.
+	 */
+	if (drv->channels->use_otg)
+		schedule_delayed_work(&drv->channels->work, msecs_to_jiffies(100));
+
 	return 0;
 }
 
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
Please read the FAQ at  http://www.tux.org/lkml/

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 3/3] arm: koelsch: make USB0 perform Host/Function switching
  2015-06-22 14:42 [PATCH 0/3] R-Car Gen2 USB0 Host/Function switching Phil Edworthy
  2015-06-22 14:42 ` [PATCH 1/3] usb: renesas_usbhs: Allow an OTG PHY driver to provide VBUS Phil Edworthy
  2015-06-22 14:42 ` [PATCH 2/3] phy: rcar-gen2 usb: Add Host/Function switching for USB0 Phil Edworthy
@ 2015-06-22 14:42 ` Phil Edworthy
  2015-06-22 17:43   ` Sergei Shtylyov
  2 siblings, 1 reply; 11+ messages in thread
From: Phil Edworthy @ 2015-06-22 14:42 UTC (permalink / raw)
  To: Kishon Vijay Abraham I, Yoshihiro Shimoda, Simon Horman
  Cc: Sergei Shtylyov, linux-kernel, linux-usb, linux-sh, Phil Edworthy

Both USB Host (pci0) and Function (USBHS) drivers are enabled.
The USB PHY driver determines which IP block should be connected
based on vbus and id signals read via gpios.

Signed-off-by: Phil Edworthy <phil.edworthy@renesas.com>
---
 arch/arm/boot/dts/r8a7791-koelsch.dts | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/arch/arm/boot/dts/r8a7791-koelsch.dts b/arch/arm/boot/dts/r8a7791-koelsch.dts
index cffe33f..8f394be 100644
--- a/arch/arm/boot/dts/r8a7791-koelsch.dts
+++ b/arch/arm/boot/dts/r8a7791-koelsch.dts
@@ -615,7 +615,6 @@
 
 &pci0 {
 	status = "okay";
-	pinctrl-0 = <&usb0_pins>;
 	pinctrl-names = "default";
 };
 
@@ -627,13 +626,15 @@
 
 &hsusb {
 	status = "okay";
-	pinctrl-0 = <&usb0_pins>;
 	pinctrl-names = "default";
-	renesas,enable-gpio = <&gpio5 31 GPIO_ACTIVE_HIGH>;
 };
 
 &usbphy {
 	status = "okay";
+	renesas,pwr = <&gpio2 4 GPIO_ACTIVE_HIGH>;
+	renesas,id = <&gpio5 31 GPIO_ACTIVE_HIGH>;
+	renesas,vbus = <&gpio7 24 GPIO_ACTIVE_HIGH>;
+	renesas,vbus-pwr = <&gpio7 23 GPIO_ACTIVE_HIGH>;
 };
 
 &pcie_bus_clk {
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
Please read the FAQ at  http://www.tux.org/lkml/

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH 2/3] phy: rcar-gen2 usb: Add Host/Function switching for USB0
  2015-06-22 14:42 ` [PATCH 2/3] phy: rcar-gen2 usb: Add Host/Function switching for USB0 Phil Edworthy
@ 2015-06-22 16:13   ` Kishon Vijay Abraham I
  2015-07-02  8:22   ` Kishon Vijay Abraham I
  1 sibling, 0 replies; 11+ messages in thread
From: Kishon Vijay Abraham I @ 2015-06-22 16:13 UTC (permalink / raw)
  To: Phil Edworthy, Yoshihiro Shimoda, Simon Horman
  Cc: Sergei Shtylyov, linux-kernel, linux-usb, linux-sh, Laurent Pinchart

+Laurent

On Monday 22 June 2015 08:12 PM, Phil Edworthy wrote:
> Instead of statically selecting the PHY connection to either the
> USBHS (Function) or PCI0 (Host) IP blocks, this change allows the
> dts to specifiy gpio pins for the vbus and id signals. Additional
> gpio pins are used to control power to an external OTG device and
> an override to turn vbus on/off.
>
> Note: the R-Car USB PHY only allows this Host/Function switching
> on channel 0.
>
> This has been tested on a r8a7791 based Koelsch board, which uses
> a MAX3355 device to supply vbus power when needed.

Looks like you are touching the part which Laurent had a problem with, so 
looping him.

Thanks
Kishon
>
> Signed-off-by: Phil Edworthy <phil.edworthy@renesas.com>
> ---
>   drivers/phy/phy-rcar-gen2.c | 269 ++++++++++++++++++++++++++++++++++++++++----
>   1 file changed, 247 insertions(+), 22 deletions(-)
>
> diff --git a/drivers/phy/phy-rcar-gen2.c b/drivers/phy/phy-rcar-gen2.c
> index 97d45f4..8564e7d 100644
> --- a/drivers/phy/phy-rcar-gen2.c
> +++ b/drivers/phy/phy-rcar-gen2.c
> @@ -1,5 +1,5 @@
>   /*
> - * Renesas R-Car Gen2 PHY driver
> + * Renesas R-Car Gen2 USB PHY driver
>    *
>    * Copyright (C) 2014 Renesas Solutions Corp.
>    * Copyright (C) 2014 Cogent Embedded, Inc.
> @@ -12,11 +12,16 @@
>   #include <linux/clk.h>
>   #include <linux/delay.h>
>   #include <linux/io.h>
> +#include <linux/interrupt.h>
>   #include <linux/module.h>
>   #include <linux/of.h>
> +#include <linux/of_gpio.h>
>   #include <linux/phy/phy.h>
>   #include <linux/platform_device.h>
>   #include <linux/spinlock.h>
> +#include <linux/usb/gadget.h>
> +#include <linux/usb/otg.h>
> +#include <linux/workqueue.h>
>
>   #include <asm/cmpxchg.h>
>
> @@ -58,6 +63,18 @@ struct rcar_gen2_channel {
>   	struct rcar_gen2_phy phys[PHYS_PER_CHANNEL];
>   	int selected_phy;
>   	u32 select_mask;
> +
> +	/* external power enable pin */
> +	int			gpio_pwr;
> +
> +	/* Host/Function switching */
> +	struct delayed_work	work;
> +	int			use_otg;
> +	int			gpio_vbus;
> +	int			gpio_id;
> +	int			gpio_vbus_pwr;
> +	struct usb_phy		*usbphy;
> +	struct usb_otg		*otg;
>   };
>
>   struct rcar_gen2_phy_driver {
> @@ -68,31 +85,50 @@ struct rcar_gen2_phy_driver {
>   	struct rcar_gen2_channel *channels;
>   };
>
> -static int rcar_gen2_phy_init(struct phy *p)
> +static void rcar_gen2_phy_switch(struct rcar_gen2_channel *channel,
> +	u32 select_value)
>   {
> -	struct rcar_gen2_phy *phy = phy_get_drvdata(p);
> -	struct rcar_gen2_channel *channel = phy->channel;
>   	struct rcar_gen2_phy_driver *drv = channel->drv;
>   	unsigned long flags;
>   	u32 ugctrl2;
>
> -	/*
> -	 * Try to acquire exclusive access to PHY.  The first driver calling
> -	 * phy_init()  on a given channel wins, and all attempts  to use another
> -	 * PHY on this channel will fail until phy_exit() is called by the first
> -	 * driver.   Achieving this with cmpxcgh() should be SMP-safe.
> -	 */
> -	if (cmpxchg(&channel->selected_phy, -1, phy->number) != -1)
> -		return -EBUSY;
> -
> -	clk_prepare_enable(drv->clk);
> -
>   	spin_lock_irqsave(&drv->lock, flags);
>   	ugctrl2 = readl(drv->base + USBHS_UGCTRL2);
>   	ugctrl2 &= ~channel->select_mask;
> -	ugctrl2 |= phy->select_value;
> +	ugctrl2 |= select_value;
>   	writel(ugctrl2, drv->base + USBHS_UGCTRL2);
>   	spin_unlock_irqrestore(&drv->lock, flags);
> +}
> +
> +static int rcar_gen2_phy_init(struct phy *p)
> +{
> +	struct rcar_gen2_phy *phy = phy_get_drvdata(p);
> +	struct rcar_gen2_channel *channel = phy->channel;
> +	struct rcar_gen2_phy_driver *drv = channel->drv;
> +
> +	if (!channel->use_otg) {
> +		/*
> +		 * Static Host/Function role.
> +		 * Try to acquire exclusive access to PHY. The first driver
> +		 * calling phy_init() on a given channel wins, and all attempts
> +		 * to use another PHY on this channel will fail until
> +		 * phy_exit() is called by the first driver. Achieving this
> +		 * with cmpxcgh() should be SMP-safe.
> +		 */
> +		if (cmpxchg(&channel->selected_phy, -1, phy->number) != -1)
> +			return -EBUSY;
> +
> +		clk_prepare_enable(drv->clk);
> +		rcar_gen2_phy_switch(channel, phy->select_value);
> +	} else {
> +		/*
> +		 * Using Host/Function switching, so schedule work to determine
> +		 * which role to use.
> +		 */
> +		clk_prepare_enable(drv->clk);
> +		schedule_delayed_work(&channel->work, 100);
> +	}
> +
>   	return 0;
>   }
>
> @@ -117,9 +153,9 @@ static int rcar_gen2_phy_power_on(struct phy *p)
>   	u32 value;
>   	int err = 0, i;
>
> -	/* Skip if it's not USBHS */
> -	if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB)
> -		return 0;
> +	/* Optional external power pin */
> +	if (gpio_is_valid(phy->channel->gpio_pwr))
> +		gpio_direction_output(phy->channel->gpio_pwr, 1);
>
>   	spin_lock_irqsave(&drv->lock, flags);
>
> @@ -160,9 +196,9 @@ static int rcar_gen2_phy_power_off(struct phy *p)
>   	unsigned long flags;
>   	u32 value;
>
> -	/* Skip if it's not USBHS */
> -	if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB)
> -		return 0;
> +	/* External power pin */
> +	if (gpio_is_valid(phy->channel->gpio_pwr))
> +		gpio_direction_output(phy->channel->gpio_pwr, 0);
>
>   	spin_lock_irqsave(&drv->lock, flags);
>
> @@ -236,6 +272,132 @@ static const u32 select_value[][PHYS_PER_CHANNEL] = {
>   	[2]	= { USBHS_UGCTRL2_USB2SEL_PCI, USBHS_UGCTRL2_USB2SEL_USB30 },
>   };
>
> +
> +#define VBUS_IRQ_FLAGS \
> +	(IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
> +
> +static void gpio_vbus_work(struct work_struct *work)
> +{
> +	struct rcar_gen2_channel *channel = container_of(work,
> +					struct rcar_gen2_channel, work.work);
> +	struct usb_phy *usbphy = channel->usbphy;
> +	int status, vbus, id;
> +
> +	vbus = !!gpio_get_value(channel->gpio_vbus);
> +	id = !!gpio_get_value(channel->gpio_id);
> +
> +	/* Switch the PHY over */
> +	if (id)
> +		rcar_gen2_phy_switch(channel, USBHS_UGCTRL2_USB0SEL_HS_USB);
> +	else
> +		rcar_gen2_phy_switch(channel, USBHS_UGCTRL2_USB0SEL_PCI);
> +
> +	/* If VBUS is powered and we are not the initial Host, turn off VBUS */
> +	if (gpio_is_valid(channel->gpio_vbus_pwr))
> +		gpio_direction_output(channel->gpio_vbus_pwr, !(id && vbus));
> +
> +	if (!channel->otg->gadget)
> +		return;
> +
> +	/* Function handling: vbus=1 when initially plugged into a Host */
> +	if (vbus) {
> +		status = USB_EVENT_VBUS;
> +		usbphy->otg->state = OTG_STATE_B_PERIPHERAL;
> +		usbphy->last_event = status;
> +		usb_gadget_vbus_connect(usbphy->otg->gadget);
> +
> +		atomic_notifier_call_chain(&usbphy->notifier,
> +					   status, usbphy->otg->gadget);
> +	} else {
> +		usb_gadget_vbus_disconnect(usbphy->otg->gadget);
> +		status = USB_EVENT_NONE;
> +		usbphy->otg->state = OTG_STATE_B_IDLE;
> +		usbphy->last_event = status;
> +
> +		atomic_notifier_call_chain(&usbphy->notifier,
> +					   status, usbphy->otg->gadget);
> +	}
> +}
> +
> +/* VBUS change IRQ handler */
> +static irqreturn_t gpio_vbus_irq(int irq, void *data)
> +{
> +	struct rcar_gen2_channel *channel = data;
> +
> +	/* Wait 20ms before doing anything as VBUS needs to settle */
> +	schedule_delayed_work(&channel->work, msecs_to_jiffies(20));
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int probe_otg(struct platform_device *pdev,
> +	struct rcar_gen2_phy_driver *drv)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct rcar_gen2_channel *ch = drv->channels;
> +	int irq;
> +	int ret;
> +
> +	/* GPIOs for Host/Fn switching */
> +	ch->gpio_id = of_get_named_gpio_flags(dev->of_node, "renesas,id",
> +				0, NULL);
> +	ch->gpio_vbus = of_get_named_gpio_flags(dev->of_node, "renesas,vbus",
> +				0, NULL);
> +
> +	/* Need valid ID and VBUS gpios for Host/Fn switching */
> +	if (gpio_is_valid(ch->gpio_id) && gpio_is_valid(ch->gpio_vbus)) {
> +		ch->use_otg = 1;
> +
> +		/* GPIO for ID input */
> +		ret = devm_gpio_request_one(dev, ch->gpio_id, GPIOF_IN, "id");
> +		if (ret)
> +			return ret;
> +
> +		/* GPIO for VBUS input */
> +		ret = devm_gpio_request_one(dev, ch->gpio_vbus, GPIOF_IN, "vbus");
> +		if (ret)
> +			return ret;
> +
> +		irq = gpio_to_irq(ch->gpio_vbus);
> +		ret = devm_request_irq(dev, irq, gpio_vbus_irq, VBUS_IRQ_FLAGS,
> +				"vbus_detect", ch);
> +		if (ret)
> +			return ret;
> +
> +		/* Optional GPIO for VBUS power */
> +		ch->gpio_vbus_pwr = of_get_named_gpio_flags(dev->of_node,
> +						"renesas,vbus-pwr", 0, NULL);
> +		if (gpio_is_valid(ch->gpio_id)) {
> +			ret = devm_gpio_request_one(dev, ch->gpio_vbus_pwr,
> +					GPIOF_OUT_INIT_LOW, "vbus-pwr");
> +			if (ret)
> +				return ret;
> +		}
> +
> +	} else if (gpio_is_valid(ch->gpio_id)) {
> +		dev_err(dev, "Failed to get VBUS gpio\n");
> +		return ch->gpio_vbus;
> +	} else if (gpio_is_valid(ch->gpio_vbus)) {
> +		dev_err(dev, "Failed to get ID gpio\n");
> +		return ch->gpio_id;
> +	}
> +
> +	return 0;
> +}
> +
> +/* bind/unbind the peripheral controller */
> +static int rcar_gen2_usb_set_peripheral(struct usb_otg *otg,
> +					struct usb_gadget *gadget)
> +{
> +	otg->gadget = gadget;
> +	if (!gadget) {
> +		usb_gadget_vbus_disconnect(otg->gadget);
> +		otg->state = OTG_STATE_UNDEFINED;
> +	}
> +
> +	return 0;
> +}
> +
>   static int rcar_gen2_phy_probe(struct platform_device *pdev)
>   {
>   	struct device *dev = &pdev->dev;
> @@ -245,7 +407,9 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
>   	struct resource *res;
>   	void __iomem *base;
>   	struct clk *clk;
> +	struct usb_otg *otg;
>   	int i = 0;
> +	int err;
>
>   	if (!dev->of_node) {
>   		dev_err(dev,
> @@ -280,6 +444,57 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
>   	if (!drv->channels)
>   		return -ENOMEM;
>
> +	/* USB0 optional GPIO power pin for external devices */
> +	drv->channels->gpio_pwr = of_get_named_gpio_flags(dev->of_node,
> +						"renesas,pwr", 0, NULL);
> +	if (drv->channels->gpio_pwr == -EPROBE_DEFER)
> +		return -EPROBE_DEFER;
> +
> +	if (gpio_is_valid(drv->channels->gpio_pwr)) {
> +		err = devm_gpio_request(dev, drv->channels->gpio_pwr, "pwr");
> +		if (err)
> +			return err;
> +	}
> +
> +	/* USB0 Host/Function switching info */
> +	err = probe_otg(pdev, drv);
> +	if (err)
> +		return err;
> +
> +	/*
> +	 * The PHY connected to channel 0 can be used to steer signals to the
> +	 * USB Host IP that stils behind a PCI bridge (pci0), or the USB Func
> +	 * IP (hsusb). We can dynamically switch this based on VBUS and ID
> +	 * signals connected to gpios, to get something approaching OTG.
> +	 */
> +	if (drv->channels->use_otg) {
> +		struct usb_phy *usbphy;
> +
> +		usbphy = devm_kzalloc(dev, sizeof(*usbphy), GFP_KERNEL);
> +		if (!usbphy)
> +			return -ENOMEM;
> +
> +		otg = devm_kzalloc(dev, sizeof(*otg), GFP_KERNEL);
> +		if (!otg)
> +			return -ENOMEM;
> +
> +		usbphy->dev		= dev;
> +		usbphy->otg		= otg;
> +
> +		otg->usb_phy		= usbphy;
> +		otg->state		= OTG_STATE_UNDEFINED;
> +		otg->set_peripheral	= rcar_gen2_usb_set_peripheral;
> +
> +		drv->channels->otg	= otg;
> +		drv->channels->usbphy	= usbphy;
> +
> +		ATOMIC_INIT_NOTIFIER_HEAD(&usbphy->notifier);
> +
> +		INIT_DELAYED_WORK(&drv->channels->work, gpio_vbus_work);
> +
> +		usb_add_phy_dev(usbphy);
> +	}
> +
>   	for_each_child_of_node(dev->of_node, np) {
>   		struct rcar_gen2_channel *channel = drv->channels + i;
>   		u32 channel_num;
> @@ -288,6 +503,8 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
>   		channel->of_node = np;
>   		channel->drv = drv;
>   		channel->selected_phy = -1;
> +		if (i != 0)
> +			channel->gpio_pwr = -ENOENT;
>
>   		error = of_property_read_u32(np, "reg", &channel_num);
>   		if (error || channel_num > 2) {
> @@ -323,6 +540,14 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
>
>   	dev_set_drvdata(dev, drv);
>
> +	/*
> +	 * If we already have something plugged into USB0, we won't get an edge
> +	 * on VBUS, so we have to manually schedule work to look at the VBUS
> +	 * and ID signals.
> +	 */
> +	if (drv->channels->use_otg)
> +		schedule_delayed_work(&drv->channels->work, msecs_to_jiffies(100));
> +
>   	return 0;
>   }
>
>
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
Please read the FAQ at  http://www.tux.org/lkml/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/3] usb: renesas_usbhs: Allow an OTG PHY driver to provide VBUS
  2015-06-22 14:42 ` [PATCH 1/3] usb: renesas_usbhs: Allow an OTG PHY driver to provide VBUS Phil Edworthy
@ 2015-06-22 17:38   ` Sergei Shtylyov
  0 siblings, 0 replies; 11+ messages in thread
From: Sergei Shtylyov @ 2015-06-22 17:38 UTC (permalink / raw)
  To: Phil Edworthy, Kishon Vijay Abraham I, Yoshihiro Shimoda, Simon Horman
  Cc: linux-kernel, linux-usb, linux-sh

Hello.

On 06/22/2015 05:42 PM, Phil Edworthy wrote:

> These changes allow a PHY driver to trigger a VBUS interrupt and
> to provide the value of VBUS.

> Signed-off-by: Phil Edworthy <phil.edworthy@renesas.com>
> ---
>   drivers/usb/renesas_usbhs/common.h     |  2 ++
>   drivers/usb/renesas_usbhs/mod.c        |  3 +++
>   drivers/usb/renesas_usbhs/mod_gadget.c | 38 ++++++++++++++++++++++++++++++++++
>   3 files changed, 43 insertions(+)

> diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h
> index 8c5fc12..94a7aeb 100644
> --- a/drivers/usb/renesas_usbhs/common.h
> +++ b/drivers/usb/renesas_usbhs/common.h
> @@ -255,6 +255,8 @@ struct usbhs_priv {
>   	struct renesas_usbhs_driver_param	dparam;
>
>   	struct delayed_work notify_hotplug_work;
> +	int vbus_is_indirect;

    s/int/bool/.

> +	int vbus_indirect_value;

    Likewise.

>   	struct platform_device *pdev;
>
>   	struct extcon_dev *edev;
[...]
> diff --git a/drivers/usb/renesas_usbhs/mod_gadget.c b/drivers/usb/renesas_usbhs/mod_gadget.c
> index dc2aa32..2cfdb50 100644
> --- a/drivers/usb/renesas_usbhs/mod_gadget.c
> +++ b/drivers/usb/renesas_usbhs/mod_gadget.c
[...]
> @@ -891,6 +895,17 @@ static int usbhsg_gadget_start(struct usb_gadget *gadget,
>   	/* first hook up the driver ... */
>   	gpriv->driver = driver;
>
> +	/* connect to bus through transceiver */
> +	if (!IS_ERR_OR_NULL(gpriv->transceiver)) {
> +		ret = otg_set_peripheral(gpriv->transceiver->otg,
> +					&gpriv->gadget);
> +		if (ret) {
> +			dev_info(dev, "%s: can't bind to transceiver\n",

    dev_err().

[...]
> @@ -994,6 +1028,10 @@ int usbhs_mod_gadget_probe(struct usbhs_priv *priv)
>   		goto usbhs_mod_gadget_probe_err_gpriv;
>   	}
>
> +	gpriv->transceiver = usb_get_phy(USB_PHY_TYPE_UNDEFINED);
> +	dev_info(dev, "%s transceiver found\n",
> +		 gpriv->transceiver ? "" : "No");

	dev_info(dev, "%stransceiver found\n",
		 gpriv->transceiver ? "" : "no ");

WBR, Sergei

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
Please read the FAQ at  http://www.tux.org/lkml/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 3/3] arm: koelsch: make USB0 perform Host/Function switching
  2015-06-22 14:42 ` [PATCH 3/3] arm: koelsch: make USB0 perform Host/Function switching Phil Edworthy
@ 2015-06-22 17:43   ` Sergei Shtylyov
  2015-06-22 17:49     ` Sergei Shtylyov
  0 siblings, 1 reply; 11+ messages in thread
From: Sergei Shtylyov @ 2015-06-22 17:43 UTC (permalink / raw)
  To: Phil Edworthy, Kishon Vijay Abraham I, Yoshihiro Shimoda, Simon Horman
  Cc: linux-kernel, linux-usb, linux-sh

Hello.

On 06/22/2015 05:42 PM, Phil Edworthy wrote:

> Both USB Host (pci0) and Function (USBHS) drivers are enabled.
> The USB PHY driver determines which IP block should be connected
> based on vbus and id signals read via gpios.

> Signed-off-by: Phil Edworthy <phil.edworthy@renesas.com>
> ---
>   arch/arm/boot/dts/r8a7791-koelsch.dts | 7 ++++---
>   1 file changed, 4 insertions(+), 3 deletions(-)
>
> diff --git a/arch/arm/boot/dts/r8a7791-koelsch.dts b/arch/arm/boot/dts/r8a7791-koelsch.dts
> index cffe33f..8f394be 100644
> --- a/arch/arm/boot/dts/r8a7791-koelsch.dts
> +++ b/arch/arm/boot/dts/r8a7791-koelsch.dts
[...]
> @@ -627,13 +626,15 @@
>
>   &hsusb {
>   	status = "okay";
> -	pinctrl-0 = <&usb0_pins>;
>   	pinctrl-names = "default";
> -	renesas,enable-gpio = <&gpio5 31 GPIO_ACTIVE_HIGH>;
>   };
>
>   &usbphy {
>   	status = "okay";
> +	renesas,pwr = <&gpio2 4 GPIO_ACTIVE_HIGH>;
> +	renesas,id = <&gpio5 31 GPIO_ACTIVE_HIGH>;
> +	renesas,vbus = <&gpio7 24 GPIO_ACTIVE_HIGH>;
> +	renesas,vbus-pwr = <&gpio7 23 GPIO_ACTIVE_HIGH>;

    The prop names should end with "-gpio" or even "-gpios", according to the 
GPIO bindings.

WBR, Sergei

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
Please read the FAQ at  http://www.tux.org/lkml/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 3/3] arm: koelsch: make USB0 perform Host/Function switching
  2015-06-22 17:43   ` Sergei Shtylyov
@ 2015-06-22 17:49     ` Sergei Shtylyov
  2015-07-01 15:10       ` Phil Edworthy
  0 siblings, 1 reply; 11+ messages in thread
From: Sergei Shtylyov @ 2015-06-22 17:49 UTC (permalink / raw)
  To: Phil Edworthy, Kishon Vijay Abraham I, Yoshihiro Shimoda, Simon Horman
  Cc: linux-kernel, linux-usb, linux-sh

Hello.

On 06/22/2015 08:43 PM, Sergei Shtylyov wrote:

>> Both USB Host (pci0) and Function (USBHS) drivers are enabled.
>> The USB PHY driver determines which IP block should be connected
>> based on vbus and id signals read via gpios.

>> Signed-off-by: Phil Edworthy <phil.edworthy@renesas.com>
>> ---
>>   arch/arm/boot/dts/r8a7791-koelsch.dts | 7 ++++---
>>   1 file changed, 4 insertions(+), 3 deletions(-)

>> diff --git a/arch/arm/boot/dts/r8a7791-koelsch.dts
>> b/arch/arm/boot/dts/r8a7791-koelsch.dts
>> index cffe33f..8f394be 100644
>> --- a/arch/arm/boot/dts/r8a7791-koelsch.dts
>> +++ b/arch/arm/boot/dts/r8a7791-koelsch.dts
> [...]
>> @@ -627,13 +626,15 @@
>>
>>   &hsusb {
>>       status = "okay";
>> -    pinctrl-0 = <&usb0_pins>;
>>       pinctrl-names = "default";
>> -    renesas,enable-gpio = <&gpio5 31 GPIO_ACTIVE_HIGH>;
>>   };
>>
>>   &usbphy {
>>       status = "okay";
>> +    renesas,pwr = <&gpio2 4 GPIO_ACTIVE_HIGH>;
>> +    renesas,id = <&gpio5 31 GPIO_ACTIVE_HIGH>;
>> +    renesas,vbus = <&gpio7 24 GPIO_ACTIVE_HIGH>;
>> +    renesas,vbus-pwr = <&gpio7 23 GPIO_ACTIVE_HIGH>;

>     The prop names should end with "-gpio" or even "-gpios", according to the
> GPIO bindings.

    Oh, and you didn't document the props in the previous patch.

WBR, Sergei

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
Please read the FAQ at  http://www.tux.org/lkml/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* RE: [PATCH 3/3] arm: koelsch: make USB0 perform Host/Function switching
  2015-06-22 17:49     ` Sergei Shtylyov
@ 2015-07-01 15:10       ` Phil Edworthy
  0 siblings, 0 replies; 11+ messages in thread
From: Phil Edworthy @ 2015-07-01 15:10 UTC (permalink / raw)
  To: Sergei Shtylyov, Kishon Vijay Abraham I, Yoshihiro Shimoda,
	Simon Horman, Laurent Pinchart
  Cc: linux-kernel, linux-usb, linux-sh

Hi Sergei,

On 22 June 2015 18:49, Sergei wrote:
> Hello.
> 
> On 06/22/2015 08:43 PM, Sergei Shtylyov wrote:
> 
> >> Both USB Host (pci0) and Function (USBHS) drivers are enabled.
> >> The USB PHY driver determines which IP block should be connected
> >> based on vbus and id signals read via gpios.
> 
> >> Signed-off-by: Phil Edworthy <phil.edworthy@renesas.com>
> >> ---
> >>   arch/arm/boot/dts/r8a7791-koelsch.dts | 7 ++++---
> >>   1 file changed, 4 insertions(+), 3 deletions(-)
> 
> >> diff --git a/arch/arm/boot/dts/r8a7791-koelsch.dts
> >> b/arch/arm/boot/dts/r8a7791-koelsch.dts
> >> index cffe33f..8f394be 100644
> >> --- a/arch/arm/boot/dts/r8a7791-koelsch.dts
> >> +++ b/arch/arm/boot/dts/r8a7791-koelsch.dts
> > [...]
> >> @@ -627,13 +626,15 @@
> >>
> >>   &hsusb {
> >>       status = "okay";
> >> -    pinctrl-0 = <&usb0_pins>;
> >>       pinctrl-names = "default";
> >> -    renesas,enable-gpio = <&gpio5 31 GPIO_ACTIVE_HIGH>;
> >>   };
> >>
> >>   &usbphy {
> >>       status = "okay";
> >> +    renesas,pwr = <&gpio2 4 GPIO_ACTIVE_HIGH>;
> >> +    renesas,id = <&gpio5 31 GPIO_ACTIVE_HIGH>;
> >> +    renesas,vbus = <&gpio7 24 GPIO_ACTIVE_HIGH>;
> >> +    renesas,vbus-pwr = <&gpio7 23 GPIO_ACTIVE_HIGH>;
> 
> >     The prop names should end with "-gpio" or even "-gpios", according to the
> > GPIO bindings.
> 
>     Oh, and you didn't document the props in the previous patch.
> 
Thanks for your comments, since I've not seen any other comments I'll fix
these and repost.

Phil

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 2/3] phy: rcar-gen2 usb: Add Host/Function switching for USB0
  2015-06-22 14:42 ` [PATCH 2/3] phy: rcar-gen2 usb: Add Host/Function switching for USB0 Phil Edworthy
  2015-06-22 16:13   ` Kishon Vijay Abraham I
@ 2015-07-02  8:22   ` Kishon Vijay Abraham I
  2015-07-02 12:45     ` Phil Edworthy
  1 sibling, 1 reply; 11+ messages in thread
From: Kishon Vijay Abraham I @ 2015-07-02  8:22 UTC (permalink / raw)
  To: Phil Edworthy, Yoshihiro Shimoda, Simon Horman
  Cc: Sergei Shtylyov, linux-kernel, linux-usb, linux-sh

Hi,

On Monday 22 June 2015 08:12 PM, Phil Edworthy wrote:
> Instead of statically selecting the PHY connection to either the
> USBHS (Function) or PCI0 (Host) IP blocks, this change allows the
> dts to specifiy gpio pins for the vbus and id signals. Additional
> gpio pins are used to control power to an external OTG device and
> an override to turn vbus on/off.
> 
> Note: the R-Car USB PHY only allows this Host/Function switching
> on channel 0.
> 
> This has been tested on a r8a7791 based Koelsch board, which uses
> a MAX3355 device to supply vbus power when needed.
> 
> Signed-off-by: Phil Edworthy <phil.edworthy@renesas.com>
> ---
>  drivers/phy/phy-rcar-gen2.c | 269 ++++++++++++++++++++++++++++++++++++++++----
>  1 file changed, 247 insertions(+), 22 deletions(-)
> 
> diff --git a/drivers/phy/phy-rcar-gen2.c b/drivers/phy/phy-rcar-gen2.c
> index 97d45f4..8564e7d 100644
> --- a/drivers/phy/phy-rcar-gen2.c
> +++ b/drivers/phy/phy-rcar-gen2.c
> @@ -1,5 +1,5 @@
>  /*
> - * Renesas R-Car Gen2 PHY driver
> + * Renesas R-Car Gen2 USB PHY driver
>   *
>   * Copyright (C) 2014 Renesas Solutions Corp.
>   * Copyright (C) 2014 Cogent Embedded, Inc.
> @@ -12,11 +12,16 @@
>  #include <linux/clk.h>
>  #include <linux/delay.h>
>  #include <linux/io.h>
> +#include <linux/interrupt.h>
>  #include <linux/module.h>
>  #include <linux/of.h>
> +#include <linux/of_gpio.h>
>  #include <linux/phy/phy.h>
>  #include <linux/platform_device.h>
>  #include <linux/spinlock.h>
> +#include <linux/usb/gadget.h>
> +#include <linux/usb/otg.h>
> +#include <linux/workqueue.h>
>  
>  #include <asm/cmpxchg.h>
>  
> @@ -58,6 +63,18 @@ struct rcar_gen2_channel {
>  	struct rcar_gen2_phy phys[PHYS_PER_CHANNEL];
>  	int selected_phy;
>  	u32 select_mask;
> +
> +	/* external power enable pin */
> +	int			gpio_pwr;
> +
> +	/* Host/Function switching */
> +	struct delayed_work	work;
> +	int			use_otg;
> +	int			gpio_vbus;
> +	int			gpio_id;
> +	int			gpio_vbus_pwr;
> +	struct usb_phy		*usbphy;

Using usb_phy is a step backwards IMO. We should rather try to get the missing
functionality adding in Generic PHY.

Thanks
Kishon

> +	struct usb_otg		*otg;
>  };
>  
>  struct rcar_gen2_phy_driver {
> @@ -68,31 +85,50 @@ struct rcar_gen2_phy_driver {
>  	struct rcar_gen2_channel *channels;
>  };
>  
> -static int rcar_gen2_phy_init(struct phy *p)
> +static void rcar_gen2_phy_switch(struct rcar_gen2_channel *channel,
> +	u32 select_value)
>  {
> -	struct rcar_gen2_phy *phy = phy_get_drvdata(p);
> -	struct rcar_gen2_channel *channel = phy->channel;
>  	struct rcar_gen2_phy_driver *drv = channel->drv;
>  	unsigned long flags;
>  	u32 ugctrl2;
>  
> -	/*
> -	 * Try to acquire exclusive access to PHY.  The first driver calling
> -	 * phy_init()  on a given channel wins, and all attempts  to use another
> -	 * PHY on this channel will fail until phy_exit() is called by the first
> -	 * driver.   Achieving this with cmpxcgh() should be SMP-safe.
> -	 */
> -	if (cmpxchg(&channel->selected_phy, -1, phy->number) != -1)
> -		return -EBUSY;
> -
> -	clk_prepare_enable(drv->clk);
> -
>  	spin_lock_irqsave(&drv->lock, flags);
>  	ugctrl2 = readl(drv->base + USBHS_UGCTRL2);
>  	ugctrl2 &= ~channel->select_mask;
> -	ugctrl2 |= phy->select_value;
> +	ugctrl2 |= select_value;
>  	writel(ugctrl2, drv->base + USBHS_UGCTRL2);
>  	spin_unlock_irqrestore(&drv->lock, flags);
> +}
> +
> +static int rcar_gen2_phy_init(struct phy *p)
> +{
> +	struct rcar_gen2_phy *phy = phy_get_drvdata(p);
> +	struct rcar_gen2_channel *channel = phy->channel;
> +	struct rcar_gen2_phy_driver *drv = channel->drv;
> +
> +	if (!channel->use_otg) {
> +		/*
> +		 * Static Host/Function role.
> +		 * Try to acquire exclusive access to PHY. The first driver
> +		 * calling phy_init() on a given channel wins, and all attempts
> +		 * to use another PHY on this channel will fail until
> +		 * phy_exit() is called by the first driver. Achieving this
> +		 * with cmpxcgh() should be SMP-safe.
> +		 */
> +		if (cmpxchg(&channel->selected_phy, -1, phy->number) != -1)
> +			return -EBUSY;
> +
> +		clk_prepare_enable(drv->clk);
> +		rcar_gen2_phy_switch(channel, phy->select_value);
> +	} else {
> +		/*
> +		 * Using Host/Function switching, so schedule work to determine
> +		 * which role to use.
> +		 */
> +		clk_prepare_enable(drv->clk);
> +		schedule_delayed_work(&channel->work, 100);
> +	}
> +
>  	return 0;
>  }
>  
> @@ -117,9 +153,9 @@ static int rcar_gen2_phy_power_on(struct phy *p)
>  	u32 value;
>  	int err = 0, i;
>  
> -	/* Skip if it's not USBHS */
> -	if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB)
> -		return 0;
> +	/* Optional external power pin */
> +	if (gpio_is_valid(phy->channel->gpio_pwr))
> +		gpio_direction_output(phy->channel->gpio_pwr, 1);
>  
>  	spin_lock_irqsave(&drv->lock, flags);
>  
> @@ -160,9 +196,9 @@ static int rcar_gen2_phy_power_off(struct phy *p)
>  	unsigned long flags;
>  	u32 value;
>  
> -	/* Skip if it's not USBHS */
> -	if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB)
> -		return 0;
> +	/* External power pin */
> +	if (gpio_is_valid(phy->channel->gpio_pwr))
> +		gpio_direction_output(phy->channel->gpio_pwr, 0);
>  
>  	spin_lock_irqsave(&drv->lock, flags);
>  
> @@ -236,6 +272,132 @@ static const u32 select_value[][PHYS_PER_CHANNEL] = {
>  	[2]	= { USBHS_UGCTRL2_USB2SEL_PCI, USBHS_UGCTRL2_USB2SEL_USB30 },
>  };
>  
> +
> +#define VBUS_IRQ_FLAGS \
> +	(IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
> +
> +static void gpio_vbus_work(struct work_struct *work)
> +{
> +	struct rcar_gen2_channel *channel = container_of(work,
> +					struct rcar_gen2_channel, work.work);
> +	struct usb_phy *usbphy = channel->usbphy;
> +	int status, vbus, id;
> +
> +	vbus = !!gpio_get_value(channel->gpio_vbus);
> +	id = !!gpio_get_value(channel->gpio_id);
> +
> +	/* Switch the PHY over */
> +	if (id)
> +		rcar_gen2_phy_switch(channel, USBHS_UGCTRL2_USB0SEL_HS_USB);
> +	else
> +		rcar_gen2_phy_switch(channel, USBHS_UGCTRL2_USB0SEL_PCI);
> +
> +	/* If VBUS is powered and we are not the initial Host, turn off VBUS */
> +	if (gpio_is_valid(channel->gpio_vbus_pwr))
> +		gpio_direction_output(channel->gpio_vbus_pwr, !(id && vbus));
> +
> +	if (!channel->otg->gadget)
> +		return;
> +
> +	/* Function handling: vbus=1 when initially plugged into a Host */
> +	if (vbus) {
> +		status = USB_EVENT_VBUS;
> +		usbphy->otg->state = OTG_STATE_B_PERIPHERAL;
> +		usbphy->last_event = status;
> +		usb_gadget_vbus_connect(usbphy->otg->gadget);
> +
> +		atomic_notifier_call_chain(&usbphy->notifier,
> +					   status, usbphy->otg->gadget);
> +	} else {
> +		usb_gadget_vbus_disconnect(usbphy->otg->gadget);
> +		status = USB_EVENT_NONE;
> +		usbphy->otg->state = OTG_STATE_B_IDLE;
> +		usbphy->last_event = status;
> +
> +		atomic_notifier_call_chain(&usbphy->notifier,
> +					   status, usbphy->otg->gadget);
> +	}
> +}
> +
> +/* VBUS change IRQ handler */
> +static irqreturn_t gpio_vbus_irq(int irq, void *data)
> +{
> +	struct rcar_gen2_channel *channel = data;
> +
> +	/* Wait 20ms before doing anything as VBUS needs to settle */
> +	schedule_delayed_work(&channel->work, msecs_to_jiffies(20));
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int probe_otg(struct platform_device *pdev,
> +	struct rcar_gen2_phy_driver *drv)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct rcar_gen2_channel *ch = drv->channels;
> +	int irq;
> +	int ret;
> +
> +	/* GPIOs for Host/Fn switching */
> +	ch->gpio_id = of_get_named_gpio_flags(dev->of_node, "renesas,id",
> +				0, NULL);
> +	ch->gpio_vbus = of_get_named_gpio_flags(dev->of_node, "renesas,vbus",
> +				0, NULL);
> +
> +	/* Need valid ID and VBUS gpios for Host/Fn switching */
> +	if (gpio_is_valid(ch->gpio_id) && gpio_is_valid(ch->gpio_vbus)) {
> +		ch->use_otg = 1;
> +
> +		/* GPIO for ID input */
> +		ret = devm_gpio_request_one(dev, ch->gpio_id, GPIOF_IN, "id");
> +		if (ret)
> +			return ret;
> +
> +		/* GPIO for VBUS input */
> +		ret = devm_gpio_request_one(dev, ch->gpio_vbus, GPIOF_IN, "vbus");
> +		if (ret)
> +			return ret;
> +
> +		irq = gpio_to_irq(ch->gpio_vbus);
> +		ret = devm_request_irq(dev, irq, gpio_vbus_irq, VBUS_IRQ_FLAGS,
> +				"vbus_detect", ch);
> +		if (ret)
> +			return ret;
> +
> +		/* Optional GPIO for VBUS power */
> +		ch->gpio_vbus_pwr = of_get_named_gpio_flags(dev->of_node,
> +						"renesas,vbus-pwr", 0, NULL);
> +		if (gpio_is_valid(ch->gpio_id)) {
> +			ret = devm_gpio_request_one(dev, ch->gpio_vbus_pwr,
> +					GPIOF_OUT_INIT_LOW, "vbus-pwr");
> +			if (ret)
> +				return ret;
> +		}
> +
> +	} else if (gpio_is_valid(ch->gpio_id)) {
> +		dev_err(dev, "Failed to get VBUS gpio\n");
> +		return ch->gpio_vbus;
> +	} else if (gpio_is_valid(ch->gpio_vbus)) {
> +		dev_err(dev, "Failed to get ID gpio\n");
> +		return ch->gpio_id;
> +	}
> +
> +	return 0;
> +}
> +
> +/* bind/unbind the peripheral controller */
> +static int rcar_gen2_usb_set_peripheral(struct usb_otg *otg,
> +					struct usb_gadget *gadget)
> +{
> +	otg->gadget = gadget;
> +	if (!gadget) {
> +		usb_gadget_vbus_disconnect(otg->gadget);
> +		otg->state = OTG_STATE_UNDEFINED;
> +	}
> +
> +	return 0;
> +}
> +
>  static int rcar_gen2_phy_probe(struct platform_device *pdev)
>  {
>  	struct device *dev = &pdev->dev;
> @@ -245,7 +407,9 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
>  	struct resource *res;
>  	void __iomem *base;
>  	struct clk *clk;
> +	struct usb_otg *otg;
>  	int i = 0;
> +	int err;
>  
>  	if (!dev->of_node) {
>  		dev_err(dev,
> @@ -280,6 +444,57 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
>  	if (!drv->channels)
>  		return -ENOMEM;
>  
> +	/* USB0 optional GPIO power pin for external devices */
> +	drv->channels->gpio_pwr = of_get_named_gpio_flags(dev->of_node,
> +						"renesas,pwr", 0, NULL);
> +	if (drv->channels->gpio_pwr == -EPROBE_DEFER)
> +		return -EPROBE_DEFER;
> +
> +	if (gpio_is_valid(drv->channels->gpio_pwr)) {
> +		err = devm_gpio_request(dev, drv->channels->gpio_pwr, "pwr");
> +		if (err)
> +			return err;
> +	}
> +
> +	/* USB0 Host/Function switching info */
> +	err = probe_otg(pdev, drv);
> +	if (err)
> +		return err;
> +
> +	/*
> +	 * The PHY connected to channel 0 can be used to steer signals to the
> +	 * USB Host IP that stils behind a PCI bridge (pci0), or the USB Func
> +	 * IP (hsusb). We can dynamically switch this based on VBUS and ID
> +	 * signals connected to gpios, to get something approaching OTG.
> +	 */
> +	if (drv->channels->use_otg) {
> +		struct usb_phy *usbphy;
> +
> +		usbphy = devm_kzalloc(dev, sizeof(*usbphy), GFP_KERNEL);
> +		if (!usbphy)
> +			return -ENOMEM;
> +
> +		otg = devm_kzalloc(dev, sizeof(*otg), GFP_KERNEL);
> +		if (!otg)
> +			return -ENOMEM;
> +
> +		usbphy->dev		= dev;
> +		usbphy->otg		= otg;
> +
> +		otg->usb_phy		= usbphy;
> +		otg->state		= OTG_STATE_UNDEFINED;
> +		otg->set_peripheral	= rcar_gen2_usb_set_peripheral;
> +
> +		drv->channels->otg	= otg;
> +		drv->channels->usbphy	= usbphy;
> +
> +		ATOMIC_INIT_NOTIFIER_HEAD(&usbphy->notifier);
> +
> +		INIT_DELAYED_WORK(&drv->channels->work, gpio_vbus_work);
> +
> +		usb_add_phy_dev(usbphy);
> +	}
> +
>  	for_each_child_of_node(dev->of_node, np) {
>  		struct rcar_gen2_channel *channel = drv->channels + i;
>  		u32 channel_num;
> @@ -288,6 +503,8 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
>  		channel->of_node = np;
>  		channel->drv = drv;
>  		channel->selected_phy = -1;
> +		if (i != 0)
> +			channel->gpio_pwr = -ENOENT;
>  
>  		error = of_property_read_u32(np, "reg", &channel_num);
>  		if (error || channel_num > 2) {
> @@ -323,6 +540,14 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
>  
>  	dev_set_drvdata(dev, drv);
>  
> +	/*
> +	 * If we already have something plugged into USB0, we won't get an edge
> +	 * on VBUS, so we have to manually schedule work to look at the VBUS
> +	 * and ID signals.
> +	 */
> +	if (drv->channels->use_otg)
> +		schedule_delayed_work(&drv->channels->work, msecs_to_jiffies(100));
> +
>  	return 0;
>  }
>  
> 

^ permalink raw reply	[flat|nested] 11+ messages in thread

* RE: [PATCH 2/3] phy: rcar-gen2 usb: Add Host/Function switching for USB0
  2015-07-02  8:22   ` Kishon Vijay Abraham I
@ 2015-07-02 12:45     ` Phil Edworthy
  0 siblings, 0 replies; 11+ messages in thread
From: Phil Edworthy @ 2015-07-02 12:45 UTC (permalink / raw)
  To: Kishon Vijay Abraham I, Yoshihiro Shimoda, Simon Horman
  Cc: Sergei Shtylyov, linux-kernel, linux-usb, linux-sh

Hi Kishon,

On 02 July 2015 09:22, Kishon wrote:
> Hi,
> 
> On Monday 22 June 2015 08:12 PM, Phil Edworthy wrote:
> > Instead of statically selecting the PHY connection to either the
> > USBHS (Function) or PCI0 (Host) IP blocks, this change allows the
> > dts to specifiy gpio pins for the vbus and id signals. Additional
> > gpio pins are used to control power to an external OTG device and
> > an override to turn vbus on/off.
> >
> > Note: the R-Car USB PHY only allows this Host/Function switching
> > on channel 0.
> >
> > This has been tested on a r8a7791 based Koelsch board, which uses
> > a MAX3355 device to supply vbus power when needed.
> >
> > Signed-off-by: Phil Edworthy <phil.edworthy@renesas.com>
> > ---
> >  drivers/phy/phy-rcar-gen2.c | 269
> ++++++++++++++++++++++++++++++++++++++++----
> >  1 file changed, 247 insertions(+), 22 deletions(-)
> >
> > diff --git a/drivers/phy/phy-rcar-gen2.c b/drivers/phy/phy-rcar-gen2.c
> > index 97d45f4..8564e7d 100644
> > --- a/drivers/phy/phy-rcar-gen2.c
> > +++ b/drivers/phy/phy-rcar-gen2.c
> > @@ -1,5 +1,5 @@
> >  /*
> > - * Renesas R-Car Gen2 PHY driver
> > + * Renesas R-Car Gen2 USB PHY driver
> >   *
> >   * Copyright (C) 2014 Renesas Solutions Corp.
> >   * Copyright (C) 2014 Cogent Embedded, Inc.
> > @@ -12,11 +12,16 @@
> >  #include <linux/clk.h>
> >  #include <linux/delay.h>
> >  #include <linux/io.h>
> > +#include <linux/interrupt.h>
> >  #include <linux/module.h>
> >  #include <linux/of.h>
> > +#include <linux/of_gpio.h>
> >  #include <linux/phy/phy.h>
> >  #include <linux/platform_device.h>
> >  #include <linux/spinlock.h>
> > +#include <linux/usb/gadget.h>
> > +#include <linux/usb/otg.h>
> > +#include <linux/workqueue.h>
> >
> >  #include <asm/cmpxchg.h>
> >
> > @@ -58,6 +63,18 @@ struct rcar_gen2_channel {
> >  	struct rcar_gen2_phy phys[PHYS_PER_CHANNEL];
> >  	int selected_phy;
> >  	u32 select_mask;
> > +
> > +	/* external power enable pin */
> > +	int			gpio_pwr;
> > +
> > +	/* Host/Function switching */
> > +	struct delayed_work	work;
> > +	int			use_otg;
> > +	int			gpio_vbus;
> > +	int			gpio_id;
> > +	int			gpio_vbus_pwr;
> > +	struct usb_phy		*usbphy;
> 
> Using usb_phy is a step backwards IMO. We should rather try to get the missing
> functionality adding in Generic PHY.
Ok, that will take quite a bit more work. Do you know if anyone is working on this?

> Thanks
> Kishon
> 
> > +	struct usb_otg		*otg;
> >  };
> >
> >  struct rcar_gen2_phy_driver {
> > @@ -68,31 +85,50 @@ struct rcar_gen2_phy_driver {
> >  	struct rcar_gen2_channel *channels;
> >  };
> >
> > -static int rcar_gen2_phy_init(struct phy *p)
> > +static void rcar_gen2_phy_switch(struct rcar_gen2_channel *channel,
> > +	u32 select_value)
> >  {
> > -	struct rcar_gen2_phy *phy = phy_get_drvdata(p);
> > -	struct rcar_gen2_channel *channel = phy->channel;
> >  	struct rcar_gen2_phy_driver *drv = channel->drv;
> >  	unsigned long flags;
> >  	u32 ugctrl2;
> >
> > -	/*
> > -	 * Try to acquire exclusive access to PHY.  The first driver calling
> > -	 * phy_init()  on a given channel wins, and all attempts  to use another
> > -	 * PHY on this channel will fail until phy_exit() is called by the first
> > -	 * driver.   Achieving this with cmpxcgh() should be SMP-safe.
> > -	 */
> > -	if (cmpxchg(&channel->selected_phy, -1, phy->number) != -1)
> > -		return -EBUSY;
> > -
> > -	clk_prepare_enable(drv->clk);
> > -
> >  	spin_lock_irqsave(&drv->lock, flags);
> >  	ugctrl2 = readl(drv->base + USBHS_UGCTRL2);
> >  	ugctrl2 &= ~channel->select_mask;
> > -	ugctrl2 |= phy->select_value;
> > +	ugctrl2 |= select_value;
> >  	writel(ugctrl2, drv->base + USBHS_UGCTRL2);
> >  	spin_unlock_irqrestore(&drv->lock, flags);
> > +}
> > +
> > +static int rcar_gen2_phy_init(struct phy *p)
> > +{
> > +	struct rcar_gen2_phy *phy = phy_get_drvdata(p);
> > +	struct rcar_gen2_channel *channel = phy->channel;
> > +	struct rcar_gen2_phy_driver *drv = channel->drv;
> > +
> > +	if (!channel->use_otg) {
> > +		/*
> > +		 * Static Host/Function role.
> > +		 * Try to acquire exclusive access to PHY. The first driver
> > +		 * calling phy_init() on a given channel wins, and all attempts
> > +		 * to use another PHY on this channel will fail until
> > +		 * phy_exit() is called by the first driver. Achieving this
> > +		 * with cmpxcgh() should be SMP-safe.
> > +		 */
> > +		if (cmpxchg(&channel->selected_phy, -1, phy->number) != -1)
> > +			return -EBUSY;
> > +
> > +		clk_prepare_enable(drv->clk);
> > +		rcar_gen2_phy_switch(channel, phy->select_value);
> > +	} else {
> > +		/*
> > +		 * Using Host/Function switching, so schedule work to determine
> > +		 * which role to use.
> > +		 */
> > +		clk_prepare_enable(drv->clk);
> > +		schedule_delayed_work(&channel->work, 100);
> > +	}
> > +
> >  	return 0;
> >  }
> >
> > @@ -117,9 +153,9 @@ static int rcar_gen2_phy_power_on(struct phy *p)
> >  	u32 value;
> >  	int err = 0, i;
> >
> > -	/* Skip if it's not USBHS */
> > -	if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB)
> > -		return 0;
> > +	/* Optional external power pin */
> > +	if (gpio_is_valid(phy->channel->gpio_pwr))
> > +		gpio_direction_output(phy->channel->gpio_pwr, 1);
> >
> >  	spin_lock_irqsave(&drv->lock, flags);
> >
> > @@ -160,9 +196,9 @@ static int rcar_gen2_phy_power_off(struct phy *p)
> >  	unsigned long flags;
> >  	u32 value;
> >
> > -	/* Skip if it's not USBHS */
> > -	if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB)
> > -		return 0;
> > +	/* External power pin */
> > +	if (gpio_is_valid(phy->channel->gpio_pwr))
> > +		gpio_direction_output(phy->channel->gpio_pwr, 0);
> >
> >  	spin_lock_irqsave(&drv->lock, flags);
> >
> > @@ -236,6 +272,132 @@ static const u32 select_value[][PHYS_PER_CHANNEL]
> = {
> >  	[2]	= { USBHS_UGCTRL2_USB2SEL_PCI,
> USBHS_UGCTRL2_USB2SEL_USB30 },
> >  };
> >
> > +
> > +#define VBUS_IRQ_FLAGS \
> > +	(IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
> > +
> > +static void gpio_vbus_work(struct work_struct *work)
> > +{
> > +	struct rcar_gen2_channel *channel = container_of(work,
> > +					struct rcar_gen2_channel, work.work);
> > +	struct usb_phy *usbphy = channel->usbphy;
> > +	int status, vbus, id;
> > +
> > +	vbus = !!gpio_get_value(channel->gpio_vbus);
> > +	id = !!gpio_get_value(channel->gpio_id);
> > +
> > +	/* Switch the PHY over */
> > +	if (id)
> > +		rcar_gen2_phy_switch(channel,
> USBHS_UGCTRL2_USB0SEL_HS_USB);
> > +	else
> > +		rcar_gen2_phy_switch(channel,
> USBHS_UGCTRL2_USB0SEL_PCI);
> > +
> > +	/* If VBUS is powered and we are not the initial Host, turn off VBUS */
> > +	if (gpio_is_valid(channel->gpio_vbus_pwr))
> > +		gpio_direction_output(channel->gpio_vbus_pwr, !(id && vbus));
> > +
> > +	if (!channel->otg->gadget)
> > +		return;
> > +
> > +	/* Function handling: vbus=1 when initially plugged into a Host */
> > +	if (vbus) {
> > +		status = USB_EVENT_VBUS;
> > +		usbphy->otg->state = OTG_STATE_B_PERIPHERAL;
> > +		usbphy->last_event = status;
> > +		usb_gadget_vbus_connect(usbphy->otg->gadget);
> > +
> > +		atomic_notifier_call_chain(&usbphy->notifier,
> > +					   status, usbphy->otg->gadget);
> > +	} else {
> > +		usb_gadget_vbus_disconnect(usbphy->otg->gadget);
> > +		status = USB_EVENT_NONE;
> > +		usbphy->otg->state = OTG_STATE_B_IDLE;
> > +		usbphy->last_event = status;
> > +
> > +		atomic_notifier_call_chain(&usbphy->notifier,
> > +					   status, usbphy->otg->gadget);
> > +	}
> > +}
> > +
> > +/* VBUS change IRQ handler */
> > +static irqreturn_t gpio_vbus_irq(int irq, void *data)
> > +{
> > +	struct rcar_gen2_channel *channel = data;
> > +
> > +	/* Wait 20ms before doing anything as VBUS needs to settle */
> > +	schedule_delayed_work(&channel->work, msecs_to_jiffies(20));
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static int probe_otg(struct platform_device *pdev,
> > +	struct rcar_gen2_phy_driver *drv)
> > +{
> > +	struct device *dev = &pdev->dev;
> > +	struct rcar_gen2_channel *ch = drv->channels;
> > +	int irq;
> > +	int ret;
> > +
> > +	/* GPIOs for Host/Fn switching */
> > +	ch->gpio_id = of_get_named_gpio_flags(dev->of_node, "renesas,id",
> > +				0, NULL);
> > +	ch->gpio_vbus = of_get_named_gpio_flags(dev->of_node,
> "renesas,vbus",
> > +				0, NULL);
> > +
> > +	/* Need valid ID and VBUS gpios for Host/Fn switching */
> > +	if (gpio_is_valid(ch->gpio_id) && gpio_is_valid(ch->gpio_vbus)) {
> > +		ch->use_otg = 1;
> > +
> > +		/* GPIO for ID input */
> > +		ret = devm_gpio_request_one(dev, ch->gpio_id, GPIOF_IN,
> "id");
> > +		if (ret)
> > +			return ret;
> > +
> > +		/* GPIO for VBUS input */
> > +		ret = devm_gpio_request_one(dev, ch->gpio_vbus, GPIOF_IN,
> "vbus");
> > +		if (ret)
> > +			return ret;
> > +
> > +		irq = gpio_to_irq(ch->gpio_vbus);
> > +		ret = devm_request_irq(dev, irq, gpio_vbus_irq,
> VBUS_IRQ_FLAGS,
> > +				"vbus_detect", ch);
> > +		if (ret)
> > +			return ret;
> > +
> > +		/* Optional GPIO for VBUS power */
> > +		ch->gpio_vbus_pwr = of_get_named_gpio_flags(dev->of_node,
> > +						"renesas,vbus-pwr", 0, NULL);
> > +		if (gpio_is_valid(ch->gpio_id)) {
> > +			ret = devm_gpio_request_one(dev, ch->gpio_vbus_pwr,
> > +					GPIOF_OUT_INIT_LOW, "vbus-pwr");
> > +			if (ret)
> > +				return ret;
> > +		}
> > +
> > +	} else if (gpio_is_valid(ch->gpio_id)) {
> > +		dev_err(dev, "Failed to get VBUS gpio\n");
> > +		return ch->gpio_vbus;
> > +	} else if (gpio_is_valid(ch->gpio_vbus)) {
> > +		dev_err(dev, "Failed to get ID gpio\n");
> > +		return ch->gpio_id;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +/* bind/unbind the peripheral controller */
> > +static int rcar_gen2_usb_set_peripheral(struct usb_otg *otg,
> > +					struct usb_gadget *gadget)
> > +{
> > +	otg->gadget = gadget;
> > +	if (!gadget) {
> > +		usb_gadget_vbus_disconnect(otg->gadget);
> > +		otg->state = OTG_STATE_UNDEFINED;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> >  static int rcar_gen2_phy_probe(struct platform_device *pdev)
> >  {
> >  	struct device *dev = &pdev->dev;
> > @@ -245,7 +407,9 @@ static int rcar_gen2_phy_probe(struct platform_device
> *pdev)
> >  	struct resource *res;
> >  	void __iomem *base;
> >  	struct clk *clk;
> > +	struct usb_otg *otg;
> >  	int i = 0;
> > +	int err;
> >
> >  	if (!dev->of_node) {
> >  		dev_err(dev,
> > @@ -280,6 +444,57 @@ static int rcar_gen2_phy_probe(struct platform_device
> *pdev)
> >  	if (!drv->channels)
> >  		return -ENOMEM;
> >
> > +	/* USB0 optional GPIO power pin for external devices */
> > +	drv->channels->gpio_pwr = of_get_named_gpio_flags(dev->of_node,
> > +						"renesas,pwr", 0, NULL);
> > +	if (drv->channels->gpio_pwr == -EPROBE_DEFER)
> > +		return -EPROBE_DEFER;
> > +
> > +	if (gpio_is_valid(drv->channels->gpio_pwr)) {
> > +		err = devm_gpio_request(dev, drv->channels->gpio_pwr,
> "pwr");
> > +		if (err)
> > +			return err;
> > +	}
> > +
> > +	/* USB0 Host/Function switching info */
> > +	err = probe_otg(pdev, drv);
> > +	if (err)
> > +		return err;
> > +
> > +	/*
> > +	 * The PHY connected to channel 0 can be used to steer signals to the
> > +	 * USB Host IP that stils behind a PCI bridge (pci0), or the USB Func
> > +	 * IP (hsusb). We can dynamically switch this based on VBUS and ID
> > +	 * signals connected to gpios, to get something approaching OTG.
> > +	 */
> > +	if (drv->channels->use_otg) {
> > +		struct usb_phy *usbphy;
> > +
> > +		usbphy = devm_kzalloc(dev, sizeof(*usbphy), GFP_KERNEL);
> > +		if (!usbphy)
> > +			return -ENOMEM;
> > +
> > +		otg = devm_kzalloc(dev, sizeof(*otg), GFP_KERNEL);
> > +		if (!otg)
> > +			return -ENOMEM;
> > +
> > +		usbphy->dev		= dev;
> > +		usbphy->otg		= otg;
> > +
> > +		otg->usb_phy		= usbphy;
> > +		otg->state		= OTG_STATE_UNDEFINED;
> > +		otg->set_peripheral	= rcar_gen2_usb_set_peripheral;
> > +
> > +		drv->channels->otg	= otg;
> > +		drv->channels->usbphy	= usbphy;
> > +
> > +		ATOMIC_INIT_NOTIFIER_HEAD(&usbphy->notifier);
> > +
> > +		INIT_DELAYED_WORK(&drv->channels->work, gpio_vbus_work);
> > +
> > +		usb_add_phy_dev(usbphy);
> > +	}
> > +
> >  	for_each_child_of_node(dev->of_node, np) {
> >  		struct rcar_gen2_channel *channel = drv->channels + i;
> >  		u32 channel_num;
> > @@ -288,6 +503,8 @@ static int rcar_gen2_phy_probe(struct platform_device
> *pdev)
> >  		channel->of_node = np;
> >  		channel->drv = drv;
> >  		channel->selected_phy = -1;
> > +		if (i != 0)
> > +			channel->gpio_pwr = -ENOENT;
> >
> >  		error = of_property_read_u32(np, "reg", &channel_num);
> >  		if (error || channel_num > 2) {
> > @@ -323,6 +540,14 @@ static int rcar_gen2_phy_probe(struct platform_device
> *pdev)
> >
> >  	dev_set_drvdata(dev, drv);
> >
> > +	/*
> > +	 * If we already have something plugged into USB0, we won't get an edge
> > +	 * on VBUS, so we have to manually schedule work to look at the VBUS
> > +	 * and ID signals.
> > +	 */
> > +	if (drv->channels->use_otg)
> > +		schedule_delayed_work(&drv->channels->work,
> msecs_to_jiffies(100));
> > +
> >  	return 0;
> >  }

Thanks
Phil

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2015-07-02 12:45 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-06-22 14:42 [PATCH 0/3] R-Car Gen2 USB0 Host/Function switching Phil Edworthy
2015-06-22 14:42 ` [PATCH 1/3] usb: renesas_usbhs: Allow an OTG PHY driver to provide VBUS Phil Edworthy
2015-06-22 17:38   ` Sergei Shtylyov
2015-06-22 14:42 ` [PATCH 2/3] phy: rcar-gen2 usb: Add Host/Function switching for USB0 Phil Edworthy
2015-06-22 16:13   ` Kishon Vijay Abraham I
2015-07-02  8:22   ` Kishon Vijay Abraham I
2015-07-02 12:45     ` Phil Edworthy
2015-06-22 14:42 ` [PATCH 3/3] arm: koelsch: make USB0 perform Host/Function switching Phil Edworthy
2015-06-22 17:43   ` Sergei Shtylyov
2015-06-22 17:49     ` Sergei Shtylyov
2015-07-01 15:10       ` Phil Edworthy

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).