patch-2.4.14 linux/drivers/sound/i810_audio.c

Next file: linux/drivers/sound/ite8172.c
Previous file: linux/drivers/sound/emu10k1/hwaccess.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.13/linux/drivers/sound/i810_audio.c linux/drivers/sound/i810_audio.c
@@ -247,6 +247,12 @@
 
 MODULE_DEVICE_TABLE (pci, i810_pci_tbl);
 
+#ifdef CONFIG_PM
+#define PM_SUSPENDED(card) (card->pm_suspended)
+#else
+#define PM_SUSPENDED(card) (0)
+#endif
+
 /* "software" or virtual channel, an instance of opened /dev/dsp */
 struct i810_state {
 	unsigned int magic;
@@ -262,6 +268,9 @@
 	/* virtual channel number */
 	int virt;
 
+#ifdef CONFIG_PM
+	unsigned int pm_saved_dac_rate,pm_saved_adc_rate;
+#endif
 	struct dmabuf {
 		/* wave sample stuff */
 		unsigned int rate;
@@ -322,7 +331,11 @@
 	/* PCI device stuff */
 	struct pci_dev * pci_dev;
 	u16 pci_id;
-
+#ifdef CONFIG_PM	
+	u16 pm_suspended;
+	u32 pm_save_state[64/sizeof(u32)];
+	int pm_saved_mixer_settings[SOUND_MIXER_NRDEVICES][NR_AC97];
+#endif
 	/* soundcore stuff */
 	int dev_audio;
 
@@ -451,7 +464,7 @@
 
 	if(!(state->card->ac97_features & 4)) {
 #ifdef DEBUG
-		printk(KERN_WARNING "i810_audio: S/PDIF transmitter not avalible.\n");
+		printk(KERN_WARNING "i810_audio: S/PDIF transmitter not available.\n");
 #endif
 		state->card->ac97_status &= ~SPDIF_ON;
 	} else {
@@ -572,6 +585,10 @@
 	if(!(state->card->ac97_features&0x0001))
 	{
 		dmabuf->rate = clocking;
+#ifdef DEBUG
+		printk("Asked for %d Hz, but ac97_features says we only do %dHz.  Sorry!\n",
+		       rate,clocking);
+#endif		       
 		return clocking;
 	}
 			
@@ -594,11 +611,11 @@
 
 	if(new_rate != rate) {
 		dmabuf->rate = (new_rate * 48000)/clocking;
-		rate = new_rate;
 	}
 #ifdef DEBUG
-	printk("i810_audio: called i810_set_dac_rate : rate = %d/%d\n", dmabuf->rate, rate);
+	printk("i810_audio: called i810_set_dac_rate : asked for %d, got %d\n", rate, dmabuf->rate);
 #endif
+	rate = new_rate;
 	return dmabuf->rate;
 }
 
@@ -1066,7 +1083,7 @@
 	for (;;) {
 		/* It seems that we have to set the current state to TASK_INTERRUPTIBLE
 		   every time to make the process really go to sleep */
-		current->state = TASK_INTERRUPTIBLE;
+		set_current_state(TASK_INTERRUPTIBLE);
 
 		spin_lock_irqsave(&state->card->lock, flags);
 		i810_update_ptr(state);
@@ -1085,7 +1102,7 @@
 
 		if (nonblock) {
 			remove_wait_queue(&dmabuf->wait, &wait);
-			current->state = TASK_RUNNING;
+			set_current_state(TASK_RUNNING);
 			return -EBUSY;
 		}
 
@@ -1099,7 +1116,7 @@
 	stop_dac(state);
 	synchronize_irq();
 	remove_wait_queue(&dmabuf->wait, &wait);
-	current->state = TASK_RUNNING;
+	set_current_state(TASK_RUNNING);
 	if (signal_pending(current))
 		return -ERESTARTSYS;
 
@@ -1201,16 +1218,20 @@
 	spin_unlock(&card->lock);
 }
 
-/* in this loop, dmabuf.count signifies the amount of data that is waiting to be copied to
-   the user's buffer.  it is filled by the dma machine and drained by this loop. */
+/* in this loop, dmabuf.count signifies the amount of data that is
+   waiting to be copied to the user's buffer.  It is filled by the dma
+   machine and drained by this loop. */
+
 static ssize_t i810_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
 {
 	struct i810_state *state = (struct i810_state *)file->private_data;
+	struct i810_card *card=state ? state->card : 0;
 	struct dmabuf *dmabuf = &state->dmabuf;
 	ssize_t ret;
 	unsigned long flags;
 	unsigned int swptr;
 	int cnt;
+        DECLARE_WAITQUEUE(waita, current);
 
 #ifdef DEBUG2
 	printk("i810_audio: i810_read called, count = %d\n", count);
@@ -1224,7 +1245,7 @@
 		return -ENODEV;
 	if (!dmabuf->read_channel) {
 		dmabuf->ready = 0;
-		dmabuf->read_channel = state->card->alloc_rec_pcm_channel(state->card);
+		dmabuf->read_channel = card->alloc_rec_pcm_channel(card);
 		if (!dmabuf->read_channel) {
 			return -EBUSY;
 		}
@@ -1236,8 +1257,19 @@
 	dmabuf->trigger &= ~PCM_ENABLE_OUTPUT;
 	ret = 0;
 
+        add_wait_queue(&dmabuf->wait, &waita);
 	while (count > 0) {
-		spin_lock_irqsave(&state->card->lock, flags);
+		spin_lock_irqsave(&card->lock, flags);
+                if (PM_SUSPENDED(card)) {
+                        spin_unlock_irqrestore(&card->lock, flags);
+                        set_current_state(TASK_INTERRUPTIBLE);
+                        schedule();
+                        if (signal_pending(current)) {
+                                if (!ret) ret = -EAGAIN;
+                                break;
+                        }
+                        continue;
+                }
 		swptr = dmabuf->swptr;
 		if (dmabuf->count > dmabuf->dmasize) {
 			dmabuf->count = dmabuf->dmasize;
@@ -1246,7 +1278,7 @@
 		// this is to make the copy_to_user simpler below
 		if(cnt > (dmabuf->dmasize - swptr))
 			cnt = dmabuf->dmasize - swptr;
-		spin_unlock_irqrestore(&state->card->lock, flags);
+		spin_unlock_irqrestore(&card->lock, flags);
 
 		if (cnt > count)
 			cnt = count;
@@ -1290,15 +1322,20 @@
 
 		if (copy_to_user(buffer, dmabuf->rawbuf + swptr, cnt)) {
 			if (!ret) ret = -EFAULT;
-			return ret;
+			goto done;
 		}
 
 		swptr = (swptr + cnt) % dmabuf->dmasize;
 
-		spin_lock_irqsave(&state->card->lock, flags);
+		spin_lock_irqsave(&card->lock, flags);
+
+                if (PM_SUSPENDED(card)) {
+                        spin_unlock_irqrestore(&card->lock, flags);
+                        continue;
+                }
 		dmabuf->swptr = swptr;
 		dmabuf->count -= cnt;
-		spin_unlock_irqrestore(&state->card->lock, flags);
+		spin_unlock_irqrestore(&card->lock, flags);
 
 		count -= cnt;
 		buffer += cnt;
@@ -1307,6 +1344,10 @@
 	i810_update_lvi(state,1);
 	if(!(dmabuf->enable & ADC_RUNNING))
 		start_adc(state);
+ done:
+        set_current_state(TASK_RUNNING);
+        remove_wait_queue(&dmabuf->wait, &waita);
+
 	return ret;
 }
 
@@ -1315,11 +1356,13 @@
 static ssize_t i810_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
 {
 	struct i810_state *state = (struct i810_state *)file->private_data;
+	struct i810_card *card=state ? state->card : 0;
 	struct dmabuf *dmabuf = &state->dmabuf;
 	ssize_t ret;
 	unsigned long flags;
 	unsigned int swptr = 0;
 	int cnt, x;
+        DECLARE_WAITQUEUE(waita, current);
 
 #ifdef DEBUG2
 	printk("i810_audio: i810_write called, count = %d\n", count);
@@ -1333,7 +1376,7 @@
 		return -ENODEV;
 	if (!dmabuf->write_channel) {
 		dmabuf->ready = 0;
-		dmabuf->write_channel = state->card->alloc_pcm_channel(state->card);
+		dmabuf->write_channel = card->alloc_pcm_channel(card);
 		if(!dmabuf->write_channel)
 			return -EBUSY;
 	}
@@ -1344,8 +1387,20 @@
 	dmabuf->trigger &= ~PCM_ENABLE_INPUT;
 	ret = 0;
 
+        add_wait_queue(&dmabuf->wait, &waita);
 	while (count > 0) {
 		spin_lock_irqsave(&state->card->lock, flags);
+                if (PM_SUSPENDED(card)) {
+                        spin_unlock_irqrestore(&card->lock, flags);
+                        set_current_state(TASK_INTERRUPTIBLE);
+                        schedule();
+                        if (signal_pending(current)) {
+                                if (!ret) ret = -EAGAIN;
+                                break;
+                        }
+                        continue;
+                }
+
 		swptr = dmabuf->swptr;
 		if (dmabuf->count < 0) {
 			dmabuf->count = 0;
@@ -1376,7 +1431,7 @@
 			i810_update_lvi(state,0);
 			if (file->f_flags & O_NONBLOCK) {
 				if (!ret) ret = -EAGAIN;
-				return ret;
+				goto ret;
 			}
 			/* Not strictly correct but works */
 			tmo = (dmabuf->dmasize * HZ) / (dmabuf->rate * 4);
@@ -1400,25 +1455,30 @@
 			}
 			if (signal_pending(current)) {
 				if (!ret) ret = -ERESTARTSYS;
-				return ret;
+				goto ret;
 			}
 			continue;
 		}
 		if (copy_from_user(dmabuf->rawbuf+swptr,buffer,cnt)) {
 			if (!ret) ret = -EFAULT;
-			return ret;
+			goto ret;
 		}
 
 		swptr = (swptr + cnt) % dmabuf->dmasize;
 
 		spin_lock_irqsave(&state->card->lock, flags);
+                if (PM_SUSPENDED(card)) {
+                        spin_unlock_irqrestore(&card->lock, flags);
+                        continue;
+                }
+
 		dmabuf->swptr = swptr;
 		dmabuf->count += cnt;
-		spin_unlock_irqrestore(&state->card->lock, flags);
 
 		count -= cnt;
 		buffer += cnt;
 		ret += cnt;
+		spin_unlock_irqrestore(&state->card->lock, flags);
 	}
 	if (swptr % dmabuf->fragsize) {
 		x = dmabuf->fragsize - (swptr % dmabuf->fragsize);
@@ -1427,6 +1487,9 @@
 	i810_update_lvi(state,0);
 	if (!dmabuf->enable && dmabuf->count >= dmabuf->userfragsize)
 		start_dac(state);
+ ret:
+        set_current_state(TASK_RUNNING);
+        remove_wait_queue(&dmabuf->wait, &waita);
 
 	return ret;
 }
@@ -2310,18 +2373,52 @@
 	open:		i810_open_mixdev,
 };
 
-/* AC97 codec initialisation. */
-static int __init i810_ac97_init(struct i810_card *card)
+/* AC97 codec initialisation.  These small functions exist so we don't
+   duplicate code between module init and apm resume */
+
+static inline int i810_ac97_exists(struct i810_card *card,int ac97_number)
 {
-	int num_ac97 = 0;
-	int total_channels = 0;
-	struct ac97_codec *codec;
-	u16 eid;
-	int i=0;
-	u32 reg;
+	u32 reg = inl(card->iobase + GLOB_STA);
+	return (reg & (0x100 << ac97_number));
+}
 
-	reg = inl(card->iobase + GLOB_CNT);
+static inline int i810_ac97_enable_variable_rate(struct ac97_codec *codec)
+{
+	i810_ac97_set(codec, AC97_EXTENDED_STATUS, 9);
+	i810_ac97_set(codec,AC97_EXTENDED_STATUS,
+		      i810_ac97_get(codec, AC97_EXTENDED_STATUS)|0xE800);
 	
+	return (i810_ac97_get(codec, AC97_EXTENDED_STATUS)&1);
+}
+
+
+static int i810_ac97_probe_and_powerup(struct i810_card *card,struct ac97_codec *codec)
+{
+	/* Returns 0 on failure */
+	int i;
+
+	if (ac97_probe_codec(codec) == 0) return 0;
+	
+	/* power it all up */
+	i810_ac97_set(codec, AC97_POWER_CONTROL,
+		      i810_ac97_get(codec, AC97_POWER_CONTROL) & ~0x7f00);
+	/* wait for analog ready */
+	for (i=10;
+	     i && ((i810_ac97_get(codec, AC97_POWER_CONTROL) & 0xf) != 0xf);
+	     i--)
+	{
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(HZ/20);
+	} 
+	return i;
+}
+
+/* if I knew what this did, I'd give it a better name */
+static int i810_ac97_random_init_stuff(struct i810_card *card)
+{	
+	u32 reg = inl(card->iobase + GLOB_CNT);
+	int i;
+
 	if((reg&2)==0)	/* Cold required */
 		reg|=2;
 	else
@@ -2330,13 +2427,13 @@
 	reg&=~8;	/* ACLink on */
 	outl(reg , card->iobase + GLOB_CNT);
 	
-	while(i<10)
+	for(i=0;i<10;i++)
 	{
 		if((inl(card->iobase+GLOB_CNT)&4)==0)
 			break;
-		current->state = TASK_UNINTERRUPTIBLE;
+
+		set_current_state(TASK_UNINTERRUPTIBLE);
 		schedule_timeout(HZ/20);
-		i++;
 	}
 	if(i==10)
 	{
@@ -2344,8 +2441,22 @@
 		return 0;
 	}
 
-	current->state = TASK_UNINTERRUPTIBLE;
-	schedule_timeout(HZ/5);
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	schedule_timeout(HZ/2);
+	reg = inl(card->iobase + GLOB_STA);
+	inw(card->ac97base);
+	return 1;
+}
+
+static int __init i810_ac97_init(struct i810_card *card)
+{
+	int num_ac97 = 0;
+	int total_channels = 0;
+	struct ac97_codec *codec;
+	u16 eid;
+	u32 reg;
+
+	if(!i810_ac97_random_init_stuff(card)) return 0;
 
 	/* Number of channels supported */
 	/* What about the codec?  Just because the ICH supports */
@@ -2371,10 +2482,10 @@
 		/* check the ready status before probing. So we chk */
 		/*   What do we do if it's not ready?  Wait and try */
 		/*   again, or abort?                               */
-		reg = inl(card->iobase + GLOB_STA);
-		if (!(reg & (0x100 << num_ac97))) {
+		if (!i810_ac97_exists(card,num_ac97)) {
 			if(num_ac97 == 0)
 				printk(KERN_ERR "i810_audio: Primary codec not ready.\n");
+			card->ac97_codec[num_ac97] = 0;
 			break; /* I think this works, if not ready stop */
 		}
 
@@ -2390,24 +2501,13 @@
 		codec->codec_read = i810_ac97_get;
 		codec->codec_write = i810_ac97_set;
 	
-		if (ac97_probe_codec(codec) == 0)
-			break;
-
-		/* power up everything, modify this when implementing power saving */
-		i810_ac97_set(codec, AC97_POWER_CONTROL,
-			i810_ac97_get(codec, AC97_POWER_CONTROL) & ~0x7f00);
-		/* wait for analog ready */
-	        for (i=10;
-		     i && ((i810_ac97_get(codec, AC97_POWER_CONTROL) & 0xf) != 0xf);
-		     i--)
-		{
-			current->state = TASK_UNINTERRUPTIBLE;
-			schedule_timeout(HZ/20);
+		if(!i810_ac97_probe_and_powerup(card,codec)) {
+			printk("i810_audio: timed out waiting for codec %d analog ready", num_ac97);
+			break;	/* it didn't work */
 		}
-
 		/* Store state information about S/PDIF transmitter */
 		card->ac97_status = 0;
-
+		
 		/* Don't attempt to get eid until powerup is complete */
 		eid = i810_ac97_get(codec, AC97_EXTENDED_ID);
 		
@@ -2427,16 +2527,10 @@
 			printk(KERN_WARNING "i810_audio: only 48Khz playback available.\n");
 		else
 		{
-			/* Enable variable rate mode */
-			i810_ac97_set(codec, AC97_EXTENDED_STATUS, 9);
-			i810_ac97_set(codec,AC97_EXTENDED_STATUS,
-				i810_ac97_get(codec, AC97_EXTENDED_STATUS)|0xE800);
-
-			if(!(i810_ac97_get(codec, AC97_EXTENDED_STATUS)&1))
-			{
+			if(!i810_ac97_enable_variable_rate(codec)) {
 				printk(KERN_WARNING "i810_audio: Codec refused to allow VRA, using 48Khz only.\n");
 				card->ac97_features&=~1;
-			}
+			}			
 		}
    		
 		/* Determine how many channels the codec(s) support   */
@@ -2609,6 +2703,9 @@
 	card->irq = pci_dev->irq;
 	card->next = devs;
 	card->magic = I810_CARD_MAGIC;
+#ifdef CONFIG_PM
+	card->pm_suspended=0;
+#endif
 	spin_lock_init(&card->lock);
 	devs = card;
 
@@ -2697,6 +2794,131 @@
 	kfree(card);
 }
 
+#ifdef CONFIG_PM
+static int i810_pm_suspend(struct pci_dev *dev, u32 pm_state)
+{
+        struct i810_card *card = dev->driver_data;
+        struct i810_state *state;
+	unsigned long flags;
+	struct dmabuf *dmabuf;
+	int i,num_ac97;
+#ifdef DEBUG
+	printk("i810_audio: i810_pm_suspend called\n");
+#endif
+	if(!card) return 0;
+	spin_lock_irqsave(&card->lock, flags);
+	card->pm_suspended=1;
+	for(i=0;i<NR_HW_CH;i++) {
+		state = card->states[i];
+		if(!state) continue;
+		/* this happens only if there are open files */
+		dmabuf = &state->dmabuf;
+		if(dmabuf->enable & DAC_RUNNING ||
+		   (dmabuf->count && (dmabuf->trigger & PCM_ENABLE_OUTPUT))) {
+			state->pm_saved_dac_rate=dmabuf->rate;
+			stop_dac(state);
+		} else {
+			state->pm_saved_dac_rate=0;
+		}
+		if(dmabuf->enable & ADC_RUNNING) {
+			state->pm_saved_adc_rate=dmabuf->rate;	
+			stop_adc(state);
+		} else {
+			state->pm_saved_adc_rate=0;
+		}
+		dmabuf->ready = 0;
+		dmabuf->swptr = dmabuf->hwptr = 0;
+		dmabuf->count = dmabuf->total_bytes = 0;
+	}
+
+	spin_unlock_irqrestore(&card->lock, flags);
+
+	/* save mixer settings */
+	for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) {
+		struct ac97_codec *codec = card->ac97_codec[num_ac97];
+		if(!codec) continue;
+		for(i=0;i< SOUND_MIXER_NRDEVICES ;i++) {
+			if((supported_mixer(codec,i)) &&
+			   (codec->read_mixer)) {
+				card->pm_saved_mixer_settings[i][num_ac97]=
+					codec->read_mixer(codec,i);
+			}
+		}
+	}
+	pci_save_state(dev,card->pm_save_state); /* XXX do we need this? */
+	pci_disable_device(dev); /* disable busmastering */
+	pci_set_power_state(dev,3); /* Zzz. */
+
+	return 0;
+}
+
+
+static int i810_pm_resume(struct pci_dev *dev)
+{
+	int num_ac97,i=0;
+	struct i810_card *card=(struct i810_card *)dev->driver_data;
+	pci_enable_device(dev);
+	pci_restore_state (dev,card->pm_save_state);
+
+	/* observation of a toshiba portege 3440ct suggests that the 
+	   hardware has to be more or less completely reinitialized from
+	   scratch after an apm suspend.  Works For Me.   -dan */
+
+	i810_ac97_random_init_stuff(card);
+
+	for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) {
+		struct ac97_codec *codec = card->ac97_codec[num_ac97];
+		/* check they haven't stolen the hardware while we were
+		   away */
+		if(!i810_ac97_exists(card,num_ac97)) {
+			if(num_ac97) continue;
+			else BUG();
+		}
+		if(!i810_ac97_probe_and_powerup(card,codec)) BUG();
+		
+		if((card->ac97_features&0x0001)) {
+			/* at probe time we found we could do variable
+			   rates, but APM suspend has made it forget
+			   its magical powers */
+			if(!i810_ac97_enable_variable_rate(codec)) BUG();
+		}
+		/* we lost our mixer settings, so restore them */
+		for(i=0;i< SOUND_MIXER_NRDEVICES ;i++) {
+			if(supported_mixer(codec,i)){
+				int val=card->
+					pm_saved_mixer_settings[i][num_ac97];
+				codec->mixer_state[i]=val;
+				codec->write_mixer(codec,i,
+						   (val  & 0xff) ,
+						   ((val >> 8)  & 0xff) );
+			}
+		}
+	}
+
+	/* we need to restore the sample rate from whatever it was */
+	for(i=0;i<NR_HW_CH;i++) {
+		struct i810_state * state=card->states[i];
+		if(state) {
+			if(state->pm_saved_adc_rate)
+				i810_set_adc_rate(state,state->pm_saved_adc_rate);
+			if(state->pm_saved_dac_rate)
+				i810_set_dac_rate(state,state->pm_saved_dac_rate);
+		}
+	}
+
+	
+        card->pm_suspended = 0;
+
+	/* any processes that were reading/writing during the suspend
+	   probably ended up here */
+	for(i=0;i<NR_HW_CH;i++) {
+		struct i810_state *state = card->states[i];
+		if(state) wake_up(&state->dmabuf.wait);
+        }
+
+	return 0;
+}	
+#endif /* CONFIG_PM */
 
 MODULE_AUTHOR("");
 MODULE_DESCRIPTION("Intel 810 audio support");
@@ -2713,6 +2935,10 @@
 	id_table:	i810_pci_tbl,
 	probe:		i810_probe,
 	remove:		i810_remove,
+#ifdef CONFIG_PM
+	suspend:	i810_pm_suspend,
+	resume:		i810_pm_resume,
+#endif /* CONFIG_PM */
 };
 
 
@@ -2753,3 +2979,9 @@
 
 module_init(i810_init_module);
 module_exit(i810_cleanup_module);
+
+/*
+Local Variables:
+c-basic-offset: 8
+End:
+*/

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)