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
- Lines: 561
- Date:
Fri May 8 00:23:41 1998
- Orig file:
v2.1.100/linux/drivers/sbus/audio/audio.c
- Orig date:
Mon Jan 12 15:15:45 1998
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