patch-2.1.101 linux/drivers/sbus/audio/audio.c

Next file: linux/drivers/sbus/audio/audio.h
Previous file: linux/drivers/sbus/audio/amd7930.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.100/linux/drivers/sbus/audio/audio.c linux/drivers/sbus/audio/audio.c
@@ -2,6 +2,12 @@
  * drivers/sbus/audio/audio.c
  *
  * Copyright (C) 1996,1997 Thomas K. Dyas (tdyas@eden.rutgers.edu)
+ * Copyright (C) 1997 Derrick J. Brashear (shadow@dementia.org)
+ * Copyright (C) 1997 Brent Baccala (baccala@freesoft.org)
+ * 
+ * Mixer code adapted from code contributed by and
+ * Copyright (C) 1998 Michael Mraka (michael@fi.muni.cz)
+ *
  *
  * This is the audio midlayer that sits between the VFS character
  * devices and the low-level audio hardware device drivers.
@@ -22,7 +28,7 @@
 #include <linux/soundcard.h>
 #include <asm/uaccess.h>
 
-#include "audio.h"
+#include <asm/audioio.h>
 
 
 /*
@@ -58,6 +64,7 @@
 	 */
 
 	drv->num_output_buffers = 32;
+        drv->playing_count = 0;
 	drv->output_front = 0;
 	drv->output_rear = 0;
 	drv->output_count = 0;
@@ -74,6 +81,7 @@
 
 	/* Setup the circular queue of input buffers. */
 	drv->num_input_buffers = 32;
+        drv->recording_count = 0;
 	drv->input_front = 0;
 	drv->input_rear = 0;
 	drv->input_count = 0;
@@ -143,28 +151,25 @@
 	return 0;
 }
 
-static void sparcaudio_output_done_task(void * arg)
+void sparcaudio_output_done(struct sparcaudio_driver * drv, int reclaim)
 {
-	struct sparcaudio_driver *drv = (struct sparcaudio_driver *)arg;
-	unsigned long flags;
+    /* Reclaim a buffer unless it's still in the DMA pipe */
+    if (reclaim) {
+        if (drv->output_count > 0) 
+            drv->output_count--;
+        else 
+            if (drv->playing_count > 0) 
+                drv->playing_count--;
+    } else 
+        drv->playing_count++;
 
-	save_and_cli(flags);
-	drv->ops->start_output(drv,
-			       drv->output_buffers[drv->output_front],
-			       drv->output_sizes[drv->output_front]);
-	drv->output_active = 1;
-	restore_flags(flags);
-}
-
-void sparcaudio_output_done(struct sparcaudio_driver * drv)
-{
 	/* Point the queue after the "done" buffer. */
 	drv->output_size -= drv->output_sizes[drv->output_front];
 	drv->output_front = (drv->output_front + 1) % drv->num_output_buffers;
-	drv->output_count--;
 
 	/* If the output queue is empty, shutdown the driver. */
 	if (drv->output_count == 0) {
+            if (drv->playing_count == 0) {
 		/* Stop the lowlevel driver from outputing. */
 		drv->ops->stop_output(drv);
 		drv->output_active = 0;
@@ -173,19 +178,16 @@
 		wake_up_interruptible(&drv->output_write_wait);
 		wake_up_interruptible(&drv->output_drain_wait);
 		return;
+            }
 	}
 
-	/* Otherwise, queue a task to give the driver the next buffer. */
-	drv->tqueue.next = NULL;
-	drv->tqueue.sync = 0;
-	drv->tqueue.routine = sparcaudio_output_done_task;
-	drv->tqueue.data = drv;
-
-	queue_task(&drv->tqueue, &tq_immediate);
-	mark_bh(IMMEDIATE_BH);
-
-	/* Wake up any tasks that are waiting. */
-	wake_up_interruptible(&drv->output_write_wait);
+    /* If we got back a buffer, see if anyone wants to write to it */
+    if (reclaim || ((drv->output_count + drv->playing_count) 
+                    < drv->num_output_buffers))
+        wake_up_interruptible(&drv->output_write_wait);
+    
+    drv->ops->start_output(drv, drv->output_buffers[drv->output_front],
+                                drv->output_sizes[drv->output_front]);
 }
 
 void sparcaudio_input_done(struct sparcaudio_driver * drv)
@@ -263,7 +265,7 @@
 
 	/* If the low-level driver is not active, activate it. */
 	save_and_cli(flags);
-	if (! driver->output_active) {
+        if ((!driver->output_active) && (driver->output_count > 0)) {
 		driver->ops->start_output(driver,
 				driver->output_buffers[driver->output_front],
 				driver->output_sizes[driver->output_front]);
@@ -277,59 +279,184 @@
 {
 	int bytes_written = 0, bytes_to_copy;
 
-	/* Ensure that we have something to write. */
-	if (count < 1) {
-		sparcaudio_sync_output(driver);
-		return 0;
-	}
+        if (! file->f_mode & FMODE_WRITE)
+            return -EINVAL;
 
 	/* Loop until all output is written to device. */
 	while (count > 0) {
-		/* Check to make sure that an output buffer is available. */
-		/* If not, make valiant attempt */
-		if (driver->output_count == driver->num_output_buffers)
-			sparcaudio_reorganize_buffers(driver);
-
-		if (driver->output_count == driver->num_output_buffers) {
- 			/* We need buffers, so... */
-			sparcaudio_sync_output(driver);
- 			interruptible_sleep_on(&driver->output_write_wait);
-			if (signal_pending(current))
-				return bytes_written > 0 ? bytes_written : -EINTR;
+            /* Check to make sure that an output buffer is available. */
+            /* If not, make valiant attempt */
+            if (driver->num_output_buffers == 
+                (driver->output_count + driver->playing_count))
+                sparcaudio_reorganize_buffers(driver);
+            
+            if (driver->num_output_buffers == 
+                (driver->output_count + driver->playing_count)) {
+                /* We need buffers, so... */
+                sparcaudio_sync_output(driver);
+                interruptible_sleep_on(&driver->output_write_wait);
+                if (signal_pending(current))
+                    return bytes_written > 0 ? bytes_written : -EINTR;
 		}
 
-		/* Determine how much we can copy in this iteration. */
-		bytes_to_copy = count;
-		if (bytes_to_copy > PAGE_SIZE)
-			bytes_to_copy = PAGE_SIZE;
+            /* No buffers were freed. Go back to sleep */
+            if (driver->num_output_buffers == 
+                (driver->output_count + driver->playing_count)) 
+                continue;
+
+            /* Determine how much we can copy in this iteration. */
+            bytes_to_copy = count;
+            if (bytes_to_copy > PAGE_SIZE)
+                bytes_to_copy = PAGE_SIZE;
 
-		copy_from_user_ret(driver->output_buffers[driver->output_rear],
+            copy_from_user_ret(driver->output_buffers[driver->output_rear],
 			       buf, bytes_to_copy, -EFAULT);
 
-		/* Update the queue pointers. */
-		buf += bytes_to_copy;
-		count -= bytes_to_copy;
-		bytes_written += bytes_to_copy;
-		driver->output_sizes[driver->output_rear] = bytes_to_copy;
-		driver->output_rear = (driver->output_rear + 1) % driver->num_output_buffers;
-		driver->output_count++;
-		driver->output_size += bytes_to_copy;
-
-		/* Activate the driver if more than page of data is waiting. */
-		if (driver->output_size > 4096)
-			sparcaudio_sync_output(driver);
+            /* Update the queue pointers. */
+            buf += bytes_to_copy;
+            count -= bytes_to_copy;
+            bytes_written += bytes_to_copy;
+            driver->output_sizes[driver->output_rear] = bytes_to_copy;
+            driver->output_rear = (driver->output_rear + 1) % driver->num_output_buffers;
+            driver->output_count++;
+            driver->output_size += bytes_to_copy;
 	}
+        sparcaudio_sync_output(driver);
 
 	/* Return the number of bytes written to the caller. */
 	return bytes_written;
 }
 
+#define COPY_IN(arg, get) get_user(get, (int *)arg)
+#define COPY_OUT(arg, ret) put_user(ret, (int *)arg)
+
+/* Add these in as new devices are supported. Belongs in audioio.h, actually */
+#define SUPPORTED_MIXER_DEVICES         (SOUND_MASK_VOLUME)
+#define MONO_DEVICES (SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_SPEAKER | SOUND_MASK_MIC)
+
+static inline int sparcaudio_mixer_ioctl(struct inode * inode, struct file * file,
+					 unsigned int cmd, unsigned long arg)
+{
+	int i = 0, j = 0;
+	if (_IOC_DIR(cmd) & _IOC_WRITE) {
+		/* For any missing routines, pretend we changed things anyhow for now */
+		switch (cmd & 0xff) {
+		case SOUND_MIXER_VOLUME:
+			if (driver->ops->get_output_channels)
+				j = driver->ops->get_output_channels(driver);
+			COPY_IN(arg, i);
+			if (j == 1) {
+				i = s_to_m(i);
+				if (driver->ops->set_output_volume)
+					driver->ops->set_output_volume(driver, i * 255/100);
+				if (driver->ops->get_output_volume)
+					i = driver->ops->get_output_volume(driver);
+				i = m_to_s(i);
+			} else {
+				/* there should be stuff here which calculates balance and
+				   volume on a stereo device. will do it eventually */
+				i = s_to_g(i);
+				if (driver->ops->set_output_volume)
+					driver->ops->set_output_volume(driver, i * 255/100);
+				if (driver->ops->get_output_volume)
+					i = driver->ops->get_output_volume(driver);
+				j = s_to_b(i);
+				if (driver->ops->set_output_balance)
+					driver->ops->set_output_balance(driver, j);
+				if (driver->ops->get_output_balance)
+					j = driver->ops->get_output_balance(driver);
+				i = b_to_s(i,j);
+			}
+			return COPY_OUT(arg, i);
+		default:
+			/* Play like we support other things */
+			return COPY_OUT(arg, i);
+		}
+	} else {
+		switch (cmd & 0xff) {
+		case SOUND_MIXER_RECSRC:
+			if (driver->ops->get_input_port)
+				i = driver->ops->get_input_port(driver);
+			/* only one should ever be selected */
+			if (i & AUDIO_ANALOG_LOOPBACK) j = SOUND_MASK_IMIX; /* ? */
+			if (i & AUDIO_CD) j = SOUND_MASK_CD;
+			if (i & AUDIO_LINE_IN) j = SOUND_MASK_LINE;
+			if (i & AUDIO_MICROPHONE) j = SOUND_MASK_MIC;
+
+			return COPY_OUT(arg, j);
+
+		case SOUND_MIXER_RECMASK:
+			if (driver->ops->get_input_ports)
+				i = driver->ops->get_input_ports(driver);
+			/* what do we support? */
+			if (i & AUDIO_MICROPHONE) j |= SOUND_MASK_MIC;
+			if (i & AUDIO_LINE_IN) j |= SOUND_MASK_LINE;
+			if (i & AUDIO_CD) j |= SOUND_MASK_CD;
+			if (i & AUDIO_ANALOG_LOOPBACK) j |= SOUND_MASK_IMIX; /* ? */
+
+			return COPY_OUT(arg, j);
+
+		case SOUND_MIXER_CAPS: /* mixer capabilities */
+			i = SOUND_CAP_EXCL_INPUT;
+			return COPY_OUT(arg, i);
+
+		case SOUND_MIXER_DEVMASK: /* all supported devices */
+		case SOUND_MIXER_STEREODEVS: /* what supports stereo */
+			if (driver->ops->get_input_ports)
+				i = driver->ops->get_input_ports(driver);
+			/* what do we support? */
+			if (i & AUDIO_MICROPHONE) j |= SOUND_MASK_MIC;
+			if (i & AUDIO_LINE_IN) j |= SOUND_MASK_LINE;
+			if (i & AUDIO_CD) j |= SOUND_MASK_CD;
+			if (i & AUDIO_ANALOG_LOOPBACK) j |= SOUND_MASK_IMIX; /* ? */
+
+			if (driver->ops->get_output_ports)
+				i = driver->ops->get_output_ports(driver);
+			if (i & AUDIO_SPEAKER) j |= SOUND_MASK_SPEAKER;
+			if (i & AUDIO_HEADPHONE) j |= SOUND_MASK_LINE; /* ? */
+			if (i & AUDIO_LINE_OUT) j |= SOUND_MASK_LINE;
+			
+			j |= SOUND_MASK_VOLUME;
+
+			if ((cmd & 0xff) == SOUND_MIXER_STEREODEVS)
+			j &= ~(MONO_DEVICES);
+			return COPY_OUT(arg, j);
+
+		case SOUND_MIXER_VOLUME:
+			if (driver->ops->get_output_channels)
+				j = driver->ops->get_output_channels(driver);
+			if (j == 1) {
+				if (driver->ops->get_output_volume)
+					i = driver->ops->get_output_volume(driver);
+				i = m_to_s(i);
+			} else {
+				/* there should be stuff here which calculates balance and
+				   volume on a stereo device. will do it eventually */
+				if (driver->ops->get_output_volume)
+					i = driver->ops->get_output_volume(driver);
+				if (driver->ops->get_output_balance)
+					j = driver->ops->get_output_balance(driver);
+				i = b_to_s(i,j);
+			}
+			return COPY_OUT(arg, i);
+
+		default:
+			/* Play like we support other things */
+			return COPY_OUT(arg, i);
+		}
+	}
+}
+
 static int sparcaudio_ioctl(struct inode * inode, struct file * file,
 			    unsigned int cmd, unsigned long arg)
 {
 	int retval = 0;
 	struct audio_info ainfo;
 
+        if (((cmd >> 8) & 0xff) == 'M') {
+            return sparcaudio_mixer_ioctl(inode, file, cmd, arg);
+        }
+
 	switch (cmd) {
 	case SNDCTL_DSP_SYNC:
 	case AUDIO_DRAIN:
@@ -339,6 +466,40 @@
 		}
 		break;
 
+        case AUDIO_FLUSH:
+                if (driver->output_active && (file->f_mode & FMODE_WRITE)) {
+                    wake_up_interruptible(&driver->output_write_wait);
+                    driver->ops->stop_output(driver);
+                    driver->output_active = 0;
+                    driver->output_front = 0;
+                    driver->output_rear = 0;
+                    driver->output_count = 0;
+                    driver->output_size = 0;
+                    driver->playing_count = 0;
+                }
+                if (driver->input_active && (file->f_mode & FMODE_READ)) {
+                    wake_up_interruptible(&driver->input_read_wait);
+                    driver->ops->stop_input(driver);
+                    driver->input_active = 0;
+                    driver->input_front = 0;
+                    driver->input_rear = 0;
+                    driver->input_count = 0;
+                    driver->recording_count = 0;
+                }
+                if ((file->f_mode & FMODE_READ) && 
+                    !(driver->flags & SDF_OPEN_READ)) {
+                    driver->ops->start_input(driver, 
+                                             driver->input_buffers[driver->input_front],
+                                             PAGE_SIZE);
+                    driver->input_active = 1;
+                    }
+                if ((file->f_mode & FMODE_WRITE) && 
+                    !(driver->flags & SDF_OPEN_WRITE)) {
+                    sparcaudio_sync_output(driver);
+                    }
+                break;
+            
+
 	case AUDIO_GETDEV:
 		if (driver->ops->sunaudio_getdev) {
 			audio_device_t tmp;
@@ -686,6 +847,19 @@
 		  }
 
 	    }
+            /* Maybe this should be a routine instead of a macro */
+#define IF_SET_DO(x,y) if ((x) && Modify(y)) x(driver, y)
+#define IF_SETC_DO(x,y) if ((x) && Modifyc(y)) x(driver, y)
+            IF_SETC_DO(driver->ops->set_input_balance, (int)ainfo.record.balance);
+            IF_SETC_DO(driver->ops->set_output_balance, (int)ainfo.play.balance);
+            IF_SET_DO(driver->ops->set_input_volume, ainfo.record.gain);
+            IF_SET_DO(driver->ops->set_output_volume, ainfo.play.gain);
+            IF_SET_DO(driver->ops->set_input_port, ainfo.record.port);
+            IF_SET_DO(driver->ops->set_output_port, ainfo.play.port);
+            IF_SET_DO(driver->ops->set_monitor_volume, ainfo.monitor_gain);
+            IF_SET_DO(driver->ops->set_output_muted, ainfo.output_muted);
+#undef IF_SET_DO
+#undef IF_SETC_DO
 
 	    printk("sparcaudio_ioctl: AUDIO_SETINFO\n");
 	    break;
@@ -725,77 +899,83 @@
 
 static int sparcaudio_open(struct inode * inode, struct file * file)
 {
-	int err;
-
-	/* A low-level audio driver must exist. */
-	if (!driver)
-		return -ENODEV;
-
-	if (MINOR(inode->i_rdev) == 5) {
+    int minor = MINOR(inode->i_rdev);
+    int err;
 
-		file->f_op = &sparcaudioctl_fops;
-
-		MOD_INC_USE_COUNT;
-
-		return 0;
-	}
-
-	/* We only support minor #4 (/dev/audio) right now. */
-	if (MINOR(inode->i_rdev) != 4)
-		return -ENXIO;
-
-	/* If the driver is busy, then wait to get through. */
-	retry_open:
+    /* A low-level audio driver must exist. */
+    if (!driver)
+        return -ENODEV;
+
+    switch (minor) {
+    case SPARCAUDIO_AUDIOCTL_MINOR:
+        file->f_op = &sparcaudioctl_fops;
+        break;
+
+    case SPARCAUDIO_DSP16_MINOR:
+    case SPARCAUDIO_DSP_MINOR:
+    case SPARCAUDIO_AUDIO_MINOR:
+        /* If the driver is busy, then wait to get through. */
+    retry_open:
 	if (file->f_mode & FMODE_READ && driver->flags & SDF_OPEN_READ) {
-		if (file->f_flags & O_NONBLOCK)
-			return -EBUSY;
-
-		interruptible_sleep_on(&driver->open_wait);
-		if (signal_pending(current))
-			return -EINTR;
-		goto retry_open;
+            if (file->f_flags & O_NONBLOCK)
+                return -EBUSY;
+            
+            interruptible_sleep_on(&driver->open_wait);
+            if (signal_pending(current))
+                return -EINTR;
+            goto retry_open;
 	}
 	if (file->f_mode & FMODE_WRITE && driver->flags & SDF_OPEN_WRITE) {
-		if (file->f_flags & O_NONBLOCK)
-			return -EBUSY;
-
-		interruptible_sleep_on(&driver->open_wait);
-		if (signal_pending(current))
-			return -EINTR;
-		goto retry_open;
+            if (file->f_flags & O_NONBLOCK)
+                return -EBUSY;
+            
+            interruptible_sleep_on(&driver->open_wait);
+            if (signal_pending(current))
+                return -EINTR;
+            goto retry_open;
 	}
 
+	/* Allow the low-level driver to initialize itself. */
+	if (driver->ops->open) {
+            err = driver->ops->open(inode,file,driver);
+            if (err < 0)
+                return err;
+	}
+        
 	/* Mark the driver as locked for read and/or write. */
 	if (file->f_mode & FMODE_READ) {
-		driver->input_offset = 0;
-		driver->input_front = 0;
-		driver->input_rear = 0;
-		driver->input_count = 0;
-		driver->ops->start_input(driver, driver->input_buffers[driver->input_front],
-					 PAGE_SIZE);
-		driver->input_active = 1;
-		driver->flags |= SDF_OPEN_READ;
+            driver->input_offset = 0;
+            driver->input_front = 0;
+            driver->input_rear = 0;
+            driver->input_count = 0;
+            driver->recording_count = 0;
+            driver->ops->start_input(driver, driver->input_buffers[driver->input_front],
+                                     PAGE_SIZE);
+            driver->input_active = 1;
+            driver->flags |= SDF_OPEN_READ;
 	}
 	if (file->f_mode & FMODE_WRITE) {
-		driver->output_size = 0;
-		driver->output_front = 0;
-		driver->output_rear = 0;
-		driver->output_count = 0;
-		driver->output_active = 0;
-		driver->flags |= SDF_OPEN_WRITE;
+            driver->playing_count = 0;
+            driver->output_size = 0;
+            driver->output_front = 0;
+            driver->output_rear = 0;
+            driver->output_count = 0;
+            driver->output_active = 0;
+            driver->flags |= SDF_OPEN_WRITE;
 	}  
-
-	/* Allow the low-level driver to initialize itself. */
-	if (driver->ops->open) {
-		err = driver->ops->open(inode,file,driver);
-		if (err < 0)
-			return err;
-	}
-
-	MOD_INC_USE_COUNT;
-
-	/* Success! */
-	return 0;
+        break;
+    case SPARCAUDIO_MIXER_MINOR:     
+        file->f_op = &sparcaudioctl_fops;
+        break;
+
+    default:
+        return -ENXIO;
+    }
+
+    MOD_INC_USE_COUNT;
+    
+    /* Success! */
+    return 0;
 }
 
 static int sparcaudio_release(struct inode * inode, struct file * file)
@@ -876,3 +1056,22 @@
 	unregister_chrdev(SOUND_MAJOR, "sparcaudio");
 }
 #endif
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: nil
+ * tab-width: 8
+ * End:
+ */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov