[PATCH v2 4/4] drm: zte: add VGA driver support

Sean Paul seanpaul at chromium.org
Fri Apr 7 14:43:02 UTC 2017


On Fri, Apr 07, 2017 at 11:02:33AM +0800, Shawn Guo wrote:
> On Thu, Apr 06, 2017 at 01:12:51PM -0400, Sean Paul wrote:
> > On Thu, Apr 06, 2017 at 11:01:10PM +0800, Shawn Guo wrote:
> > > +static int zx_vga_connector_get_modes(struct drm_connector *connector)
> > > +{
> > > +	struct zx_vga *vga = to_zx_vga(connector);
> > > +	unsigned long flags;
> > > +	struct edid *edid;
> > > +	int ret;
> > > +
> > > +	/*
> > > +	 * Clear both detection bits to switch I2C bus from device
> > > +	 * detecting to EDID reading.
> > > +	 */
> > > +	spin_lock_irqsave(&vga->lock, flags);
> > > +	zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, 0);
> > > +	spin_unlock_irqrestore(&vga->lock, flags);
> > > +
> > > +	edid = drm_get_edid(connector, &vga->ddc->adap);
> > > +	if (!edid)
> > > +		return 0;
> > > +
> > > +	/*
> > > +	 * As edid reading succeeds, device must be connected, so we set
> > > +	 * up detection bit for unplug interrupt here.
> > > +	 */
> > > +	spin_lock_irqsave(&vga->lock, flags);
> > > +	zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, VGA_DETECT_SEL_HAS_DEVICE);
> > > +	spin_unlock_irqrestore(&vga->lock, flags);
> > > +
> > > +	drm_mode_connector_update_edid_property(connector, edid);
> > > +	ret = drm_add_edid_modes(connector, edid);
> > > +	kfree(edid);
> > > +
> > > +	return ret;
> > > +}
> 
> <snip>
> 
> > > +static irqreturn_t zx_vga_irq_handler(int irq, void *dev_id)
> > > +{
> > > +	struct zx_vga *vga = dev_id;
> > > +	u32 status;
> > > +
> > > +	status = zx_readl(vga->mmio + VGA_I2C_STATUS);
> > > +
> > > +	/* Clear interrupt status */
> > > +	zx_writel_mask(vga->mmio + VGA_I2C_STATUS, VGA_CLEAR_IRQ,
> > > +		       VGA_CLEAR_IRQ);
> > > +
> > > +	if (status & VGA_DEVICE_CONNECTED) {
> > > +		/*
> > > +		 * Since VGA_DETECT_SEL bits need to be reset for switching DDC
> > > +		 * bus from device detection to EDID read, rather than setting
> > > +		 * up HAS_DEVICE bit here, we need to do that in .get_modes
> > > +		 * hook for unplug detecting after EDID read succeeds.
> > > +		 */
> > > +		vga->connected = true;
> > > +		return IRQ_WAKE_THREAD;
> > > +	}
> > > +
> > > +	if (status & VGA_DEVICE_DISCONNECTED) {
> > > +		spin_lock(&vga->lock);
> > > +		zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL,
> > > +			  VGA_DETECT_SEL_NO_DEVICE);
> > > +		spin_unlock(&vga->lock);
> > 
> > I think you still have the race here. If you get a disconnect between get_edid
> > successfully finishing, and resetting the DETECT_SEL register, you will end up
> > with the device being disconnected and DETECT_SEL == VGA_DETECT_SEL_HAS_DEVICE.
> > 
> > In order to close this, you'll need to hold the lock across the edid read.
> 
> We cannot hold a spin lock across the EDID read procedure, which does
> sleep.

Yeah, this is a pretty common problem. We usually use a mutex in conjunction
with a work function to handle this type of thing.

> 
> When you suggested to have a lock for DETECT_SEL register access, I
> thought we are accessing it in a read-modify-write way and thus agreed
> to add a lock.  However, I just found that it's not the case actually.
> All the accesses to the register are single direct write.
> 
> And here is my understanding about the condition you described above.
> Once DETECT_SEL register gets reset (both bits cleared), the hardware
> switches DDC bus for EDID read, and hotplug detection will stop working
> right away (this is how ZTE hardware works).  That said, if we get a
> disconnect before drm_get_edid() successfully finishes, two points:
> 
> - The irq handler will not be triggered, so it will not get a chance to
>   update DETECT_SEL register.

Ah, this was the missing piece I needed. I hadn't realized that the first
VGA_AUTO_DETECT_SEL write in get_modes disabled the irq. 


> - The drm_get_edid() fails, and .get_modes returns with both detect bits
>   kept cleared.
> 
> I think what we can do better in this case is that we should set the
> device state into disconnected, before returning from .get_modes,
> something like the code below.  But still, I do not see we need a lock
> for that.

Yep, agreed. Can you also please add to your comment in the !edid case,
something like:

* Locking is not required here since the only other place to write this register
* (and connected var) is the irq handler. The irq handler is disabled because
* we've cleared AUTO_DETECT_SEL above.

Thanks for walking me through this.

Sean

> 
> Shawn
> 
> static int zx_vga_connector_get_modes(struct drm_connector *connector)
> {
>         struct zx_vga *vga = to_zx_vga(connector);
>         unsigned long flags;
>         struct edid *edid;
>         int ret;
> 
>         /*
>          * Clear both detection bits to switch I2C bus from device
>          * detecting to EDID reading.
>          */
>         zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, 0);
> 
>         edid = drm_get_edid(connector, &vga->ddc->adap);
>         if (!edid) {
>                 /*
>                  * If EDID reading fails, we set the device state into
>                  * disconnected.
>                  */
>                 zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL,
>                           VGA_DETECT_SEL_NO_DEVICE);
>                 vga->connected = false;
>                 return 0;
>         }
> 
>         /*
>          * As edid reading succeeds, device must be connected, so we set
>          * up detection bit for unplug interrupt here.
>          */
>         zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, VGA_DETECT_SEL_HAS_DEVICE);
> 
>         drm_mode_connector_update_edid_property(connector, edid);
>         ret = drm_add_edid_modes(connector, edid);
>         kfree(edid);
> 
>         return ret;
> }

-- 
Sean Paul, Software Engineer, Google / Chromium OS


More information about the dri-devel mailing list