Peter Chen <peter.c...@freescale.com> writes:

> The main design flow is the same with msm otg driver, that is the id and
> vbus interrupt are handled at core driver, others are handled by
> individual drivers.

This looks good to me in general. Few comments below.

> - At former design, when switch usb role from device->host, it will call
> udc_stop, it will remove the gadget driver, so when switch role
> from host->device, it can't add gadget driver any more.

Well, it can, only you need to reload the gadget module for that, but
that's more current gadget framework's shortcoming...

> At new design, when role switch occurs, the gadget just calls
> usb_gadget_vbus_disconnect/usb_gadget_vbus_connect as well as
> reset controller, it will not free any device/gadget structure

...but I do agree that this is much better.
One thing we need to address here is CI13XXX_PULLUP_ON_VBUS. Since we
now handle vbus session in the core driver, we should just retire this
flag. And this vbus_disconnect() method won't work if PULLUP_ON_VBUS is
not set anyway, since vbus_session is a nop in that case.

> - Add vbus connect and disconnect to core interrupt handler, it can
> notify udc driver by calling
> usb_gadget_vbus_disconnect/usb_gadget_vbus_connect.
>
> - Add otg.c to implement struct usb_otg, in that case, calling 
> otg_set_peripheral
> will not be failed at udc.c. Besides, we enable id interrupt at
> ci_hdrc_otg_init.

Can you make this a separate patch?

> - Add special case handling, like connecting to host during boot up at device
> mode, usb device at the MicroB to A cable at host mode, etc.

This can probably be a separate patch too, since it's kind of a separate
issue.

> Signed-off-by: Peter Chen <peter.c...@freescale.com>
> ---
>  drivers/usb/chipidea/Makefile |    2 +-
>  drivers/usb/chipidea/bits.h   |   10 ++
>  drivers/usb/chipidea/ci.h     |    6 +
>  drivers/usb/chipidea/core.c   |  217 
> +++++++++++++++++++++++++++++++++++++----
>  drivers/usb/chipidea/otg.c    |   60 +++++++++++
>  drivers/usb/chipidea/otg.h    |    6 +
>  drivers/usb/chipidea/udc.c    |    2 +
>  7 files changed, 284 insertions(+), 19 deletions(-)
>
> diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile
> index d92ca32..11f513c 100644
> --- a/drivers/usb/chipidea/Makefile
> +++ b/drivers/usb/chipidea/Makefile
> @@ -2,7 +2,7 @@ ccflags-$(CONFIG_USB_CHIPIDEA_DEBUG) := -DDEBUG
>  
>  obj-$(CONFIG_USB_CHIPIDEA)           += ci_hdrc.o
>  
> -ci_hdrc-y                            := core.o
> +ci_hdrc-y                            := core.o otg.o
>  ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC)   += udc.o
>  ci_hdrc-$(CONFIG_USB_CHIPIDEA_HOST)  += host.o
>  ci_hdrc-$(CONFIG_USB_CHIPIDEA_DEBUG) += debug.o
> diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h
> index 050de85..4b6ae3e 100644
> --- a/drivers/usb/chipidea/bits.h
> +++ b/drivers/usb/chipidea/bits.h
> @@ -65,11 +65,21 @@
>  #define OTGSC_ASVIS        BIT(18)
>  #define OTGSC_BSVIS        BIT(19)
>  #define OTGSC_BSEIS        BIT(20)
> +#define OTGSC_1MSIS        BIT(21)
> +#define OTGSC_DPIS         BIT(22)
>  #define OTGSC_IDIE         BIT(24)
>  #define OTGSC_AVVIE        BIT(25)
>  #define OTGSC_ASVIE        BIT(26)
>  #define OTGSC_BSVIE        BIT(27)
>  #define OTGSC_BSEIE        BIT(28)
> +#define OTGSC_1MSIE        BIT(29)
> +#define OTGSC_DPIE         BIT(30)
> +#define OTGSC_INT_EN_BITS    (BIT(24) | BIT(25) | BIT(26)    \
> +                             | BIT(27) | BIT(28) | BIT(29)   \
> +                             | BIT(30))
> +#define OTGSC_INT_STATUS_BITS        (BIT(16) | BIT(17) | BIT(18)    \
> +                             | BIT(19) | BIT(20) | BIT(21)   \
> +                             | BIT(22))

Why not use the OTGSC_* defines instead of bit numbers?

>  
>  /* USBMODE */
>  #define USBMODE_CM            (0x03UL <<  0)
> diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
> index d738603..d32f932 100644
> --- a/drivers/usb/chipidea/ci.h
> +++ b/drivers/usb/chipidea/ci.h
> @@ -139,6 +139,7 @@ struct ci13xxx {
>       enum ci_role                    role;
>       bool                            is_otg;
>       struct work_struct              work;
> +     struct delayed_work             dwork;
>       struct workqueue_struct         *wq;
>  
>       struct dma_pool                 *qh_pool;
> @@ -164,6 +165,11 @@ struct ci13xxx {
>       bool                            global_phy;
>       struct usb_phy                  *transceiver;
>       struct usb_hcd                  *hcd;
> +     /* events handled at ci_role_work */
> +#define ID           0
> +#define B_SESS_VALID 1
> +     unsigned long events;

I would just use bools instead.

> +     struct usb_otg                  otg;
>  };
>  
>  static inline struct ci_role_driver *ci_role(struct ci13xxx *ci)
> diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
> index f69d029..b50b77a 100644
> --- a/drivers/usb/chipidea/core.c
> +++ b/drivers/usb/chipidea/core.c
> @@ -73,6 +73,7 @@
>  #include "bits.h"
>  #include "host.h"
>  #include "debug.h"
> +#include "otg.h"
>  
>  /* Controller register map */
>  static uintptr_t ci_regs_nolpm[] = {
> @@ -264,25 +265,138 @@ static enum ci_role ci_otg_role(struct ci13xxx *ci)
>       return role;
>  }
>  
> +#define CI_WAIT_VBUS_STABLE_TIMEOUT 500
>  /**
> - * ci_role_work - perform role changing based on ID pin
> - * @work: work struct
> + * ci_wait_vbus_stable
> + * When usb role switches, we need to turn on/off internal vbus
> + * regulaor, sometimes, the real vbus value may not lower fast
> + * enough due to capacitance, and we do want the vbus lower
> + * than 0.8v if it is device mode, and higher than 4.4v, if it
> + * is host mode.
> + *
> + * @low: true, Wait vbus lower than B_SESSION_VALID
> + *     : false, Wait vbus higher than A_VBUS_VALID

Instead of this you could just pass otgsc bit mask here (OTGSC_BSV or
OTGSC_AVV), would make it shorter and easier to read from the caller.

>   */
> -static void ci_role_work(struct work_struct *work)
> +static void ci_wait_vbus_stable(struct ci13xxx *ci, bool low)
> +{
> +     unsigned long timeout;
> +     u32 otgsc = hw_read(ci, OP_OTGSC, ~0);
> +
> +     timeout = jiffies + CI_WAIT_VBUS_STABLE_TIMEOUT;
> +
> +     if (low) {
> +             while (otgsc & OTGSC_BSV) {
> +                     if (time_after(jiffies, timeout)) {
> +                             dev_err(ci->dev, "wait vbus lower than\
> +                                             B_SESS_VALID timeout!\n");
> +                             return;
> +                     }
> +                     msleep(20);
> +                     otgsc = hw_read(ci, OP_OTGSC, ~0);
> +             }
> +     } else {
> +             while (!(otgsc & OTGSC_AVV)) {
> +                     if (time_after(jiffies, timeout)) {
> +                             dev_err(ci->dev, "wait vbus higher than\
> +                                             A_VBUS_VALID timeout!\n");
> +                             return;
> +                     }
> +                     msleep(20);
> +                     otgsc = hw_read(ci, OP_OTGSC, ~0);
> +             }
> +
> +     }
> +}
> +
> +static void ci_handle_id_switch(struct ci13xxx *ci)
>  {
> -     struct ci13xxx *ci = container_of(work, struct ci13xxx, work);
>       enum ci_role role = ci_otg_role(ci);
>  
>       if (role != ci->role) {
>               dev_dbg(ci->dev, "switching from %s to %s\n",
>                       ci_role(ci)->name, ci->roles[role]->name);
>  
> -             ci_role_stop(ci);
> -             ci_role_start(ci, role);
> -             enable_irq(ci->irq);
> +             /* 1. Finish the current role */
> +             if (ci->role == CI_ROLE_GADGET) {
> +                     usb_gadget_vbus_disconnect(&ci->gadget);
> +                     /* host doesn't care B_SESSION_VALID event */
> +                     hw_write(ci, OP_OTGSC, OTGSC_BSVIE, ~OTGSC_BSVIE);
> +                     hw_write(ci, OP_OTGSC, OTGSC_BSVIS, OTGSC_BSVIS);
> +                     ci->role = CI_ROLE_END;
> +                     /* reset controller */
> +                     hw_write(ci, OP_USBCMD, USBCMD_RST, USBCMD_RST);
> +                     while (hw_read(ci, OP_USBCMD, USBCMD_RST))
> +                             udelay(10);

I would prefer we use hw_device_reset() everywhere where we reset the
controller. Actually, same goes for run/stop bit.

> +             } else if (ci->role == CI_ROLE_HOST) {
> +                     ci_role_stop(ci);
> +                     /* reset controller */
> +                     hw_write(ci, OP_USBCMD, USBCMD_RST, USBCMD_RST);
> +                     while (hw_read(ci, OP_USBCMD, USBCMD_RST))
> +                             udelay(10);
> +             }
> +
> +             /* 2. Turn on/off vbus according to coming role */
> +             if (ci_otg_role(ci) == CI_ROLE_GADGET) {
> +                     otg_set_vbus(&ci->otg, false);
> +                     ci_wait_vbus_stable(ci, true);
> +             } else if (ci_otg_role(ci) == CI_ROLE_HOST) {
> +                     otg_set_vbus(&ci->otg, true);
> +                     ci_wait_vbus_stable(ci, false);
> +             }
> +
> +             /* 3. Begin the new role */
> +             if (ci_otg_role(ci) == CI_ROLE_GADGET) {
> +                     ci->role = role;
> +                     hw_write(ci, OP_OTGSC, OTGSC_BSVIS, OTGSC_BSVIS);
> +                     hw_write(ci, OP_OTGSC, OTGSC_BSVIE, OTGSC_BSVIE);
> +             } else if (ci_otg_role(ci) == CI_ROLE_HOST) {
> +                     ci_role_start(ci, role);
> +             }
>       }
>  }
>  
> +static void ci_handle_vbus_change(struct ci13xxx *ci)
> +{
> +     u32 otgsc = hw_read(ci, OP_OTGSC, ~0);
> +
> +     if (otgsc & OTGSC_BSV)
> +             usb_gadget_vbus_connect(&ci->gadget);
> +     else
> +             usb_gadget_vbus_disconnect(&ci->gadget);
> +}
> +
> +/**
> + * ci_otg_work - perform otg (vbus/id) event handle
> + * @work: work struct
> + */
> +static void ci_otg_work(struct work_struct *work)
> +{
> +     struct ci13xxx *ci = container_of(work, struct ci13xxx, work);
> +
> +     if (test_bit(ID, &ci->events)) {
> +             clear_bit(ID, &ci->events);
> +             ci_handle_id_switch(ci);
> +     } else if (test_bit(B_SESS_VALID, &ci->events)) {
> +             clear_bit(B_SESS_VALID, &ci->events);
> +             ci_handle_vbus_change(ci);
> +     } else
> +             dev_err(ci->dev, "unexpected event occurs at %s\n", __func__);
> +
> +     enable_irq(ci->irq);
> +}
> +
> +static void ci_delayed_work(struct work_struct *work)
> +{
> +     struct delayed_work *dwork = to_delayed_work(work);
> +     struct ci13xxx *ci = container_of(dwork, struct ci13xxx, dwork);
> +     u32 otgsc = hw_read(ci, OP_OTGSC, ~0);
> +
> +     /* Handle the situation that usb device at the MicroB to A cable */
> +     if (ci->is_otg && !(otgsc & OTGSC_ID))
> +             otg_set_vbus(&ci->otg, true);
> +
> +}
> +
>  static ssize_t show_role(struct device *dev, struct device_attribute *attr,
>                        char *buf)
>  {
> @@ -321,19 +435,36 @@ static irqreturn_t ci_irq(int irq, void *data)
>       irqreturn_t ret = IRQ_NONE;
>       u32 otgsc = 0;
>  
> -     if (ci->is_otg)
> -             otgsc = hw_read(ci, OP_OTGSC, ~0);
> +     otgsc = hw_read(ci, OP_OTGSC, ~0);
>  
> -     if (ci->role != CI_ROLE_END)
> -             ret = ci_role(ci)->irq(ci);
> -
> -     if (ci->is_otg && (otgsc & OTGSC_IDIS)) {
> +     /*
> +      * Handle id change interrupt, it indicates device/host function
> +      * switch.
> +      */
> +     if (ci->is_otg && (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) {
> +             set_bit(ID, &ci->events);
>               hw_write(ci, OP_OTGSC, OTGSC_IDIS, OTGSC_IDIS);
>               disable_irq_nosync(ci->irq);
>               queue_work(ci->wq, &ci->work);
> -             ret = IRQ_HANDLED;
> +             return IRQ_HANDLED;
>       }
>  
> +     /*
> +      * Handle vbus change interrupt, it indicates device connection
> +      * and disconnection events.
> +      */
> +     if ((otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) {
> +             set_bit(B_SESS_VALID, &ci->events);
> +             hw_write(ci, OP_OTGSC, OTGSC_BSVIS, OTGSC_BSVIS);
> +             disable_irq_nosync(ci->irq);
> +             queue_work(ci->wq, &ci->work);
> +             return IRQ_HANDLED;
> +     }
> +
> +     /* Handle device/host interrupt */
> +     if (ci->role != CI_ROLE_END)
> +             ret = ci_role(ci)->irq(ci);
> +
>       return ret;
>  }
>  
> @@ -397,6 +528,7 @@ static int __devinit ci_hdrc_probe(struct platform_device 
> *pdev)
>       struct resource *res;
>       void __iomem    *base;
>       int             ret;
> +     u32             otgsc;
>  
>       if (!dev->platform_data) {
>               dev_err(dev, "platform data missing\n");
> @@ -421,6 +553,7 @@ static int __devinit ci_hdrc_probe(struct platform_device 
> *pdev)
>               return -ENOMEM;
>       }
>  
> +     ci->events = 0;
>       ci->dev = dev;
>       ci->platdata = dev->platform_data;
>       if (ci->platdata->phy)
> @@ -442,12 +575,20 @@ static int __devinit ci_hdrc_probe(struct 
> platform_device *pdev)
>               return -ENODEV;
>       }
>  
> -     INIT_WORK(&ci->work, ci_role_work);
> +     INIT_WORK(&ci->work, ci_otg_work);
> +     INIT_DELAYED_WORK(&ci->dwork, ci_delayed_work);
>       ci->wq = create_singlethread_workqueue("ci_otg");
>       if (!ci->wq) {
>               dev_err(dev, "can't create workqueue\n");
>               return -ENODEV;
>       }
> +     /* Disable all interrupts bits */
> +     hw_write(ci, OP_USBINTR, 0xffffffff, 0);
> +     hw_write(ci, OP_OTGSC, OTGSC_INT_EN_BITS, ~OTGSC_INT_EN_BITS);

hw_write(ci, OP_OTGSC, OTGSC_INT_EN_BITS, 0);
will work to the same effect and is a bit easier on the eyes.

> +
> +     /* Clear all interrupts status bits*/
> +     hw_write(ci, OP_USBSTS, 0xffffffff, 0xffffffff);
> +     hw_write(ci, OP_OTGSC, OTGSC_INT_STATUS_BITS, OTGSC_INT_STATUS_BITS);
>  
>       /* initialize role(s) before the interrupt is requested */
>       ret = ci_hdrc_host_init(ci);
> @@ -465,6 +606,7 @@ static int __devinit ci_hdrc_probe(struct platform_device 
> *pdev)
>       }
>  
>       if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
> +             dev_info(dev, "support otg\n");

Looks more like a dev_dbg() to me.

>               ci->is_otg = true;
>               /* ID pin needs 1ms debouce time, we delay 2ms for safe */
>               mdelay(2);
> @@ -475,13 +617,53 @@ static int __devinit ci_hdrc_probe(struct 
> platform_device *pdev)
>                       : CI_ROLE_GADGET;
>       }
>  
> +     if (ci->is_otg) {
> +             /* if otg is supported, create struct usb_otg */
> +             ci_hdrc_otg_init(ci);
> +
> +             /*
> +              * If it is host role at otg port, start gadget role first
> +              * as we need to allocate device struct.
> +              */
> +             if (ci->role == CI_ROLE_HOST) {
> +
> +                     ret = ci_role_start(ci, CI_ROLE_GADGET);

This is kind of a hack. In the worst case you could move gadegt
initialization to the role's init method. Or we could add a role
destructor method and then
  1) allocate gadget stuff on first call to role start
  2) make role stop a nop
  3) deallocate gadget stuff on role destroy()
  4) call role's destroy() in device remove path instead of role stop
  5) call ci_role_start/ci_role_stop from ci_handle_id_switch()
  unconditionally.
Does this make sense to you?

> +                     if (ret) {
> +                             dev_err(dev, "can't start gadget role, 
> ret=%d\n",
> +                                             ret);
> +                             ret = -ENODEV;
> +                             goto rm_wq;
> +                     }
> +
> +                     usb_gadget_vbus_disconnect(&ci->gadget);
> +                     /* host doesn't care B_SESSION_VALID event */
> +                     hw_write(ci, OP_OTGSC, OTGSC_BSVIE, ~OTGSC_BSVIE);
> +                     hw_write(ci, OP_OTGSC, OTGSC_BSVIS, OTGSC_BSVIS);
> +                     ci->role = CI_ROLE_HOST;
> +             }
> +             msleep(20);
> +     }
> +
>       ret = ci_role_start(ci, ci->role);
>       if (ret) {
> -             dev_err(dev, "can't start %s role\n", ci_role(ci)->name);
> +             dev_err(dev, "can't start %s role, ret=%d\n",
> +                             ci_role(ci)->name, ret);
>               ret = -ENODEV;
>               goto rm_wq;
>       }
>  
> +     otgsc = hw_read(ci, OP_OTGSC, ~0);
> +     /*
> +      * if it is device mode:
> +      * - Enable vbus detect
> +      * - If it has already connected to host, notify udc
> +      */
> +     if (ci->role == CI_ROLE_GADGET) {
> +             hw_write(ci, OP_OTGSC, OTGSC_BSVIE, OTGSC_BSVIE);
> +             if (otgsc & OTGSC_BSV)
> +                     usb_gadget_vbus_connect(&ci->gadget);

This looks a lot like ci_handle_vbus_change(), doesn't it?

> +     }
> +
>       platform_set_drvdata(pdev, ci);
>       ret = request_irq(ci->irq, ci_irq, IRQF_SHARED, ci->platdata->name,
>                         ci);
> @@ -492,8 +674,7 @@ static int __devinit ci_hdrc_probe(struct platform_device 
> *pdev)
>       if (ret)
>               goto rm_attr;
>  
> -     if (ci->is_otg)
> -             hw_write(ci, OP_OTGSC, OTGSC_IDIE, OTGSC_IDIE);
> +     queue_delayed_work(ci->wq, &ci->dwork, msecs_to_jiffies(500));

CI_WAIT_VBUS_STABLE_TIMEOUT?

>  
>       return ret;
>  
> diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
> new file mode 100644
> index 0000000..cc2864f
> --- /dev/null
> +++ b/drivers/usb/chipidea/otg.c
> @@ -0,0 +1,60 @@
> +/*
> + * otg.c - ChipIdea USB IP core OTG driver
> + *
> + * Copyright 2012 Freescale Semiconductor, Inc.
> + *
> + * Author: Peter Chen
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/platform_device.h>
> +#include <linux/module.h>
> +#include <linux/io.h>
> +#include <linux/irq.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/usb/gadget.h>
> +#include <linux/usb/otg.h>
> +#include <linux/usb/chipidea.h>
> +
> +#include "ci.h"
> +#include "udc.h"
> +#include "bits.h"
> +#include "host.h"
> +#include "debug.h"
> +
> +static int ci_otg_set_peripheral(struct usb_otg *otg,
> +             struct usb_gadget *periph)
> +{
> +     otg->gadget = periph;
> +
> +     return 0;
> +}
> +
> +static int ci_otg_set_host(struct usb_otg *otg, struct usb_bus *host)
> +{
> +     otg->host = host;
> +
> +     return 0;
> +}
> +
> +/**
> + * ci_hdrc_otg_init - initialize device related bits
> + * ci: the controller
> + *
> + * This function create otg struct, if the device can switch between
> + * device and host.
> + */
> +int ci_hdrc_otg_init(struct ci13xxx *ci)
> +{
> +     /* Useless at current */
> +     ci->otg.set_peripheral = ci_otg_set_peripheral;
> +     ci->otg.set_host = ci_otg_set_host;
> +     ci->transceiver->otg = &ci->otg;

This will blow up if we don't have a transceiver.

> +     hw_write(ci, OP_OTGSC, OTGSC_IDIE, OTGSC_IDIE);
> +
> +     return 0;
> +}
> diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h
> new file mode 100644
> index 0000000..b4c6b3e
> --- /dev/null
> +++ b/drivers/usb/chipidea/otg.h
> @@ -0,0 +1,6 @@
> +#ifndef __DRIVERS_USB_CHIPIDEA_OTG_H
> +#define __DRIVERS_USB_CHIPIDEA_OTG_H
> +
> +int ci_hdrc_otg_init(struct ci13xxx *ci);
> +
> +#endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */
> diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
> index d214448..b52cb10 100644
> --- a/drivers/usb/chipidea/udc.c
> +++ b/drivers/usb/chipidea/udc.c
> @@ -1371,6 +1371,7 @@ static int ci13xxx_vbus_session(struct usb_gadget 
> *_gadget, int is_active)
>                       pm_runtime_get_sync(&_gadget->dev);
>                       hw_device_reset(ci, USBMODE_CM_DC);
>                       hw_device_state(ci, ci->ep0out->qh.dma);
> +                     dev_dbg(ci->dev, "Connected to host\n");
>               } else {
>                       hw_device_state(ci, 0);
>                       if (ci->platdata->notify_event)
> @@ -1378,6 +1379,7 @@ static int ci13xxx_vbus_session(struct usb_gadget 
> *_gadget, int is_active)
>                               CI13XXX_CONTROLLER_STOPPED_EVENT);
>                       _gadget_stop_activity(&ci->gadget);
>                       pm_runtime_put_sync(&_gadget->dev);
> +                     dev_dbg(ci->dev, "disconnected from host\n");
>               }
>       }
>  
> -- 
> 1.7.0.4
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to