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