RE: [PATCH 1/2] USB host: Move AMD PLL quirk to pci-quirks.c

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

 



> -----Original Message-----
> From: Sarah Sharp [mailto:sarah.a.sharp@xxxxxxxxxxxxxxx]
> Sent: Wednesday, January 12, 2011 4:07 AM
> To: Xu, Andiry
> Cc: gregkh@xxxxxxx; linux-usb@xxxxxxxxxxxxxxx;
> dbrownell@xxxxxxxxxxxxxxxxxxxxx
> Subject: Re: [PATCH 1/2] USB host: Move AMD PLL quirk to pci-quirks.c
> 
> On Tue, Jan 11, 2011 at 05:57:02PM +0800, Andiry Xu wrote:
> > This patch move the AMD PLL quirk code in OHCI/EHCI driver to pci-
> quirks.c,
> > and export the functions to be used by xHCI driver later.
> >
> > AMD PLL quirk disable the optional PM feature inside specific
> > SB700/SB800/Hudson-2/3 platforms under the following conditions:
> >
> > 1. If an isochronous device is connected to OHCI/EHCI/xHCI port and
is
> active;
> > 2. Optional PM feature that powers down the internal Bus PLL when
the
> link is
> >    in low power state is enabled.
> >
> > Without AMD PLL quirk, USB isochronous stream may stutter or have
breaks
> > occasionally, which greatly impair the performance of audio/video
> streams.
> >
> > Currently AMD PLL quirk is implemented in OHCI and EHCI driver, and
will
> be
> > added to xHCI driver too. They are doing similar things actually, so
> move
> > the quirk code to pci-quirks.c, which have several advantages:
> >
> > 1. Remove repeat defines and functions in OHCI/EHCI (and xHCI)
driver
> and
> >    make them cleaner;
> > 2. AMD chipset information will be probed only once and then stored.
> >    Currently they're probed during every OHCI/EHCI initialization,
move
> >    the detect code to pci-quirks.c saves the repeat detect cost;
> > 3. Build up synchronization among OHCI/EHCI/xHCI driver. In current
> >    code, every host controller enable/disable PLL only according to
> >    its own status, and may enable PLL while there is still isoc
transfer
> on
> >    other HCs. Move the quirk to pci-quirks.c prevents this issue.
> 
> So is the PLL shared across the OHCI/EHCI/xHCI hosts?  I could
> understand it being shared across the OHCI/EHCI hosts, but it seems a
> bit odd to also share it across the xHCI host.  It also seems a bit
odd
> to have an EHCI or OHCI host controller in a system with an xHCI host
> controller, since xHCI is supposed to handle all device speeds.
> 
> Are you sure the PLL is shared by all three hosts, and there's an EHCI
> or OHCI host in the same system as an xHCI host?
> 

The PLL is set in SB/NB registers, not OCHI/EHCI/xHCI registers. Here
"PLL share" means share the quirk code. PLL is set differently according
to the SB/NB models, regardless of USB host controllers. 

Although xHCI can handle all device speeds, I think it's common that
people plug USB2.0/1.1 devices to EHCI/OHCI controllers, even if there
are xHCI controllers on the platform too.
And I think it's regular that a system has OHCI/EHCI/xHCI controllers at
the same time. For example, my develop platform has 2 EHCI, 3 OHCI and 2
xHCI controllers. 
AFAIK, many platforms on the market have xHCI as well as EHCI and
OHCI/UHCI controllers.

> 
> I have a couple more comments below.
> 
> > diff --git a/drivers/usb/host/ehci-pci.c
b/drivers/usb/host/ehci-pci.c
> > index 76179c3..d52be7a 100644
> > --- a/drivers/usb/host/ehci-pci.c
> > +++ b/drivers/usb/host/ehci-pci.c
> 
> ...
> 
> >  /* called during probe() after chip reset completes */
> >  static int ehci_pci_setup(struct usb_hcd *hcd)
> >  {
> > @@ -131,8 +102,8 @@ static int ehci_pci_setup(struct usb_hcd *hcd)
> >  	/* cache this readonly data; minimize chip reads */
> >  	ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
> >
> > -	if (ehci_quirk_amd_SB800(ehci))
> > -		ehci->amd_l1_fix = 1;
> > +	if (amd_find_chipset_info())
> > +		ehci->amd_pll_fix = 1;
> >
> >  	retval = ehci_halt(ehci);
> >  	if (retval)
> 
> ...
> 
> > diff --git a/drivers/usb/host/ohci-pci.c
b/drivers/usb/host/ohci-pci.c
> > index 36ee9a6..4b7efbb 100644
> > --- a/drivers/usb/host/ohci-pci.c
> > +++ b/drivers/usb/host/ohci-pci.c
> 
> ...
> 
> > @@ -168,11 +150,14 @@ static int ohci_quirk_nec(struct usb_hcd *hcd)
> >  static int ohci_quirk_amd700(struct usb_hcd *hcd)
> >  {
> >  	struct ohci_hcd *ohci = hcd_to_ohci(hcd);
> > +	struct pci_dev *amd_smbus_dev;
> >  	u8 rev = 0;
> >
> > -	if (!amd_smbus_dev)
> > -		amd_smbus_dev = pci_get_device(PCI_VENDOR_ID_ATI,
> > -				PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL);
> > +	if (amd_find_chipset_info())
> > +		ohci->flags |= OHCI_QUIRK_AMD_PLL;
> > +
> > +	amd_smbus_dev = pci_get_device(PCI_VENDOR_ID_ATI,
> > +			PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL);
> >  	if (!amd_smbus_dev)
> >  		return 0;
> 
> ...
> 
> > diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-
> quirks.c
> > index 4c502c8..0272dc1 100644
> > --- a/drivers/usb/host/pci-quirks.c
> > +++ b/drivers/usb/host/pci-quirks.c
> > @@ -53,6 +53,229 @@
> >  #define EHCI_USBLEGCTLSTS_SOOE	(1 << 13)	/* SMI on
ownership change
> */
> >
> >
> > +/* AMD quirk use */
> > +#define	AB_REG_BAR_LOW		0xe0
> > +#define	AB_REG_BAR_HIGH		0xe1
> > +#define	AB_REG_BAR_SB700	0xf0
> > +#define	AB_INDX(addr)		((addr) + 0x00)
> > +#define	AB_DATA(addr)		((addr) + 0x04)
> > +#define	AX_INDXC		0x30
> > +#define	AX_DATAC		0x34
> > +
> > +#define	NB_PCIE_INDX_ADDR	0xe0
> > +#define	NB_PCIE_INDX_DATA	0xe4
> > +#define	PCIE_P_CNTL		0x10040
> > +#define	BIF_NB			0x10002
> > +#define	NB_PIF0_PWRDOWN_0	0x01100012
> > +#define	NB_PIF0_PWRDOWN_1	0x01100013
> > +
> > +static struct pci_dev	*amd_nb_dev;
> > +static struct pci_dev	*amd_smbus_dev;
> > +static int amd_nb_type;
> > +static int amd_sb_type;
> > +static int probe_count;
> > +static DEFINE_SPINLOCK(amd_lock);
> > +
> > +int amd_find_chipset_info(void)
> > +{
> > +	u8 rev = 0;
> > +	static int ret;
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&amd_lock, flags);
> > +
> > +	probe_count++;
> > +	/* probe only once */
> > +	if (probe_count > 1) {
> > +		spin_unlock_irqrestore(&amd_lock, flags);
> > +		return ret;
> > +	}
> > +
> > +	spin_unlock_irqrestore(&amd_lock, flags);
> > +
> > +	amd_smbus_dev = pci_get_device(PCI_VENDOR_ID_ATI, 0x4385, NULL);
> 
> So you're making the PCI core scan the bus for an ATI chipset?  Won't
> that take a lot of time at boot?  Will the scan happen if the driver
is
> loaded on a system without ATI or AMD chipsets?
> 
> Is there a way you can avoid the scan on non-AMD systems, perhaps by
> looking at the PCI device you're probing and seeing that it's not an
ATI
> or AMD USB host controller?
> 

Sure. I'll add the check of pdev->vendor and scan the chipset only on
AMD/ATI platforms. 

> > +	if (amd_smbus_dev) {
> > +		pci_read_config_byte(amd_smbus_dev, PCI_REVISION_ID,
&rev);
> > +		if (rev >= 0x40)
> > +			amd_sb_type = 1;
> > +		else if (rev >= 0x30 && rev <= 0x3b)
> > +			amd_sb_type = 3;
> > +	} else {
> > +		amd_smbus_dev = pci_get_device(PCI_VENDOR_ID_AMD,
0x780b,
> NULL);
> > +		if (!amd_smbus_dev)
> > +			return 0;
> > +		pci_read_config_byte(amd_smbus_dev, PCI_REVISION_ID,
&rev);
> > +		if (rev >= 0x11 && rev <= 0x18)
> > +			amd_sb_type = 2;
> > +	}
> > +
> > +	if (amd_sb_type == 0) {
> > +		if (amd_smbus_dev) {
> > +			pci_dev_put(amd_smbus_dev);
> > +			amd_smbus_dev = NULL;
> > +		}
> > +		return 0;
> > +	}
> > +
> > +	amd_nb_dev = pci_get_device(PCI_VENDOR_ID_AMD, 0x9601, NULL);
> > +	if (amd_nb_dev) {
> > +		amd_nb_type = 1;
> > +	} else {
> > +		amd_nb_dev = pci_get_device(PCI_VENDOR_ID_AMD, 0x1510,
NULL);
> > +		if (amd_nb_dev) {
> > +			amd_nb_type = 2;
> > +		} else  {
> > +			amd_nb_dev = pci_get_device(PCI_VENDOR_ID_AMD,
> > +						0x9600, NULL);
> > +			if (amd_nb_dev)
> > +				amd_nb_type = 3;
> > +		}
> > +	}
> > +
> > +	ret = 1;
> > +	printk(KERN_DEBUG "QUIRK: Enable AMD PLL fix\n");
> > +
> > +	return ret;
> 
> Why not just say "return 1;" here?
> 

ret is a static variable. The logic of this function is:

The first caller probes the whole chipset info, and set ret depends on
the probe result; The following callers just increase probe_count and
return ret, save the cost to probe again. 

So cannot just return 1 here. I'll modify the function that the first
caller should hold the spinlock until it sets the ret value.

> > +}
> > +EXPORT_SYMBOL_GPL(amd_find_chipset_info);
> > +
> > +/*
> > + * The hardware normally enables the A-link power management
feature,
> which
> > + * lets the system lower the power consumption in idle states.
> > + *
> > + * Without this quirk, isochronous stream on OHCI/EHCI/xHCI
controllers
> of
> > + * some AMD platforms may stutter or have breaks occasionally.
> > + *
> > + * This USB quirk prevents the link going into that lower power
state
> > + * during isochronous transfers.
> > + */
> 
> Can you swap the last two sentences?  It makes more sense that way.
> 

Sure, will do.

> > +void amd_quirk_pll(int disable)
> > +{
> > +	u32 addr, addr_low, addr_high, val;
> > +	u32 bit = disable ? 0 : 1;
> > +	static int isoc_reqs;
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&amd_lock, flags);
> > +
> > +	if (disable) {
> > +		isoc_reqs++;
> > +		if (isoc_reqs > 1) {
> > +			spin_unlock_irqrestore(&amd_lock, flags);
> > +			return;
> > +		}
> > +	} else {
> > +		isoc_reqs--;
> > +		if (isoc_reqs > 0) {
> > +			spin_unlock_irqrestore(&amd_lock, flags);
> > +			return;
> > +		}
> > +	}
> > +
> > +	spin_unlock_irqrestore(&amd_lock, flags);
> > +
> > +	if (amd_sb_type == 1 || amd_sb_type == 2) {
> > +		outb_p(AB_REG_BAR_LOW, 0xcd6);
> > +		addr_low = inb_p(0xcd7);
> > +		outb_p(AB_REG_BAR_HIGH, 0xcd6);
> > +		addr_high = inb_p(0xcd7);
> > +		addr = addr_high << 8 | addr_low;
> > +
> > +		outl_p(0x30, AB_INDX(addr));
> > +		outl_p(0x40, AB_DATA(addr));
> > +		outl_p(0x34, AB_INDX(addr));
> > +		val = inl_p(AB_DATA(addr));
> > +	} else if (amd_sb_type == 3) {
> > +		pci_read_config_dword(amd_smbus_dev, AB_REG_BAR_SB700,
&addr);
> > +		outl(AX_INDXC, AB_INDX(addr));
> > +		outl(0x40, AB_DATA(addr));
> > +		outl(AX_DATAC, AB_INDX(addr));
> > +		val = inl(AB_DATA(addr));
> > +	} else
> > +		return;
> > +
> > +	if (disable) {
> > +		val &= ~0x08;
> > +		val |= (1 << 4) | (1 << 9);
> > +	} else {
> > +		val |= 0x08;
> > +		val &= ~((1 << 4) | (1 << 9));
> > +	}
> > +	outl_p(val, AB_DATA(addr));
> > +
> > +	if (!amd_nb_dev)
> > +		return;
> > +
> > +	if (amd_nb_type == 1 || amd_nb_type == 3) {
> > +		addr = PCIE_P_CNTL;
> > +		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_ADDR,
addr);
> > +		pci_read_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA,
&val);
> > +
> > +		val &= ~(1 | (1 << 3) | (1 << 4) | (1 << 9) | (1 <<
12));
> > +		val |= bit | (bit << 3) | (bit << 12);
> > +		val |= ((!bit) << 4) | ((!bit) << 9);
> > +		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA,
val);
> > +
> > +		addr = BIF_NB;
> > +		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_ADDR,
addr);
> > +
> > +		pci_read_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA,
&val);
> > +		val &= ~(1 << 8);
> > +		val |= bit << 8;
> > +		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA,
val);
> > +	} else if (amd_nb_type == 2) {
> > +		addr = NB_PIF0_PWRDOWN_0;
> > +		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_ADDR,
addr);
> > +		pci_read_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA,
&val);
> > +		if (disable)
> > +			val &= ~(0x3f << 7);
> > +		else
> > +			val |= 0x3f << 7;
> > +
> > +		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA,
val);
> > +
> > +		addr = NB_PIF0_PWRDOWN_1;
> > +		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_ADDR,
addr);
> > +		pci_read_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA,
&val);
> > +		if (disable)
> > +			val &= ~(0x3f << 7);
> > +		else
> > +			val |= 0x3f << 7;
> > +
> > +		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA,
val);
> > +	}
> > +
> > +	return;
> > +}
> > +EXPORT_SYMBOL_GPL(amd_quirk_pll);
> 
> I really can't comment on the AMD-specific code without documentation
> for the chipsets.  You should send this patch for review by the folks
> that did the original AMD PLL quirks, as Alan suggested.
> 
> > +
> > +void amd_nb_dev_put(void)
> > +{
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&amd_lock, flags);
> > +
> > +	probe_count--;
> > +	if (probe_count > 0) {
> > +		spin_unlock_irqrestore(&amd_lock, flags);
> > +		return;
> > +	}
> > +
> > +	spin_unlock_irqrestore(&amd_lock, flags);
> 
> Should probe_count just be an atomic variable?  I think it would be
less
> expensive to have the CPU uses atomic increment and decrement
> instructions than manage a lock (potentially across multiple cores)
and
> modify a normal integer.
> 

I've thought about using atomic variables. But amd_find_chipset_info()
needs a spinlock to protect the probe process. And atomic_inc_and_test()
just compares the variable to 0. I'm not sure whether clauses like "if
(atomic_inc_return(v) > 1)" spoils the atomic attributes.

Thanks,
Andiry

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