Skip to content

Commit ab1666c

Browse files
Libin Yanggregkh
authored andcommitted
USB: quirk PLL power down mode
On some AMD 700 series southbridges, ISO OUT transfers (such as audio playback through speakers) on the USB OHCI controller may be corrupted when an A-Link express power saving feature is active. PLL power down mode in conjunction with link power management feature L1 being enabled is the bad combination ... this patch prevents them from being enabled when ISO transfers are pending. Signed-off-by: Crane Cai <crane.cai@amd.com> Signed-off-by: Libin Yang <libin.yang@amd.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
1 parent e12cc34 commit ab1666c

File tree

4 files changed

+164
-0
lines changed

4 files changed

+164
-0
lines changed

drivers/usb/host/ohci-hcd.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,21 @@ static void ohci_stop (struct usb_hcd *hcd);
8686
static int ohci_restart (struct ohci_hcd *ohci);
8787
#endif
8888

89+
#ifdef CONFIG_PCI
90+
static void quirk_amd_pll(int state);
91+
static void amd_iso_dev_put(void);
92+
#else
93+
static inline void quirk_amd_pll(int state)
94+
{
95+
return;
96+
}
97+
static inline void amd_iso_dev_put(void)
98+
{
99+
return;
100+
}
101+
#endif
102+
103+
89104
#include "ohci-hub.c"
90105
#include "ohci-dbg.c"
91106
#include "ohci-mem.c"
@@ -886,6 +901,8 @@ static void ohci_stop (struct usb_hcd *hcd)
886901

887902
if (quirk_zfmicro(ohci))
888903
del_timer(&ohci->unlink_watchdog);
904+
if (quirk_amdiso(ohci))
905+
amd_iso_dev_put();
889906

890907
remove_debug_files (ohci);
891908
ohci_mem_cleanup (ohci);

drivers/usb/host/ohci-pci.c

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,28 @@
1818
#error "This file is PCI bus glue. CONFIG_PCI must be defined."
1919
#endif
2020

21+
#include <linux/pci.h>
22+
#include <linux/io.h>
23+
24+
25+
/* constants used to work around PM-related transfer
26+
* glitches in some AMD 700 series southbridges
27+
*/
28+
#define AB_REG_BAR 0xf0
29+
#define AB_INDX(addr) ((addr) + 0x00)
30+
#define AB_DATA(addr) ((addr) + 0x04)
31+
#define AX_INDXC 0X30
32+
#define AX_DATAC 0x34
33+
34+
#define NB_PCIE_INDX_ADDR 0xe0
35+
#define NB_PCIE_INDX_DATA 0xe4
36+
#define PCIE_P_CNTL 0x10040
37+
#define BIF_NB 0x10002
38+
39+
static struct pci_dev *amd_smbus_dev;
40+
static struct pci_dev *amd_hb_dev;
41+
static int amd_ohci_iso_count;
42+
2143
/*-------------------------------------------------------------------------*/
2244

2345
static int broken_suspend(struct usb_hcd *hcd)
@@ -143,6 +165,103 @@ static int ohci_quirk_nec(struct usb_hcd *hcd)
143165
return 0;
144166
}
145167

168+
static int ohci_quirk_amd700(struct usb_hcd *hcd)
169+
{
170+
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
171+
u8 rev = 0;
172+
173+
if (!amd_smbus_dev)
174+
amd_smbus_dev = pci_get_device(PCI_VENDOR_ID_ATI,
175+
PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL);
176+
if (!amd_smbus_dev)
177+
return 0;
178+
179+
pci_read_config_byte(amd_smbus_dev, PCI_REVISION_ID, &rev);
180+
if ((rev > 0x3b) || (rev < 0x30)) {
181+
pci_dev_put(amd_smbus_dev);
182+
amd_smbus_dev = NULL;
183+
return 0;
184+
}
185+
186+
amd_ohci_iso_count++;
187+
188+
if (!amd_hb_dev)
189+
amd_hb_dev = pci_get_device(PCI_VENDOR_ID_AMD, 0x9600, NULL);
190+
191+
ohci->flags |= OHCI_QUIRK_AMD_ISO;
192+
ohci_dbg(ohci, "enabled AMD ISO transfers quirk\n");
193+
194+
return 0;
195+
}
196+
197+
/*
198+
* The hardware normally enables the A-link power management feature, which
199+
* lets the system lower the power consumption in idle states.
200+
*
201+
* Assume the system is configured to have USB 1.1 ISO transfers going
202+
* to or from a USB device. Without this quirk, that stream may stutter
203+
* or have breaks occasionally. For transfers going to speakers, this
204+
* makes a very audible mess...
205+
*
206+
* That audio playback corruption is due to the audio stream getting
207+
* interrupted occasionally when the link goes in lower power state
208+
* This USB quirk prevents the link going into that lower power state
209+
* during audio playback or other ISO operations.
210+
*/
211+
static void quirk_amd_pll(int on)
212+
{
213+
u32 addr;
214+
u32 val;
215+
u32 bit = (on > 0) ? 1 : 0;
216+
217+
pci_read_config_dword(amd_smbus_dev, AB_REG_BAR, &addr);
218+
219+
/* BIT names/meanings are NDA-protected, sorry ... */
220+
221+
outl(AX_INDXC, AB_INDX(addr));
222+
outl(0x40, AB_DATA(addr));
223+
outl(AX_DATAC, AB_INDX(addr));
224+
val = inl(AB_DATA(addr));
225+
val &= ~((1 << 3) | (1 << 4) | (1 << 9));
226+
val |= (bit << 3) | ((!bit) << 4) | ((!bit) << 9);
227+
outl(val, AB_DATA(addr));
228+
229+
if (amd_hb_dev) {
230+
addr = PCIE_P_CNTL;
231+
pci_write_config_dword(amd_hb_dev, NB_PCIE_INDX_ADDR, addr);
232+
233+
pci_read_config_dword(amd_hb_dev, NB_PCIE_INDX_DATA, &val);
234+
val &= ~(1 | (1 << 3) | (1 << 4) | (1 << 9) | (1 << 12));
235+
val |= bit | (bit << 3) | (bit << 12);
236+
val |= ((!bit) << 4) | ((!bit) << 9);
237+
pci_write_config_dword(amd_hb_dev, NB_PCIE_INDX_DATA, val);
238+
239+
addr = BIF_NB;
240+
pci_write_config_dword(amd_hb_dev, NB_PCIE_INDX_ADDR, addr);
241+
242+
pci_read_config_dword(amd_hb_dev, NB_PCIE_INDX_DATA, &val);
243+
val &= ~(1 << 8);
244+
val |= bit << 8;
245+
pci_write_config_dword(amd_hb_dev, NB_PCIE_INDX_DATA, val);
246+
}
247+
}
248+
249+
static void amd_iso_dev_put(void)
250+
{
251+
amd_ohci_iso_count--;
252+
if (amd_ohci_iso_count == 0) {
253+
if (amd_smbus_dev) {
254+
pci_dev_put(amd_smbus_dev);
255+
amd_smbus_dev = NULL;
256+
}
257+
if (amd_hb_dev) {
258+
pci_dev_put(amd_hb_dev);
259+
amd_hb_dev = NULL;
260+
}
261+
}
262+
263+
}
264+
146265
/* List of quirks for OHCI */
147266
static const struct pci_device_id ohci_pci_quirks[] = {
148267
{
@@ -181,6 +300,19 @@ static const struct pci_device_id ohci_pci_quirks[] = {
181300
PCI_DEVICE(PCI_VENDOR_ID_ITE, 0x8152),
182301
.driver_data = (unsigned long) broken_suspend,
183302
},
303+
{
304+
PCI_DEVICE(PCI_VENDOR_ID_ATI, 0x4397),
305+
.driver_data = (unsigned long)ohci_quirk_amd700,
306+
},
307+
{
308+
PCI_DEVICE(PCI_VENDOR_ID_ATI, 0x4398),
309+
.driver_data = (unsigned long)ohci_quirk_amd700,
310+
},
311+
{
312+
PCI_DEVICE(PCI_VENDOR_ID_ATI, 0x4399),
313+
.driver_data = (unsigned long)ohci_quirk_amd700,
314+
},
315+
184316
/* FIXME for some of the early AMD 760 southbridges, OHCI
185317
* won't work at all. blacklist them.
186318
*/

drivers/usb/host/ohci-q.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ __acquires(ohci->lock)
4949
switch (usb_pipetype (urb->pipe)) {
5050
case PIPE_ISOCHRONOUS:
5151
ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs--;
52+
if (ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0
53+
&& quirk_amdiso(ohci))
54+
quirk_amd_pll(1);
5255
break;
5356
case PIPE_INTERRUPT:
5457
ohci_to_hcd(ohci)->self.bandwidth_int_reqs--;
@@ -677,6 +680,9 @@ static void td_submit_urb (
677680
data + urb->iso_frame_desc [cnt].offset,
678681
urb->iso_frame_desc [cnt].length, urb, cnt);
679682
}
683+
if (ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0
684+
&& quirk_amdiso(ohci))
685+
quirk_amd_pll(0);
680686
periodic = ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs++ == 0
681687
&& ohci_to_hcd(ohci)->self.bandwidth_int_reqs == 0;
682688
break;

drivers/usb/host/ohci.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ struct ohci_hcd {
401401
#define OHCI_QUIRK_NEC 0x40 /* lost interrupts */
402402
#define OHCI_QUIRK_FRAME_NO 0x80 /* no big endian frame_no shift */
403403
#define OHCI_QUIRK_HUB_POWER 0x100 /* distrust firmware power/oc setup */
404+
#define OHCI_QUIRK_AMD_ISO 0x200 /* ISO transfers*/
404405
// there are also chip quirks/bugs in init logic
405406

406407
struct work_struct nec_work; /* Worker for NEC quirk */
@@ -428,6 +429,10 @@ static inline int quirk_zfmicro(struct ohci_hcd *ohci)
428429
{
429430
return ohci->flags & OHCI_QUIRK_ZFMICRO;
430431
}
432+
static inline int quirk_amdiso(struct ohci_hcd *ohci)
433+
{
434+
return ohci->flags & OHCI_QUIRK_AMD_ISO;
435+
}
431436
#else
432437
static inline int quirk_nec(struct ohci_hcd *ohci)
433438
{
@@ -437,6 +442,10 @@ static inline int quirk_zfmicro(struct ohci_hcd *ohci)
437442
{
438443
return 0;
439444
}
445+
static inline int quirk_amdiso(struct ohci_hcd *ohci)
446+
{
447+
return 0;
448+
}
440449
#endif
441450

442451
/* convert between an hcd pointer and the corresponding ohci_hcd */

0 commit comments

Comments
 (0)