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

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

diff -u --recursive --new-file v2.1.28/linux/drivers/sbus/audio/audio.c linux/drivers/sbus/audio/audio.c
@@ -17,6 +17,7 @@
 #include <linux/tqueue.h>
 #include <linux/major.h>
 #include <linux/malloc.h>
+#include <linux/interrupt.h>
 #include <linux/init.h>
 
 #include "audio.h"
@@ -26,15 +27,53 @@
  *	Low-level driver interface.
  */
 
-/* We only support one low-level audio driver. */
-static struct sparcaudio_driver *driver;
+/* We only support one low-level audio driver currently. */
+static struct sparcaudio_driver *driver = NULL;
 
 int register_sparcaudio_driver(struct sparcaudio_driver *drv)
 {
-	/* If a driver is already present, don't allow it to register. */
+	int i;
+
+	/* If a driver is already present, don't allow the register. */
 	if (driver)
 		return -EIO;
 
+	/* Ensure that the driver has a proper operations structure. */
+	if (!drv->ops || !drv->ops->start_output || !drv->ops->stop_output)
+		return -EINVAL;
+
+	/* Setup the circular queue of output buffers. */
+	drv->num_output_buffers = 32;
+	drv->output_front = 0;
+	drv->output_rear = 0;
+	drv->output_count = 0;
+	drv->output_active = 0;
+	drv->output_buffers = kmalloc(32 * sizeof(__u8 *), GFP_KERNEL);
+	drv->output_sizes = kmalloc(32 * sizeof(size_t), GFP_KERNEL);
+	if (!drv->output_buffers || !drv->output_sizes) {
+		if (drv->output_buffers)
+			kfree(drv->output_buffers);
+		if (drv->output_sizes)
+			kfree(drv->output_sizes);
+		return -ENOMEM;
+	}
+
+	/* Allocate the pages for each output buffer. */
+	for (i = 0; i < drv->num_output_buffers; i++) {
+		drv->output_buffers[i] = (void *) __get_free_page(GFP_KERNEL);
+		if (!drv->output_buffers[i]) {
+			int j;
+			for (j = 0; j < i; j++)
+				free_page((unsigned long) drv->output_buffers[j]);
+			kfree(drv->output_buffers);
+			kfree(drv->output_sizes);
+			return -ENOMEM;
+		}
+	}
+
+	/* Ensure that the driver is marked as not being open. */
+	drv->flags = 0;
+
 	MOD_INC_USE_COUNT;
 
 	driver = drv;
@@ -43,53 +82,76 @@
 
 int unregister_sparcaudio_driver(struct sparcaudio_driver *drv)
 {
+	int i;
+
 	/* Make sure that the current driver is unregistering. */
 	if (driver != drv)
 		return -EIO;
 
+	/* Deallocate the queue of output buffers. */
+	for (i = 0; i < driver->num_output_buffers; i++)
+		free_page((unsigned long) driver->output_buffers[i]);
+	kfree(driver->output_buffers);
+	kfree(driver->output_sizes);
+
 	MOD_DEC_USE_COUNT;
 
 	driver = NULL;
 	return 0;
 }
 
-static void sparcaudio_output_done_task(void * unused)
+static void sparcaudio_output_done_task(void * arg)
 {
+	struct sparcaudio_driver *drv = (struct sparcaudio_driver *)arg;
 	unsigned long flags;
 
+	printk(KERN_DEBUG "sparcaudio: next buffer (of=%d)\n",drv->output_front);
 	save_and_cli(flags);
-	printk(KERN_DEBUG "sparcaudio: next buffer\n");
-	driver->ops->start_output(driver, driver->output_buffers[driver->output_front],
-				  driver->output_sizes[driver->output_front]);
-	driver->output_active = 1;
+	drv->ops->start_output(drv,
+			       drv->output_buffers[drv->output_front],
+			       drv->output_sizes[drv->output_front]);
+	drv->output_active = 1;
 	restore_flags(flags);
 }
 
-static struct tq_struct sparcaudio_output_tqueue = {
-	0, 0, sparcaudio_output_done_task, 0 };
-
-void sparcaudio_output_done(void)
+void sparcaudio_output_done(struct sparcaudio_driver * drv)
 {
 	/* Point the queue after the "done" buffer. */
-	driver->output_front++;
-	driver->output_count--;
+	drv->output_front = (drv->output_front + 1) % drv->num_output_buffers;
+	drv->output_count--;
 
 	/* If the output queue is empty, shutdown the driver. */
-	if (driver->output_count == 0) {
+	if (drv->output_count == 0) {
 		/* Stop the lowlevel driver from outputing. */
-		printk(KERN_DEBUG "sparcaudio: lowlevel driver shutdown\n");
-		driver->ops->stop_output(driver);
-		driver->output_active = 0;
+		printk("sparcaudio: lowlevel driver shutdown\n");
+		drv->ops->stop_output(drv);
+		drv->output_active = 0;
+
+		/* Wake up any waiting writers or syncers and return. */
+		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. */
-	queue_task(&sparcaudio_output_tqueue, &tq_immediate);
+	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(&driver->output_write_wait);
+	wake_up_interruptible(&drv->output_write_wait);
 }
 
+void sparcaudio_input_done(struct sparcaudio_driver * drv)
+{
+	/* XXX Implement! */
+}
+
+
 
 /*
  *	VFS layer interface
@@ -104,6 +166,7 @@
 static int sparcaudio_read(struct inode * inode, struct file * file,
 			   char *buf, int count)
 {
+	/* XXX Implement me! */
 	return -EINVAL;
 }
 
@@ -127,18 +190,20 @@
 				return bytes_written > 0 ? bytes_written : -EINTR;
 		}
 
-		/* Determine how much we can copy in this run. */
+		/* Determine how much we can copy in this iteration. */
 		bytes_to_copy = count;
 		if (bytes_to_copy > PAGE_SIZE)
 			bytes_to_copy = PAGE_SIZE;
 
-		err = verify_area(VERIFY_READ, buf, bytes_to_copy);
-		if (err)
-			return err;
+		copy_from_user_ret(driver->output_buffers[driver->output_rear],
+			       buf, bytes_to_copy, -EFAULT);
 
-		memcpy_fromfs(driver->output_buffers[driver->output_rear], buf, bytes_to_copy);
+		printk(KERN_DEBUG "Stuffing %d in %d\n",
+			 bytes_to_copy, driver->output_rear);
 
 		/* 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;
@@ -162,76 +227,117 @@
 static int sparcaudio_ioctl(struct inode * inode, struct file * file,
 			    unsigned int cmd, unsigned long arg)
 {
+	int retval = 0;
+
 	switch (cmd) {
+	case AUDIO_DRAIN:
+		if (driver->output_count > 0) {
+			interruptible_sleep_on(&driver->output_drain_wait);
+			retval = (current->signal & ~current->blocked) ? -EINTR : 0;
+		}
+		break;
+
+	case AUDIO_GETDEV:
+		if (driver->ops->sunaudio_getdev) {
+			audio_device_t tmp;
+
+			driver->ops->sunaudio_getdev(driver, &tmp);
+
+			copy_to_user_ret((audio_device_t *)arg, &tmp, sizeof(tmp), -EFAULT);
+		} else
+			retval = -EINVAL;
+		break;
+
 	default:
 		if (driver->ops->ioctl)
-			return driver->ops->ioctl(inode,file,cmd,arg,driver);
+			retval = driver->ops->ioctl(inode,file,cmd,arg,driver);
 		else
-			return -EINVAL;
+			retval = -EINVAL;
 	}
-	return 0;
+
+	return retval;
 }
 
 static int sparcaudio_open(struct inode * inode, struct file * file)
 {
-	unsigned int minor = MINOR(inode->i_rdev);
-	int i;
+	int err;
 
-	/* We only support minor #4 (/dev/audio right now. */
-	if (minor != 4)
-		return -ENXIO;
+	/* A low-level audio driver must exist. */
+	if (!driver)
+		return -ENODEV;
 
-	/* Make sure that the driver is not busy. */
-	if (driver->busy)
-		return -EBUSY;
-
-	/* Setup the queue of output buffers. */
-	driver->num_output_buffers = 32;
-	driver->output_front = 0;
-	driver->output_rear = 0;
-	driver->output_count = 0;
-	driver->output_active = 0;
-	driver->output_buffers = kmalloc(32 * sizeof(__u8 *), GFP_KERNEL);
-	driver->output_sizes = kmalloc(32 * sizeof(__u8 *), GFP_KERNEL);
-	if (!driver->output_buffers || !driver->output_sizes)
-		return -ENOMEM;
+	/* We only support minor #4 (/dev/audio) right now. */
+	if (MINOR(inode->i_rdev) != 4)
+		return -ENXIO;
 
-	/* Allocate space for the output buffers. */
-	for (i = 0; i < driver->num_output_buffers; i++) {
-		driver->output_buffers[i] = (void *) __get_free_page(GFP_KERNEL);
-		if (!driver->output_buffers[i])
-			return -ENOMEM;
+	/* 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 (current->signal & ~current->blocked)
+			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 (current->signal & ~current->blocked)
+			return -EINTR;
+		goto retry_open;
 	}
 
-	/* Allow the low-level driver to initialize itself. */
-	if (driver->ops->open)
-		driver->ops->open(inode,file,driver);
-
+	/* Mark the driver as locked for read and/or write. */
+	if (file->f_mode & FMODE_READ)
+		driver->flags |= SDF_OPEN_READ;
+	if (file->f_mode & FMODE_WRITE) {
+		driver->output_front = 0;
+		driver->output_rear = 0;
+		driver->output_count = 0;
+		driver->output_active = 0;
+		driver->flags |= SDF_OPEN_WRITE;
+	}  
 
-	/* Mark the driver as busy. */
-	driver->busy = 1;
+	/* 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. */
+	/* Success! */
 	return 0;
 }
 
 static void sparcaudio_release(struct inode * inode, struct file * file)
 {
-	int i;
+	/* Wait for any output still in the queue to be played. */
+	if (driver->output_count > 0)
+		interruptible_sleep_on(&driver->output_drain_wait);
 
+	/* Force any output to be stopped. */
+	driver->ops->stop_output(driver);
+	driver->output_active = 0;
+
+	/* Let the low-level driver do any release processing. */
 	if (driver->ops->release)
 		driver->ops->release(inode,file,driver);
 
-	MOD_DEC_USE_COUNT;
+	if (file->f_mode & FMODE_READ)
+		driver->flags &= ~(SDF_OPEN_READ);
 
-	driver->busy = 0;
+	if (file->f_mode & FMODE_WRITE)
+		driver->flags &= ~(SDF_OPEN_WRITE);
 
-	for (i = 0; i < driver->num_output_buffers; i++)
-		kfree(driver->output_buffers[i]);
-	kfree(driver->output_buffers);
-	kfree(driver->output_sizes);
+	MOD_DEC_USE_COUNT;
+
+	wake_up_interruptible(&driver->open_wait);
 }
 
 static struct file_operations sparcaudio_fops = {
@@ -239,13 +345,17 @@
 	sparcaudio_read,
 	sparcaudio_write,
 	NULL,			/* sparcaudio_readdir */
-	NULL,			/* sparcaudio_poll */
+	NULL,			/* sparcaudio_select */
 	sparcaudio_ioctl,
 	NULL,			/* sparcaudio_mmap */
 	sparcaudio_open,
 	sparcaudio_release
 };
 
+EXPORT_SYMBOL(register_sparcaudio_driver);
+EXPORT_SYMBOL(unregister_sparcaudio_driver);
+EXPORT_SYMBOL(sparcaudio_output_done);
+EXPORT_SYMBOL(sparcaudio_input_done);
 
 #ifdef MODULE
 int init_module(void)
@@ -261,11 +371,16 @@
 	amd7930_init();
 #endif
 
+#ifdef CONFIG_SPARCAUDIO_CS4231
+	cs4231_init();
+#endif
+
 	return 0;
 }
 
 #ifdef MODULE
 void cleanup_module(void)
 {
+	unregister_chrdev(SOUND_MAJOR, "sparcaudio");
 }
 #endif

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