Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/sound/pci/cs46xx/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 105 kB image not shown  

Quelle  cs46xx_lib.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
 *                   Abramo Bagnara <abramo@alsa-project.org>
 *                   Cirrus Logic, Inc.
 *  Routines for control of Cirrus Logic CS461x chips
 *
 *  KNOWN BUGS:
 *    - Sometimes the SPDIF input DSP tasks get's unsynchronized
 *      and the SPDIF get somewhat "distorcionated", or/and left right channel
 *      are swapped. To get around this problem when it happens, mute and unmute 
 *      the SPDIF input mixer control.
 *    - On the Hercules Game Theater XP the amplifier are sometimes turned
 *      off on inadecuate moments which causes distorcions on sound.
 *
 *  TODO:
 *    - Secondary CODEC on some soundcards
 *    - SPDIF input support for other sample rates then 48khz
 *    - Posibility to mix the SPDIF output with analog sources.
 *    - PCM channels for Center and LFE on secondary codec
 *
 *  NOTE: with CONFIG_SND_CS46XX_NEW_DSP unset uses old DSP image (which
 *        is default configuration), no SPDIF, no secondary codec, no
 *        multi channel PCM.  But known to work.
 *
 *  FINALLY: A credit to the developers Tom and Jordan 
 *           at Cirrus for have helping me out with the DSP, however we
 *           still don't have sufficient documentation and technical
 *           references to be able to implement all fancy feutures
 *           supported by the cs46xx DSP's. 
 *           Benny <benny@hostmobility.com>
 */


#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/pm.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/gameport.h>
#include <linux/mutex.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/vmalloc.h>
#include <linux/io.h>

#include <sound/core.h>
#include <sound/control.h>
#include <sound/info.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "cs46xx.h"

#include "cs46xx_lib.h"
#include "dsp_spos.h"

static void amp_voyetra(struct snd_cs46xx *chip, int change);

#ifdef CONFIG_SND_CS46XX_NEW_DSP
static const struct snd_pcm_ops snd_cs46xx_playback_rear_ops;
static const struct snd_pcm_ops snd_cs46xx_playback_indirect_rear_ops;
static const struct snd_pcm_ops snd_cs46xx_playback_clfe_ops;
static const struct snd_pcm_ops snd_cs46xx_playback_indirect_clfe_ops;
static const struct snd_pcm_ops snd_cs46xx_playback_iec958_ops;
static const struct snd_pcm_ops snd_cs46xx_playback_indirect_iec958_ops;
#endif

static const struct snd_pcm_ops snd_cs46xx_playback_ops;
static const struct snd_pcm_ops snd_cs46xx_playback_indirect_ops;
static const struct snd_pcm_ops snd_cs46xx_capture_ops;
static const struct snd_pcm_ops snd_cs46xx_capture_indirect_ops;

static unsigned short snd_cs46xx_codec_read(struct snd_cs46xx *chip,
         unsigned short reg,
         int codec_index)
{
 int count;
 unsigned short result,tmp;
 u32 offset = 0;

 if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX &&
         codec_index != CS46XX_SECONDARY_CODEC_INDEX))
  return 0xffff;

 chip->active_ctrl(chip, 1);

 if (codec_index == CS46XX_SECONDARY_CODEC_INDEX)
  offset = CS46XX_SECONDARY_CODEC_OFFSET;

 /*
 *  1. Write ACCAD = Command Address Register = 46Ch for AC97 register address
 *  2. Write ACCDA = Command Data Register = 470h    for data to write to AC97 
 *  3. Write ACCTL = Control Register = 460h for initiating the write7---55
 *  4. Read ACCTL = 460h, DCV should be reset by now and 460h = 17h
 *  5. if DCV not cleared, break and return error
 *  6. Read ACSTS = Status Register = 464h, check VSTS bit
 */


 snd_cs46xx_peekBA0(chip, BA0_ACSDA + offset);

 tmp = snd_cs46xx_peekBA0(chip, BA0_ACCTL);
 if ((tmp & ACCTL_VFRM) == 0) {
  dev_warn(chip->card->dev, "ACCTL_VFRM not set 0x%x\n", tmp);
  snd_cs46xx_pokeBA0(chip, BA0_ACCTL, (tmp & (~ACCTL_ESYN)) | ACCTL_VFRM );
  msleep(50);
  tmp = snd_cs46xx_peekBA0(chip, BA0_ACCTL + offset);
  snd_cs46xx_pokeBA0(chip, BA0_ACCTL, tmp | ACCTL_ESYN | ACCTL_VFRM );

 }

 /*
 *  Setup the AC97 control registers on the CS461x to send the
 *  appropriate command to the AC97 to perform the read.
 *  ACCAD = Command Address Register = 46Ch
 *  ACCDA = Command Data Register = 470h
 *  ACCTL = Control Register = 460h
 *  set DCV - will clear when process completed
 *  set CRW - Read command
 *  set VFRM - valid frame enabled
 *  set ESYN - ASYNC generation enabled
 *  set RSTN - ARST# inactive, AC97 codec not reset
 */


 snd_cs46xx_pokeBA0(chip, BA0_ACCAD, reg);
 snd_cs46xx_pokeBA0(chip, BA0_ACCDA, 0);
 if (codec_index == CS46XX_PRIMARY_CODEC_INDEX) {
  snd_cs46xx_pokeBA0(chip, BA0_ACCTL,/* clear ACCTL_DCV */ ACCTL_CRW | 
       ACCTL_VFRM | ACCTL_ESYN |
       ACCTL_RSTN);
  snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_CRW |
       ACCTL_VFRM | ACCTL_ESYN |
       ACCTL_RSTN);
 } else {
  snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_TC |
       ACCTL_CRW | ACCTL_VFRM | ACCTL_ESYN |
       ACCTL_RSTN);
 }

 /*
 *  Wait for the read to occur.
 */

 for (count = 0; count < 1000; count++) {
  /*
 *  First, we want to wait for a short time.
   */

  udelay(10);
  /*
 *  Now, check to see if the read has completed.
 *  ACCTL = 460h, DCV should be reset by now and 460h = 17h
 */

  if (!(snd_cs46xx_peekBA0(chip, BA0_ACCTL) & ACCTL_DCV))
   goto ok1;
 }

 dev_err(chip->card->dev,
  "AC'97 read problem (ACCTL_DCV), reg = 0x%x\n", reg);
 result = 0xffff;
 goto end;
 
 ok1:
 /*
 *  Wait for the valid status bit to go active.
 */

 for (count = 0; count < 100; count++) {
  /*
 *  Read the AC97 status register.
 *  ACSTS = Status Register = 464h
 *  VSTS - Valid Status
 */

  if (snd_cs46xx_peekBA0(chip, BA0_ACSTS + offset) & ACSTS_VSTS)
   goto ok2;
  udelay(10);
 }
 
 dev_err(chip->card->dev,
  "AC'97 read problem (ACSTS_VSTS), codec_index %d, reg = 0x%x\n",
  codec_index, reg);
 result = 0xffff;
 goto end;

 ok2:
 /*
 *  Read the data returned from the AC97 register.
 *  ACSDA = Status Data Register = 474h
 */

#if 0
 dev_dbg(chip->card->dev,
  "e) reg = 0x%x, val = 0x%x, BA0_ACCAD = 0x%x\n", reg,
   snd_cs46xx_peekBA0(chip, BA0_ACSDA),
   snd_cs46xx_peekBA0(chip, BA0_ACCAD));
#endif

 //snd_cs46xx_peekBA0(chip, BA0_ACCAD);
 result = snd_cs46xx_peekBA0(chip, BA0_ACSDA + offset);
 end:
 chip->active_ctrl(chip, -1);
 return result;
}

static unsigned short snd_cs46xx_ac97_read(struct snd_ac97 * ac97,
         unsigned short reg)
{
 struct snd_cs46xx *chip = ac97->private_data;
 unsigned short val;
 int codec_index = ac97->num;

 if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX &&
         codec_index != CS46XX_SECONDARY_CODEC_INDEX))
  return 0xffff;

 val = snd_cs46xx_codec_read(chip, reg, codec_index);

 return val;
}


static void snd_cs46xx_codec_write(struct snd_cs46xx *chip,
       unsigned short reg,
       unsigned short val,
       int codec_index)
{
 int count;

 if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX &&
         codec_index != CS46XX_SECONDARY_CODEC_INDEX))
  return;

 chip->active_ctrl(chip, 1);

 /*
 *  1. Write ACCAD = Command Address Register = 46Ch for AC97 register address
 *  2. Write ACCDA = Command Data Register = 470h    for data to write to AC97
 *  3. Write ACCTL = Control Register = 460h for initiating the write
 *  4. Read ACCTL = 460h, DCV should be reset by now and 460h = 07h
 *  5. if DCV not cleared, break and return error
 */


 /*
 *  Setup the AC97 control registers on the CS461x to send the
 *  appropriate command to the AC97 to perform the read.
 *  ACCAD = Command Address Register = 46Ch
 *  ACCDA = Command Data Register = 470h
 *  ACCTL = Control Register = 460h
 *  set DCV - will clear when process completed
 *  reset CRW - Write command
 *  set VFRM - valid frame enabled
 *  set ESYN - ASYNC generation enabled
 *  set RSTN - ARST# inactive, AC97 codec not reset
         */

 snd_cs46xx_pokeBA0(chip, BA0_ACCAD , reg);
 snd_cs46xx_pokeBA0(chip, BA0_ACCDA , val);
 snd_cs46xx_peekBA0(chip, BA0_ACCTL);

 if (codec_index == CS46XX_PRIMARY_CODEC_INDEX) {
  snd_cs46xx_pokeBA0(chip, BA0_ACCTL, /* clear ACCTL_DCV */ ACCTL_VFRM |
       ACCTL_ESYN | ACCTL_RSTN);
  snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_VFRM |
       ACCTL_ESYN | ACCTL_RSTN);
 } else {
  snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_TC |
       ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN);
 }

 for (count = 0; count < 4000; count++) {
  /*
 *  First, we want to wait for a short time.
 */

  udelay(10);
  /*
 *  Now, check to see if the write has completed.
 *  ACCTL = 460h, DCV should be reset by now and 460h = 07h
 */

  if (!(snd_cs46xx_peekBA0(chip, BA0_ACCTL) & ACCTL_DCV)) {
   goto end;
  }
 }
 dev_err(chip->card->dev,
  "AC'97 write problem, codec_index = %d, reg = 0x%x, val = 0x%x\n",
  codec_index, reg, val);
 end:
 chip->active_ctrl(chip, -1);
}

static void snd_cs46xx_ac97_write(struct snd_ac97 *ac97,
       unsigned short reg,
       unsigned short val)
{
 struct snd_cs46xx *chip = ac97->private_data;
 int codec_index = ac97->num;

 if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX &&
         codec_index != CS46XX_SECONDARY_CODEC_INDEX))
  return;

 snd_cs46xx_codec_write(chip, reg, val, codec_index);
}


/*
 *  Chip initialization
 */


int snd_cs46xx_download(struct snd_cs46xx *chip,
   u32 *src,
                        unsigned long offset,
                        unsigned long len)
{
 void __iomem *dst;
 unsigned int bank = offset >> 16;
 offset = offset & 0xffff;

 if (snd_BUG_ON((offset & 3) || (len & 3)))
  return -EINVAL;
 dst = chip->region.idx[bank+1].remap_addr + offset;
 len /= sizeof(u32);

 /* writel already converts 32-bit value to right endianess */
 while (len-- > 0) {
  writel(*src++, dst);
  dst += sizeof(u32);
 }
 return 0;
}

static inline void memcpy_le32(void *dst, const void *src, unsigned int len)
{
#ifdef __LITTLE_ENDIAN
 memcpy(dst, src, len);
#else
 u32 *_dst = dst;
 const __le32 *_src = src;
 len /= 4;
 while (len-- > 0)
  *_dst++ = le32_to_cpu(*_src++);
#endif
}

#ifdef CONFIG_SND_CS46XX_NEW_DSP

static const char *module_names[CS46XX_DSP_MODULES] = {
 "cwc4630""cwcasync""cwcsnoop""cwcbinhack""cwcdma"
};

MODULE_FIRMWARE("cs46xx/cwc4630");
MODULE_FIRMWARE("cs46xx/cwcasync");
MODULE_FIRMWARE("cs46xx/cwcsnoop");
MODULE_FIRMWARE("cs46xx/cwcbinhack");
MODULE_FIRMWARE("cs46xx/cwcdma");

static void free_module_desc(struct dsp_module_desc *module)
{
 if (!module)
  return;
 kfree(module->module_name);
 kfree(module->symbol_table.symbols);
 if (module->segments) {
  int i;
  for (i = 0; i < module->nsegments; i++)
   kfree(module->segments[i].data);
  kfree(module->segments);
 }
 kfree(module);
}

/* firmware binary format:
 * le32 nsymbols;
 * struct {
 * le32 address;
 * char symbol_name[DSP_MAX_SYMBOL_NAME];
 * le32 symbol_type;
 * } symbols[nsymbols];
 * le32 nsegments;
 * struct {
 * le32 segment_type;
 * le32 offset;
 * le32 size;
 * le32 data[size];
 * } segments[nsegments];
 */


static int load_firmware(struct snd_cs46xx *chip,
    struct dsp_module_desc **module_ret,
    const char *fw_name)
{
 int i, err;
 unsigned int nums, fwlen, fwsize;
 const __le32 *fwdat;
 struct dsp_module_desc *module = NULL;
 const struct firmware *fw;
 char fw_path[32];

 sprintf(fw_path, "cs46xx/%s", fw_name);
 err = request_firmware(&fw, fw_path, &chip->pci->dev);
 if (err < 0)
  return err;
 fwsize = fw->size / 4;
 if (fwsize < 2) {
  err = -EINVAL;
  goto error;
 }

 err = -ENOMEM;
 module = kzalloc(sizeof(*module), GFP_KERNEL);
 if (!module)
  goto error;
 module->module_name = kstrdup(fw_name, GFP_KERNEL);
 if (!module->module_name)
  goto error;

 fwlen = 0;
 fwdat = (const __le32 *)fw->data;
 nums = module->symbol_table.nsymbols = le32_to_cpu(fwdat[fwlen++]);
 if (nums >= 40)
  goto error_inval;
 module->symbol_table.symbols =
  kcalloc(nums, sizeof(struct dsp_symbol_entry), GFP_KERNEL);
 if (!module->symbol_table.symbols)
  goto error;
 for (i = 0; i < nums; i++) {
  struct dsp_symbol_entry *entry =
   &module->symbol_table.symbols[i];
  if (fwlen + 2 + DSP_MAX_SYMBOL_NAME / 4 > fwsize)
   goto error_inval;
  entry->address = le32_to_cpu(fwdat[fwlen++]);
  memcpy(entry->symbol_name, &fwdat[fwlen], DSP_MAX_SYMBOL_NAME - 1);
  fwlen += DSP_MAX_SYMBOL_NAME / 4;
  entry->symbol_type = le32_to_cpu(fwdat[fwlen++]);
 }

 if (fwlen >= fwsize)
  goto error_inval;
 nums = module->nsegments = le32_to_cpu(fwdat[fwlen++]);
 if (nums > 10)
  goto error_inval;
 module->segments =
  kcalloc(nums, sizeof(struct dsp_segment_desc), GFP_KERNEL);
 if (!module->segments)
  goto error;
 for (i = 0; i < nums; i++) {
  struct dsp_segment_desc *entry = &module->segments[i];
  if (fwlen + 3 > fwsize)
   goto error_inval;
  entry->segment_type = le32_to_cpu(fwdat[fwlen++]);
  entry->offset = le32_to_cpu(fwdat[fwlen++]);
  entry->size = le32_to_cpu(fwdat[fwlen++]);
  if (fwlen + entry->size > fwsize)
   goto error_inval;
  entry->data = kmalloc_array(entry->size, 4, GFP_KERNEL);
  if (!entry->data)
   goto error;
  memcpy_le32(entry->data, &fwdat[fwlen], entry->size * 4);
  fwlen += entry->size;
 }

 *module_ret = module;
 release_firmware(fw);
 return 0;

 error_inval:
 err = -EINVAL;
 error:
 free_module_desc(module);
 release_firmware(fw);
 return err;
}

int snd_cs46xx_clear_BA1(struct snd_cs46xx *chip,
                         unsigned long offset,
                         unsigned long len) 
{
 void __iomem *dst;
 unsigned int bank = offset >> 16;
 offset = offset & 0xffff;

 if (snd_BUG_ON((offset & 3) || (len & 3)))
  return -EINVAL;
 dst = chip->region.idx[bank+1].remap_addr + offset;
 len /= sizeof(u32);

 /* writel already converts 32-bit value to right endianess */
 while (len-- > 0) {
  writel(0, dst);
  dst += sizeof(u32);
 }
 return 0;
}

#else /* old DSP image */

struct ba1_struct {
 struct {
  u32 offset;
  u32 size;
 } memory[BA1_MEMORY_COUNT];
 u32 map[BA1_DWORD_SIZE];
};

MODULE_FIRMWARE("cs46xx/ba1");

static int load_firmware(struct snd_cs46xx *chip)
{
 const struct firmware *fw;
 int i, size, err;

 err = request_firmware(&fw, "cs46xx/ba1", &chip->pci->dev);
 if (err < 0)
  return err;
 if (fw->size != sizeof(*chip->ba1)) {
  err = -EINVAL;
  goto error;
 }

 chip->ba1 = vmalloc(sizeof(*chip->ba1));
 if (!chip->ba1) {
  err = -ENOMEM;
  goto error;
 }

 memcpy_le32(chip->ba1, fw->data, sizeof(*chip->ba1));

 /* sanity check */
 size = 0;
 for (i = 0; i < BA1_MEMORY_COUNT; i++)
  size += chip->ba1->memory[i].size;
 if (size > BA1_DWORD_SIZE * 4)
  err = -EINVAL;

 error:
 release_firmware(fw);
 return err;
}

static __maybe_unused int snd_cs46xx_download_image(struct snd_cs46xx *chip)
{
 int idx, err;
 unsigned int offset = 0;
 struct ba1_struct *ba1 = chip->ba1;

 for (idx = 0; idx < BA1_MEMORY_COUNT; idx++) {
  err = snd_cs46xx_download(chip,
       &ba1->map[offset],
       ba1->memory[idx].offset,
       ba1->memory[idx].size);
  if (err < 0)
   return err;
  offset += ba1->memory[idx].size >> 2;
 } 
 return 0;
}
#endif /* CONFIG_SND_CS46XX_NEW_DSP */

/*
 *  Chip reset
 */


static void snd_cs46xx_reset(struct snd_cs46xx *chip)
{
 int idx;

 /*
 *  Write the reset bit of the SP control register.
 */

 snd_cs46xx_poke(chip, BA1_SPCR, SPCR_RSTSP);

 /*
 *  Write the control register.
 */

 snd_cs46xx_poke(chip, BA1_SPCR, SPCR_DRQEN);

 /*
 *  Clear the trap registers.
 */

 for (idx = 0; idx < 8; idx++) {
  snd_cs46xx_poke(chip, BA1_DREG, DREG_REGID_TRAP_SELECT + idx);
  snd_cs46xx_poke(chip, BA1_TWPR, 0xFFFF);
 }
 snd_cs46xx_poke(chip, BA1_DREG, 0);

 /*
 *  Set the frame timer to reflect the number of cycles per frame.
 */

 snd_cs46xx_poke(chip, BA1_FRMT, 0xadf);
}

static int cs46xx_wait_for_fifo(struct snd_cs46xx * chip,int retry_timeout) 
{
 u32 i, status = 0;
 /*
 * Make sure the previous FIFO write operation has completed.
 */

 for(i = 0; i < 50; i++){
  status = snd_cs46xx_peekBA0(chip, BA0_SERBST);
    
  if( !(status & SERBST_WBSY) )
   break;

  mdelay(retry_timeout);
 }
  
 if(status & SERBST_WBSY) {
  dev_err(chip->card->dev,
   "failure waiting for FIFO command to complete\n");
  return -EINVAL;
 }

 return 0;
}

static void snd_cs46xx_clear_serial_FIFOs(struct snd_cs46xx *chip)
{
 int idx, powerdown = 0;
 unsigned int tmp;

 /*
 *  See if the devices are powered down.  If so, we must power them up first
 *  or they will not respond.
 */

 tmp = snd_cs46xx_peekBA0(chip, BA0_CLKCR1);
 if (!(tmp & CLKCR1_SWCE)) {
  snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp | CLKCR1_SWCE);
  powerdown = 1;
 }

 /*
 *  We want to clear out the serial port FIFOs so we don't end up playing
 *  whatever random garbage happens to be in them.  We fill the sample FIFOS
 *  with zero (silence).
 */

 snd_cs46xx_pokeBA0(chip, BA0_SERBWP, 0);

 /*
 *  Fill all 256 sample FIFO locations.
 */

 for (idx = 0; idx < 0xFF; idx++) {
  /*
 *  Make sure the previous FIFO write operation has completed.
 */

  if (cs46xx_wait_for_fifo(chip,1)) {
   dev_dbg(chip->card->dev,
    "failed waiting for FIFO at addr (%02X)\n",
    idx);

   if (powerdown)
    snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp);
          
   break;
  }
  /*
 *  Write the serial port FIFO index.
 */

  snd_cs46xx_pokeBA0(chip, BA0_SERBAD, idx);
  /*
 *  Tell the serial port to load the new value into the FIFO location.
 */

  snd_cs46xx_pokeBA0(chip, BA0_SERBCM, SERBCM_WRC);
 }
 /*
 *  Now, if we powered up the devices, then power them back down again.
 *  This is kinda ugly, but should never happen.
 */

 if (powerdown)
  snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp);
}

static void snd_cs46xx_proc_start(struct snd_cs46xx *chip)
{
 int cnt;

 /*
 *  Set the frame timer to reflect the number of cycles per frame.
 */

 snd_cs46xx_poke(chip, BA1_FRMT, 0xadf);
 /*
 *  Turn on the run, run at frame, and DMA enable bits in the local copy of
 *  the SP control register.
 */

 snd_cs46xx_poke(chip, BA1_SPCR, SPCR_RUN | SPCR_RUNFR | SPCR_DRQEN);
 /*
 *  Wait until the run at frame bit resets itself in the SP control
 *  register.
 */

 for (cnt = 0; cnt < 25; cnt++) {
  udelay(50);
  if (!(snd_cs46xx_peek(chip, BA1_SPCR) & SPCR_RUNFR))
   break;
 }

 if (snd_cs46xx_peek(chip, BA1_SPCR) & SPCR_RUNFR)
  dev_err(chip->card->dev, "SPCR_RUNFR never reset\n");
}

static void snd_cs46xx_proc_stop(struct snd_cs46xx *chip)
{
 /*
 *  Turn off the run, run at frame, and DMA enable bits in the local copy of
 *  the SP control register.
 */

 snd_cs46xx_poke(chip, BA1_SPCR, 0);
}

/*
 *  Sample rate routines
 */


#define GOF_PER_SEC 200

static void snd_cs46xx_set_play_sample_rate(struct snd_cs46xx *chip, unsigned int rate)
{
 unsigned long flags;
 unsigned int tmp1, tmp2;
 unsigned int phiIncr;
 unsigned int correctionPerGOF, correctionPerSec;

 /*
 *  Compute the values used to drive the actual sample rate conversion.
 *  The following formulas are being computed, using inline assembly
 *  since we need to use 64 bit arithmetic to compute the values:
 *
 *  phiIncr = floor((Fs,in * 2^26) / Fs,out)
 *  correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) /
         *                                   GOF_PER_SEC)
         *  ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr -M
         *                       GOF_PER_SEC * correctionPerGOF
 *
 *  i.e.
 *
 *  phiIncr:other = dividend:remainder((Fs,in * 2^26) / Fs,out)
 *  correctionPerGOF:correctionPerSec =
 *      dividend:remainder(ulOther / GOF_PER_SEC)
 */

 tmp1 = rate << 16;
 phiIncr = tmp1 / 48000;
 tmp1 -= phiIncr * 48000;
 tmp1 <<= 10;
 phiIncr <<= 10;
 tmp2 = tmp1 / 48000;
 phiIncr += tmp2;
 tmp1 -= tmp2 * 48000;
 correctionPerGOF = tmp1 / GOF_PER_SEC;
 tmp1 -= correctionPerGOF * GOF_PER_SEC;
 correctionPerSec = tmp1;

 /*
 *  Fill in the SampleRateConverter control block.
 */

 spin_lock_irqsave(&chip->reg_lock, flags);
 snd_cs46xx_poke(chip, BA1_PSRC,
   ((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF));
 snd_cs46xx_poke(chip, BA1_PPI, phiIncr);
 spin_unlock_irqrestore(&chip->reg_lock, flags);
}

static void snd_cs46xx_set_capture_sample_rate(struct snd_cs46xx *chip, unsigned int rate)
{
 unsigned long flags;
 unsigned int phiIncr, coeffIncr, tmp1, tmp2;
 unsigned int correctionPerGOF, correctionPerSec, initialDelay;
 unsigned int frameGroupLength, cnt;

 /*
 *  We can only decimate by up to a factor of 1/9th the hardware rate.
 *  Correct the value if an attempt is made to stray outside that limit.
 */

 if ((rate * 9) < 48000)
  rate = 48000 / 9;

 /*
 *  We can not capture at a rate greater than the Input Rate (48000).
 *  Return an error if an attempt is made to stray outside that limit.
 */

 if (rate > 48000)
  rate = 48000;

 /*
 *  Compute the values used to drive the actual sample rate conversion.
 *  The following formulas are being computed, using inline assembly
 *  since we need to use 64 bit arithmetic to compute the values:
 *
 *     coeffIncr = -floor((Fs,out * 2^23) / Fs,in)
 *     phiIncr = floor((Fs,in * 2^26) / Fs,out)
 *     correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) /
 *                                GOF_PER_SEC)
 *     correctionPerSec = Fs,in * 2^26 - Fs,out * phiIncr -
 *                          GOF_PER_SEC * correctionPerGOF
 *     initialDelay = ceil((24 * Fs,in) / Fs,out)
 *
 * i.e.
 *
 *     coeffIncr = neg(dividend((Fs,out * 2^23) / Fs,in))
 *     phiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out)
 *     correctionPerGOF:correctionPerSec =
 *      dividend:remainder(ulOther / GOF_PER_SEC)
 *     initialDelay = dividend(((24 * Fs,in) + Fs,out - 1) / Fs,out)
 */


 tmp1 = rate << 16;
 coeffIncr = tmp1 / 48000;
 tmp1 -= coeffIncr * 48000;
 tmp1 <<= 7;
 coeffIncr <<= 7;
 coeffIncr += tmp1 / 48000;
 coeffIncr ^= 0xFFFFFFFF;
 coeffIncr++;
 tmp1 = 48000 << 16;
 phiIncr = tmp1 / rate;
 tmp1 -= phiIncr * rate;
 tmp1 <<= 10;
 phiIncr <<= 10;
 tmp2 = tmp1 / rate;
 phiIncr += tmp2;
 tmp1 -= tmp2 * rate;
 correctionPerGOF = tmp1 / GOF_PER_SEC;
 tmp1 -= correctionPerGOF * GOF_PER_SEC;
 correctionPerSec = tmp1;
 initialDelay = DIV_ROUND_UP(48000 * 24, rate);

 /*
 *  Fill in the VariDecimate control block.
 */

 spin_lock_irqsave(&chip->reg_lock, flags);
 snd_cs46xx_poke(chip, BA1_CSRC,
  ((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF));
 snd_cs46xx_poke(chip, BA1_CCI, coeffIncr);
 snd_cs46xx_poke(chip, BA1_CD,
  (((BA1_VARIDEC_BUF_1 + (initialDelay << 2)) << 16) & 0xFFFF0000) | 0x80);
 snd_cs46xx_poke(chip, BA1_CPI, phiIncr);
 spin_unlock_irqrestore(&chip->reg_lock, flags);

 /*
 *  Figure out the frame group length for the write back task.  Basically,
 *  this is just the factors of 24000 (2^6*3*5^3) that are not present in
 *  the output sample rate.
 */

 frameGroupLength = 1;
 for (cnt = 2; cnt <= 64; cnt *= 2) {
  if (((rate / cnt) * cnt) != rate)
   frameGroupLength *= 2;
 }
 if (((rate / 3) * 3) != rate) {
  frameGroupLength *= 3;
 }
 for (cnt = 5; cnt <= 125; cnt *= 5) {
  if (((rate / cnt) * cnt) != rate) 
   frameGroupLength *= 5;
        }

 /*
 * Fill in the WriteBack control block.
 */

 spin_lock_irqsave(&chip->reg_lock, flags);
 snd_cs46xx_poke(chip, BA1_CFG1, frameGroupLength);
 snd_cs46xx_poke(chip, BA1_CFG2, (0x00800000 | frameGroupLength));
 snd_cs46xx_poke(chip, BA1_CCST, 0x0000FFFF);
 snd_cs46xx_poke(chip, BA1_CSPB, ((65536 * rate) / 24000));
 snd_cs46xx_poke(chip, (BA1_CSPB + 4), 0x0000FFFF);
 spin_unlock_irqrestore(&chip->reg_lock, flags);
}

/*
 *  PCM part
 */


static void snd_cs46xx_pb_trans_copy(struct snd_pcm_substream *substream,
         struct snd_pcm_indirect *rec, size_t bytes)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct snd_cs46xx_pcm * cpcm = runtime->private_data;
 memcpy(cpcm->hw_buf.area + rec->hw_data, runtime->dma_area + rec->sw_data, bytes);
}

static int snd_cs46xx_playback_transfer(struct snd_pcm_substream *substream)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct snd_cs46xx_pcm * cpcm = runtime->private_data;
 return snd_pcm_indirect_playback_transfer(substream, &cpcm->pcm_rec,
        snd_cs46xx_pb_trans_copy);
}

static void snd_cs46xx_cp_trans_copy(struct snd_pcm_substream *substream,
         struct snd_pcm_indirect *rec, size_t bytes)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 struct snd_pcm_runtime *runtime = substream->runtime;
 memcpy(runtime->dma_area + rec->sw_data,
        chip->capt.hw_buf.area + rec->hw_data, bytes);
}

static int snd_cs46xx_capture_transfer(struct snd_pcm_substream *substream)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 return snd_pcm_indirect_capture_transfer(substream, &chip->capt.pcm_rec,
       snd_cs46xx_cp_trans_copy);
}

static snd_pcm_uframes_t snd_cs46xx_playback_direct_pointer(struct snd_pcm_substream *substream)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 size_t ptr;
 struct snd_cs46xx_pcm *cpcm = substream->runtime->private_data;

 if (snd_BUG_ON(!cpcm->pcm_channel))
  return -ENXIO;

#ifdef CONFIG_SND_CS46XX_NEW_DSP
 ptr = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 2) << 2);
#else
 ptr = snd_cs46xx_peek(chip, BA1_PBA);
#endif
 ptr -= cpcm->hw_buf.addr;
 return ptr >> cpcm->shift;
}

static snd_pcm_uframes_t snd_cs46xx_playback_indirect_pointer(struct snd_pcm_substream *substream)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 size_t ptr;
 struct snd_cs46xx_pcm *cpcm = substream->runtime->private_data;

#ifdef CONFIG_SND_CS46XX_NEW_DSP
 if (snd_BUG_ON(!cpcm->pcm_channel))
  return -ENXIO;
 ptr = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 2) << 2);
#else
 ptr = snd_cs46xx_peek(chip, BA1_PBA);
#endif
 ptr -= cpcm->hw_buf.addr;
 return snd_pcm_indirect_playback_pointer(substream, &cpcm->pcm_rec, ptr);
}

static snd_pcm_uframes_t snd_cs46xx_capture_direct_pointer(struct snd_pcm_substream *substream)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 size_t ptr = snd_cs46xx_peek(chip, BA1_CBA) - chip->capt.hw_buf.addr;
 return ptr >> chip->capt.shift;
}

static snd_pcm_uframes_t snd_cs46xx_capture_indirect_pointer(struct snd_pcm_substream *substream)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 size_t ptr = snd_cs46xx_peek(chip, BA1_CBA) - chip->capt.hw_buf.addr;
 return snd_pcm_indirect_capture_pointer(substream, &chip->capt.pcm_rec, ptr);
}

static int snd_cs46xx_playback_trigger(struct snd_pcm_substream *substream,
           int cmd)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 /*struct snd_pcm_runtime *runtime = substream->runtime;*/
 int result = 0;

#ifdef CONFIG_SND_CS46XX_NEW_DSP
 struct snd_cs46xx_pcm *cpcm = substream->runtime->private_data;
 if (! cpcm->pcm_channel) {
  return -ENXIO;
 }
#endif
 switch (cmd) {
 case SNDRV_PCM_TRIGGER_START:
 case SNDRV_PCM_TRIGGER_RESUME:
#ifdef CONFIG_SND_CS46XX_NEW_DSP
  /* magic value to unmute PCM stream  playback volume */
  snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 
           SCBVolumeCtrl) << 2, 0x80008000);

  if (cpcm->pcm_channel->unlinked)
   cs46xx_dsp_pcm_link(chip,cpcm->pcm_channel);

  if (substream->runtime->periods != CS46XX_FRAGS)
   snd_cs46xx_playback_transfer(substream);
#else
  spin_lock(&chip->reg_lock);
  if (substream->runtime->periods != CS46XX_FRAGS)
   snd_cs46xx_playback_transfer(substream);
  { unsigned int tmp;
  tmp = snd_cs46xx_peek(chip, BA1_PCTL);
  tmp &= 0x0000ffff;
  snd_cs46xx_poke(chip, BA1_PCTL, chip->play_ctl | tmp);
  }
  spin_unlock(&chip->reg_lock);
#endif
  break;
 case SNDRV_PCM_TRIGGER_STOP:
 case SNDRV_PCM_TRIGGER_SUSPEND:
#ifdef CONFIG_SND_CS46XX_NEW_DSP
  /* magic mute channel */
  snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 
           SCBVolumeCtrl) << 2, 0xffffffff);

  if (!cpcm->pcm_channel->unlinked)
   cs46xx_dsp_pcm_unlink(chip,cpcm->pcm_channel);
#else
  spin_lock(&chip->reg_lock);
  { unsigned int tmp;
  tmp = snd_cs46xx_peek(chip, BA1_PCTL);
  tmp &= 0x0000ffff;
  snd_cs46xx_poke(chip, BA1_PCTL, tmp);
  }
  spin_unlock(&chip->reg_lock);
#endif
  break;
 default:
  result = -EINVAL;
  break;
 }

 return result;
}

static int snd_cs46xx_capture_trigger(struct snd_pcm_substream *substream,
          int cmd)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 unsigned int tmp;
 int result = 0;

 spin_lock(&chip->reg_lock);
 switch (cmd) {
 case SNDRV_PCM_TRIGGER_START:
 case SNDRV_PCM_TRIGGER_RESUME:
  tmp = snd_cs46xx_peek(chip, BA1_CCTL);
  tmp &= 0xffff0000;
  snd_cs46xx_poke(chip, BA1_CCTL, chip->capt.ctl | tmp);
  break;
 case SNDRV_PCM_TRIGGER_STOP:
 case SNDRV_PCM_TRIGGER_SUSPEND:
  tmp = snd_cs46xx_peek(chip, BA1_CCTL);
  tmp &= 0xffff0000;
  snd_cs46xx_poke(chip, BA1_CCTL, tmp);
  break;
 default:
  result = -EINVAL;
  break;
 }
 spin_unlock(&chip->reg_lock);

 return result;
}

#ifdef CONFIG_SND_CS46XX_NEW_DSP
static int _cs46xx_adjust_sample_rate (struct snd_cs46xx *chip, struct snd_cs46xx_pcm *cpcm,
           int sample_rate) 
{

 /* If PCMReaderSCB and SrcTaskSCB not created yet ... */
 if ( cpcm->pcm_channel == NULL) {
  cpcm->pcm_channel = cs46xx_dsp_create_pcm_channel (chip, sample_rate, 
           cpcm, cpcm->hw_buf.addr,cpcm->pcm_channel_id);
  if (cpcm->pcm_channel == NULL) {
   dev_err(chip->card->dev,
    "failed to create virtual PCM channel\n");
   return -ENOMEM;
  }
  cpcm->pcm_channel->sample_rate = sample_rate;
 } else
 /* if sample rate is changed */
 if ((int)cpcm->pcm_channel->sample_rate != sample_rate) {
  int unlinked = cpcm->pcm_channel->unlinked;
  cs46xx_dsp_destroy_pcm_channel (chip,cpcm->pcm_channel);

  cpcm->pcm_channel = cs46xx_dsp_create_pcm_channel(chip, sample_rate, cpcm,
          cpcm->hw_buf.addr,
          cpcm->pcm_channel_id);
  if (!cpcm->pcm_channel) {
   dev_err(chip->card->dev,
    "failed to re-create virtual PCM channel\n");
   return -ENOMEM;
  }

  if (!unlinked) cs46xx_dsp_pcm_link (chip,cpcm->pcm_channel);
  cpcm->pcm_channel->sample_rate = sample_rate;
 }

 return 0;
}
#endif


static int snd_cs46xx_playback_hw_params(struct snd_pcm_substream *substream,
      struct snd_pcm_hw_params *hw_params)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct snd_cs46xx_pcm *cpcm;
 int err;
#ifdef CONFIG_SND_CS46XX_NEW_DSP
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 int sample_rate = params_rate(hw_params);
 int period_size = params_period_bytes(hw_params);
#endif
 cpcm = runtime->private_data;

#ifdef CONFIG_SND_CS46XX_NEW_DSP
 if (snd_BUG_ON(!sample_rate))
  return -ENXIO;

 mutex_lock(&chip->spos_mutex);

 if (_cs46xx_adjust_sample_rate (chip,cpcm,sample_rate)) {
  mutex_unlock(&chip->spos_mutex);
  return -ENXIO;
 }

 snd_BUG_ON(!cpcm->pcm_channel);
 if (!cpcm->pcm_channel) {
  mutex_unlock(&chip->spos_mutex);
  return -ENXIO;
 }


 if (cs46xx_dsp_pcm_channel_set_period (chip,cpcm->pcm_channel,period_size)) {
   mutex_unlock(&chip->spos_mutex);
   return -EINVAL;
  }

 dev_dbg(chip->card->dev,
  "period_size (%d), periods (%d) buffer_size(%d)\n",
       period_size, params_periods(hw_params),
       params_buffer_bytes(hw_params));
#endif

 if (params_periods(hw_params) == CS46XX_FRAGS) {
  if (runtime->dma_area != cpcm->hw_buf.area)
   snd_pcm_lib_free_pages(substream);
  snd_pcm_set_runtime_buffer(substream, &cpcm->hw_buf);


#ifdef CONFIG_SND_CS46XX_NEW_DSP
  if (cpcm->pcm_channel_id == DSP_PCM_MAIN_CHANNEL) {
   substream->ops = &snd_cs46xx_playback_ops;
  } else if (cpcm->pcm_channel_id == DSP_PCM_REAR_CHANNEL) {
   substream->ops = &snd_cs46xx_playback_rear_ops;
  } else if (cpcm->pcm_channel_id == DSP_PCM_CENTER_LFE_CHANNEL) {
   substream->ops = &snd_cs46xx_playback_clfe_ops;
  } else if (cpcm->pcm_channel_id == DSP_IEC958_CHANNEL) {
   substream->ops = &snd_cs46xx_playback_iec958_ops;
  } else {
   snd_BUG();
  }
#else
  substream->ops = &snd_cs46xx_playback_ops;
#endif

 } else {
  if (runtime->dma_area == cpcm->hw_buf.area)
   snd_pcm_set_runtime_buffer(substream, NULL);
  err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
  if (err < 0) {
#ifdef CONFIG_SND_CS46XX_NEW_DSP
   mutex_unlock(&chip->spos_mutex);
#endif
   return err;
  }

#ifdef CONFIG_SND_CS46XX_NEW_DSP
  if (cpcm->pcm_channel_id == DSP_PCM_MAIN_CHANNEL) {
   substream->ops = &snd_cs46xx_playback_indirect_ops;
  } else if (cpcm->pcm_channel_id == DSP_PCM_REAR_CHANNEL) {
   substream->ops = &snd_cs46xx_playback_indirect_rear_ops;
  } else if (cpcm->pcm_channel_id == DSP_PCM_CENTER_LFE_CHANNEL) {
   substream->ops = &snd_cs46xx_playback_indirect_clfe_ops;
  } else if (cpcm->pcm_channel_id == DSP_IEC958_CHANNEL) {
   substream->ops = &snd_cs46xx_playback_indirect_iec958_ops;
  } else {
   snd_BUG();
  }
#else
  substream->ops = &snd_cs46xx_playback_indirect_ops;
#endif

 }

#ifdef CONFIG_SND_CS46XX_NEW_DSP
 mutex_unlock(&chip->spos_mutex);
#endif

 return 0;
}

static int snd_cs46xx_playback_hw_free(struct snd_pcm_substream *substream)
{
 /*struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);*/
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct snd_cs46xx_pcm *cpcm;

 cpcm = runtime->private_data;

 /* if play_back open fails, then this function
   is called and cpcm can actually be NULL here */

 if (!cpcm) return -ENXIO;

 if (runtime->dma_area != cpcm->hw_buf.area)
  snd_pcm_lib_free_pages(substream);
    
 snd_pcm_set_runtime_buffer(substream, NULL);

 return 0;
}

static int snd_cs46xx_playback_prepare(struct snd_pcm_substream *substream)
{
 unsigned int tmp;
 unsigned int pfie;
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct snd_cs46xx_pcm *cpcm;

 cpcm = runtime->private_data;

#ifdef CONFIG_SND_CS46XX_NEW_DSP
 if (snd_BUG_ON(!cpcm->pcm_channel))
  return -ENXIO;

 pfie = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 1) << 2 );
 pfie &= ~0x0000f03f;
#else
 /* old dsp */
 pfie = snd_cs46xx_peek(chip, BA1_PFIE);
  pfie &= ~0x0000f03f;
#endif

 cpcm->shift = 2;
 /* if to convert from stereo to mono */
 if (runtime->channels == 1) {
  cpcm->shift--;
  pfie |= 0x00002000;
 }
 /* if to convert from 8 bit to 16 bit */
 if (snd_pcm_format_width(runtime->format) == 8) {
  cpcm->shift--;
  pfie |= 0x00001000;
 }
 /* if to convert to unsigned */
 if (snd_pcm_format_unsigned(runtime->format))
  pfie |= 0x00008000;

 /* Never convert byte order when sample stream is 8 bit */
 if (snd_pcm_format_width(runtime->format) != 8) {
  /* convert from big endian to little endian */
  if (snd_pcm_format_big_endian(runtime->format))
   pfie |= 0x00004000;
 }
 
 memset(&cpcm->pcm_rec, 0, sizeof(cpcm->pcm_rec));
 cpcm->pcm_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
 cpcm->pcm_rec.hw_buffer_size = runtime->period_size * CS46XX_FRAGS << cpcm->shift;

#ifdef CONFIG_SND_CS46XX_NEW_DSP

 tmp = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address) << 2);
 tmp &= ~0x000003ff;
 tmp |= (4 << cpcm->shift) - 1;
 /* playback transaction count register */
 snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address) << 2, tmp);

 /* playback format && interrupt enable */
 snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 1) << 2, pfie | cpcm->pcm_channel->pcm_slot);
#else
 snd_cs46xx_poke(chip, BA1_PBA, cpcm->hw_buf.addr);
 tmp = snd_cs46xx_peek(chip, BA1_PDTC);
 tmp &= ~0x000003ff;
 tmp |= (4 << cpcm->shift) - 1;
 snd_cs46xx_poke(chip, BA1_PDTC, tmp);
 snd_cs46xx_poke(chip, BA1_PFIE, pfie);
 snd_cs46xx_set_play_sample_rate(chip, runtime->rate);
#endif

 return 0;
}

static int snd_cs46xx_capture_hw_params(struct snd_pcm_substream *substream,
     struct snd_pcm_hw_params *hw_params)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 struct snd_pcm_runtime *runtime = substream->runtime;
 int err;

#ifdef CONFIG_SND_CS46XX_NEW_DSP
 cs46xx_dsp_pcm_ostream_set_period (chip, params_period_bytes(hw_params));
#endif
 if (runtime->periods == CS46XX_FRAGS) {
  if (runtime->dma_area != chip->capt.hw_buf.area)
   snd_pcm_lib_free_pages(substream);
  snd_pcm_set_runtime_buffer(substream, &chip->capt.hw_buf);
  substream->ops = &snd_cs46xx_capture_ops;
 } else {
  if (runtime->dma_area == chip->capt.hw_buf.area)
   snd_pcm_set_runtime_buffer(substream, NULL);
  err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
  if (err < 0)
   return err;
  substream->ops = &snd_cs46xx_capture_indirect_ops;
 }

 return 0;
}

static int snd_cs46xx_capture_hw_free(struct snd_pcm_substream *substream)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 struct snd_pcm_runtime *runtime = substream->runtime;

 if (runtime->dma_area != chip->capt.hw_buf.area)
  snd_pcm_lib_free_pages(substream);
 snd_pcm_set_runtime_buffer(substream, NULL);

 return 0;
}

static int snd_cs46xx_capture_prepare(struct snd_pcm_substream *substream)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 struct snd_pcm_runtime *runtime = substream->runtime;

 snd_cs46xx_poke(chip, BA1_CBA, chip->capt.hw_buf.addr);
 chip->capt.shift = 2;
 memset(&chip->capt.pcm_rec, 0, sizeof(chip->capt.pcm_rec));
 chip->capt.pcm_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
 chip->capt.pcm_rec.hw_buffer_size = runtime->period_size * CS46XX_FRAGS << 2;
 snd_cs46xx_set_capture_sample_rate(chip, runtime->rate);

 return 0;
}

static irqreturn_t snd_cs46xx_interrupt(int irq, void *dev_id)
{
 struct snd_cs46xx *chip = dev_id;
 u32 status1;
#ifdef CONFIG_SND_CS46XX_NEW_DSP
 struct dsp_spos_instance * ins = chip->dsp_spos_instance;
 u32 status2;
 int i;
 struct snd_cs46xx_pcm *cpcm = NULL;
#endif

 /*
 *  Read the Interrupt Status Register to clear the interrupt
 */

 status1 = snd_cs46xx_peekBA0(chip, BA0_HISR);
 if ((status1 & 0x7fffffff) == 0) {
  snd_cs46xx_pokeBA0(chip, BA0_HICR, HICR_CHGM | HICR_IEV);
  return IRQ_NONE;
 }

#ifdef CONFIG_SND_CS46XX_NEW_DSP
 status2 = snd_cs46xx_peekBA0(chip, BA0_HSR0);

 for (i = 0; i < DSP_MAX_PCM_CHANNELS; ++i) {
  if (i <= 15) {
   if ( status1 & (1 << i) ) {
    if (i == CS46XX_DSP_CAPTURE_CHANNEL) {
     if (chip->capt.substream)
      snd_pcm_period_elapsed(chip->capt.substream);
    } else {
     if (ins->pcm_channels[i].active &&
         ins->pcm_channels[i].private_data &&
         !ins->pcm_channels[i].unlinked) {
      cpcm = ins->pcm_channels[i].private_data;
      snd_pcm_period_elapsed(cpcm->substream);
     }
    }
   }
  } else {
   if ( status2 & (1 << (i - 16))) {
    if (ins->pcm_channels[i].active && 
        ins->pcm_channels[i].private_data &&
        !ins->pcm_channels[i].unlinked) {
     cpcm = ins->pcm_channels[i].private_data;
     snd_pcm_period_elapsed(cpcm->substream);
    }
   }
  }
 }

#else
 /* old dsp */
 if ((status1 & HISR_VC0) && chip->playback_pcm) {
  if (chip->playback_pcm->substream)
   snd_pcm_period_elapsed(chip->playback_pcm->substream);
 }
 if ((status1 & HISR_VC1) && chip->pcm) {
  if (chip->capt.substream)
   snd_pcm_period_elapsed(chip->capt.substream);
 }
#endif

 if ((status1 & HISR_MIDI) && chip->rmidi) {
  unsigned char c;
  
  spin_lock(&chip->reg_lock);
  while ((snd_cs46xx_peekBA0(chip, BA0_MIDSR) & MIDSR_RBE) == 0) {
   c = snd_cs46xx_peekBA0(chip, BA0_MIDRP);
   if ((chip->midcr & MIDCR_RIE) == 0)
    continue;
   snd_rawmidi_receive(chip->midi_input, &c, 1);
  }
  while ((snd_cs46xx_peekBA0(chip, BA0_MIDSR) & MIDSR_TBF) == 0) {
   if ((chip->midcr & MIDCR_TIE) == 0)
    break;
   if (snd_rawmidi_transmit(chip->midi_output, &c, 1) != 1) {
    chip->midcr &= ~MIDCR_TIE;
    snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr);
    break;
   }
   snd_cs46xx_pokeBA0(chip, BA0_MIDWP, c);
  }
  spin_unlock(&chip->reg_lock);
 }
 /*
 *  EOI to the PCI part....reenables interrupts
 */

 snd_cs46xx_pokeBA0(chip, BA0_HICR, HICR_CHGM | HICR_IEV);

 return IRQ_HANDLED;
}

static const struct snd_pcm_hardware snd_cs46xx_playback =
{
 .info =   (SNDRV_PCM_INFO_MMAP |
     SNDRV_PCM_INFO_INTERLEAVED | 
     SNDRV_PCM_INFO_BLOCK_TRANSFER /*|*/
     /*SNDRV_PCM_INFO_RESUME*/ |
     SNDRV_PCM_INFO_SYNC_APPLPTR),
 .formats =  (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
     SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |
     SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE),
 .rates =  SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
 .rate_min =  5500,
 .rate_max =  48000,
 .channels_min =  1,
 .channels_max =  2,
 .buffer_bytes_max = (256 * 1024),
 .period_bytes_min = CS46XX_MIN_PERIOD_SIZE,
 .period_bytes_max = CS46XX_MAX_PERIOD_SIZE,
 .periods_min =  CS46XX_FRAGS,
 .periods_max =  1024,
 .fifo_size =  0,
};

static const struct snd_pcm_hardware snd_cs46xx_capture =
{
 .info =   (SNDRV_PCM_INFO_MMAP |
     SNDRV_PCM_INFO_INTERLEAVED |
     SNDRV_PCM_INFO_BLOCK_TRANSFER /*|*/
     /*SNDRV_PCM_INFO_RESUME*/ |
     SNDRV_PCM_INFO_SYNC_APPLPTR),
 .formats =  SNDRV_PCM_FMTBIT_S16_LE,
 .rates =  SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
 .rate_min =  5500,
 .rate_max =  48000,
 .channels_min =  2,
 .channels_max =  2,
 .buffer_bytes_max = (256 * 1024),
 .period_bytes_min = CS46XX_MIN_PERIOD_SIZE,
 .period_bytes_max = CS46XX_MAX_PERIOD_SIZE,
 .periods_min =  CS46XX_FRAGS,
 .periods_max =  1024,
 .fifo_size =  0,
};

#ifdef CONFIG_SND_CS46XX_NEW_DSP

static const unsigned int period_sizes[] = { 32, 64, 128, 256, 512, 1024, 2048 };

static const struct snd_pcm_hw_constraint_list hw_constraints_period_sizes = {
 .count = ARRAY_SIZE(period_sizes),
 .list = period_sizes,
 .mask = 0
};

#endif

static void snd_cs46xx_pcm_free_substream(struct snd_pcm_runtime *runtime)
{
 kfree(runtime->private_data);
}

static int _cs46xx_playback_open_channel (struct snd_pcm_substream *substream,int pcm_channel_id)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 struct snd_cs46xx_pcm * cpcm;
 struct snd_pcm_runtime *runtime = substream->runtime;

 cpcm = kzalloc(sizeof(*cpcm), GFP_KERNEL);
 if (cpcm == NULL)
  return -ENOMEM;
 if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev,
    PAGE_SIZE, &cpcm->hw_buf) < 0) {
  kfree(cpcm);
  return -ENOMEM;
 }

 runtime->hw = snd_cs46xx_playback;
 runtime->private_data = cpcm;
 runtime->private_free = snd_cs46xx_pcm_free_substream;

 cpcm->substream = substream;
#ifdef CONFIG_SND_CS46XX_NEW_DSP
 mutex_lock(&chip->spos_mutex);
 cpcm->pcm_channel = NULL; 
 cpcm->pcm_channel_id = pcm_channel_id;


 snd_pcm_hw_constraint_list(runtime, 0,
       SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 
       &hw_constraints_period_sizes);

 mutex_unlock(&chip->spos_mutex);
#else
 chip->playback_pcm = cpcm; /* HACK */
#endif

 if (chip->accept_valid)
  substream->runtime->hw.info |= SNDRV_PCM_INFO_MMAP_VALID;
 chip->active_ctrl(chip, 1);

 return 0;
}

static int snd_cs46xx_playback_open(struct snd_pcm_substream *substream)
{
 dev_dbg(substream->pcm->card->dev, "open front channel\n");
 return _cs46xx_playback_open_channel(substream,DSP_PCM_MAIN_CHANNEL);
}

#ifdef CONFIG_SND_CS46XX_NEW_DSP
static int snd_cs46xx_playback_open_rear(struct snd_pcm_substream *substream)
{
 dev_dbg(substream->pcm->card->dev, "open rear channel\n");
 return _cs46xx_playback_open_channel(substream,DSP_PCM_REAR_CHANNEL);
}

static int snd_cs46xx_playback_open_clfe(struct snd_pcm_substream *substream)
{
 dev_dbg(substream->pcm->card->dev, "open center - LFE channel\n");
 return _cs46xx_playback_open_channel(substream,DSP_PCM_CENTER_LFE_CHANNEL);
}

static int snd_cs46xx_playback_open_iec958(struct snd_pcm_substream *substream)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);

 dev_dbg(chip->card->dev, "open raw iec958 channel\n");

 mutex_lock(&chip->spos_mutex);
 cs46xx_iec958_pre_open (chip);
 mutex_unlock(&chip->spos_mutex);

 return _cs46xx_playback_open_channel(substream,DSP_IEC958_CHANNEL);
}

static int snd_cs46xx_playback_close(struct snd_pcm_substream *substream);

static int snd_cs46xx_playback_close_iec958(struct snd_pcm_substream *substream)
{
 int err;
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
  
 dev_dbg(chip->card->dev, "close raw iec958 channel\n");

 err = snd_cs46xx_playback_close(substream);

 mutex_lock(&chip->spos_mutex);
 cs46xx_iec958_post_close (chip);
 mutex_unlock(&chip->spos_mutex);

 return err;
}
#endif

static int snd_cs46xx_capture_open(struct snd_pcm_substream *substream)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);

 if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev,
    PAGE_SIZE, &chip->capt.hw_buf) < 0)
  return -ENOMEM;
 chip->capt.substream = substream;
 substream->runtime->hw = snd_cs46xx_capture;

 if (chip->accept_valid)
  substream->runtime->hw.info |= SNDRV_PCM_INFO_MMAP_VALID;

 chip->active_ctrl(chip, 1);

#ifdef CONFIG_SND_CS46XX_NEW_DSP
 snd_pcm_hw_constraint_list(substream->runtime, 0,
       SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 
       &hw_constraints_period_sizes);
#endif
 return 0;
}

static int snd_cs46xx_playback_close(struct snd_pcm_substream *substream)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct snd_cs46xx_pcm * cpcm;

 cpcm = runtime->private_data;

 /* when playback_open fails, then cpcm can be NULL */
 if (!cpcm) return -ENXIO;

#ifdef CONFIG_SND_CS46XX_NEW_DSP
 mutex_lock(&chip->spos_mutex);
 if (cpcm->pcm_channel) {
  cs46xx_dsp_destroy_pcm_channel(chip,cpcm->pcm_channel);
  cpcm->pcm_channel = NULL;
 }
 mutex_unlock(&chip->spos_mutex);
#else
 chip->playback_pcm = NULL;
#endif

 cpcm->substream = NULL;
 snd_dma_free_pages(&cpcm->hw_buf);
 chip->active_ctrl(chip, -1);

 return 0;
}

static int snd_cs46xx_capture_close(struct snd_pcm_substream *substream)
{
 struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);

 chip->capt.substream = NULL;
 snd_dma_free_pages(&chip->capt.hw_buf);
 chip->active_ctrl(chip, -1);

 return 0;
}

#ifdef CONFIG_SND_CS46XX_NEW_DSP
static const struct snd_pcm_ops snd_cs46xx_playback_rear_ops = {
 .open =   snd_cs46xx_playback_open_rear,
 .close =  snd_cs46xx_playback_close,
 .hw_params =  snd_cs46xx_playback_hw_params,
 .hw_free =  snd_cs46xx_playback_hw_free,
 .prepare =  snd_cs46xx_playback_prepare,
 .trigger =  snd_cs46xx_playback_trigger,
 .pointer =  snd_cs46xx_playback_direct_pointer,
};

static const struct snd_pcm_ops snd_cs46xx_playback_indirect_rear_ops = {
 .open =   snd_cs46xx_playback_open_rear,
 .close =  snd_cs46xx_playback_close,
 .hw_params =  snd_cs46xx_playback_hw_params,
 .hw_free =  snd_cs46xx_playback_hw_free,
 .prepare =  snd_cs46xx_playback_prepare,
 .trigger =  snd_cs46xx_playback_trigger,
 .pointer =  snd_cs46xx_playback_indirect_pointer,
 .ack =   snd_cs46xx_playback_transfer,
};

static const struct snd_pcm_ops snd_cs46xx_playback_clfe_ops = {
 .open =   snd_cs46xx_playback_open_clfe,
 .close =  snd_cs46xx_playback_close,
 .hw_params =  snd_cs46xx_playback_hw_params,
 .hw_free =  snd_cs46xx_playback_hw_free,
 .prepare =  snd_cs46xx_playback_prepare,
 .trigger =  snd_cs46xx_playback_trigger,
 .pointer =  snd_cs46xx_playback_direct_pointer,
};

static const struct snd_pcm_ops snd_cs46xx_playback_indirect_clfe_ops = {
 .open =   snd_cs46xx_playback_open_clfe,
 .close =  snd_cs46xx_playback_close,
 .hw_params =  snd_cs46xx_playback_hw_params,
 .hw_free =  snd_cs46xx_playback_hw_free,
 .prepare =  snd_cs46xx_playback_prepare,
 .trigger =  snd_cs46xx_playback_trigger,
 .pointer =  snd_cs46xx_playback_indirect_pointer,
 .ack =   snd_cs46xx_playback_transfer,
};

static const struct snd_pcm_ops snd_cs46xx_playback_iec958_ops = {
 .open =   snd_cs46xx_playback_open_iec958,
 .close =  snd_cs46xx_playback_close_iec958,
 .hw_params =  snd_cs46xx_playback_hw_params,
 .hw_free =  snd_cs46xx_playback_hw_free,
 .prepare =  snd_cs46xx_playback_prepare,
 .trigger =  snd_cs46xx_playback_trigger,
 .pointer =  snd_cs46xx_playback_direct_pointer,
};

static const struct snd_pcm_ops snd_cs46xx_playback_indirect_iec958_ops = {
 .open =   snd_cs46xx_playback_open_iec958,
 .close =  snd_cs46xx_playback_close_iec958,
 .hw_params =  snd_cs46xx_playback_hw_params,
 .hw_free =  snd_cs46xx_playback_hw_free,
 .prepare =  snd_cs46xx_playback_prepare,
 .trigger =  snd_cs46xx_playback_trigger,
 .pointer =  snd_cs46xx_playback_indirect_pointer,
 .ack =   snd_cs46xx_playback_transfer,
};

#endif

static const struct snd_pcm_ops snd_cs46xx_playback_ops = {
 .open =   snd_cs46xx_playback_open,
 .close =  snd_cs46xx_playback_close,
 .hw_params =  snd_cs46xx_playback_hw_params,
 .hw_free =  snd_cs46xx_playback_hw_free,
 .prepare =  snd_cs46xx_playback_prepare,
 .trigger =  snd_cs46xx_playback_trigger,
 .pointer =  snd_cs46xx_playback_direct_pointer,
};

static const struct snd_pcm_ops snd_cs46xx_playback_indirect_ops = {
 .open =   snd_cs46xx_playback_open,
 .close =  snd_cs46xx_playback_close,
 .hw_params =  snd_cs46xx_playback_hw_params,
 .hw_free =  snd_cs46xx_playback_hw_free,
 .prepare =  snd_cs46xx_playback_prepare,
 .trigger =  snd_cs46xx_playback_trigger,
 .pointer =  snd_cs46xx_playback_indirect_pointer,
 .ack =   snd_cs46xx_playback_transfer,
};

static const struct snd_pcm_ops snd_cs46xx_capture_ops = {
 .open =   snd_cs46xx_capture_open,
 .close =  snd_cs46xx_capture_close,
 .hw_params =  snd_cs46xx_capture_hw_params,
 .hw_free =  snd_cs46xx_capture_hw_free,
 .prepare =  snd_cs46xx_capture_prepare,
 .trigger =  snd_cs46xx_capture_trigger,
 .pointer =  snd_cs46xx_capture_direct_pointer,
};

static const struct snd_pcm_ops snd_cs46xx_capture_indirect_ops = {
 .open =   snd_cs46xx_capture_open,
 .close =  snd_cs46xx_capture_close,
 .hw_params =  snd_cs46xx_capture_hw_params,
 .hw_free =  snd_cs46xx_capture_hw_free,
 .prepare =  snd_cs46xx_capture_prepare,
 .trigger =  snd_cs46xx_capture_trigger,
 .pointer =  snd_cs46xx_capture_indirect_pointer,
 .ack =   snd_cs46xx_capture_transfer,
};

#ifdef CONFIG_SND_CS46XX_NEW_DSP
#define MAX_PLAYBACK_CHANNELS (DSP_MAX_PCM_CHANNELS - 1)
#else
#define MAX_PLAYBACK_CHANNELS 1
#endif

int snd_cs46xx_pcm(struct snd_cs46xx *chip, int device)
{
 struct snd_pcm *pcm;
 int err;

 err = snd_pcm_new(chip->card, "CS46xx", device, MAX_PLAYBACK_CHANNELS, 1, &pcm);
 if (err < 0)
  return err;

 pcm->private_data = chip;

 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs46xx_playback_ops);
 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cs46xx_capture_ops);

 /* global setup */
 pcm->info_flags = 0;
 strscpy(pcm->name, "CS46xx");
 chip->pcm = pcm;

 snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
           &chip->pci->dev,
           64*1024, 256*1024);

 return 0;
}


#ifdef CONFIG_SND_CS46XX_NEW_DSP
int snd_cs46xx_pcm_rear(struct snd_cs46xx *chip, int device)
{
 struct snd_pcm *pcm;
 int err;

 err = snd_pcm_new(chip->card, "CS46xx - Rear", device, MAX_PLAYBACK_CHANNELS, 0, &pcm);
 if (err < 0)
  return err;

 pcm->private_data = chip;

 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs46xx_playback_rear_ops);

 /* global setup */
 pcm->info_flags = 0;
 strscpy(pcm->name, "CS46xx - Rear");
 chip->pcm_rear = pcm;

 snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
           &chip->pci->dev,
           64*1024, 256*1024);

 return 0;
}

int snd_cs46xx_pcm_center_lfe(struct snd_cs46xx *chip, int device)
{
 struct snd_pcm *pcm;
 int err;

 err = snd_pcm_new(chip->card, "CS46xx - Center LFE", device, MAX_PLAYBACK_CHANNELS, 0, &pcm);
 if (err < 0)
  return err;

 pcm->private_data = chip;

 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs46xx_playback_clfe_ops);

 /* global setup */
 pcm->info_flags = 0;
 strscpy(pcm->name, "CS46xx - Center LFE");
 chip->pcm_center_lfe = pcm;

 snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
           &chip->pci->dev,
           64*1024, 256*1024);

 return 0;
}

int snd_cs46xx_pcm_iec958(struct snd_cs46xx *chip, int device)
{
 struct snd_pcm *pcm;
 int err;

 err = snd_pcm_new(chip->card, "CS46xx - IEC958", device, 1, 0, &pcm);
 if (err < 0)
  return err;

 pcm->private_data = chip;

 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs46xx_playback_iec958_ops);

 /* global setup */
 pcm->info_flags = 0;
 strscpy(pcm->name, "CS46xx - IEC958");
 chip->pcm_iec958 = pcm;

 snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
           &chip->pci->dev,
           64*1024, 256*1024);

 return 0;
}
#endif

/*
 *  Mixer routines
 */

static void snd_cs46xx_mixer_free_ac97(struct snd_ac97 *ac97)
{
 struct snd_cs46xx *chip = ac97->private_data;

 if (snd_BUG_ON(ac97 != chip->ac97[CS46XX_PRIMARY_CODEC_INDEX] &&
         ac97 != chip->ac97[CS46XX_SECONDARY_CODEC_INDEX]))
  return;

 if (ac97 == chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]) {
  chip->ac97[CS46XX_PRIMARY_CODEC_INDEX] = NULL;
  chip->eapd_switch = NULL;
 }
 else
  chip->ac97[CS46XX_SECONDARY_CODEC_INDEX] = NULL;
}

static int snd_cs46xx_vol_info(struct snd_kcontrol *kcontrol, 
          struct snd_ctl_elem_info *uinfo)
{
 uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
 uinfo->count = 2;
 uinfo->value.integer.min = 0;
 uinfo->value.integer.max = 0x7fff;
 return 0;
}

static int snd_cs46xx_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
 int reg = kcontrol->private_value;
 unsigned int val = snd_cs46xx_peek(chip, reg);
 ucontrol->value.integer.value[0] = 0xffff - (val >> 16);
 ucontrol->value.integer.value[1] = 0xffff - (val & 0xffff);
 return 0;
}

static int snd_cs46xx_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
 int reg = kcontrol->private_value;
 unsigned int val = ((0xffff - ucontrol->value.integer.value[0]) << 16 | 
       (0xffff - ucontrol->value.integer.value[1]));
 unsigned int old = snd_cs46xx_peek(chip, reg);
 int change = (old != val);

 if (change) {
  snd_cs46xx_poke(chip, reg, val);
 }

 return change;
}

#ifdef CONFIG_SND_CS46XX_NEW_DSP

static int snd_cs46xx_vol_dac_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);

 ucontrol->value.integer.value[0] = chip->dsp_spos_instance->dac_volume_left;
 ucontrol->value.integer.value[1] = chip->dsp_spos_instance->dac_volume_right;

 return 0;
}

static int snd_cs46xx_vol_dac_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
 int change = 0;

 if (chip->dsp_spos_instance->dac_volume_right != ucontrol->value.integer.value[0] ||
     chip->dsp_spos_instance->dac_volume_left != ucontrol->value.integer.value[1]) {
  cs46xx_dsp_set_dac_volume(chip,
       ucontrol->value.integer.value[0],
       ucontrol->value.integer.value[1]);
  change = 1;
 }

 return change;
}

#if 0
static int snd_cs46xx_vol_iec958_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);

 ucontrol->value.integer.value[0] = chip->dsp_spos_instance->spdif_input_volume_left;
 ucontrol->value.integer.value[1] = chip->dsp_spos_instance->spdif_input_volume_right;
 return 0;
}

static int snd_cs46xx_vol_iec958_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
 int change = 0;

 if (chip->dsp_spos_instance->spdif_input_volume_left  != ucontrol->value.integer.value[0] ||
     chip->dsp_spos_instance->spdif_input_volume_right!= ucontrol->value.integer.value[1]) {
  cs46xx_dsp_set_iec958_volume (chip,
           ucontrol->value.integer.value[0],
           ucontrol->value.integer.value[1]);
  change = 1;
 }

 return change;
}
#endif

#define snd_mixer_boolean_info  snd_ctl_boolean_mono_info

static int snd_cs46xx_iec958_get(struct snd_kcontrol *kcontrol, 
                                 struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
 int reg = kcontrol->private_value;

 if (reg == CS46XX_MIXER_SPDIF_OUTPUT_ELEMENT)
  ucontrol->value.integer.value[0] = (chip->dsp_spos_instance->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED);
 else
  ucontrol->value.integer.value[0] = chip->dsp_spos_instance->spdif_status_in;

 return 0;
}

static int snd_cs46xx_iec958_put(struct snd_kcontrol *kcontrol, 
                                  struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
 int change, res;

 switch (kcontrol->private_value) {
 case CS46XX_MIXER_SPDIF_OUTPUT_ELEMENT:
  mutex_lock(&chip->spos_mutex);
  change = (chip->dsp_spos_instance->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED);
  if (ucontrol->value.integer.value[0] && !change) 
   cs46xx_dsp_enable_spdif_out(chip);
  else if (change && !ucontrol->value.integer.value[0])
   cs46xx_dsp_disable_spdif_out(chip);

  res = (change != (chip->dsp_spos_instance->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED));
  mutex_unlock(&chip->spos_mutex);
  break;
 case CS46XX_MIXER_SPDIF_INPUT_ELEMENT:
  change = chip->dsp_spos_instance->spdif_status_in;
  if (ucontrol->value.integer.value[0] && !change) {
   cs46xx_dsp_enable_spdif_in(chip);
   /* restore volume */
  }
  else if (change && !ucontrol->value.integer.value[0])
   cs46xx_dsp_disable_spdif_in(chip);
  
  res = (change != chip->dsp_spos_instance->spdif_status_in);
  break;
 default:
  res = -EINVAL;
  snd_BUG(); /* should never happen ... */
 }

 return res;
}

static int snd_cs46xx_adc_capture_get(struct snd_kcontrol *kcontrol, 
                                      struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
 struct dsp_spos_instance * ins = chip->dsp_spos_instance;

 if (ins->adc_input != NULL) 
  ucontrol->value.integer.value[0] = 1;
 else 
  ucontrol->value.integer.value[0] = 0;
 
 return 0;
}

static int snd_cs46xx_adc_capture_put(struct snd_kcontrol *kcontrol, 
                                      struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
 struct dsp_spos_instance * ins = chip->dsp_spos_instance;
 int change = 0;

 if (ucontrol->value.integer.value[0] && !ins->adc_input) {
  cs46xx_dsp_enable_adc_capture(chip);
  change = 1;
 } else  if (!ucontrol->value.integer.value[0] && ins->adc_input) {
  cs46xx_dsp_disable_adc_capture(chip);
  change = 1;
 }
 return change;
}

static int snd_cs46xx_pcm_capture_get(struct snd_kcontrol *kcontrol, 
                                      struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
 struct dsp_spos_instance * ins = chip->dsp_spos_instance;

 if (ins->pcm_input != NULL) 
  ucontrol->value.integer.value[0] = 1;
 else 
  ucontrol->value.integer.value[0] = 0;

 return 0;
}


static int snd_cs46xx_pcm_capture_put(struct snd_kcontrol *kcontrol, 
                                      struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
 struct dsp_spos_instance * ins = chip->dsp_spos_instance;
 int change = 0;

 if (ucontrol->value.integer.value[0] && !ins->pcm_input) {
  cs46xx_dsp_enable_pcm_capture(chip);
  change = 1;
 } else  if (!ucontrol->value.integer.value[0] && ins->pcm_input) {
  cs46xx_dsp_disable_pcm_capture(chip);
  change = 1;
 }

 return change;
}

static int snd_herc_spdif_select_get(struct snd_kcontrol *kcontrol, 
                                     struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);

 int val1 = snd_cs46xx_peekBA0(chip, BA0_EGPIODR);

 if (val1 & EGPIODR_GPOE0)
  ucontrol->value.integer.value[0] = 1;
 else
  ucontrol->value.integer.value[0] = 0;

 return 0;
}

/*
 * Game Theatre XP card - EGPIO[0] is used to select SPDIF input optical or coaxial.
 */

static int snd_herc_spdif_select_put(struct snd_kcontrol *kcontrol, 
                                       struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
 int val1 = snd_cs46xx_peekBA0(chip, BA0_EGPIODR);
 int val2 = snd_cs46xx_peekBA0(chip, BA0_EGPIOPTR);

 if (ucontrol->value.integer.value[0]) {
  /* optical is default */
  snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, 
       EGPIODR_GPOE0 | val1);  /* enable EGPIO0 output */
  snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, 
       EGPIOPTR_GPPT0 | val2); /* open-drain on output */
 } else {
  /* coaxial */
  snd_cs46xx_pokeBA0(chip, BA0_EGPIODR,  val1 & ~EGPIODR_GPOE0); /* disable */
  snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, val2 & ~EGPIOPTR_GPPT0); /* disable */
 }

 /* checking diff from the EGPIO direction register 
   should be enough */

 return (val1 != (int)snd_cs46xx_peekBA0(chip, BA0_EGPIODR));
}


static int snd_cs46xx_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
 uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
 uinfo->count = 1;
 return 0;
}

static int snd_cs46xx_spdif_default_get(struct snd_kcontrol *kcontrol,
     struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
 struct dsp_spos_instance * ins = chip->dsp_spos_instance;

 mutex_lock(&chip->spos_mutex);
 ucontrol->value.iec958.status[0] = _wrap_all_bits((ins->spdif_csuv_default >> 24) & 0xff);
 ucontrol->value.iec958.status[1] = _wrap_all_bits((ins->spdif_csuv_default >> 16) & 0xff);
 ucontrol->value.iec958.status[2] = 0;
 ucontrol->value.iec958.status[3] = _wrap_all_bits((ins->spdif_csuv_default) & 0xff);
 mutex_unlock(&chip->spos_mutex);

 return 0;
}

static int snd_cs46xx_spdif_default_put(struct snd_kcontrol *kcontrol,
     struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx * chip = snd_kcontrol_chip(kcontrol);
 struct dsp_spos_instance * ins = chip->dsp_spos_instance;
 unsigned int val;
 int change;

 mutex_lock(&chip->spos_mutex);
 val = ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[0]) << 24) |
  ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[2]) << 16) |
  ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[3]))  |
  /* left and right validity bit */
  (1 << 13) | (1 << 12);


 change = (unsigned int)ins->spdif_csuv_default != val;
 ins->spdif_csuv_default = val;

 if ( !(ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN) )
  cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV,val);

 mutex_unlock(&chip->spos_mutex);

 return change;
}

static int snd_cs46xx_spdif_mask_get(struct snd_kcontrol *kcontrol,
         struct snd_ctl_elem_value *ucontrol)
{
 ucontrol->value.iec958.status[0] = 0xff;
 ucontrol->value.iec958.status[1] = 0xff;
 ucontrol->value.iec958.status[2] = 0x00;
 ucontrol->value.iec958.status[3] = 0xff;
 return 0;
}

static int snd_cs46xx_spdif_stream_get(struct snd_kcontrol *kcontrol,
                                         struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
 struct dsp_spos_instance * ins = chip->dsp_spos_instance;

 mutex_lock(&chip->spos_mutex);
 ucontrol->value.iec958.status[0] = _wrap_all_bits((ins->spdif_csuv_stream >> 24) & 0xff);
 ucontrol->value.iec958.status[1] = _wrap_all_bits((ins->spdif_csuv_stream >> 16) & 0xff);
 ucontrol->value.iec958.status[2] = 0;
 ucontrol->value.iec958.status[3] = _wrap_all_bits((ins->spdif_csuv_stream) & 0xff);
 mutex_unlock(&chip->spos_mutex);

 return 0;
}

static int snd_cs46xx_spdif_stream_put(struct snd_kcontrol *kcontrol,
                                        struct snd_ctl_elem_value *ucontrol)
{
 struct snd_cs46xx * chip = snd_kcontrol_chip(kcontrol);
 struct dsp_spos_instance * ins = chip->dsp_spos_instance;
 unsigned int val;
 int change;

 mutex_lock(&chip->spos_mutex);
 val = ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[0]) << 24) |
  ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[1]) << 16) |
  ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[3])) |
  /* left and right validity bit */
  (1 << 13) | (1 << 12);


 change = ins->spdif_csuv_stream != val;
 ins->spdif_csuv_stream = val;

 if ( ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN )
  cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV,val);

 mutex_unlock(&chip->spos_mutex);

 return change;
}

#endif /* CONFIG_SND_CS46XX_NEW_DSP */


static const struct snd_kcontrol_new snd_cs46xx_controls[] = {
{
 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 .name = "DAC Volume",
 .info = snd_cs46xx_vol_info,
#ifndef CONFIG_SND_CS46XX_NEW_DSP
 .get = snd_cs46xx_vol_get,
 .put = snd_cs46xx_vol_put,
 .private_value = BA1_PVOL,
#else
 .get = snd_cs46xx_vol_dac_get,
 .put = snd_cs46xx_vol_dac_put,
#endif
},

{
 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 .name = "ADC Volume",
 .info = snd_cs46xx_vol_info,
 .get = snd_cs46xx_vol_get,
 .put = snd_cs46xx_vol_put,
#ifndef CONFIG_SND_CS46XX_NEW_DSP
 .private_value = BA1_CVOL,
#else
 .private_value = (VARIDECIMATE_SCB_ADDR + 0xE) << 2,
#endif
},
#ifdef CONFIG_SND_CS46XX_NEW_DSP
{
 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 .name = "ADC Capture Switch",
 .info = snd_mixer_boolean_info,
 .get = snd_cs46xx_adc_capture_get,
 .put = snd_cs46xx_adc_capture_put
},
{
 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 .name = "DAC Capture Switch",
 .info = snd_mixer_boolean_info,
 .get = snd_cs46xx_pcm_capture_get,
 .put = snd_cs46xx_pcm_capture_put
},
{
 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 .name = SNDRV_CTL_NAME_IEC958("Output ",NONE,SWITCH),
 .info = snd_mixer_boolean_info,
 .get = snd_cs46xx_iec958_get,
 .put = snd_cs46xx_iec958_put,
 .private_value = CS46XX_MIXER_SPDIF_OUTPUT_ELEMENT,
},
{
 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 .name = SNDRV_CTL_NAME_IEC958("Input ",NONE,SWITCH),
 .info = snd_mixer_boolean_info,
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=97 H=92 G=94

¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.