Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753626AbbGBMpx (ORCPT ); Thu, 2 Jul 2015 08:45:53 -0400 Received: from relmlor3.renesas.com ([210.160.252.173]:57544 "EHLO relmlie2.idc.renesas.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1753230AbbGBMpn convert rfc822-to-8bit (ORCPT ); Thu, 2 Jul 2015 08:45:43 -0400 X-IronPort-AV: E=Sophos;i="5.15,392,1432566000"; d="scan'208";a="189597034" From: Phil Edworthy To: Kishon Vijay Abraham I , Yoshihiro Shimoda , Simon Horman CC: Sergei Shtylyov , "linux-kernel@vger.kernel.org" , "linux-usb@vger.kernel.org" , "linux-sh@vger.kernel.org" Subject: RE: [PATCH 2/3] phy: rcar-gen2 usb: Add Host/Function switching for USB0 Thread-Topic: [PATCH 2/3] phy: rcar-gen2 usb: Add Host/Function switching for USB0 Thread-Index: AQHQrPpYfOAJ4FuBiEaw+ZkpL4thsZ3H5oIAgABJPCA= Date: Thu, 2 Jul 2015 12:45:37 +0000 Message-ID: References: <1434984151-27274-1-git-send-email-phil.edworthy@renesas.com> <1434984151-27274-3-git-send-email-phil.edworthy@renesas.com> <5594F4C4.2030700@ti.com> In-Reply-To: <5594F4C4.2030700@ti.com> Accept-Language: en-GB, en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: ti.com; dkim=none (message not signed) header.d=none; x-originating-ip: [193.141.220.21] x-microsoft-exchange-diagnostics: 1;SIXPR06MB0921;5:F3/6uUDnJZ9aqDElLTcDH3iWx4TrIz47EOmoYyDRmEyUorFr4C64iJ/TvY/LLZPJvLcehQW/gDwUG6NI+bm5zBXsdCp06J2bMGtFg2q3u4mRdzMkf6iQWzGXAlZA9TkpL7nDUmrPcRzlncQT2XmEKg==;24:lgH7lMsta+m+lDBsOImkyCfkHWe8Xhcsr6oZ0c+dwRrPRl/DIWbSFYmwZjKmIyd5YeX2eklrJt520CBu0R4m3UQQyCwVXIX7gsFeNS5AW28=;20:HB7aXS6pNy7CBozDrUwh51NiCS0WTo3USGZZZsA0PNeBNPKbm7nMQgzferKrq9Yt4P97DkzTGNaWmznyISmO3vC33ID4onqT/nOOQjQnkaczZG5E6ZbbsOVcSCxD0o6b3Y1hwyPK4AQWg0NH2t0O2Zhr04CrTiiSatNm4Xn6YzY= x-microsoft-antispam: UriScan:;BCL:0;PCL:0;RULEID:;SRVR:SIXPR06MB0921; x-microsoft-antispam-prvs: x-exchange-antispam-report-test: UriScan:; x-exchange-antispam-report-cfa-test: BCL:0;PCL:0;RULEID:(601004)(5005006)(3002001);SRVR:SIXPR06MB0921;BCL:0;PCL:0;RULEID:;SRVR:SIXPR06MB0921; x-forefront-prvs: 06259BA5A2 x-forefront-antispam-report: SFV:NSPM;SFS:(10019020)(6009001)(377454003)(51704005)(24454002)(46102003)(77156002)(62966003)(122556002)(19580395003)(19580405001)(92566002)(40100003)(54356999)(66066001)(106116001)(5003600100002)(5001770100001)(2900100001)(575784001)(50986999)(2950100001)(76176999)(5002640100001)(87936001)(2656002)(76576001)(86362001)(5001960100002)(189998001)(33656002)(74316001)(77096005)(102836002)(5001920100001);DIR:OUT;SFP:1102;SCL:1;SRVR:SIXPR06MB0921;H:SIXPR06MB0639.apcprd06.prod.outlook.com;FPR:;SPF:None;MLV:sfv;LANG:en; spamdiagnosticoutput: 1:23 spamdiagnosticmetadata: NSPM Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 8BIT MIME-Version: 1.0 X-OriginatorOrg: renesas.com X-MS-Exchange-CrossTenant-originalarrivaltime: 02 Jul 2015 12:45:37.7560 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: 53d82571-da19-47e4-9cb4-625a166a4a2a X-MS-Exchange-Transport-CrossTenantHeadersStamped: SIXPR06MB0921 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 13163 Lines: 417 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 > > --- > > 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 > > #include > > #include > > +#include > > #include > > #include > > +#include > > #include > > #include > > #include > > +#include > > +#include > > +#include > > > > #include > > > > @@ -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 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/