/* CSL - Common Sound Layer
 * Copyright (C) 2000-2001 Stefan Westerfeld and Tim Janik
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include	"cslprivate.h"

#include	"cslutils.h"
#include	<string.h>
#include	<fcntl.h>
#include	<errno.h>
#include	<unistd.h>
#include	<stdlib.h>
#include	<sys/ioctl.h>
#include	<linux/soundcard.h>


#define	FMT_ERROR (0xffffffff)

typedef struct {
  CslPcmStream stream;
} OSSPcmStream;
#define	OSS_PCM_STREAM(pcmstream)	((OSSPcmStream*) (pcmstream))

struct _CslPcmDriver
{
  char		*device_name;

  /* opened stream setup */
  unsigned int		 readable : 1;
  unsigned int		 writable : 1;
  unsigned int		 blocking : 1;
  unsigned int		 stream_hint : 1;
  int			 device_fd;

  /* requested upon first open() */
  unsigned int		 req_rate;
  unsigned int		 req_n_channels;
  CslPcmFormatType	 req_format;

  /* assigned */
  CslPcmFormatType	 format;
  unsigned int		 n_channels;
  unsigned int		 rate;
  char			*channel_mappings[2];
  
  unsigned int		 packet_size;
  unsigned int		 n_total_packets;
  unsigned int		 buffer_hint;

  OSSPcmStream	*write_stream;
  OSSPcmStream	*read_stream;
};


/* --- prototypes --- */
static CslErrorType  oss_pcm_driver_init		(CslDriver      *driver);
static void	     oss_pcm_driver_shutdown		(CslDriver      *driver);
static CslErrorType  oss_pcm_stream_init		(CslDriver       *driver,
							 const char      *role,
							 unsigned int     rate,
							 unsigned int     n_channels,
							 CslPcmFormatType format,
							 CslBool	  readable,
							 CslPcmStream	**stream_p);
static void	     oss_pcm_stream_destroy            	(CslPcmStream    *stream);
static int	     oss_pcm_read               	(CslPcmStream    *stream,
							 unsigned int     n_bytes,
							 char            *bytes);
static int	     oss_pcm_write              	(CslPcmStream    *stream,
							 unsigned int     n_bytes,
							 char            *bytes);
static CslErrorType  oss_pcm_update_status         	(CslPcmStream    *stream);
static CslErrorType  oss_pcm_sync               	(CslPcmStream    *stream);
static CslErrorType  oss_pcm_flush              	(CslPcmStream    *stream);
static CslErrorType  oss_pcm_set_title          	(CslPcmStream   *stream,
							 const char     *title);
static CslErrorType  oss_pcm_set_stream_mode    	(CslPcmStream   *stream,
							 unsigned int    buffer_size,
							 unsigned int	 byte_watermark);
static CslErrorType  oss_pcm_set_packet_mode		(CslPcmStream   *stream,
							 unsigned int    n_packets,
							 unsigned int    packet_size,
							 unsigned int	 packet_watermark);
static CslErrorType  oss_pcm_set_channel_mapping        (CslPcmStream   *stream,
							 unsigned int    channel,
							 const char     *mapping);
static CslErrorType  pcm_device_oss_setup		(CslPcmDriver	*pdriver);
static CslErrorType  pcm_device_oss_set_mode		(CslPcmDriver	*pdriver);


/* --- varables --- */
CslPcmDriverVTable _csl_oss_pcm_vtable = {
  oss_pcm_driver_init,
  oss_pcm_driver_shutdown,
  oss_pcm_stream_init,
  oss_pcm_stream_destroy,
  oss_pcm_read,
  oss_pcm_write,
  oss_pcm_update_status,
  oss_pcm_sync,
  oss_pcm_flush,
  oss_pcm_set_title,
  oss_pcm_set_stream_mode,
  oss_pcm_set_packet_mode,
  oss_pcm_set_channel_mapping,
};


/* --- functions --- */
static CslErrorType
oss_pcm_driver_init (CslDriver *driver)
{
  CslErrorType error;
  char *device_name;
  int fd;

  device_name = getenv ("OSS_DEVICE");
  device_name = csl_strdup (device_name ? device_name : "/dev/dsp");

  /* check if OSS driver is available */
  fd = open (device_name, O_WRONLY | O_NONBLOCK, 0); /* always open non-blocking to avoid open() hangs */
  if (fd >= 0 || errno == EBUSY)
    {
      static const char *pcm_mappings[] = {
	CSL_PCM_CHANNEL_CENTER,
	CSL_PCM_CHANNEL_FRONT_LEFT,
	CSL_PCM_CHANNEL_FRONT_RIGHT,
      };
      CslPcmDriver *pdriver = csl_new0 (CslPcmDriver, 1);

      if (fd >= 0)
	{
	  audio_buf_info info = { 0, };
#if 0
	  const struct { unsigned int fcount, fsize; } fsizes[] = {
	    { 128, 32768 },	/* 4Mb, OSS max settings */
	    {  32, 32768 },	/* 1Mb */
	    {  16, 32768 },	/* 512Kb */
	    {   8, 16384 },	/* 128Kb (most cards) */
	    {   4, 16384 },	/* 64Kb (many cards) */
	    {   4,  8192 },	/* 32Kb */
	    {   4,  4096 },	/* 16Kb */
	    {   8,  1024 },	/* 8Kb (ridiculous) */
	  };
	  unsigend int n;

	  for (n = 0; n < sizeof (fsizes) / sizeof (fsizes[0]); n++)
	    {
	      pdriver->n_total_packets = fsizes[n].fcount;
	      pdriver->packet_size = fsizes[n].fsize;
	      if (pcm_device_oss_set_mode (pdriver) == CSL_ENONE)
		{
		  /* OSS aproved size, figure what we got */
		  pdriver->buffer_hint = pdriver->n_total_packets * pdriver->packet_size;
		  break;
		}
	    }
#endif
	  /* try to get buffer size */
	  if (ioctl (fd, SNDCTL_DSP_GETOSPACE, &info) < 0)
	    pdriver->buffer_hint = 4 * 32768;	/* need to guess, 128Kb */
	  else
	    pdriver->buffer_hint = info.fragsize * info.fragstotal;

	  (void) close (fd);
	}
      else
	pdriver->buffer_hint = 4 * 32768;	/* need to guess, 128Kb */

      /* initialize _all_ of driver's PCM portion */
      driver->pcm_vtable = &_csl_oss_pcm_vtable;
      driver->pcm_data = pdriver;
      driver->n_pcm_mappings = sizeof (pcm_mappings) / sizeof (pcm_mappings[0]);
      driver->pcm_mappings = pcm_mappings;
      pdriver->device_name = device_name;
      pdriver->device_fd = -1;

      error = CSL_ENONE;
    }
  else
    {
      error = CSL_ENODRIVER;
      csl_free (device_name);
    }

  if (csl_debug (PCM))
    csl_message ("+OSS-PDRIVER-INIT(%s): %s%s%s",
		 device_name,
		 error == CSL_ENONE ? "succeeded" : "failed",
		 error == CSL_ENONE ? "" : ": ",
		 error == CSL_ENONE ? "" : csl_strerror (error));

  return error;
}

static void
oss_pcm_driver_shutdown (CslDriver *driver)
{
  CslPcmDriver *pdriver = driver->pcm_data;

  if (pdriver->write_stream || pdriver->read_stream)
    csl_warning ("shutting down PCM driver while streams still alive");
  csl_free (pdriver->device_name);
  csl_free (pdriver);
  if (csl_debug (PCM))
    csl_message ("-OSS-PDRIVER-SHUTDOWN");
}

static CslErrorType
oss_pcm_stream_init (CslDriver       *driver,
		     const char      *role,
		     unsigned int     rate,
		     unsigned int     n_channels,
		     CslPcmFormatType format,
		     CslBool	      readable,
		     CslPcmStream   **stream_p)
{
  CslPcmDriver *pdriver = driver->pcm_data;
  OSSPcmStream *stream, *ostream = NULL;
  CslErrorType error;
  int omode;
  int ofd = -1;

  /* we only allow a subsequent open() if its not for
   * the same direction (read/write) and requests the
   * same setup
   */
  if ((readable && pdriver->read_stream) ||
      (!readable && pdriver->write_stream) ||
      (pdriver->device_fd >= 0 && (pdriver->req_rate != rate ||
				   pdriver->req_n_channels != n_channels ||
				   pdriver->req_format != format)))
    return CSL_EBUSY;
  
  if (pdriver->read_stream || pdriver->write_stream)
    {
      omode = O_RDWR;	/* reopen blocking */
      ostream = pdriver->read_stream ? pdriver->read_stream : pdriver->write_stream;
      ofd = pdriver->device_fd;
      /* cut remaining noise */
      (void) ioctl (ofd, SNDCTL_DSP_RESET);
      (void) close (ofd);
      pdriver->device_fd = -1;
      pdriver->writable = TRUE;
      pdriver->readable = TRUE;
      /* buffering fields already setup */
    }
  else
    {
      omode = O_NONBLOCK;	/* open non-blocking to avoid open() hangs */
      omode |= readable ? O_RDONLY : O_WRONLY;
      pdriver->readable = readable != FALSE;
      pdriver->writable = !pdriver->readable;
      /* default guess buffering */
      pdriver->blocking = TRUE;
      pdriver->stream_hint = FALSE;
      pdriver->packet_size = 1024;
      pdriver->n_total_packets = 128;
    }
  
  pdriver->device_fd = open (pdriver->device_name, omode, 0);
  if (pdriver->device_fd < 0)
    {
      /* we should never end up here upon reopening */
      csl_assert (ofd == -1);
      
      if (errno == EBUSY)
	return CSL_EBUSY;
      else if (errno == EISDIR || errno == EACCES || errno == EROFS)
	return CSL_EPERMS;
      else
	return CSL_EIO;
    }
  
  pdriver->req_rate = rate;
  pdriver->req_n_channels = n_channels;
  pdriver->req_format = format;
  error = pcm_device_oss_setup (pdriver);
  if (!error)
    error = pcm_device_oss_set_mode (pdriver);
  if (error)
    {
      /* we should never end up here upon reopening */
      csl_assert (!pdriver->readable || !pdriver->writable);
      
      close (pdriver->device_fd);
      pdriver->device_fd = -1;
      
      return error;
    }

  /* OSS needs extra activation */
  if (1)
    {
      struct timeval tv = { 0, 0, };
      fd_set in_fds;
      fd_set out_fds;
      
      FD_ZERO (&in_fds);
      FD_ZERO (&out_fds);
      if (pdriver->readable)
	{
	  FD_SET (pdriver->device_fd, &in_fds);
	}
      if (pdriver->writable)
	{
	  FD_SET (pdriver->device_fd, &out_fds);
	}
      select (pdriver->device_fd + 1, &in_fds, &out_fds, NULL, &tv);
    }
  
  stream = csl_new0 (OSSPcmStream, 1);
  if (pdriver->write_stream)
    pdriver->read_stream = stream;
  else if (pdriver->read_stream)
    pdriver->write_stream = stream;
  else if (readable)
    pdriver->read_stream = stream;
  else /* writable */
    pdriver->write_stream = stream;
  stream->stream.driver = driver;
  stream->stream.role = csl_strdup (role);
  stream->stream.title = csl_strdup (role);
  stream->stream.blocking = pdriver->blocking;
  stream->stream.readable = readable != FALSE;
  stream->stream.writable = !stream->stream.readable;
  stream->stream.packet_mode = !pdriver->stream_hint;
  stream->stream.stream_mode = pdriver->stream_hint;
  stream->stream.n_channels = pdriver->n_channels;
  stream->stream.channel_mappings = csl_memdup (pdriver->channel_mappings, sizeof (pdriver->channel_mappings));
  stream->stream.format = pdriver->format;
  stream->stream.rate = pdriver->rate;
  stream->stream.packet.n_total_packets = pdriver->n_total_packets;
  stream->stream.packet.packet_size = pdriver->packet_size;
  stream->stream.packet.n_packets_available = 0;
  stream->stream.packet.packet_watermark = 1; /* OSS supports just one packet */
  stream->stream.buffer_size = (stream->stream.packet.n_total_packets *
				stream->stream.packet.packet_size);
  stream->stream.n_bytes_available = 0;
  stream->stream.buffer_watermark = (stream->stream.packet.packet_size *
				     stream->stream.packet.packet_watermark);
  /* fill in buffer availability settings */
  oss_pcm_update_status (&stream->stream);

  /* done */
  *stream_p = &stream->stream;
  
  return CSL_ENONE;
}

static void
oss_pcm_stream_destroy (CslPcmStream *_stream)
{
  OSSPcmStream *stream = OSS_PCM_STREAM (_stream);
  CslPcmDriver *pdriver = stream->stream.driver->pcm_data;

  csl_assert (stream == pdriver->read_stream || stream == pdriver->write_stream);

  if (pdriver->read_stream == stream)
    pdriver->read_stream = NULL;
  else
    pdriver->write_stream = NULL;
  if (!pdriver->read_stream && !pdriver->write_stream)
    {
      (void) close (pdriver->device_fd);
      pdriver->device_fd = -1;
    }
  csl_free (stream->stream.channel_mappings);
  csl_free (stream->stream.role);
  csl_free (stream->stream.title);
  csl_free (stream);
}

static int
oss_pcm_read (CslPcmStream *_stream,
	      unsigned int  n_bytes,
	      char         *bytes)
{
  OSSPcmStream *stream = OSS_PCM_STREAM (_stream);
  CslPcmDriver *pdriver = stream->stream.driver->pcm_data;
  int n;

  n = read (pdriver->device_fd, bytes, n_bytes);

  return MAX (n, 0);	/* -1 shouldn't occour in OSS */
}

static int
oss_pcm_write (CslPcmStream *_stream,
	       unsigned int  n_bytes,
	       char         *bytes)
{
  OSSPcmStream *stream = OSS_PCM_STREAM (_stream);
  CslPcmDriver *pdriver = stream->stream.driver->pcm_data;
  int n;

  n = write (pdriver->device_fd, bytes, n_bytes);

  return MAX (n, 0);	/* -1 shouldn't occour in OSS */
}

static CslErrorType
oss_pcm_update_status (CslPcmStream *_stream)
{
  OSSPcmStream *stream = OSS_PCM_STREAM (_stream);
  CslPcmDriver *pdriver = stream->stream.driver->pcm_data;
  audio_buf_info info;
  int err;

  memset (&info, 0, sizeof (info));
  if (stream->stream.readable)
    err = ioctl (pdriver->device_fd, SNDCTL_DSP_GETISPACE, &info);
  else /* stream->stream.writable */
    err = ioctl (pdriver->device_fd, SNDCTL_DSP_GETOSPACE, &info);

  if (err < 0)
    {
      /* urg, guess stuff from driver so apps get values that
       * at least make *some* sense
       */
      info.fragsize = pdriver->packet_size;
      info.fragstotal = pdriver->n_total_packets;
      info.fragments = 0;
      info.bytes = 0;
    }

  /* always fill package info, deduce stream buffer sizes from there */
  stream->stream.packet.n_total_packets = info.fragstotal;
  stream->stream.packet.packet_size = info.fragsize;
  stream->stream.packet.n_packets_available = info.fragments;
  stream->stream.packet.packet_watermark = 1; /* OSS supports just one packet */

  /* setup buffering values from packet information */
  stream->stream.buffer_size = (stream->stream.packet.n_total_packets *
				stream->stream.packet.packet_size);
  stream->stream.n_bytes_available = info.bytes; /* info.fragments * info.fragsize */
  stream->stream.buffer_watermark = (stream->stream.packet.packet_size *
				     stream->stream.packet.packet_watermark);
  /* kludge around buggy OSS drivers (e.g. es1371 in 2.3.34) */
  stream->stream.n_bytes_available = MIN (stream->stream.n_bytes_available,
					  stream->stream.buffer_size);
  if (csl_debug (PCM))
    csl_message ("OSS-STATUS(%s): left=%d/%d frags: total=%d size=%d count=%d bytes=%d",
		 pdriver->device_name,
		 stream->stream.n_bytes_available,
		 stream->stream.buffer_size,
		 info.fragstotal,
		 info.fragsize,
		 info.fragments,
		 info.bytes);

  return CSL_ENONE;
}

static CslErrorType
oss_pcm_flush (CslPcmStream *_stream)
{
  OSSPcmStream *stream = OSS_PCM_STREAM (_stream);
  CslPcmDriver *pdriver = stream->stream.driver->pcm_data;
  
  /* format, fragment, rate, etc. settings _should_ remain
   * untouched across OSS driver RESET implementations
   */
  (void) ioctl (pdriver->device_fd, SNDCTL_DSP_RESET);

  return CSL_ENONE;
}

static CslErrorType
oss_pcm_sync (CslPcmStream *_stream)
{
  OSSPcmStream *stream = OSS_PCM_STREAM (_stream);
  CslPcmDriver *pdriver = stream->stream.driver->pcm_data;
  
  (void) ioctl (pdriver->device_fd, SNDCTL_DSP_SYNC);

  return CSL_ENONE;
}

static CslErrorType
oss_pcm_set_title (CslPcmStream *_stream,
		   const char   *title)
{
  OSSPcmStream *stream = OSS_PCM_STREAM (_stream);
  
  csl_free (stream->stream.title);
  stream->stream.title = csl_strdup (title);
  
  return CSL_ENONE;
}

static unsigned int
bit_storage (unsigned int number)
{
  register unsigned int n_bits = 0;

  do
    {
      n_bits++;
      number >>= 1;
    }
  while (number);
  return n_bits;
}

static CslErrorType
oss_pcm_set_stream_mode (CslPcmStream *_stream,
			 unsigned int  buffer_size,
			 unsigned int  byte_watermark)
{
  OSSPcmStream *stream = OSS_PCM_STREAM (_stream);
  CslPcmDriver *pdriver = stream->stream.driver->pcm_data;
  CslErrorType error;

  /* we do our best to emulate stream watermark through
   * the size of the first packet, as OSS has a fixed
   * packet watermark of 1 ;(
   */

  /* OSS needs power2 aligned packet sizes */
  pdriver->packet_size = 1 << bit_storage (byte_watermark - 1);
  pdriver->packet_size = CLAMP (pdriver->packet_size, 128, MIN (32768, pdriver->buffer_hint / 2));
  buffer_size = CLAMP (buffer_size, pdriver->packet_size * 2, pdriver->buffer_hint);
  pdriver->n_total_packets = buffer_size / pdriver->packet_size;
  pdriver->stream_hint = TRUE;

  error = pcm_device_oss_set_mode (pdriver);

  /* regardless of errors, the stream's mode changed */
  stream->stream.stream_mode = TRUE;
  stream->stream.packet_mode = FALSE;

  /* fill in new buffer settings */
  oss_pcm_update_status (_stream);

  return error;
}

static CslErrorType
oss_pcm_set_packet_mode	(CslPcmStream *_stream,
			 unsigned int  n_packets,
			 unsigned int  packet_size,
			 unsigned int  packet_watermark)
{
  OSSPcmStream *stream = OSS_PCM_STREAM (_stream);
  CslPcmDriver *pdriver = stream->stream.driver->pcm_data;
  CslErrorType error;

  /* we don't honour packet_watermark, as OSS allowes just 1 for it */

  /* OSS needs power2 aligned packet sizes */
  pdriver->packet_size = 1 << bit_storage (packet_size - 1);
  pdriver->packet_size = CLAMP (pdriver->packet_size, 128, MIN (32768, pdriver->buffer_hint / 2));
  pdriver->n_total_packets = CLAMP (n_packets, 2, pdriver->buffer_hint / pdriver->packet_size);
  pdriver->stream_hint = FALSE;

  error = pcm_device_oss_set_mode (pdriver);

  /* regardless of errors, the stream's mode changed */
  stream->stream.stream_mode = FALSE;
  stream->stream.packet_mode = TRUE;

  /* fill in new buffer settings */
  oss_pcm_update_status (_stream);

  return error;
}

static CslErrorType
oss_pcm_set_channel_mapping (CslPcmStream *stream,
			     unsigned int  channel,
			     const char   *mapping)
{
  /* FIXME: channel mapping for OSS? HAHA <evil-grin> */
  return CSL_ECAPSUPPORT;
}

static unsigned int
oss_format_translate (unsigned int csl_fmt)
{
  unsigned int oss_fmt = 0;

  switch (csl_fmt & (CSL_PCM_FORMAT_SIZE_MASK | CSL_PCM_FORMAT_ENDIAN_MASK | CSL_PCM_FORMAT_ENCODING_MASK))
    {
    case CSL_PCM_FORMAT_SIZE_8 | CSL_PCM_FORMAT_ENCODING_LINEAR_UNSIGNED | CSL_PCM_FORMAT_ENDIAN_BE:
    case CSL_PCM_FORMAT_SIZE_8 | CSL_PCM_FORMAT_ENCODING_LINEAR_UNSIGNED | CSL_PCM_FORMAT_ENDIAN_LE:
    case CSL_PCM_FORMAT_SIZE_8 | CSL_PCM_FORMAT_ENCODING_LINEAR_UNSIGNED:
      oss_fmt |= AFMT_U8;
      break;
    case CSL_PCM_FORMAT_SIZE_16 | CSL_PCM_FORMAT_ENCODING_LINEAR_UNSIGNED | CSL_PCM_FORMAT_ENDIAN_BE:
      oss_fmt |= AFMT_U16_BE;
      break;
    case CSL_PCM_FORMAT_SIZE_16 | CSL_PCM_FORMAT_ENCODING_LINEAR_UNSIGNED | CSL_PCM_FORMAT_ENDIAN_LE:
      oss_fmt |= AFMT_U16_LE;
      break;
    case CSL_PCM_FORMAT_SIZE_16 | CSL_PCM_FORMAT_ENCODING_LINEAR_SIGNED | CSL_PCM_FORMAT_ENDIAN_BE:
      oss_fmt |= AFMT_S16_BE;
      break;
    case CSL_PCM_FORMAT_SIZE_16 | CSL_PCM_FORMAT_ENCODING_LINEAR_SIGNED | CSL_PCM_FORMAT_ENDIAN_LE:
      oss_fmt |= AFMT_S16_LE;
      break;
    default:
      oss_fmt |= FMT_ERROR;
      break;
    }
  return oss_fmt;
}

static CslErrorType
pcm_device_oss_setup (CslPcmDriver *pdriver)
{
  unsigned int oss_format;
  int fd = pdriver->device_fd;
  long d_long;
  int d_int;

  /* flush buffers, reset to sane state */
  (void) ioctl (fd, SNDCTL_DSP_RESET);

  /* control blocking */
  d_long = fcntl (fd, F_GETFL, 0);
  if (pdriver->blocking)
    d_long &= ~O_NONBLOCK;
  else
    d_long |= O_NONBLOCK;
  if (fcntl (fd, F_SETFL, d_long))
    return CSL_EIO;

  /* sample format */
  oss_format = oss_format_translate (pdriver->req_format);
  if (oss_format == FMT_ERROR)
    return CSL_EFMTINVAL;
  d_int = oss_format;
  if (ioctl (fd, SNDCTL_DSP_GETFMTS, &d_int) < 0)
    return CSL_EGETCAPS;
  if ((d_int & oss_format) != oss_format)
    return CSL_ECAPSUPPORT;
  d_int = oss_format;
  if (ioctl (fd, SNDCTL_DSP_SETFMT, &d_int) < 0 ||
      d_int != oss_format)
    return CSL_ESETCAPS;
  pdriver->format = pdriver->req_format;
  
  /* number of channels (STEREO || MONO) */
  if (pdriver->req_n_channels < 1 || pdriver->req_n_channels > 2)
    return CSL_ECAPSUPPORT;
  d_int = pdriver->req_n_channels - 1;
  if (ioctl (fd, SNDCTL_DSP_STEREO, &d_int) < 0)
    return CSL_ESETCAPS;
  pdriver->n_channels = d_int + 1;

  /* frequency or sample rate */
  d_int = pdriver->req_rate;
  if (ioctl (fd, SNDCTL_DSP_SPEED, &d_int) < 0)
    return CSL_ESETCAPS;
  pdriver->rate = d_int;

  /* channel mappings */
  if (pdriver->n_channels == 1)
    {
      pdriver->channel_mappings[0] = CSL_PCM_CHANNEL_CENTER;
      pdriver->channel_mappings[1] = NULL;
    }
  else /* stereo */
    {
      pdriver->channel_mappings[0] = CSL_PCM_CHANNEL_FRONT_LEFT;
      pdriver->channel_mappings[1] = CSL_PCM_CHANNEL_FRONT_RIGHT;
    }

  if (csl_debug (PCM))
    csl_message ("OSS-SETUP(%s): w=%d r=%d n_channels=%d rate=%d",
		 pdriver->device_name,
		 pdriver->writable,
		 pdriver->readable,
		 pdriver->n_channels,
		 pdriver->rate);

  return CSL_ENONE;
}

static CslErrorType
pcm_device_oss_set_mode (CslPcmDriver *pdriver)
{
  int fd = pdriver->device_fd;
  int d_int, bsize = 0, bfree = 0;
  
  /* flush buffers, reset to sane state.
   * this _shouldn't_ alter alter stereo/rate etc. settings
   */
  (void) ioctl (fd, SNDCTL_DSP_RESET);

  /* Note: fragment = n_fragments << 16;
   *       fragment |= g_bit_storage (fragment_size - 1);
   */
  pdriver->packet_size = CLAMP (pdriver->packet_size, 128, 32768);
  pdriver->n_total_packets = CLAMP (pdriver->n_total_packets, 2, 128);
  d_int = (pdriver->n_total_packets << 16) | bit_storage (pdriver->packet_size - 1);
  if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &d_int) < 0)
    return CSL_ESETCAPS;

  /* check buffer settings */
  if (pdriver->writable)
    {
      audio_buf_info info = { 0, };

      if (ioctl (fd, SNDCTL_DSP_GETOSPACE, &info) < 0)
	return CSL_EGETCAPS;
      pdriver->packet_size = info.fragsize;
      pdriver->n_total_packets = info.fragstotal;
      bsize = info.fragsize * info.fragstotal;
      bfree = info.bytes;
    }
  if (pdriver->readable)
    {
      audio_buf_info info = { 0, };

      if (ioctl (fd, SNDCTL_DSP_GETISPACE, &info) < 0)
	return CSL_EGETCAPS;
      pdriver->packet_size = info.fragsize;
      pdriver->n_total_packets = info.fragstotal;
      bsize = info.fragsize * info.fragstotal;
      bfree = info.bytes;
    }

  if (csl_debug (PCM))
    csl_message ("OSS-MODE(%s): w=%d r=%d n_channels=%d rate=%d bsize=%d bfree=%d",
		 pdriver->device_name,
		 pdriver->writable,
		 pdriver->readable,
		 pdriver->n_channels,
		 pdriver->rate,
		 bsize, bfree);

  return CSL_ENONE;
}
