Logo Search packages:      
Sourcecode: alsa-driver version File versions  Download package

asihpi.c

/*
 *  Asihpi soundcard
 *  Copyright (c) by AudioScience Inc <alsa@audioscience.com>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of version 2 of the GNU General Public License as
 *   published by the Free Software Foundation;
 *
 *   This program 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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 *
 *  The following is not a condition of use, merely a request:
 *  If you modify this program, particularly if you fix errors, AudioScience Inc
 *  would appreciate it if you grant us the right to use those modifications
 *  for any purpose including commercial applications.
 */
/* >0: print Hw params, timer vars. >1: print stream write/copy sizes  */
#define REALLY_VERBOSE_LOGGING 0

#if REALLY_VERBOSE_LOGGING
#define VPRINTK1 printk
#else
#define VPRINTK1(...)
#endif

#if REALLY_VERBOSE_LOGGING > 1
#define VPRINTK2 printk
#else
#define VPRINTK2(...)
#endif

#if REALLY_VERBOSE_LOGGING > 2
#define VPRINTK3 printk
#else
#define VPRINTK3(...)
#endif

#ifndef ASI_STYLE_NAMES
/* not sure how ALSA style name should look */
#define ASI_STYLE_NAMES 1
#endif

#include "hpi_internal.h"
#include "hpimsginit.h"
#include "hpioctl.h"

#ifndef KERNEL_ALSA_BUILD
/* building in ALSA source tree */
#include "adriver.h"
#else
/* building in kernel tree */
#include <sound/driver.h>
#endif

#include <linux/pci.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/info.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include <sound/hwdep.h>


MODULE_LICENSE("GPL");
MODULE_AUTHOR("AudioScience Inc. <support@audioscience.com>");
MODULE_DESCRIPTION("AudioScience ALSA ASI5000 ASI6000 ASI87xx ASI89xx");

static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;    /* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;     /* ID for this card */
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
static int enable_hpi_hwdep = 1;

module_param_array(index, int, NULL, S_IRUGO);
MODULE_PARM_DESC(index, "ALSA Index value for AudioScience soundcard.");

module_param_array(id, charp, NULL, S_IRUGO);
MODULE_PARM_DESC(id, "ALSA ID string for AudioScience soundcard.");

module_param_array(enable, bool, NULL, S_IRUGO);
MODULE_PARM_DESC(enable, "ALSA Enable AudioScience soundcard.");

module_param(enable_hpi_hwdep, bool, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(enable_hpi_hwdep,
            "ALSA Enable HPI hwdep for AudioScience soundcard ");

/* identify driver */
#ifdef KERNEL_ALSA_BUILD
static char *build_info = "Built using headers from kernel source";
module_param(build_info, charp, S_IRUGO);
MODULE_PARM_DESC(build_info, "Built using headers from kernel source");
#else
static char *build_info = "Built within ALSA source";
module_param(build_info, charp, S_IRUGO);
MODULE_PARM_DESC(build_info, "Built within ALSA source");
#endif

/* set to 1 to dump every control from adapter to log */
static const int mixer_dump;

#define DEFAULT_SAMPLERATE 44100
static int adapter_fs = DEFAULT_SAMPLERATE;

static struct hpi_hsubsys *phSubSys;      /* handle to HPI audio subsystem */

/* defaults */
#define PERIODS_MIN 2
#define PERIOD_BYTES_MIN  2304
#define BUFFER_BYTES_MAX      (128*1024)

/*#define TIMER_MILLISECONDS 20
#define FORCE_TIMER_JIFFIES ((TIMER_MILLISECONDS * HZ + 999)/1000)
*/

#define MAX_CLOCKSOURCES (HPI_SAMPLECLOCK_SOURCE_LAST + 1 + 7)

struct clk_source {
      int source;
      int index;
      char *name;
};

struct clk_cache {
      int count;
      int has_local;
      struct clk_source s[MAX_CLOCKSOURCES];
};

/* Per card data */
struct snd_card_asihpi {
      struct snd_card *card;
      struct pci_dev *pci;
      u16 wAdapterIndex;
      u32 dwSerialNumber;
      u16 wType;
      u16 wVersion;
      u16 wNumOutStreams;
      u16 wNumInStreams;

      u32 hMixer;
      struct clk_cache cc;

      int support_mmap;
      int support_grouping;
      int support_mrx;
      u16 in_max_chans;
      u16 out_max_chans;
};

/* Per stream data */
struct snd_card_asihpi_pcm {
      struct timer_list timer;
      unsigned int respawn_timer;
      unsigned int hpi_buffer_attached;
      unsigned int pcm_size;
      unsigned int pcm_count;
      unsigned int bytes_per_sec;
      unsigned int pcm_irq_pos;     /* IRQ position */
      unsigned int pcm_buf_pos;     /* position in buffer */
      struct snd_pcm_substream *substream;
      u32 hStream;
      struct hpi_format Format;
};

/* universal stream verbs work with out or in stream handles */

/* Functions to allow driver to give a buffer to HPI for busmastering */

static u16 HPI_StreamHostBufferAttach(
      struct hpi_hsubsys *hS,
      u32 hStream,   /* Handle to OutStream. */
      u32 dwSizeInBytes, /* Size in bytes of bus mastering buffer */
      u32 dwPciAddress
)
{
      struct hpi_message hm;
      struct hpi_response hr;
      unsigned int obj = HPI_HandleObject(hStream);

      if (!hStream)
            return HPI_ERROR_INVALID_OBJ;
      HPI_InitMessage(&hm,  obj,
                  obj == HPI_OBJ_OSTREAM ?
                        HPI_OSTREAM_HOSTBUFFER_ALLOC :
                        HPI_ISTREAM_HOSTBUFFER_ALLOC);

      HPI_HandleToIndexes(hStream, &hm.wAdapterIndex,
                        &hm.u.d.wStreamIndex, NULL);

      hm.u.d.u.Buffer.dwBufferSize = dwSizeInBytes;
      hm.u.d.u.Buffer.dwPciAddress = dwPciAddress;
      hm.u.d.u.Buffer.dwCommand = HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER;
      HPI_Message(&hm, &hr);
      return(hr.wError);
}

static u16 HPI_StreamHostBufferDetach(
      struct hpi_hsubsys *hS,
      u32  hStream
)
{
      struct hpi_message hm;
      struct hpi_response hr;
      unsigned int obj = HPI_HandleObject(hStream);

      if (!hStream)
            return HPI_ERROR_INVALID_OBJ;

      HPI_InitMessage(&hm,  obj,
                  obj == HPI_OBJ_OSTREAM ?
                        HPI_OSTREAM_HOSTBUFFER_FREE :
                        HPI_ISTREAM_HOSTBUFFER_FREE);

      HPI_HandleToIndexes(hStream, &hm.wAdapterIndex,
                        &hm.u.d.wStreamIndex, NULL);
      hm.u.d.u.Buffer.dwCommand = HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER;
      HPI_Message(&hm, &hr);
      return(hr.wError);
}

static inline u16 HPI_StreamStart(struct hpi_hsubsys *hS, u32 hStream)
{
      if (HPI_HandleObject(hStream) ==  HPI_OBJ_OSTREAM)
            return HPI_OutStreamStart(hS, hStream);
      else
            return HPI_InStreamStart(hS, hStream);
}

static inline u16 HPI_StreamStop(struct hpi_hsubsys *hS, u32 hStream)
{
      if (HPI_HandleObject(hStream) ==  HPI_OBJ_OSTREAM)
            return HPI_OutStreamStop(hS, hStream);
      else
            return HPI_InStreamStop(hS, hStream);
}

static inline u16 HPI_StreamGetInfoEx(
    struct hpi_hsubsys *hS,
    u32 hStream,
    u16        *pwState,
    u32        *pdwBufferSize,
    u32        *pdwDataInBuffer,
    u32        *pdwSampleCount,
    u32        *pdwAuxiliaryData
)
{
      if (HPI_HandleObject(hStream)  ==  HPI_OBJ_OSTREAM)
            return HPI_OutStreamGetInfoEx(hS, hStream, pwState,
                              pdwBufferSize, pdwDataInBuffer,
                              pdwSampleCount, pdwAuxiliaryData);
      else
            return HPI_InStreamGetInfoEx(hS, hStream, pwState,
                              pdwBufferSize, pdwDataInBuffer,
                              pdwSampleCount, pdwAuxiliaryData);
}

static inline u16 HPI_StreamGroupAdd(struct hpi_hsubsys *hS,
                              u32 hMaster,
                              u32 hStream)
{
      if (HPI_HandleObject(hMaster) ==  HPI_OBJ_OSTREAM)
            return HPI_OutStreamGroupAdd(hS, hMaster, hStream);
      else
            return HPI_InStreamGroupAdd(hS, hMaster, hStream);
}

static inline u16 HPI_StreamGroupReset(struct hpi_hsubsys *hS,
                                    u32 hStream)
{
      if (HPI_HandleObject(hStream) ==  HPI_OBJ_OSTREAM)
            return HPI_OutStreamGroupReset(hS, hStream);
      else
            return HPI_InStreamGroupReset(hS, hStream);
}

static inline u16 HPI_StreamGroupGetMap(struct hpi_hsubsys *hS,
                        u32 hStream, u32 *mo, u32 *mi)
{
      if (HPI_HandleObject(hStream) ==  HPI_OBJ_OSTREAM)
            return HPI_OutStreamGroupGetMap(hS, hStream, mo, mi);
      else
            return HPI_InStreamGroupGetMap(hS, hStream, mo, mi);
}

static u16 _HandleError(u16 err, int line, char *filename)
{
      if (err)
            printk(KERN_WARNING
                  "in file %s, line %d: HPI error %d\n",
                  filename, line, err);
      return err;
}

#define HPI_HandleError(x)  _HandleError(x, __LINE__, __FILE__)

/***************************** GENERAL PCM ****************/
#if REALLY_VERBOSE_LOGGING
static void print_hwparams(struct snd_pcm_hw_params *p)
{
      snd_printd("HWPARAMS \n");
      snd_printd("samplerate %d \n", params_rate(p));
      snd_printd("Channels %d \n", params_channels(p));
      snd_printd("Format %d \n", params_format(p));
      snd_printd("subformat %d \n", params_subformat(p));
      snd_printd("Buffer bytes %d \n", params_buffer_bytes(p));
      snd_printd("Period bytes %d \n", params_period_bytes(p));
      snd_printd("access %d \n", params_access(p));
      snd_printd("period_size %d \n", params_period_size(p));
      snd_printd("periods %d \n", params_periods(p));
      snd_printd("buffer_size %d \n", params_buffer_size(p));
}
#else
#define print_hwparams(x)
#endif

static snd_pcm_format_t hpi_to_alsa_formats[] = {
      -1,               /* INVALID */
      SNDRV_PCM_FORMAT_U8,    /* HPI_FORMAT_PCM8_UNSIGNED        1 */
      SNDRV_PCM_FORMAT_S16,   /* HPI_FORMAT_PCM16_SIGNED         2 */
      -1,               /* HPI_FORMAT_MPEG_L1              3 */
      SNDRV_PCM_FORMAT_MPEG,  /* HPI_FORMAT_MPEG_L2              4 */
      SNDRV_PCM_FORMAT_MPEG,  /* HPI_FORMAT_MPEG_L3              5 */
      -1,               /* HPI_FORMAT_DOLBY_AC2            6 */
      -1,               /* HPI_FORMAT_DOLBY_AC3            7 */
      SNDRV_PCM_FORMAT_S16_BE,/* HPI_FORMAT_PCM16_BIGENDIAN      8 */
      -1,               /* HPI_FORMAT_AA_TAGIT1_HITS       9 */
      -1,               /* HPI_FORMAT_AA_TAGIT1_INSERTS   10 */
      SNDRV_PCM_FORMAT_S32,   /* HPI_FORMAT_PCM32_SIGNED        11 */
      -1,               /* HPI_FORMAT_RAW_BITSTREAM       12 */
      -1,               /* HPI_FORMAT_AA_TAGIT1_HITS_EX1  13 */
      SNDRV_PCM_FORMAT_FLOAT, /* HPI_FORMAT_PCM32_FLOAT         14 */
#if 1
      /* ALSA can't handle 3 byte sample size together with power-of-2
       *  constraint on buffer_bytes, so disable this format
       */
      -1
#else
      /* SNDRV_PCM_FORMAT_S24_3LE */      /* { HPI_FORMAT_PCM24_SIGNED        15 */
#endif
};


static int snd_card_asihpi_format_alsa2hpi(snd_pcm_format_t alsaFormat,
                                 u16 *hpiFormat)
{
      u16 wFormat;

      for (wFormat = HPI_FORMAT_PCM8_UNSIGNED;
           wFormat <= HPI_FORMAT_PCM24_SIGNED; wFormat++) {
            if (hpi_to_alsa_formats[wFormat] == alsaFormat) {
                  *hpiFormat = wFormat;
                  return 0;
            }
      }

      snd_printd(KERN_WARNING "Failed match for alsa format %d\n",
               alsaFormat);
      *hpiFormat = 0;
      return -EINVAL;
}

static void snd_card_asihpi_pcm_samplerates(struct snd_card_asihpi *asihpi,
                               struct snd_pcm_hardware *pcmhw)
{
      u16 err;
      u32 hControl;
      u32 sampleRate;
      int idx;
      unsigned int rate_min = 200000;
      unsigned int rate_max = 0;
      unsigned int rates = 0;

      if (asihpi->support_mrx) {
            rates |= SNDRV_PCM_RATE_CONTINUOUS;
            rates |= SNDRV_PCM_RATE_8000_96000;
            rate_min = 8000;
            rate_max = 100000;
      } else {
            /* on cards without SRC,
               valid rates are determined by sampleclock */
            err = HPI_MixerGetControl(phSubSys, asihpi->hMixer,
                                HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
                                HPI_CONTROL_SAMPLECLOCK, &hControl);
            if (err) {
                  printk(KERN_ERR "No local sampleclock, err %d\n", err);
            }

            for (idx = 0; idx < 100; idx++) {
                  if (HPI_SampleClock_QueryLocalRate(phSubSys, hControl,
                              idx, &sampleRate)) {
                        if (!idx)
                              printk(KERN_ERR "Local rate query failed\n");

                        break;
                  }

                  rate_min = min(rate_min, sampleRate);
                  rate_max = max(rate_max, sampleRate);

                  switch (sampleRate) {
                  case 5512:
                        rates |= SNDRV_PCM_RATE_5512;
                        break;
                  case 8000:
                        rates |= SNDRV_PCM_RATE_8000;
                        break;
                  case 11025:
                        rates |= SNDRV_PCM_RATE_11025;
                        break;
                  case 16000:
                        rates |= SNDRV_PCM_RATE_16000;
                        break;
                  case 22050:
                        rates |= SNDRV_PCM_RATE_22050;
                        break;
                  case 32000:
                        rates |= SNDRV_PCM_RATE_32000;
                        break;
                  case 44100:
                        rates |= SNDRV_PCM_RATE_44100;
                        break;
                  case 48000:
                        rates |= SNDRV_PCM_RATE_48000;
                        break;
                  case 64000:
                        rates |= SNDRV_PCM_RATE_64000;
                        break;
                  case 88200:
                        rates |= SNDRV_PCM_RATE_88200;
                        break;
                  case 96000:
                        rates |= SNDRV_PCM_RATE_96000;
                        break;
                  case 176400:
                        rates |= SNDRV_PCM_RATE_176400;
                        break;
                  case 192000:
                        rates |= SNDRV_PCM_RATE_192000;
                        break;
                  default: /* some other rate */
                        rates |= SNDRV_PCM_RATE_KNOT;
                  }
            }
      }

      /* printk(KERN_INFO "Supported rates %X %d %d\n",
         rates, rate_min, rate_max); */
      pcmhw->rates = rates;
      pcmhw->rate_min = rate_min;
      pcmhw->rate_max = rate_max;

}

static int snd_card_asihpi_pcm_hw_params(struct snd_pcm_substream *substream,
                               struct snd_pcm_hw_params *params)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
      struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
      int err;
      u16 wFormat;
      unsigned int bytes_per_sec;

      print_hwparams(params);
      err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
      if (err < 0)
            return err;
      err = snd_card_asihpi_format_alsa2hpi(params_format(params), &wFormat);
      if (err)
            return err;

      VPRINTK1(KERN_INFO "Format %d, %d chans, %dHz\n",
                        wFormat,params_channels(params),
                        params_rate(params));

      HPI_HandleError(HPI_FormatCreate(&dpcm->Format, params_channels(params),
                       wFormat, params_rate(params), 0, 0));

      if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
            if (HPI_InStreamReset(phSubSys, dpcm->hStream) != 0)
                  return -EINVAL;

            if (HPI_InStreamSetFormat(phSubSys,
                  dpcm->hStream, &dpcm->Format) != 0)
                  return -EINVAL;
      }

      dpcm->hpi_buffer_attached = 0;
      if (card->support_mmap) {

            err = HPI_StreamHostBufferAttach(phSubSys, dpcm->hStream,
                  params_buffer_bytes(params),  runtime->dma_addr);
            if (err == 0) {
                  snd_printd(KERN_INFO
                           "StreamHostBufferAttach succeeded %u %lu\n",
                           params_buffer_bytes(params),
                           (unsigned long)runtime->dma_addr);
            } else {
                  snd_printd(KERN_INFO
                              "StreamHostBufferAttach error %d\n",
                              err);
                  return -ENOMEM;
            }

            err = HPI_StreamGetInfoEx(phSubSys, dpcm->hStream, NULL,
                                    &dpcm->hpi_buffer_attached,
                                    NULL, NULL, NULL);

            snd_printd(KERN_INFO "StreamHostBufferAttach status 0x%x\n",
                        dpcm->hpi_buffer_attached);
      }
      bytes_per_sec = params_rate(params) * params_channels(params);
      bytes_per_sec *= snd_pcm_format_width(params_format(params));
      bytes_per_sec /= 8;
      if (bytes_per_sec <= 0)
            return -EINVAL;

      dpcm->bytes_per_sec = bytes_per_sec;
      dpcm->pcm_size = params_buffer_bytes(params);
      dpcm->pcm_count = params_period_bytes(params);
      snd_printd(KERN_INFO "pcm_size x%x, pcm_count x%x, Bps %d\n",
                  dpcm->pcm_size, dpcm->pcm_count, bytes_per_sec);

      dpcm->pcm_irq_pos = 0;
      dpcm->pcm_buf_pos = 0;
      return 0;
}

static void snd_card_asihpi_pcm_timer_start(struct snd_pcm_substream *
                                  substream)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
      int expiry;

      /* wait longer the first time, for samples to propagate */
      expiry = (dpcm->pcm_count * HZ / dpcm->bytes_per_sec);
      expiry = max(expiry, 20);
      dpcm->timer.expires = jiffies + expiry;
      dpcm->respawn_timer = 1;
      add_timer(&dpcm->timer);
}

static void snd_card_asihpi_pcm_timer_stop(struct snd_pcm_substream *substream)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi_pcm *dpcm = runtime->private_data;

      dpcm->respawn_timer = 0;
      del_timer(&dpcm->timer);
}

static int snd_card_asihpi_trigger(struct snd_pcm_substream *substream,
                                 int cmd)
{
      struct snd_card_asihpi_pcm *dpcm = substream->runtime->private_data;
      struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
      struct snd_pcm_substream *s;
      u16 e;

      snd_printd("Trigger %dstream %d\n",
                  substream->stream, substream->number);
      switch (cmd) {
      case SNDRV_PCM_TRIGGER_START:
            snd_pcm_group_for_each_entry(s, substream) {
                  struct snd_card_asihpi_pcm *ds;
                  ds = s->runtime->private_data;

                  if (snd_pcm_substream_chip(s) != card)
                        continue;

                  if ((s->stream == SNDRV_PCM_STREAM_PLAYBACK) &&
                        (card->support_mmap)) {
                        /* How do I know how much valid data is present
                        * in buffer? Just guessing 2 periods, but if
                        * buffer is bigger it may contain even more
                        * data??
                        */
                        unsigned int preload = ds->pcm_count*2;
                        VPRINTK2("Preload x%x\n", preload);
                        HPI_OutStreamWriteBuf(phSubSys, ds->hStream,
                                    &s->runtime->dma_area[0],
                                    preload,
                                    &ds->Format);
                        ds->pcm_irq_pos = ds->pcm_irq_pos + preload;
                  }

                  if (card->support_grouping) {
                        VPRINTK1("\tGroup %dstream %d\n", s->stream,
                                    s->number);
                        e = HPI_StreamGroupAdd(phSubSys, dpcm->hStream,
                                          ds->hStream);
                        HPI_HandleError(e);
                        if (!e)
                              snd_pcm_trigger_done(s, substream);
                        else
                              break;
                  } else
                        break;
            }
            snd_printd("Start\n");
            /* start the master stream */
            snd_card_asihpi_pcm_timer_start(substream);
            HPI_HandleError(HPI_StreamStart(phSubSys, dpcm->hStream));
            break;

      case SNDRV_PCM_TRIGGER_STOP:
            snd_card_asihpi_pcm_timer_stop(substream);
            snd_pcm_group_for_each_entry(s, substream) {
                  if (snd_pcm_substream_chip(s) != card)
                        continue;

                  /*? workaround linked streams don't
                  transition to SETUP 20070706*/
                  s->runtime->status->state = SNDRV_PCM_STATE_SETUP;

                  if (card->support_grouping) {
                        VPRINTK1("\tGroup %dstream %d\n", s->stream,
                              s->number);
                        snd_pcm_trigger_done(s, substream);
                  } else
                        break;
            }
            snd_printd("Stop\n");

            /* _prepare and _hwparams reset the stream */
            HPI_HandleError(HPI_StreamStop(phSubSys, dpcm->hStream));
            if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                  HPI_OutStreamReset(phSubSys, dpcm->hStream);

            if (card->support_grouping)
                  HPI_HandleError(HPI_StreamGroupReset(phSubSys,
                                    dpcm->hStream));
            break;

      case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
            snd_printd("Pause release\n");
            HPI_HandleError(HPI_StreamStart(phSubSys, dpcm->hStream));
            snd_card_asihpi_pcm_timer_start(substream);
            break;
      case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
            snd_printd("Pause\n");
            snd_card_asihpi_pcm_timer_stop(substream);
            HPI_HandleError(HPI_StreamStop(phSubSys, dpcm->hStream));
            break;
      default:
            snd_printd("\tINVALID\n");
            return -EINVAL;
      }

      return 0;
}

static int
snd_card_asihpi_hw_free(struct snd_pcm_substream *substream)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
      if (dpcm->hpi_buffer_attached)
            HPI_StreamHostBufferDetach(phSubSys, dpcm->hStream);

      snd_pcm_lib_free_pages(substream);
      return 0;
}

static void snd_card_asihpi_runtime_free(struct snd_pcm_runtime *runtime)
{
      struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
      kfree(dpcm);
}

/*algorithm outline
 Without linking degenerates to getting single stream pos etc
 Without mmap 2nd loop degenerates to snd_pcm_period_elapsed
*/
/*
buf_pos=get_buf_pos(s);
for_each_linked_stream(s) {
      buf_pos=get_buf_pos(s);
      min_buf_pos = modulo_min(min_buf_pos, buf_pos, pcm_size)
      new_data = min(new_data, calc_new_data(buf_pos,irq_pos)
}
timer.expires = jiffies + predict_next_period_ready(min_buf_pos);
for_each_linked_stream(s) {
      s->buf_pos = min_buf_pos;
      if (new_data > pcm_count) {
            if (mmap) {
                  irq_pos = (irq_pos + pcm_count) % pcm_size;
                  if (playback) {
                        write(pcm_count);
                  } else {
                        read(pcm_count);
                  }
            }
            snd_pcm_period_elapsed(s);
      }
}
*/

/** Minimum of 2 modulo values.  Works correctly when the difference between
* the values is less than half the modulus
*/
static inline unsigned int modulo_min(unsigned int a, unsigned int b,
                              unsigned long int modulus)
{
      unsigned int result;
      if (((a-b) % modulus) < (modulus/2))
            result = b;
      else
            result = a;

      return result;
}

/** Timer function, equivalent to interrupt service routine for cards
*/
static void snd_card_asihpi_timer_function(unsigned long data)
{
      struct snd_card_asihpi_pcm *dpcm = (struct snd_card_asihpi_pcm *)data;
      struct snd_card_asihpi *card = snd_pcm_substream_chip(dpcm->substream);
      struct snd_pcm_runtime *runtime;
      struct snd_pcm_substream *s;
      unsigned int newdata = 0;
      unsigned int buf_pos, min_buf_pos = 0;
      unsigned int remdata, xfercount, next_jiffies;
      int first = 1;
      u16 wState;
      u32 dwBufferSize, dwDataAvail, dwSamplesPlayed, dwAux;

      /* find minimum newdata and buffer pos in group */
      snd_pcm_group_for_each_entry(s, dpcm->substream) {
            struct snd_card_asihpi_pcm *ds = s->runtime->private_data;
            runtime = s->runtime;

            if (snd_pcm_substream_chip(s) != card)
                  continue;

            HPI_HandleError(HPI_StreamGetInfoEx(phSubSys,
                              ds->hStream, &wState,
                              &dwBufferSize, &dwDataAvail,
                              &dwSamplesPlayed, &dwAux));

            if (wState == HPI_STATE_DRAINED) {
                  snd_printd(KERN_WARNING  "OStream %d drained\n",
                              s->number);
                  snd_pcm_stop(s, SNDRV_PCM_STATE_XRUN);
                  return;
            }

            if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) {
                  buf_pos = frames_to_bytes(runtime, dwSamplesPlayed);
            } else {
                  buf_pos = dwDataAvail + ds->pcm_irq_pos;
            }

            if (first) {
                  /* can't statically init min when wrap is involved */
                  min_buf_pos = buf_pos;
                  newdata = (buf_pos - ds->pcm_irq_pos) % ds->pcm_size;
                  first = 0;
            } else {
                  min_buf_pos =
                        modulo_min(min_buf_pos, buf_pos, UINT_MAX+1L);
                  newdata = min(
                        (buf_pos - ds->pcm_irq_pos) % ds->pcm_size,
                        newdata);
            }

            VPRINTK1("PB timer hw_ptr x%04lX, appl_ptr x%04lX ",
                  (unsigned long)frames_to_bytes(runtime,
                                    runtime->status->hw_ptr),
                  (unsigned long)frames_to_bytes(runtime,
                                    runtime->control->appl_ptr));
            VPRINTK1("%d S=%d, irq=%04X, pos=x%04X, left=x%04X,"
                  " aux=x%04X space=x%04X\n", s->number,
                  wState,     ds->pcm_irq_pos, buf_pos, (int)dwDataAvail,
                  (int)dwAux, dwBufferSize-dwDataAvail);
      }

      remdata = newdata % dpcm->pcm_count;
      xfercount = newdata - remdata; /* a multiple of pcm_count */
      next_jiffies = ((dpcm->pcm_count-remdata) * HZ / dpcm->bytes_per_sec)+1;
      next_jiffies = max(next_jiffies, 2U * HZ / 1000U);
      dpcm->timer.expires = jiffies + next_jiffies;
      VPRINTK1("jif %d buf pos x%04X newdata x%04X\n",
                  next_jiffies, min_buf_pos, newdata);

      snd_pcm_group_for_each_entry(s, dpcm->substream) {
            struct snd_card_asihpi_pcm *ds = s->runtime->private_data;
            ds->pcm_buf_pos = min_buf_pos;

            if (xfercount) {
                  if (card->support_mmap) {
                        ds->pcm_irq_pos = ds->pcm_irq_pos + xfercount;
                        if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) {
                              VPRINTK2("Write OS%d x%04x\n",
                                          s->number,
                                          ds->pcm_count);
                              HPI_HandleError(
                                    HPI_OutStreamWriteBuf(
                                          phSubSys, ds->hStream,
                                          &s->runtime->
                                                dma_area[0],
                                          xfercount,
                                          &ds->Format));
                        } else {
                              VPRINTK2("Read IS%d x%04x\n",
                                    s->number,
                                    dpcm->pcm_count);
                              HPI_HandleError(
                                    HPI_InStreamReadBuf(
                                          phSubSys, ds->hStream,
                                          NULL, xfercount));
                        }
                  } /* else R/W will be handled by read/write callbacks */
                  snd_pcm_period_elapsed(s);
            }
      }

      if (dpcm->respawn_timer)
            add_timer(&dpcm->timer);
}

/***************************** PLAYBACK OPS ****************/
static int snd_card_asihpi_playback_ioctl(struct snd_pcm_substream *substream,
                                unsigned int cmd, void *arg)
{
      snd_printd(KERN_INFO "Playback ioctl %d\n", cmd);
      return snd_pcm_lib_ioctl(substream, cmd, arg);
}

static int snd_card_asihpi_playback_prepare(struct snd_pcm_substream *
                                  substream)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi_pcm *dpcm = runtime->private_data;

      snd_printd(KERN_INFO "Playback prepare %d\n", substream->number);

      HPI_HandleError(HPI_OutStreamReset(phSubSys, dpcm->hStream));
      dpcm->pcm_irq_pos = 0;
      dpcm->pcm_buf_pos = 0;

      return 0;
}

static snd_pcm_uframes_t
snd_card_asihpi_playback_pointer(struct snd_pcm_substream *substream)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
      snd_pcm_uframes_t ptr;
      u32 dwSamplesPlayed;
      u16 err;

      if (!snd_pcm_stream_linked(substream)) {
            /* NOTE, can use samples played for playback position here and
            * in timer fn because it LAGS the actual read pointer, and is a
            * better representation of actual playout position
            */
            err = HPI_OutStreamGetInfoEx(phSubSys, dpcm->hStream, NULL,
                              NULL, NULL,
                              &dwSamplesPlayed, NULL);
            HPI_HandleError(err);

            dpcm->pcm_buf_pos = frames_to_bytes(runtime, dwSamplesPlayed);
      }
      /* else must return most conservative value found in timer func
       * by looping over all streams
       */

      ptr = bytes_to_frames(runtime, dpcm->pcm_buf_pos  % dpcm->pcm_size);
      VPRINTK2("Playback ptr x%04lx\n", (unsigned long)ptr);
      return ptr;
}

static void snd_card_asihpi_playback_format(struct snd_card_asihpi *asihpi,
                                    u32 hStream,
                                    struct snd_pcm_hardware *pcmhw)
{
      struct hpi_format hpi_format;
      u16 wFormat;
      u16 err;
      u32 hControl;
      u32 dwSampleRate = 48000;

      /* on cards without SRC, must query at valid rate,
      * maybe set by external sync
      */
      err = HPI_MixerGetControl(phSubSys, asihpi->hMixer,
                          HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
                          HPI_CONTROL_SAMPLECLOCK, &hControl);

      if (!err)
            err = HPI_SampleClock_GetSampleRate(phSubSys, hControl,
                        &dwSampleRate);

      for (wFormat = HPI_FORMAT_PCM8_UNSIGNED;
           wFormat <= HPI_FORMAT_PCM24_SIGNED; wFormat++) {
            HPI_FormatCreate(&hpi_format,
                              2, wFormat, dwSampleRate, 128000, 0);
            err = HPI_OutStreamQueryFormat(phSubSys, hStream,
                                   &hpi_format);
            if (!err && (hpi_to_alsa_formats[wFormat] != -1))
                  pcmhw->formats |=
                        (1ULL << hpi_to_alsa_formats[wFormat]);
      }
}

static struct snd_pcm_hardware snd_card_asihpi_playback = {
      .channels_min = 1,
      .channels_max = 2,
      .buffer_bytes_max = BUFFER_BYTES_MAX,
      .period_bytes_min = PERIOD_BYTES_MIN,
      .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
      .periods_min = PERIODS_MIN,
      .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
      .fifo_size = 0,
};

static int snd_card_asihpi_playback_open(struct snd_pcm_substream *substream)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi_pcm *dpcm;
      struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
      int err;

      dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
      if (dpcm == NULL)
            return -ENOMEM;

      err =
          HPI_OutStreamOpen(phSubSys, card->wAdapterIndex,
                        substream->number, &dpcm->hStream);
      HPI_HandleError(err);
      if (err)
            kfree(dpcm);
      if (err == HPI_ERROR_OBJ_ALREADY_OPEN)
            return -EBUSY;
      if (err)
            return -EIO;

      /*? also check ASI5000 samplerate source
          If external, only support external rate.
          If internal and other stream playing, cant switch
      */

      init_timer(&dpcm->timer);
      dpcm->timer.data = (unsigned long) dpcm;
      dpcm->timer.function = snd_card_asihpi_timer_function;
      dpcm->substream = substream;
      runtime->private_data = dpcm;
      runtime->private_free = snd_card_asihpi_runtime_free;

      snd_card_asihpi_playback.channels_max = card->out_max_chans;
      snd_card_asihpi_playback_format(card, dpcm->hStream,
                              &snd_card_asihpi_playback);

      snd_card_asihpi_pcm_samplerates(card,  &snd_card_asihpi_playback);

      snd_card_asihpi_playback.info = SNDRV_PCM_INFO_INTERLEAVED |
                              SNDRV_PCM_INFO_PAUSE;

      if (card->support_mmap)
            snd_card_asihpi_playback.info |= SNDRV_PCM_INFO_MMAP |
                                    SNDRV_PCM_INFO_MMAP_VALID;

      if (card->support_grouping)
            snd_card_asihpi_playback.info |= SNDRV_PCM_INFO_SYNC_START;

      /* struct is copied, so can create initializer dynamically */
      runtime->hw = snd_card_asihpi_playback;

      if (card->support_mmap)
            err = snd_pcm_hw_constraint_pow2(runtime, 0,
                              SNDRV_PCM_HW_PARAM_BUFFER_BYTES);
      if (err < 0)
            return err;

      snd_pcm_set_sync(substream);

      snd_printd(KERN_INFO "Playback open\n");

      return 0;
}

static int snd_card_asihpi_playback_close(struct snd_pcm_substream *substream)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi_pcm *dpcm = runtime->private_data;

      HPI_HandleError(HPI_OutStreamClose(phSubSys, dpcm->hStream));
      snd_printd(KERN_INFO "Playback close\n");

      return 0;
}

static int snd_card_asihpi_playback_copy(struct snd_pcm_substream *substream,
                              int channel,
                              snd_pcm_uframes_t pos,
                              void __user *src,
                              snd_pcm_uframes_t count)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
      unsigned int len;

      len = frames_to_bytes(runtime, count);

      if (copy_from_user(runtime->dma_area, src, len))
            return -EFAULT;

      VPRINTK2(KERN_DEBUG "Playback copy%d %u bytes\n",
                  substream->number, len);

      HPI_HandleError(HPI_OutStreamWriteBuf(phSubSys, dpcm->hStream,
                        runtime->dma_area, len, &dpcm->Format));

      return 0;
}

static int snd_card_asihpi_playback_silence(struct snd_pcm_substream *
                                  substream, int channel,
                                  snd_pcm_uframes_t pos,
                                  snd_pcm_uframes_t count)
{
      unsigned int len;
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi_pcm *dpcm = runtime->private_data;

      len = frames_to_bytes(runtime, count);
      snd_printd(KERN_INFO "Playback silence  %u bytes\n", len);

      memset(runtime->dma_area, 0, len);
      HPI_HandleError(HPI_OutStreamWriteBuf(phSubSys, dpcm->hStream,
                        runtime->dma_area, len, &dpcm->Format));
      return 0;
}

static struct snd_pcm_ops snd_card_asihpi_playback_ops = {
      .open = snd_card_asihpi_playback_open,
      .close = snd_card_asihpi_playback_close,
      .ioctl = snd_card_asihpi_playback_ioctl,
      .hw_params = snd_card_asihpi_pcm_hw_params,
      .hw_free = snd_card_asihpi_hw_free,
      .prepare = snd_card_asihpi_playback_prepare,
      .trigger = snd_card_asihpi_trigger,
      .pointer = snd_card_asihpi_playback_pointer,
      .copy = snd_card_asihpi_playback_copy,
      .silence = snd_card_asihpi_playback_silence,
};

static struct snd_pcm_ops snd_card_asihpi_playback_mmap_ops = {
      .open = snd_card_asihpi_playback_open,
      .close = snd_card_asihpi_playback_close,
      .ioctl = snd_card_asihpi_playback_ioctl,
      .hw_params = snd_card_asihpi_pcm_hw_params,
      .hw_free = snd_card_asihpi_hw_free,
      .prepare = snd_card_asihpi_playback_prepare,
      .trigger = snd_card_asihpi_trigger,
      .pointer = snd_card_asihpi_playback_pointer,
};

/***************************** CAPTURE OPS ****************/
static snd_pcm_uframes_t
snd_card_asihpi_capture_pointer(struct snd_pcm_substream *substream)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi_pcm *dpcm = runtime->private_data;

      VPRINTK3("Capture pointer%d x%04x\n",
                  substream->number, dpcm->pcm_buf_pos);
      /* NOTE Unlike playback can't use actual dwSamplesPlayed
            for the capture position, because those samples aren't yet in
            the local buffer available for reading.
      */
      return bytes_to_frames(runtime, dpcm->pcm_buf_pos % dpcm->pcm_size);
}

static int snd_card_asihpi_capture_ioctl(struct snd_pcm_substream *substream,
                               unsigned int cmd, void *arg)
{
      return snd_pcm_lib_ioctl(substream, cmd, arg);
}

static int snd_card_asihpi_capture_prepare(struct snd_pcm_substream *substream)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi_pcm *dpcm = runtime->private_data;

      HPI_HandleError(HPI_InStreamReset(phSubSys, dpcm->hStream));
      dpcm->pcm_irq_pos = 0;
      dpcm->pcm_buf_pos = 0;

      snd_printd("Capture Prepare %d\n", substream->number);
      return 0;
}



static void snd_card_asihpi_capture_format(struct snd_card_asihpi *asihpi,
                              u32 hStream,
                               struct snd_pcm_hardware *pcmhw)
{
  struct hpi_format hpi_format;
      u16 wFormat;
      u16 err;
      u32 hControl;
      u32 dwSampleRate = 48000;

      /* on cards without SRC, must query at valid rate,
            maybe set by external sync */
      err = HPI_MixerGetControl(phSubSys, asihpi->hMixer,
                          HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
                          HPI_CONTROL_SAMPLECLOCK, &hControl);

      if (!err)
            err = HPI_SampleClock_GetSampleRate(phSubSys, hControl,
                  &dwSampleRate);

      for (wFormat = HPI_FORMAT_PCM8_UNSIGNED;
            wFormat <= HPI_FORMAT_PCM24_SIGNED; wFormat++) {

            HPI_FormatCreate(&hpi_format, 2, wFormat, dwSampleRate,
                                    128000, 0);
            err =
                HPI_InStreamQueryFormat(phSubSys, hStream,
                                  &hpi_format);
            if (!err)
                  pcmhw->formats |=
                        (1ULL << hpi_to_alsa_formats[wFormat]);
      }
}


static struct snd_pcm_hardware snd_card_asihpi_capture = {
      .channels_min = 1,
      .channels_max = 2,
      .buffer_bytes_max = BUFFER_BYTES_MAX,
      .period_bytes_min = PERIOD_BYTES_MIN,
      .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
      .periods_min = PERIODS_MIN,
      .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
      .fifo_size = 0,
};

static int snd_card_asihpi_capture_open(struct snd_pcm_substream *substream)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
      struct snd_card_asihpi_pcm *dpcm;
      int err;

      dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
      if (dpcm == NULL)
            return -ENOMEM;

      snd_printd("HPI_InStreamOpen Adapter %d Stream %d\n",
               card->wAdapterIndex, substream->number);

      err = HPI_HandleError(
          HPI_InStreamOpen(phSubSys, card->wAdapterIndex,
                       substream->number, &dpcm->hStream));
      if (err)
            kfree(dpcm);
      if (err == HPI_ERROR_OBJ_ALREADY_OPEN)
            return -EBUSY;
      if (err)
            return -EIO;


      init_timer(&dpcm->timer);
      dpcm->timer.data = (unsigned long) dpcm;
      dpcm->timer.function = snd_card_asihpi_timer_function;
      dpcm->substream = substream;
      runtime->private_data = dpcm;
      runtime->private_free = snd_card_asihpi_runtime_free;

      snd_card_asihpi_capture.channels_max = card->in_max_chans;
      snd_card_asihpi_capture_format(card, dpcm->hStream,
                               &snd_card_asihpi_capture);
      snd_card_asihpi_pcm_samplerates(card,  &snd_card_asihpi_capture);
      snd_card_asihpi_capture.info = SNDRV_PCM_INFO_INTERLEAVED;

      if (card->support_mmap)
            snd_card_asihpi_capture.info |= SNDRV_PCM_INFO_MMAP |
                                    SNDRV_PCM_INFO_MMAP_VALID;

      runtime->hw = snd_card_asihpi_capture;

      if (card->support_mmap)
            err = snd_pcm_hw_constraint_pow2(runtime, 0,
                              SNDRV_PCM_HW_PARAM_BUFFER_BYTES);
      if (err < 0)
            return err;

      snd_pcm_set_sync(substream);

      return 0;
}

static int snd_card_asihpi_capture_close(struct snd_pcm_substream *substream)
{
      struct snd_card_asihpi_pcm *dpcm = substream->runtime->private_data;

      HPI_HandleError(HPI_InStreamClose(phSubSys, dpcm->hStream));
      return 0;
}

static int snd_card_asihpi_capture_copy(struct snd_pcm_substream *substream,
                        int channel, snd_pcm_uframes_t pos,
                        void __user *dst, snd_pcm_uframes_t count)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
      u32 dwDataSize;

      dwDataSize = frames_to_bytes(runtime, count);

      VPRINTK2("Capture copy%d %d bytes\n", substream->number, dwDataSize);
      HPI_HandleError(HPI_InStreamReadBuf(phSubSys, dpcm->hStream,
                        runtime->dma_area, dwDataSize));

      /* Used by capture_pointer */
      dpcm->pcm_irq_pos = dpcm->pcm_irq_pos + dwDataSize;

      if (copy_to_user(dst, runtime->dma_area, dwDataSize))
            return -EFAULT;

      return 0;
}

static struct snd_pcm_ops snd_card_asihpi_capture_mmap_ops = {
      .open = snd_card_asihpi_capture_open,
      .close = snd_card_asihpi_capture_close,
      .ioctl = snd_card_asihpi_capture_ioctl,
      .hw_params = snd_card_asihpi_pcm_hw_params,
      .hw_free = snd_card_asihpi_hw_free,
      .prepare = snd_card_asihpi_capture_prepare,
      .trigger = snd_card_asihpi_trigger,
      .pointer = snd_card_asihpi_capture_pointer,
};

static struct snd_pcm_ops snd_card_asihpi_capture_ops = {
      .open = snd_card_asihpi_capture_open,
      .close = snd_card_asihpi_capture_close,
      .ioctl = snd_card_asihpi_capture_ioctl,
      .hw_params = snd_card_asihpi_pcm_hw_params,
      .hw_free = snd_card_asihpi_hw_free,
      .prepare = snd_card_asihpi_capture_prepare,
      .trigger = snd_card_asihpi_trigger,
      .pointer = snd_card_asihpi_capture_pointer,
      .copy = snd_card_asihpi_capture_copy
};

static int __devinit snd_card_asihpi_pcm_new(struct snd_card_asihpi *asihpi,
                              int device, int substreams)
{
      struct snd_pcm *pcm;
      int err;

      err = snd_pcm_new(asihpi->card, "Asihpi PCM", device,
                   asihpi->wNumOutStreams, asihpi->wNumInStreams,
                   &pcm);
      if (err < 0)
            return err;
      /* pointer to ops struct is stored, dont change ops afterwards! */
      if (asihpi->support_mmap) {
            snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
                        &snd_card_asihpi_playback_mmap_ops);
            snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
                        &snd_card_asihpi_capture_mmap_ops);
      } else {
            snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
                        &snd_card_asihpi_playback_ops);
            snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
                        &snd_card_asihpi_capture_ops);
      }

      pcm->private_data = asihpi;
      pcm->info_flags = 0;
      strcpy(pcm->name, "Asihpi PCM");

/* ? do we want to emulate MMAP for non-BBM cards? Jack doesn't work with ALSAs
   MMAP emulation - WHY NOT? */
      snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
                                    snd_dma_pci_data(asihpi->pci),
                                    64*1024, BUFFER_BYTES_MAX);

      return 0;
}

/***************************** MIXER CONTROLS ****************/
struct hpi_control {
      u32 hControl;
      u16 wControlType;
      u16 wSrcNodeType;
      u16 wSrcNodeIndex;
      u16 wDstNodeType;
      u16 wDstNodeIndex;
      u16 wBand;
      char name[44]; /* copied to snd_ctl_elem_id.name[44]; */
};

static char *asihpi_tuner_band_names[] =
{
      "invalid",
      "AM",
      "FM mono",
      "TV NTSC-M",
      "FM stereo",
      "AUX",
      "TV PAL BG",
      "TV PAL I",
      "TV PAL DK",
      "TV SECAM",
};

#define NUM_TUNER_STRINGS 10

#if (NUM_TUNER_STRINGS != (HPI_TUNER_BAND_LAST+1))
#error "Tuner band strings don't agree with HPI defines - version mismatch?"
#endif

#if ASI_STYLE_NAMES
static char *asihpi_src_names[] =
{
      "no source",
      "OutStream",
      "LineIn",
      "AesIn",
      "Tuner",
      "RF",
      "Clock",
      "Bitstr",
      "Mic",
      "Cobranet",
      "AnalogIn",
};
#else
static char *asihpi_src_names[] =
{
      "no source",
      "PCM Playback",
      "Line in",
      "Digital in",
      "Tuner",
      "RF",
      "Clock",
      "Bitstream",
      "Mic",
      "Cobranet in",
      "Analog in",
};
#endif

#if ((11 != (HPI_SOURCENODE_LAST_INDEX-HPI_SOURCENODE_BASE+1)))
#error "SourceNode strings don't agree with HPI defines - version mismatch?"
#endif

#if ASI_STYLE_NAMES
static char *asihpi_dst_names[] =
{
      "no destination",
      "InStream",
      "LineOut",
      "AesOut",
      "RF",
      "Speaker" ,
      "Cobranet",
      "AnalogOut",
};
#else
static char *asihpi_dst_names[] =
{
      "no destination",
      "PCM Capture",
      "Line out",
      "Digital out",
      "RF",
      "Speaker",
      "Cobranet out",
      "Analog out",
};
#endif

#if ((8 != (HPI_DESTNODE_LAST_INDEX-HPI_DESTNODE_BASE+1)))
#error "DestNode strings don't agree with HPI defines - version mismatch?"
#endif


static inline int ctl_add(struct snd_card *card, struct snd_kcontrol_new *ctl,
                        struct snd_card_asihpi *asihpi)
{
      int err;

      err = snd_ctl_add(card, snd_ctl_new1(ctl, asihpi));
      if (err < 0)
            return err;
      else if (mixer_dump)
            snd_printk(KERN_INFO "Added %s(%d)\n", ctl->name, ctl->index);

      return 0;
}

/* Convert HPI control name and location into ALSA control name */
static void asihpi_ctl_init(struct snd_kcontrol_new *snd_control,
                        struct hpi_control *asihpi_control,
                        char *name)
{
      memset(snd_control, 0, sizeof(*snd_control));
      snd_control->name = asihpi_control->name;
      snd_control->private_value = asihpi_control->hControl;
      snd_control->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
      snd_control->index = 0;

      if (asihpi_control->wSrcNodeType && asihpi_control->wDstNodeType)
            sprintf(asihpi_control->name, "%s%d to %s%d %s",
                  asihpi_src_names[asihpi_control->wSrcNodeType],
                  asihpi_control->wSrcNodeIndex,
                  asihpi_dst_names[asihpi_control->wDstNodeType],
                  asihpi_control->wDstNodeIndex,
                  name);
      else if (asihpi_control->wDstNodeType) {
            sprintf(asihpi_control->name, "%s%d %s",
            asihpi_dst_names[asihpi_control->wDstNodeType],
            asihpi_control->wDstNodeIndex,
            name);
      } else {
            sprintf(asihpi_control->name, "%s%d %s",
            asihpi_src_names[asihpi_control->wSrcNodeType],
            asihpi_control->wSrcNodeIndex,
            name);
      }
}

/*------------------------------------------------------------
   Volume controls
 ------------------------------------------------------------*/
static int snd_asihpi_volume_info(struct snd_kcontrol *kcontrol,
                          struct snd_ctl_elem_info *uinfo)
{
      u32 hControl = kcontrol->private_value;
      u16 err;
      short nMinGain_01dB;
      short nMaxGain_01dB;
      short nStepGain_01dB;

      err =
          HPI_VolumeQueryRange(phSubSys, hControl, &nMinGain_01dB,
                         &nMaxGain_01dB, &nStepGain_01dB);
      if (err) {
            nMaxGain_01dB = 0;
            nMinGain_01dB = -10000;
            nStepGain_01dB = 100;
      }

      uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
      uinfo->count = 2;
      uinfo->value.integer.min = nMinGain_01dB / HPI_UNITS_PER_dB;
      uinfo->value.integer.max = nMaxGain_01dB / HPI_UNITS_PER_dB;
      uinfo->value.integer.step = nStepGain_01dB / HPI_UNITS_PER_dB;
      return 0;
}

static int snd_asihpi_volume_get(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol)
{
      u32 hControl = kcontrol->private_value;
      short anGain0_01dB[HPI_MAX_CHANNELS];

      HPI_VolumeGetGain(phSubSys, hControl, anGain0_01dB);
      ucontrol->value.integer.value[0] = anGain0_01dB[0] / 100;
      ucontrol->value.integer.value[1] = anGain0_01dB[1] / 100;

      return 0;
}

static int snd_asihpi_volume_put(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol)
{
      int change;
      u32 hControl = kcontrol->private_value;
      short anGain0_01dB[HPI_MAX_CHANNELS];


      anGain0_01dB[0] =
          (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB;
      anGain0_01dB[1] =
          (ucontrol->value.integer.value[1]) * HPI_UNITS_PER_dB;
      /*  change = asihpi->mixer_volume[addr][0] != left ||
         asihpi->mixer_volume[addr][1] != right;
       */
      change = 1;
      HPI_VolumeSetGain(phSubSys, hControl, anGain0_01dB);
      return change;
}

static const DECLARE_TLV_DB_SCALE(db_scale_100, -10000, 100, 0);

static int __devinit snd_asihpi_volume_add(struct snd_card_asihpi *asihpi,
                              struct hpi_control *asihpi_control)
{
      struct snd_card *card = asihpi->card;
      struct snd_kcontrol_new snd_control;

      asihpi_ctl_init(&snd_control, asihpi_control, "Volume");
      snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
                        SNDRV_CTL_ELEM_ACCESS_TLV_READ;
      snd_control.info = snd_asihpi_volume_info;
      snd_control.get = snd_asihpi_volume_get;
      snd_control.put = snd_asihpi_volume_put;
      snd_control.tlv.p = db_scale_100;

      return (ctl_add(card, &snd_control, asihpi));
}

/*------------------------------------------------------------
   Level controls
 ------------------------------------------------------------*/
static int snd_asihpi_level_info(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_info *uinfo)
{
      u32 hControl = kcontrol->private_value;
      u16 err;
      short nMinGain_01dB;
      short nMaxGain_01dB;
      short nStepGain_01dB;

      err =
          HPI_LevelQueryRange(phSubSys, hControl, &nMinGain_01dB,
                         &nMaxGain_01dB, &nStepGain_01dB);
      if (err) {
            nMaxGain_01dB = 2400;
            nMinGain_01dB = -1000;
            nStepGain_01dB = 100;
      }

      uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
      uinfo->count = 2;
      uinfo->value.integer.min = nMinGain_01dB / HPI_UNITS_PER_dB;
      uinfo->value.integer.max = nMaxGain_01dB / HPI_UNITS_PER_dB;
      uinfo->value.integer.step = nStepGain_01dB / HPI_UNITS_PER_dB;
      return 0;
}

static int snd_asihpi_level_get(struct snd_kcontrol *kcontrol,
                        struct snd_ctl_elem_value *ucontrol)
{
      u32 hControl = kcontrol->private_value;
      short anGain0_01dB[HPI_MAX_CHANNELS];

      HPI_LevelGetGain(phSubSys, hControl, anGain0_01dB);
      ucontrol->value.integer.value[0] =
          anGain0_01dB[0] / HPI_UNITS_PER_dB;
      ucontrol->value.integer.value[1] =
          anGain0_01dB[1] / HPI_UNITS_PER_dB;

      return 0;
}

static int snd_asihpi_level_put(struct snd_kcontrol *kcontrol,
                        struct snd_ctl_elem_value *ucontrol)
{
      int change;
      u32 hControl = kcontrol->private_value;
      short anGain0_01dB[HPI_MAX_CHANNELS];

      anGain0_01dB[0] =
          (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB;
      anGain0_01dB[1] =
          (ucontrol->value.integer.value[1]) * HPI_UNITS_PER_dB;
      /*  change = asihpi->mixer_level[addr][0] != left ||
         asihpi->mixer_level[addr][1] != right;
       */
      change = 1;
      HPI_LevelSetGain(phSubSys, hControl, anGain0_01dB);
      return change;
}

static const DECLARE_TLV_DB_SCALE(db_scale_level, -1000, 100, 0);

static int __devinit snd_asihpi_level_add(struct snd_card_asihpi *asihpi,
                              struct hpi_control *asihpi_control)
{
      struct snd_card *card = asihpi->card;
      struct snd_kcontrol_new snd_control;

      /* can't use 'volume' cos some nodes have volume as well */
      asihpi_ctl_init(&snd_control, asihpi_control, "Level");
      snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
                        SNDRV_CTL_ELEM_ACCESS_TLV_READ;
      snd_control.info = snd_asihpi_level_info;
      snd_control.get = snd_asihpi_level_get;
      snd_control.put = snd_asihpi_level_put;
      snd_control.tlv.p = db_scale_level;

      return (ctl_add(card, &snd_control, asihpi));
}

/*------------------------------------------------------------
   AESEBU controls
 ------------------------------------------------------------*/

/* AESEBU format */
static char *asihpi_aesebu_format_names[] =
{
      "N/A",
      "S/PDIF",
      "AES/EBU",
};

static int snd_asihpi_aesebu_format_info(struct snd_kcontrol *kcontrol,
                          struct snd_ctl_elem_info *uinfo)
{
      uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
      uinfo->count = 1;
      uinfo->value.enumerated.items = 3;

      if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
            uinfo->value.enumerated.item =
                  uinfo->value.enumerated.items - 1;

      strcpy(uinfo->value.enumerated.name,
            asihpi_aesebu_format_names[uinfo->value.enumerated.item]);

      return 0;
}

static int snd_asihpi_aesebu_format_get(struct snd_kcontrol *kcontrol,
                  struct snd_ctl_elem_value *ucontrol,
                  u16 (*func)(struct hpi_hsubsys *, u32, u16 *))
{
      u32 hControl = kcontrol->private_value;
      u16 source, err;

      err = func(phSubSys, hControl, &source);

      /* default to N/A */
      ucontrol->value.enumerated.item[0] = 0;
      /* return success but set the control to N/A */
      if (err)
            return 0;
      if (source == HPI_AESEBU_FORMAT_SPDIF)
            ucontrol->value.enumerated.item[0] = 1;
      if (source == HPI_AESEBU_FORMAT_AESEBU)
            ucontrol->value.enumerated.item[0] = 2;

      return 0;
}

static int snd_asihpi_aesebu_format_put(struct snd_kcontrol *kcontrol,
                  struct snd_ctl_elem_value *ucontrol,
                   u16 (*func)(struct hpi_hsubsys *, u32, u16))
{
      u32 hControl = kcontrol->private_value;

      /* default to S/PDIF */
      u16 source = HPI_AESEBU_FORMAT_SPDIF;

      if (ucontrol->value.enumerated.item[0] == 1)
            source = HPI_AESEBU_FORMAT_SPDIF;
      if (ucontrol->value.enumerated.item[0] == 2)
            source = HPI_AESEBU_FORMAT_AESEBU;

      if (func(phSubSys, hControl, source) != 0)
            return -EINVAL;

      return 1;
}

static int snd_asihpi_aesebu_rx_format_get(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol) {
      return snd_asihpi_aesebu_format_get(kcontrol, ucontrol,
                              HPI_AESEBU_Receiver_GetFormat);
}

static int snd_asihpi_aesebu_rx_format_put(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol) {
      return snd_asihpi_aesebu_format_put(kcontrol, ucontrol,
                              HPI_AESEBU_Receiver_SetFormat);
}

static int snd_asihpi_aesebu_rxstatus_info(struct snd_kcontrol *kcontrol,
                          struct snd_ctl_elem_info *uinfo)
{
      uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
      uinfo->count = 1;

      uinfo->value.integer.min = 0;
      uinfo->value.integer.max = 0X1F;
      uinfo->value.integer.step = 1;

      return 0;
}

static int snd_asihpi_aesebu_rxstatus_get(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol) {

      u32 hControl = kcontrol->private_value;
      u16 status;

      HPI_AESEBU_Receiver_GetErrorStatus(phSubSys, hControl, &status);
      ucontrol->value.integer.value[0] = status;
      return 0;
}

static int __devinit snd_asihpi_aesebu_rx_add(struct snd_card_asihpi *asihpi,
                              struct hpi_control *asihpi_control)
{
      struct snd_card *card = asihpi->card;
      struct snd_kcontrol_new snd_control;

      asihpi_ctl_init(&snd_control, asihpi_control, "Format");
      snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
      snd_control.info = snd_asihpi_aesebu_format_info;
      snd_control.get = snd_asihpi_aesebu_rx_format_get;
      snd_control.put = snd_asihpi_aesebu_rx_format_put;


      if (ctl_add(card, &snd_control, asihpi) < 0)
            return -EINVAL;

      asihpi_ctl_init(&snd_control, asihpi_control, "Status");
      snd_control.access =
          SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ;
      snd_control.info = snd_asihpi_aesebu_rxstatus_info;
      snd_control.get = snd_asihpi_aesebu_rxstatus_get;

      return ctl_add(card, &snd_control, asihpi);
}

static int snd_asihpi_aesebu_tx_format_get(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol) {
      return snd_asihpi_aesebu_format_get(kcontrol, ucontrol,
                              HPI_AESEBU_Transmitter_GetFormat);
}

static int snd_asihpi_aesebu_tx_format_put(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol) {
      return snd_asihpi_aesebu_format_put(kcontrol, ucontrol,
                              HPI_AESEBU_Transmitter_SetFormat);
}


static int __devinit snd_asihpi_aesebu_tx_add(struct snd_card_asihpi *asihpi,
                              struct hpi_control *asihpi_control)
{
      struct snd_card *card = asihpi->card;
      struct snd_kcontrol_new snd_control;

      asihpi_ctl_init(&snd_control, asihpi_control, "Format");
      snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
      snd_control.info = snd_asihpi_aesebu_format_info;
      snd_control.get = snd_asihpi_aesebu_tx_format_get;
      snd_control.put = snd_asihpi_aesebu_tx_format_put;

      return ctl_add(card, &snd_control, asihpi);
}

/*------------------------------------------------------------
   Tuner controls
 ------------------------------------------------------------*/

/* Gain */

static int snd_asihpi_tuner_gain_info(struct snd_kcontrol *kcontrol,
                          struct snd_ctl_elem_info *uinfo)
{
      u32 hControl = kcontrol->private_value;
      u16 err;
      short idx;
      u16 gainRange[3];

      for (idx = 0; idx < 3; idx++) {
      err = HPI_Tuner_QueryGain(phSubSys, hControl, idx, &gainRange[idx]);
            if (err != 0)
                  return err;
      }

      uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
      uinfo->count = 1;
      uinfo->value.integer.min = ((int)gainRange[0]) / HPI_UNITS_PER_dB;
      uinfo->value.integer.max = ((int)gainRange[1]) / HPI_UNITS_PER_dB;
      uinfo->value.integer.step = ((int) gainRange[2]) / HPI_UNITS_PER_dB;
      return 0;
}

static int snd_asihpi_tuner_gain_get(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol)
{
      /*
      struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
      */
      u32 hControl = kcontrol->private_value;
      short gain;

      HPI_Tuner_GetGain(phSubSys, hControl, &gain);
      ucontrol->value.integer.value[0] = gain / HPI_UNITS_PER_dB;

      return 0;
}

static int snd_asihpi_tuner_gain_put(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol)
{
      /*
      struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
      */
      u32 hControl = kcontrol->private_value;
      short gain;

      gain = (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB;
      HPI_Tuner_SetGain(phSubSys, hControl, gain);

      return 1;
}

/* Band  */

static int asihpi_tuner_band_query(struct snd_kcontrol *kcontrol,
                              u16 *bandList, u32 len) {
      u32 hControl = kcontrol->private_value;
      u16 err = 0;
      u32 i;

      for (i = 0; i < len; i++) {
            err = HPI_Tuner_QueryBand(phSubSys, hControl, i, &bandList[i]);
            if (err != 0)
                  break;
      }

      if (err && (err != HPI_ERROR_INVALID_OBJ_INDEX))
            return -EIO;

      return i;
}

static int snd_asihpi_tuner_band_info(struct snd_kcontrol *kcontrol,
                          struct snd_ctl_elem_info *uinfo)
{
      u16 tunerBands[HPI_TUNER_BAND_LAST];
      int numBands = 0;

      numBands = asihpi_tuner_band_query(kcontrol, tunerBands,
                        HPI_TUNER_BAND_LAST);

      if (numBands < 0)
            return numBands;

      uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
      uinfo->count = 1;
      uinfo->value.enumerated.items = numBands;

      if (numBands > 0) {
            if (uinfo->value.enumerated.item >=
                              uinfo->value.enumerated.items)
                  uinfo->value.enumerated.item =
                        uinfo->value.enumerated.items - 1;

            strcpy(uinfo->value.enumerated.name,
                  asihpi_tuner_band_names[
                        tunerBands[uinfo->value.enumerated.item]]);

      }
      return 0;
}

static int snd_asihpi_tuner_band_get(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol)
{
      u32 hControl = kcontrol->private_value;
      /*
      struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
      */
      u16 band, idx;
      u16 tunerBands[HPI_TUNER_BAND_LAST];
      u32 numBands = 0;

      numBands = asihpi_tuner_band_query(kcontrol, tunerBands,
                        HPI_TUNER_BAND_LAST);

      HPI_Tuner_GetBand(phSubSys, hControl, &band);

      ucontrol->value.enumerated.item[0] = -1;
      for (idx = 0; idx < HPI_TUNER_BAND_LAST; idx++)
            if (tunerBands[idx] == band) {
                  ucontrol->value.enumerated.item[0] = idx;
                  break;
            }

      return 0;
}

static int snd_asihpi_tuner_band_put(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol)
{
      /*
      struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
      */
      u32 hControl = kcontrol->private_value;
      u16 band;
      u16 tunerBands[HPI_TUNER_BAND_LAST];
      u32 numBands = 0;

      numBands = asihpi_tuner_band_query(kcontrol, tunerBands,
                  HPI_TUNER_BAND_LAST);

      band = tunerBands[ucontrol->value.enumerated.item[0]];
      HPI_Tuner_SetBand(phSubSys, hControl, band);

      return 1;
}

/* Freq */

static int snd_asihpi_tuner_freq_info(struct snd_kcontrol *kcontrol,
                          struct snd_ctl_elem_info *uinfo)
{
      u32 hControl = kcontrol->private_value;
      u16 err;
      u16 tunerBands[HPI_TUNER_BAND_LAST];
      u16 numBands = 0, band_iter, idx;
      u32 freqRange[3], tempFreqRange[3];

      numBands = asihpi_tuner_band_query(kcontrol, tunerBands,
                  HPI_TUNER_BAND_LAST);

      freqRange[0] = INT_MAX;
      freqRange[1] = 0;
      freqRange[2] = INT_MAX;

      for (band_iter = 0; band_iter < numBands; band_iter++) {
            for (idx = 0; idx < 3; idx++) {
                  err = HPI_Tuner_QueryFrequency(phSubSys, hControl,
                        idx, tunerBands[band_iter],
                        &tempFreqRange[idx]);
                  if (err != 0)
                        return err;
            }

            /* skip band with bogus stepping */
            if (tempFreqRange[2] <= 0)
                  continue;

            if (tempFreqRange[0] < freqRange[0])
                  freqRange[0] = tempFreqRange[0];
            if (tempFreqRange[1] > freqRange[1])
                  freqRange[1] = tempFreqRange[1];
            if (tempFreqRange[2] < freqRange[2])
                  freqRange[2] = tempFreqRange[2];
      }

      uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
      uinfo->count = 1;
      uinfo->value.integer.min = ((int)freqRange[0]);
      uinfo->value.integer.max = ((int)freqRange[1]);
      uinfo->value.integer.step = ((int)freqRange[2]);
      return 0;
}

static int snd_asihpi_tuner_freq_get(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol)
{
      u32 hControl = kcontrol->private_value;
      u32 freq;

      HPI_Tuner_GetFrequency(phSubSys, hControl, &freq);
      ucontrol->value.integer.value[0] = freq;

      return 0;
}

static int snd_asihpi_tuner_freq_put(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol)
{
      u32 hControl = kcontrol->private_value;
      u32 freq;

      freq = ucontrol->value.integer.value[0];
      HPI_Tuner_SetFrequency(phSubSys, hControl, freq);

      return 1;
}

/* Tuner control group initializer  */
static int __devinit snd_asihpi_tuner_add(struct snd_card_asihpi *asihpi,
                              struct hpi_control *asihpi_control)
{
      struct snd_card *card = asihpi->card;
      struct snd_kcontrol_new snd_control;

      snd_control.private_value = asihpi_control->hControl;
      snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;

      if (HPI_Tuner_GetGain(phSubSys, asihpi_control->hControl, NULL) == 0) {
            asihpi_ctl_init(&snd_control, asihpi_control, "Gain");
            snd_control.info = snd_asihpi_tuner_gain_info;
            snd_control.get = snd_asihpi_tuner_gain_get;
            snd_control.put = snd_asihpi_tuner_gain_put;

            if (ctl_add(card, &snd_control, asihpi) < 0)
                  return -EINVAL;
      }

      asihpi_ctl_init(&snd_control, asihpi_control, "Band");
      snd_control.info = snd_asihpi_tuner_band_info;
      snd_control.get = snd_asihpi_tuner_band_get;
      snd_control.put = snd_asihpi_tuner_band_put;

      if (ctl_add(card, &snd_control, asihpi) < 0)
            return -EINVAL;

      asihpi_ctl_init(&snd_control, asihpi_control, "Freq");
      snd_control.info = snd_asihpi_tuner_freq_info;
      snd_control.get = snd_asihpi_tuner_freq_get;
      snd_control.put = snd_asihpi_tuner_freq_put;

      return (ctl_add(card, &snd_control, asihpi));
}

/*------------------------------------------------------------
   Meter controls
 ------------------------------------------------------------*/
#define ASIHPI_LINEAR_METERS 1
#define ASIHPI_LOG_METERS 0

static int snd_asihpi_meter_info(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_info *uinfo)
{
      uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
      uinfo->count = HPI_MAX_CHANNELS;
#if ASIHPI_LINEAR_METERS
      uinfo->value.integer.min = 0;
      uinfo->value.integer.max = 0x7FFFFFFF;
#else
      uinfo->value.integer.min = HPI_METER_MINIMUM;
#if ASIHPI_LOG_METERS
      uinfo->value.integer.max = 0;
#else
      uinfo->value.integer.max = 32767;
#endif
#endif

      return 0;
}

#if ASIHPI_LINEAR_METERS
/* linear values for 10dB steps */
static int log2lin[] = {
      0x7FFFFFFF, /* 0dB */
      679093956,
      214748365,
       67909396,
       21474837,
        6790940,
        2147484, /* -60dB */
         679094,
         214748, /* -80 */
          67909,
          21475, /* -100 */
           6791,
           2147,
            679,
            214,
             68,
             21,
            7,
            2
};
#endif

static int snd_asihpi_meter_get(struct snd_kcontrol *kcontrol,
                        struct snd_ctl_elem_value *ucontrol)
{
      u32 hControl = kcontrol->private_value;
      short anGain0_01dB[HPI_MAX_CHANNELS], i;
      u16 err;

      //err = HPI_MeterGetRms(phSubSys, hControl, anGain0_01dB);
      err = HPI_MeterGetPeak(phSubSys, hControl, anGain0_01dB);

      for (i = 0; i < HPI_MAX_CHANNELS; i++) {
#if ASIHPI_LINEAR_METERS
            if (err) {
                  ucontrol->value.integer.value[i] = 0;
            } else if (anGain0_01dB[i] >= 0) {
                  ucontrol->value.integer.value[i] =
                        anGain0_01dB[i] << 16;
            } else {
                  /* -ve is log value in millibels < -60dB,
                  * convert to (roughly!) linear,
                  */
                  ucontrol->value.integer.value[i] =
                              log2lin[anGain0_01dB[i] / -1000];
            }
#elif ASIHPI_LOG_METERS
            /* convert 0..32767 level to 0.01dB (20log10), 0 is -100.00dB */
            ucontrol->value.integer.value[i] = anGain0_01dB[i];
            if ((anGain0_01dB[i] == 0) || err)
                  ucontrol->value.integer.value[i] = HPI_METER_MINIMUM;
            else if (anGain0_01dB[i] > 0) {
      /* need fixed point log calculation convert 32..32767 to -60.00..0.00dB
       * actually should always be > 32  32767 => 0dB, 32.767=>-60dB
       * anGain0_01dB[i] =
       *     (short)((float)(20*log10((float)nLinear/32767.0))*100.0);
       * units are 0.01dB
       */
             }
             /* else don't have to touch the LogValue when it is -ve
             *  since it is already a log value
             */
#else /* RAW_METERS */
            ucontrol->value.integer.value[i] = anGain0_01dB[i];
            if (err)
                  ucontrol->value.integer.value[i] = 0;
#endif
      }

      return 0;
}

static int __devinit snd_asihpi_meter_add(struct snd_card_asihpi *asihpi,
                              struct hpi_control *asihpi_control, int subidx)
{
      struct snd_card *card = asihpi->card;
      struct snd_kcontrol_new snd_control;

      asihpi_ctl_init(&snd_control, asihpi_control, "Meter");
      snd_control.access =
          SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ;
      snd_control.info = snd_asihpi_meter_info;
      snd_control.get = snd_asihpi_meter_get;

      snd_control.index = subidx;

      return ctl_add(card, &snd_control, asihpi);
}

/*------------------------------------------------------------
   Multiplexer controls
 ------------------------------------------------------------*/
static int snd_card_asihpi_mux_count_sources(struct snd_kcontrol *snd_control)
{
      u32 hControl = snd_control->private_value;
      struct hpi_control asihpi_control;
      int s, err;
      for (s = 0; s < 32; s++) {
            err = HPI_Multiplexer_QuerySource(phSubSys, hControl, s,
                                      &asihpi_control.
                                      wSrcNodeType,
                                      &asihpi_control.
                                      wSrcNodeIndex);
            if (err)
                  break;
      }
      return (s);
}

static int snd_asihpi_mux_info(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_info *uinfo)
{
      int err;
      u16 wSrcNodeType, wSrcNodeIndex;
      u32 hControl = kcontrol->private_value;

      uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
      uinfo->count = 1;
      uinfo->value.enumerated.items =
          snd_card_asihpi_mux_count_sources(kcontrol);

      if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
            uinfo->value.enumerated.item =
                uinfo->value.enumerated.items - 1;

      err =
          HPI_Multiplexer_QuerySource(phSubSys, hControl,
                              uinfo->value.enumerated.item,
                              &wSrcNodeType, &wSrcNodeIndex);

      sprintf(uinfo->value.enumerated.name, "%s %d",
            asihpi_src_names[wSrcNodeType - HPI_SOURCENODE_BASE],
            wSrcNodeIndex);
      return 0;
}

static int snd_asihpi_mux_get(struct snd_kcontrol *kcontrol,
                        struct snd_ctl_elem_value *ucontrol)
{
      u32 hControl = kcontrol->private_value;
      u16 wSourceType, wSourceIndex;
      u16 wSrcNodeType, wSrcNodeIndex;
      int s;

      HPI_Multiplexer_GetSource(phSubSys, hControl, &wSourceType,
                          &wSourceIndex);
      /* Should cache this search result! */
      for (s = 0; s < 16; s++) {
            if (HPI_Multiplexer_QuerySource(phSubSys, hControl, s,
                                  &wSrcNodeType, &wSrcNodeIndex))
                  break;

            if ((wSourceType == wSrcNodeType)
                && (wSourceIndex == wSrcNodeIndex)) {
                  ucontrol->value.enumerated.item[0] = s;
                  return 0;
            }
      }
      return -EINVAL;
}

static int snd_asihpi_mux_put(struct snd_kcontrol *kcontrol,
                        struct snd_ctl_elem_value *ucontrol)
{
      int change;
      u32 hControl = kcontrol->private_value;
      u16 wSourceType, wSourceIndex;

      change = 1;

      HPI_Multiplexer_QuerySource(phSubSys, hControl,
                            ucontrol->value.enumerated.item[0],
                            &wSourceType, &wSourceIndex);
      HPI_Multiplexer_SetSource(phSubSys, hControl, wSourceType,
                          wSourceIndex);
      return change;
}


static int  __devinit snd_asihpi_mux_add(struct snd_card_asihpi *asihpi,
                              struct hpi_control *asihpi_control)
{
      struct snd_card *card = asihpi->card;
      struct snd_kcontrol_new snd_control;

#if ASI_STYLE_NAMES
      asihpi_ctl_init(&snd_control, asihpi_control, "Multiplexer");
#else
      asihpi_ctl_init(&snd_control, asihpi_control, "Route");
#endif
      snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
      snd_control.info = snd_asihpi_mux_info;
      snd_control.get = snd_asihpi_mux_get;
      snd_control.put = snd_asihpi_mux_put;

      return (ctl_add(card, &snd_control, asihpi));

}

/*------------------------------------------------------------
   Channel mode controls
 ------------------------------------------------------------*/
static int snd_asihpi_cmode_info(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_info *uinfo)
{
      static char *mode_names[HPI_CHANNEL_MODE_LAST] = {
            "Normal", "Swap", "FromLeft", "FromRight", "ToLeft", "ToRight"
      };

      u32 hControl = kcontrol->private_value;
      u16 wMode;
      int i;

      /* HPI channel mode values can be from 1 to 6
      Some adapters only support a contiguous subset
      */
      for (i = 0; i < HPI_CHANNEL_MODE_LAST; i++)
            if (HPI_ChannelMode_QueryMode(phSubSys,  hControl, i, &wMode))
                  break;

      uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
      uinfo->count = 1;
      uinfo->value.enumerated.items = i;

      if (uinfo->value.enumerated.item >= i)
            uinfo->value.enumerated.item = i - 1;

      strcpy(uinfo->value.enumerated.name,
             mode_names[uinfo->value.enumerated.item]);

      return 0;
}

static int snd_asihpi_cmode_get(struct snd_kcontrol *kcontrol,
                        struct snd_ctl_elem_value *ucontrol)
{
      u32 hControl = kcontrol->private_value;
      u16 wMode;

      if (HPI_ChannelModeGet(phSubSys, hControl, &wMode))
            wMode = 1;

      ucontrol->value.enumerated.item[0] = wMode - 1;

      return 0;
}

static int snd_asihpi_cmode_put(struct snd_kcontrol *kcontrol,
                        struct snd_ctl_elem_value *ucontrol)
{
      int change;
      u32 hControl = kcontrol->private_value;

      change = 1;

      HPI_ChannelModeSet(phSubSys, hControl,
                     ucontrol->value.enumerated.item[0] + 1);
      return change;
}


static int __devinit snd_asihpi_cmode_add(struct snd_card_asihpi *asihpi,
                              struct hpi_control *asihpi_control)
{
      struct snd_card *card = asihpi->card;
      struct snd_kcontrol_new snd_control;

      asihpi_ctl_init(&snd_control, asihpi_control, "Channel Mode");
      snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
      snd_control.info = snd_asihpi_cmode_info;
      snd_control.get = snd_asihpi_cmode_get;
      snd_control.put = snd_asihpi_cmode_put;

      return (ctl_add(card, &snd_control, asihpi));
}

/*------------------------------------------------------------
   Sampleclock source  controls
 ------------------------------------------------------------*/

static char *sampleclock_sources[MAX_CLOCKSOURCES] =
    { "N/A", "Local PLL", "AES/EBU Sync", "Word External", "Word Header",
        "SMPTE", "AES/EBU In1", "Auto", "Network", "Invalid",
        "Prev Module",
        "AES/EBU In2", "AES/EBU In3", "AES/EBU In4", "AES/EBU In5",
        "AES/EBU In6", "AES/EBU In7", "AES/EBU In8"};



static int snd_asihpi_clksrc_info(struct snd_kcontrol *kcontrol,
                          struct snd_ctl_elem_info *uinfo)
{
      struct snd_card_asihpi *asihpi =
                  (struct snd_card_asihpi *)(kcontrol->private_data);
      struct clk_cache *clkcache = &asihpi->cc;
      uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
      uinfo->count = 1;
      uinfo->value.enumerated.items = clkcache->count;

      if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
            uinfo->value.enumerated.item =
                        uinfo->value.enumerated.items - 1;

      strcpy(uinfo->value.enumerated.name,
             clkcache->s[uinfo->value.enumerated.item].name);
      return 0;
}

static int snd_asihpi_clksrc_get(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol)
{
      struct snd_card_asihpi *asihpi =
                  (struct snd_card_asihpi *)(kcontrol->private_data);
      struct clk_cache *clkcache = &asihpi->cc;
      u32 hControl = kcontrol->private_value;
      u16 wSource, wIndex = 0;
      int i;

      ucontrol->value.enumerated.item[0] = 0;
      if (HPI_SampleClock_GetSource(phSubSys, hControl, &wSource))
            wSource = 0;

      if (wSource == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT)
            if (HPI_SampleClock_GetSourceIndex(phSubSys, hControl, &wIndex))
                  wIndex = 0;

      for (i = 0; i < clkcache->count; i++)
            if ((clkcache->s[i].source == wSource) &&
                  (clkcache->s[i].index == wIndex))
                  break;

      ucontrol->value.enumerated.item[0] = i;

      return 0;
}

static int snd_asihpi_clksrc_put(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol)
{
      struct snd_card_asihpi *asihpi =
                  (struct snd_card_asihpi *)(kcontrol->private_data);
      struct clk_cache *clkcache = &asihpi->cc;
      int change, item;
      u32 hControl = kcontrol->private_value;

      change = 1;
      item = ucontrol->value.enumerated.item[0];
      if (item >= clkcache->count)
            item = clkcache->count-1;

      HPI_SampleClock_SetSource(phSubSys, hControl, clkcache->s[item].source);
      HPI_SampleClock_SetSourceIndex(phSubSys,
                  hControl, clkcache->s[item].index);
      return change;
}

/*------------------------------------------------------------
   Clkrate controls
 ------------------------------------------------------------*/
/* Need to change this to enumerated control with list of rates */
static int snd_asihpi_clklocal_info(struct snd_kcontrol *kcontrol,
                           struct snd_ctl_elem_info *uinfo)
{
      uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
      uinfo->count = 1;
      uinfo->value.integer.min = 8000;
      uinfo->value.integer.max = 192000;
      uinfo->value.integer.step = 100;

      return 0;
}

static int snd_asihpi_clklocal_get(struct snd_kcontrol *kcontrol,
                          struct snd_ctl_elem_value *ucontrol)
{
      u32 hControl = kcontrol->private_value;
      u32 dwRate;

      HPI_SampleClock_GetLocalRate(phSubSys, hControl, &dwRate);
      ucontrol->value.integer.value[0] = dwRate;
      return 0;
}

static int snd_asihpi_clklocal_put(struct snd_kcontrol *kcontrol,
                          struct snd_ctl_elem_value *ucontrol)
{
      int change;
      u32 hControl = kcontrol->private_value;

      /*  change = asihpi->mixer_clkrate[addr][0] != left ||
         asihpi->mixer_clkrate[addr][1] != right;
       */
      change = 1;
      HPI_SampleClock_SetLocalRate(phSubSys, hControl,
                              ucontrol->value.integer.value[0]);
      return change;
}

static int snd_asihpi_clkrate_info(struct snd_kcontrol *kcontrol,
                           struct snd_ctl_elem_info *uinfo)
{
      uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
      uinfo->count = 1;
      uinfo->value.integer.min = 8000;
      uinfo->value.integer.max = 192000;
      uinfo->value.integer.step = 100;

      return 0;
}

static int snd_asihpi_clkrate_get(struct snd_kcontrol *kcontrol,
                          struct snd_ctl_elem_value *ucontrol)
{
      u32 hControl = kcontrol->private_value;
      u32 dwRate;

      HPI_SampleClock_GetSampleRate(phSubSys, hControl, &dwRate);
      ucontrol->value.integer.value[0] = dwRate;
      return 0;
}

static int __devinit snd_asihpi_sampleclock_add(struct snd_card_asihpi *asihpi,
                              struct hpi_control *asihpi_control)
{
      struct snd_card *card = asihpi->card;
      struct snd_kcontrol_new snd_control;

      struct clk_cache *clkcache = &asihpi->cc;
      u32 hSC =  asihpi_control->hControl;
      int hasAesIn = 0;
      int i, j;
      u16 wSource;

      snd_control.private_value = asihpi_control->hControl;

      clkcache->has_local = 0;

      for (i = 0; i <= HPI_SAMPLECLOCK_SOURCE_LAST; i++) {
            if  (HPI_SampleClock_QuerySource(phSubSys, hSC,
                        i, &wSource))
                  break;
            clkcache->s[i].source = wSource;
            clkcache->s[i].index = 0;
            clkcache->s[i].name = sampleclock_sources[wSource];
            if (wSource == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT)
                  hasAesIn = 1;
            if (wSource == HPI_SAMPLECLOCK_SOURCE_LOCAL)
                  clkcache->has_local = 1;
      }
      if (hasAesIn)
            /* already will have picked up index 0 above */
            for (j = 1; j < 8; j++) {
                  if (HPI_SampleClock_QuerySourceIndex(phSubSys, hSC,
                        j, HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT,
                        &wSource))
                        break;
                  clkcache->s[i].source =
                        HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT;
                  clkcache->s[i].index = j;
                  clkcache->s[i].name = sampleclock_sources[
                              j+HPI_SAMPLECLOCK_SOURCE_LAST];
                  i++;
            }
      clkcache->count = i;

      asihpi_ctl_init(&snd_control, asihpi_control, "Source");
      snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE ;
      snd_control.info = snd_asihpi_clksrc_info;
      snd_control.get = snd_asihpi_clksrc_get;
      snd_control.put = snd_asihpi_clksrc_put;
      if (ctl_add(card, &snd_control, asihpi) < 0)
            return -EINVAL;


      if (clkcache->has_local) {
            asihpi_ctl_init(&snd_control, asihpi_control, "LocalRate");
            snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE ;
            snd_control.info = snd_asihpi_clklocal_info;
            snd_control.get = snd_asihpi_clklocal_get;
            snd_control.put = snd_asihpi_clklocal_put;


            if (ctl_add(card, &snd_control, asihpi) < 0)
                  return -EINVAL;
      }

      asihpi_ctl_init(&snd_control, asihpi_control, "Rate");
      snd_control.access =
          SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ;
      snd_control.info = snd_asihpi_clkrate_info;
      snd_control.get = snd_asihpi_clkrate_get;

      return (ctl_add(card, &snd_control, asihpi));
}
/*------------------------------------------------------------
   Mixer
 ------------------------------------------------------------*/

static int __devinit snd_card_asihpi_mixer_new(struct snd_card_asihpi *asihpi)
{
      struct snd_card *card = asihpi->card;
      unsigned int idx = 0;
      unsigned int subindex = 0;
      int err;
      struct hpi_control asihpi_control, prev_control;

      if (snd_BUG_ON(!asihpi))
            return -EINVAL;
      strcpy(card->mixername, "Asihpi Mixer");

      err =
          HPI_MixerOpen(phSubSys, asihpi->wAdapterIndex,
                    &asihpi->hMixer);
      HPI_HandleError(err);
      if (err)
            return -err;

      for (idx = 0; idx < 2000; idx++) {
            err = HPI_MixerGetControlByIndex(phSubSys, asihpi->hMixer,
                                     idx,
                                     &asihpi_control.wSrcNodeType,
                                     &asihpi_control.wSrcNodeIndex,
                                     &asihpi_control.wDstNodeType,
                                     &asihpi_control.wDstNodeIndex,
                                     &asihpi_control.wControlType,
                                     &asihpi_control.hControl);
            if (err) {
                  if (err == HPI_ERROR_CONTROL_DISABLED) {
                        if (mixer_dump)
                              snd_printk(KERN_INFO
                                       "Disabled HPI Control(%d)\n",
                                       idx);
                        continue;
                  } else
                        break;

            }

            asihpi_control.wSrcNodeType -= HPI_SOURCENODE_BASE;
            asihpi_control.wDstNodeType -= HPI_DESTNODE_BASE;

            /* ASI50xx in SSX mode has multiple meters on the same node.
               Use subindex to create distinct ALSA controls
               for any duplicated controls.
            */
            if ((asihpi_control.wControlType == prev_control.wControlType) &&
                (asihpi_control.wSrcNodeType == prev_control.wSrcNodeType) &&
                (asihpi_control.wSrcNodeIndex == prev_control.wSrcNodeIndex) &&
                (asihpi_control.wDstNodeType == prev_control.wDstNodeType) &&
                (asihpi_control.wDstNodeIndex == prev_control.wDstNodeIndex))
                  subindex++;
            else
                  subindex = 0;

            prev_control = asihpi_control;

            switch (asihpi_control.wControlType) {
            case HPI_CONTROL_VOLUME:
                  err = snd_asihpi_volume_add(asihpi, &asihpi_control);
                  break;
            case HPI_CONTROL_LEVEL:
                  err = snd_asihpi_level_add(asihpi, &asihpi_control);
                  break;
            case HPI_CONTROL_MULTIPLEXER:
                  err = snd_asihpi_mux_add(asihpi, &asihpi_control);
                  break;
            case HPI_CONTROL_CHANNEL_MODE:
                  err = snd_asihpi_cmode_add(asihpi, &asihpi_control);
                  break;
            case HPI_CONTROL_METER:
                  err = snd_asihpi_meter_add(asihpi, &asihpi_control, subindex);
                  break;
            case HPI_CONTROL_SAMPLECLOCK:
                  err = snd_asihpi_sampleclock_add(
                                    asihpi, &asihpi_control);
                  break;
            case HPI_CONTROL_CONNECTION:  /* ignore these */
                  continue;
            case HPI_CONTROL_TUNER:
                  err = snd_asihpi_tuner_add(asihpi, &asihpi_control);
                  break;
            case HPI_CONTROL_AESEBU_TRANSMITTER:
                  err = snd_asihpi_aesebu_tx_add(asihpi, &asihpi_control);
                  break;
            case HPI_CONTROL_AESEBU_RECEIVER:
                  err = snd_asihpi_aesebu_rx_add(asihpi, &asihpi_control);
                  break;
            case HPI_CONTROL_VOX:
            case HPI_CONTROL_BITSTREAM:
            case HPI_CONTROL_MICROPHONE:
            case HPI_CONTROL_PARAMETRIC_EQ:
            case HPI_CONTROL_COMPANDER:
            default:
                  if (mixer_dump)
                        snd_printk(KERN_INFO
                              "Untranslated HPI Control"
                              "(%d) %d %d %d %d %d\n",
                              idx,
                              asihpi_control.wControlType,
                              asihpi_control.wSrcNodeType,
                              asihpi_control.wSrcNodeIndex,
                              asihpi_control.wDstNodeType,
                              asihpi_control.wDstNodeIndex);
                  continue;
            };
            if (err < 0)
                  return err;
      }
      if (HPI_ERROR_INVALID_OBJ_INDEX != err)
            HPI_HandleError(err);

      snd_printk(KERN_INFO "%d mixer controls found\n", idx);

      return 0;
}

/*------------------------------------------------------------
   /proc interface
 ------------------------------------------------------------*/

static void
snd_asihpi_proc_read(struct snd_info_entry *entry,
                  struct snd_info_buffer *buffer)
{
      struct snd_card_asihpi *asihpi = entry->private_data;
      u16 wVersion;
      u32 hControl;
      u32 dwRate = 0;
      u16 wSource = 0;
      int err;

      snd_iprintf(buffer, "ASIHPI driver proc file\n");
      snd_iprintf(buffer,
            "Adapter ID=%4X\nIndex=%d\n"
            "NumOutStreams=%d\nNumInStreams=%d\n",
            asihpi->wType, asihpi->wAdapterIndex,
            asihpi->wNumOutStreams, asihpi->wNumInStreams);

      wVersion = asihpi->wVersion;
      snd_iprintf(buffer,
            "Serial#=%d\nHw Version %c%d\nDSP code version %03d\n",
            asihpi->dwSerialNumber, ((wVersion >> 3) & 0xf) + 'A',
            wVersion & 0x7,
            ((wVersion >> 13) * 100) + ((wVersion >> 7) & 0x3f));

      err = HPI_MixerGetControl(phSubSys, asihpi->hMixer,
                          HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
                          HPI_CONTROL_SAMPLECLOCK, &hControl);

      if (!err) {
            err = HPI_SampleClock_GetSampleRate(phSubSys,
                              hControl, &dwRate);
            err += HPI_SampleClock_GetSource(phSubSys, hControl, &wSource);

            if (!err)
                  snd_iprintf(buffer, "SampleClock=%dHz, source %s\n",
                  dwRate, sampleclock_sources[wSource]);
      }

}


static void __devinit snd_asihpi_proc_init(struct snd_card_asihpi *asihpi)
{
      struct snd_info_entry *entry;

      if (!snd_card_proc_new(asihpi->card, "info", &entry))
            snd_info_set_text_ops(entry, asihpi, snd_asihpi_proc_read);
}

/*------------------------------------------------------------
   HWDEP
 ------------------------------------------------------------*/

static int snd_asihpi_hpi_open(struct snd_hwdep *hw, struct file *file)
{
      if (enable_hpi_hwdep)
            return 0;
      else
            return -ENODEV;

}

static int snd_asihpi_hpi_release(struct snd_hwdep *hw, struct file *file)
{
      if (enable_hpi_hwdep)
            return asihpi_hpi_release(file);
      else
            return -ENODEV;
}

static int snd_asihpi_hpi_ioctl(struct snd_hwdep *hw, struct file *file,
                        unsigned int cmd, unsigned long arg)
{
      if (enable_hpi_hwdep)
            return asihpi_hpi_ioctl(file, cmd, arg);
      else
            return -ENODEV;
}


/* results in /dev/snd/hwC#D0 file for each card with index #
   also /proc/asound/hwdep will contain '#-00: asihpi (HPI) for each card'
*/
static int __devinit snd_asihpi_hpi_new(struct snd_card_asihpi *asihpi,
      int device, struct snd_hwdep **rhwdep)
{
      struct snd_hwdep *hw;
      int err;

      if (rhwdep)
            *rhwdep = NULL;
      err = snd_hwdep_new(asihpi->card, "HPI", device, &hw);
      if (err < 0)
            return err;
      strcpy(hw->name, "asihpi (HPI)");
      hw->iface = SNDRV_HWDEP_IFACE_LAST;
      hw->ops.open = snd_asihpi_hpi_open;
      hw->ops.ioctl = snd_asihpi_hpi_ioctl;
      hw->ops.release = snd_asihpi_hpi_release;
      hw->private_data = asihpi;
      if (rhwdep)
            *rhwdep = hw;
      return 0;
}

/*------------------------------------------------------------
   CARD
 ------------------------------------------------------------*/
static int __devinit snd_asihpi_probe(struct pci_dev *pci_dev,
                               const struct pci_device_id *pci_id)
{
      int err;

      u16 wVersion;
      int pcm_substreams;

      struct hpi_adapter *hpi_card;
      struct snd_card *card;
      struct snd_card_asihpi *asihpi;

      u32 hControl;
      u32 hStream;

      static int dev;
      if (dev >= SNDRV_CARDS)
            return -ENODEV;

      /* Should this be enable[hpi_card->index] ? */
      if (!enable[dev]) {
            dev++;
            return -ENOENT;
      }

      err = asihpi_adapter_probe(pci_dev, pci_id);
      if (err < 0)
            return err;

      hpi_card = pci_get_drvdata(pci_dev);

      /* first try to give the card the same index as its hardware index */
      card = snd_card_new(hpi_card->index,
                  id[hpi_card->index], THIS_MODULE,
                  sizeof(struct snd_card_asihpi));
      if (card == NULL) {
            /* if that fails, try the default index==next available */
            card =
                snd_card_new(index[dev], id[dev],
                        THIS_MODULE,
                        sizeof(struct snd_card_asihpi));
            if (card == NULL)
                  return -ENOMEM;
            snd_printk(KERN_WARNING
                  "**** WARNING **** Adapter index %d->ALSA index %d\n",
                  hpi_card->index, card->number);
      }

      asihpi = (struct snd_card_asihpi *) card->private_data;
      asihpi->card = card;
      asihpi->pci = hpi_card->pci;
      asihpi->wAdapterIndex = hpi_card->index;
      HPI_HandleError(HPI_AdapterGetInfo(phSubSys,
                         asihpi->wAdapterIndex,
                         &asihpi->wNumOutStreams,
                         &asihpi->wNumInStreams,
                         &asihpi->wVersion,
                         &asihpi->dwSerialNumber, &asihpi->wType));

      wVersion = asihpi->wVersion;
      snd_printk(KERN_INFO "Adapter ID=%4X Index=%d NumOutStreams=%d "
                  "NumInStreams=%d S/N=%d\n"
                  "Hw Version %c%d DSP code version %03d\n",
                  asihpi->wType, asihpi->wAdapterIndex,
                  asihpi->wNumOutStreams,
                  asihpi->wNumInStreams, asihpi->dwSerialNumber,
                  ((wVersion >> 3) & 0xf) + 'A',
                  wVersion & 0x7,
                  ((wVersion >> 13) * 100) + ((wVersion >> 7) & 0x3f));

      pcm_substreams = asihpi->wNumOutStreams;
      if (pcm_substreams < asihpi->wNumInStreams)
            pcm_substreams = asihpi->wNumInStreams;

      HPI_HandleError(HPI_InStreamOpen(phSubSys, asihpi->wAdapterIndex,
                       0, &hStream));

      err = HPI_InStreamGroupAdd(phSubSys, hStream, hStream);
      asihpi->support_grouping = (!err);

      err = HPI_InStreamHostBufferFree(phSubSys, hStream);
      asihpi->support_mmap = (!err);

      asihpi->support_mrx = (((asihpi->wType & 0xFF00) == 0x8900) ||
                  ((asihpi->wType & 0xF000) == 0x6000));


      err = HPI_AdapterGetProperty(phSubSys, asihpi->wAdapterIndex,
            HPI_ADAPTER_PROPERTY_CURCHANNELS,
            &asihpi->in_max_chans, &asihpi->out_max_chans);
      if (err) {
            asihpi->in_max_chans = 2;
            asihpi->out_max_chans = 2;
      }

      printk(KERN_INFO "Supports mmap:%d grouping:%d mrx:%d\n",
                  asihpi->support_mmap,
                  asihpi->support_grouping,
                  asihpi->support_mrx
            );

      HPI_InStreamClose(phSubSys, hStream);

      err = snd_card_asihpi_pcm_new(asihpi, 0, pcm_substreams);
      if (err < 0)
            goto __nodev;
      err = snd_card_asihpi_mixer_new(asihpi);
      if (err < 0)
            goto __nodev;

      err = HPI_MixerGetControl(phSubSys, asihpi->hMixer,
                          HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
                          HPI_CONTROL_SAMPLECLOCK, &hControl);

      if (!err)
            err = HPI_SampleClock_SetLocalRate(phSubSys,
                        hControl, adapter_fs);

      snd_asihpi_proc_init(asihpi);

      /* always create, can be enabled or disabled dynamically
          by enable_hwdep  module param*/
      snd_asihpi_hpi_new(asihpi, 0, NULL);

      if (asihpi->support_mmap)
            strcpy(card->driver, "ASIHPI-MMAP");
      else
            strcpy(card->driver, "ASIHPI");

      sprintf(card->shortname, "AudioScience ASI%4X", asihpi->wType);
      sprintf(card->longname, "%s %i",
                  card->shortname, asihpi->wAdapterIndex);
      err = snd_card_register(card);
      if (!err) {
            hpi_card->snd_card_asihpi = card;
            dev++;
            return 0;
      }
__nodev:
      snd_card_free(card);
      return err;

}

static void __devexit snd_asihpi_remove(struct pci_dev *pci_dev)
{
      struct hpi_adapter *hpi_card = pci_get_drvdata(pci_dev);

      snd_card_free(hpi_card->snd_card_asihpi);
      hpi_card->snd_card_asihpi = NULL;
      asihpi_adapter_remove(pci_dev);
}

static struct pci_device_id  asihpi_pci_tbl[] = {
      {HPI_PCI_VENDOR_ID_TI, HPI_ADAPTER_DSP6205,
            HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
            (kernel_ulong_t)HPI_6205},
      {HPI_PCI_VENDOR_ID_TI, HPI_ADAPTER_PCI2040,
            HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
            (kernel_ulong_t)HPI_6000},
      {0,}
};
MODULE_DEVICE_TABLE(pci, asihpi_pci_tbl);

static struct pci_driver driver = {
      .name = "asihpi",
      .id_table = asihpi_pci_tbl,
      .probe = snd_asihpi_probe,
      .remove = __devexit_p(snd_asihpi_remove),
#ifdef CONFIG_PM
/*    .suspend = snd_asihpi_suspend,
      .resume = snd_asihpi_resume, */
#endif
};

static int __init snd_asihpi_init(void)
{
      asihpi_init();
      return pci_register_driver(&driver);
}

static void __exit snd_asihpi_exit(void)
{

      pci_unregister_driver(&driver);
      asihpi_exit();
}

module_init(snd_asihpi_init)
module_exit(snd_asihpi_exit)


Generated by  Doxygen 1.6.0   Back to index