Re: [PATCH 2/4] usb: chipidea: add otg id switch and vbus connect/disconnect detect

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Nov 13, 2012 at 02:25:59PM +0200, Alexander Shishkin wrote:

Alex, Thanks for review.

> > 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.

Agree, I will change.

> 
> > - 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?
OK
> 
> > - 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.
OK
> > +#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?
OK, will change
> 
> >  	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.

We may add other otg events (like AVBUS) to support fully otg function.

> 
> > +	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.
> 
OK, will change
> >   */
> > -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.
> 
OK, I will use hw_device_reset(ci, USBMODE_CM_IDLE) to reset controller
For setting run/stop bit, I will use hw_device_state at ci13xxx_pullup

> > +	/* 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.

OK, I will change.
> 
> >  
> >  	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.
OK, will change.
> 
> >  		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?

I prefer to just add gadget initialization at the role's init method.
Or we need to add some role_create and role_destroy API for device,
and add some conditional code.

For host, everything is ok
ci_hdrc_probe: call ci_role_start at host mode
ci_hdrc_remove: call ci_role_stop
ci_handle_id_switch: call ci_role_start/ci_role_stop when role changes.

For device, we can't make things the same.
ci_hdrc_probe: call udc_start at both device/host mode.
ci_hdrc_remove: call ci_role_stop
ci_handle_id_switch: call usb_gadget_vbus_connect/usb_gadget_vbus_disconnect

> >  
> > +	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?
Great, I will change.
> 
> > +	}
> > +
> >  	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?

Not exactly, it is the time for waiting platform probe finishes, 
as the vbus callback will be created according to different platforms.
> 
> > +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.
> 
will add 
if (!IS_ERR_OR_NULL(ci->transceiver)) {
	ci->transceiver->otg = &ci->otg;
}

-- 

Best Regards,
Peter Chen

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux