kern/82243: [patch] csa sound driver suspend/resume on Thinkpad
Serge Semenenko
serge at a1.com.ua
Tue Jun 14 20:30:21 GMT 2005
>Number: 82243
>Category: kern
>Synopsis: [patch] csa sound driver suspend/resume on Thinkpad
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: freebsd-bugs
>State: open
>Quarter:
>Keywords:
>Date-Required:
>Class: change-request
>Submitter-Id: current-users
>Arrival-Date: Tue Jun 14 20:30:20 GMT 2005
>Closed-Date:
>Last-Modified:
>Originator: Serge Semenenko
>Release: FreeBSD 5.4-RELEASE i386
>Organization:
>Environment:
System: FreeBSD serge.a1.lan 5.4-RELEASE FreeBSD 5.4-RELEASE #0: Sun May 29 01:14:20 EEST 2005 root at serge.a1.lan:/usr/obj/usr/src/sys/SERGE i386
>Description:
Sound won't work after suspend/resume on Thinkpad T21 (T20,T22 etc.)
>How-To-Repeat:
Suspend/resume the system...
>Fix:
apply the patch. It's a port of linux cs46xx driver.
--- csa_patch begins here ---
--- csa.c.orig Sat May 14 23:31:10 2005
+++ csa.c Sat May 14 23:32:08 2005
@@ -87,7 +87,6 @@
struct resource *irq, void *cookie);
static driver_intr_t csa_intr;
static int csa_initialize(sc_p scp);
-static void csa_resetdsp(csa_res *resp);
static int csa_downloadimage(csa_res *resp);
static devclass_t csa_devclass;
@@ -365,15 +364,24 @@
static int
csa_resume(device_t dev)
{
-#if 0
- /*
- * XXX: this cannot possibly work
- * needs to be properly implemented
- */
- csa_detach(dev);
- csa_attach(dev);
-#endif
- return 0;
+ csa_res *resp;
+ sc_p scp;
+
+ scp = device_get_softc(dev);
+ resp = &scp->res;
+
+ /* Initialize the chip. */
+ if (csa_initialize(scp))
+ return (ENXIO);
+
+ /* Reset the Processor. */
+ csa_resetdsp(resp);
+
+ /* Download the Processor Image to the processor. */
+ if (csa_downloadimage(resp))
+ return (ENXIO);
+
+ return (bus_generic_resume(dev));
}
static struct resource *
@@ -793,7 +801,7 @@
csa_writeio(resp, BA0_CLKCR1, clkcr1);
}
-static void
+void
csa_resetdsp(csa_res *resp)
{
int i;
--- csapcm.c.orig Sat May 14 23:31:22 2005
+++ csapcm.c Sat May 14 23:32:17 2005
@@ -45,6 +45,23 @@
#define GOF_PER_SEC 200
+#define CS461x_AC97_HIGHESTREGTORESTORE 0x26
+#define CS461x_AC97_NUMBER_RESTORE_REGS (CS461x_AC97_HIGHESTREGTORESTORE/2-1)
+
+#define CS_POWER_DAC 0x0001
+#define CS_POWER_ADC 0x0002
+#define CS_POWER_MIXVON 0x0004
+#define CS_POWER_MIXVOFF 0x0008
+#define CS_AC97_POWER_CONTROL_ON 0xf000 /* always on bits (inverted) */
+#define CS_AC97_POWER_CONTROL_ADC 0x0100
+#define CS_AC97_POWER_CONTROL_DAC 0x0200
+#define CS_AC97_POWER_CONTROL_MIXVON 0x0400
+#define CS_AC97_POWER_CONTROL_MIXVOFF 0x0800
+#define CS_AC97_POWER_CONTROL_ADC_ON 0x0001
+#define CS_AC97_POWER_CONTROL_DAC_ON 0x0002
+#define CS_AC97_POWER_CONTROL_MIXVON_ON 0x0004
+#define CS_AC97_POWER_CONTROL_MIXVOFF_ON 0x0008
+
/* device private data */
struct csa_info;
@@ -70,6 +87,9 @@
u_long pctl;
u_long cctl;
struct csa_chinfo pch, rch;
+ u_int32_t ac97[CS461x_AC97_NUMBER_RESTORE_REGS];
+ u_int32_t ac97_powerdown;
+ u_int32_t ac97_general_purpose;
};
/* -------------------------------------------------------------------- */
@@ -84,8 +104,11 @@
static void csa_stopplaydma(struct csa_info *csa);
static void csa_stopcapturedma(struct csa_info *csa);
static int csa_startdsp(csa_res *resp);
+static int csa_stopdsp(csa_res *resp);
static int csa_allocres(struct csa_info *scp, device_t dev);
static void csa_releaseres(struct csa_info *scp, device_t dev);
+static void csa_ac97_suspend(struct csa_info *csa);
+static void csa_ac97_resume(struct csa_info *csa);
static u_int32_t csa_playfmt[] = {
AFMT_U8,
@@ -112,16 +135,11 @@
static int
csa_active(struct csa_info *csa, int run)
{
- int old, go;
-
- old = csa->active;
+ int old = csa->active;
csa->active += run;
-
- if ((csa->active == 0 && old == 1) || (csa->active == 1 && old == 0)) {
- go = csa->active;
- if (csa->card->active)
- return csa->card->active(go);
- }
+ if ((csa->active>1)||(csa->active<-1)) csa->active=0;
+ if (csa->card->active)
+ return (csa->card->active(!(csa->active && old)));
return 0;
}
@@ -455,6 +473,18 @@
}
static int
+csa_stopdsp(csa_res *resp)
+{
+ /*
+ * Turn off the run, run at frame, and DMA enable bits in the local copy of
+ * the SP control register.
+ */
+ csa_writemem(resp, BA1_SPCR, 0);
+
+ return (0);
+}
+
+static int
csa_setupchan(struct csa_chinfo *ch)
{
struct csa_info *csa = ch->parent;
@@ -833,11 +863,156 @@
return 0;
}
+static void
+csa_ac97_suspend(struct csa_info *csa)
+{
+ int Count, i;
+ u_int32_t tmp;
+
+ for(Count = 0x2, i=0; (Count <= CS461x_AC97_HIGHESTREGTORESTORE) && (i < CS461x_AC97_NUMBER_RESTORE_REGS); Count += 2, i++)
+ {
+ csa_readcodec(&csa->res, BA0_AC97_RESET + Count, &csa->ac97[i]);
+ }
+ /* mute the outputs */
+ csa_writecodec(&csa->res, BA0_AC97_MASTER_VOLUME, 0x8000);
+ csa_writecodec(&csa->res, BA0_AC97_HEADPHONE_VOLUME, 0x8000);
+ csa_writecodec(&csa->res, BA0_AC97_MASTER_VOLUME_MONO, 0x8000);
+ csa_writecodec(&csa->res, BA0_AC97_PCM_OUT_VOLUME, 0x8000);
+ /* save the registers that cause pops */
+ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &csa->ac97_powerdown);
+ csa_readcodec(&csa->res, BA0_AC97_GENERAL_PURPOSE, &csa->ac97_general_purpose);
+ /*
+ * And power down everything on the AC97 codec.
+ * well, for now, only power down the DAC/ADC and MIXER VREFON components.
+ * trouble with removing VREF.
+ */
+ /* MIXVON*/
+ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp);
+ csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_MIXVON);
+ /* ADC */
+ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp);
+ csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_ADC);
+ /* DAC */
+ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp);
+ csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_DAC);
+}
+
+static void
+csa_ac97_resume(struct csa_info *csa)
+{
+ int Count, i;
+
+ /*
+ * First, we restore the state of the general purpose register. This
+ * contains the mic select (mic1 or mic2) and if we restore this after
+ * we restore the mic volume/boost state and mic2 was selected at
+ * suspend time, we will end up with a brief period of time where mic1
+ * is selected with the volume/boost settings for mic2, causing
+ * acoustic feedback. So we restore the general purpose register
+ * first, thereby getting the correct mic selected before we restore
+ * the mic volume/boost.
+ */
+ csa_writecodec(&csa->res, BA0_AC97_GENERAL_PURPOSE, csa->ac97_general_purpose);
+ /*
+ * Now, while the outputs are still muted, restore the state of power
+ * on the AC97 part.
+ */
+ csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, csa->ac97_powerdown);
+ /*
+ * Restore just the first set of registers, from register number
+ * 0x02 to the register number that ulHighestRegToRestore specifies.
+ */
+ for(Count = 0x2, i=0; (Count <= CS461x_AC97_HIGHESTREGTORESTORE) && (i < CS461x_AC97_NUMBER_RESTORE_REGS); Count += 2, i++)
+ {
+ csa_writecodec(&csa->res, BA0_AC97_RESET + Count, csa->ac97[i]);
+ }
+
+}
+
+static int
+pcmcsa_suspend(device_t dev)
+{
+ struct csa_info *csa;
+ csa_res *resp;
+
+ csa = pcm_getdevinfo(dev);
+ resp = &csa->res;
+
+ csa_active(csa, 1);
+
+ /* playback interrupt disable */
+ csa_writemem(resp, BA1_PFIE, (csa_readmem(resp, BA1_PFIE) & ~0x0000f03f) | 0x00000010);
+ /* capture interrupt disable */
+ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000011);
+ csa_stopplaydma(csa);
+ csa_stopcapturedma(csa);
+
+ csa_ac97_suspend(csa);
+
+ csa_resetdsp(resp);
+
+ csa_stopdsp(resp);
+ /*
+ * Power down the DAC and ADC. For now leave the other areas on.
+ */
+ csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, 0x300);
+ /*
+ * Power down the PLL.
+ */
+ csa_writemem(resp, BA0_CLKCR1, 0);
+ /*
+ * Turn off the Processor by turning off the software clock enable flag in
+ * the clock control register.
+ */
+ csa_writemem(resp, BA0_CLKCR1, csa_readmem(resp, BA0_CLKCR1) & ~CLKCR1_SWCE);
+
+ csa_active(csa, -1);
+
+ return 0;
+}
+
+static int
+pcmcsa_resume(device_t dev)
+{
+ struct csa_info *csa;
+ csa_res *resp;
+
+ csa = pcm_getdevinfo(dev);
+ resp = &csa->res;
+
+ csa_active(csa, 1);
+
+ /* cs_hardware_init */
+ csa_stopplaydma(csa);
+ csa_stopcapturedma(csa);
+ csa_ac97_resume(csa);
+ if (csa_startdsp(resp))
+ return (ENXIO);
+ /* Enable interrupts on the part. */
+ if ((csa_readio(resp, BA0_HISR) & HISR_INTENA) == 0)
+ csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM);
+ /* playback interrupt enable */
+ csa_writemem(resp, BA1_PFIE, csa_readmem(resp, BA1_PFIE) & ~0x0000f03f);
+ /* capture interrupt enable */
+ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001);
+ /* cs_restart_part */
+ csa_setupchan(&csa->pch);
+ csa_startplaydma(csa);
+ csa_setupchan(&csa->rch);
+ csa_startcapturedma(csa);
+
+ csa_active(csa, -1);
+
+ return 0;
+}
+
static device_method_t pcmcsa_methods[] = {
/* Device interface */
DEVMETHOD(device_probe , pcmcsa_probe ),
DEVMETHOD(device_attach, pcmcsa_attach),
DEVMETHOD(device_detach, pcmcsa_detach),
+ DEVMETHOD(device_suspend, pcmcsa_suspend),
+ DEVMETHOD(device_resume, pcmcsa_resume),
{ 0, 0 },
};
--- csavar.h.orig Sat May 14 23:31:44 2005
+++ csavar.h Sat May 14 23:32:29 2005
@@ -66,4 +66,5 @@
u_int32_t csa_readmem(csa_res *resp, u_long offset);
void csa_writemem(csa_res *resp, u_long offset, u_int32_t data);
+void csa_resetdsp(csa_res *resp);
#endif /* _CSA_VAR_H */
--- csa_patch ends here ---
>Release-Note:
>Audit-Trail:
>Unformatted:
More information about the freebsd-bugs
mailing list