diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /sound/pci/cs46xx | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | sound/pci/cs46xx/Makefile | 11 | ||||
-rw-r--r-- | sound/pci/cs46xx/cs46xx.c | 142 | ||||
-rw-r--r-- | sound/pci/cs46xx/cs46xx.h | 1732 | ||||
-rw-r--r-- | sound/pci/cs46xx/cs46xx_dsp_scb_types.h | 1198 | ||||
-rw-r--r-- | sound/pci/cs46xx/cs46xx_dsp_spos.h | 213 | ||||
-rw-r--r-- | sound/pci/cs46xx/cs46xx_dsp_task_types.h | 237 | ||||
-rw-r--r-- | sound/pci/cs46xx/cs46xx_lib.c | 3967 | ||||
-rw-r--r-- | sound/pci/cs46xx/cs46xx_lib.h | 195 | ||||
-rw-r--r-- | sound/pci/cs46xx/dsp_spos.c | 1983 | ||||
-rw-r--r-- | sound/pci/cs46xx/dsp_spos.h | 216 | ||||
-rw-r--r-- | sound/pci/cs46xx/dsp_spos_scb_lib.c | 1777 |
11 files changed, 11671 insertions, 0 deletions
diff --git a/sound/pci/cs46xx/Makefile b/sound/pci/cs46xx/Makefile new file mode 100644 index 000000000..3ed2ceb40 --- /dev/null +++ b/sound/pci/cs46xx/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz> +# + +snd-cs46xx-y := cs46xx.o cs46xx_lib.o +snd-cs46xx-$(CONFIG_SND_CS46XX_NEW_DSP) += dsp_spos.o dsp_spos_scb_lib.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_CS46XX) += snd-cs46xx.o diff --git a/sound/pci/cs46xx/cs46xx.c b/sound/pci/cs46xx/cs46xx.c new file mode 100644 index 000000000..8634004a6 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +/* + NOTES: + - sometimes the sound is metallic and sibilant, unloading and + reloading the module may solve this. +*/ + +#include <linux/pci.h> +#include <linux/time.h> +#include <linux/init.h> +#include <linux/module.h> +#include <sound/core.h> +#include "cs46xx.h" +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Cirrus Logic Sound Fusion CS46XX"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ +static bool external_amp[SNDRV_CARDS]; +static bool thinkpad[SNDRV_CARDS]; +static bool mmap_valid[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the CS46xx soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the CS46xx soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable CS46xx soundcard."); +module_param_array(external_amp, bool, NULL, 0444); +MODULE_PARM_DESC(external_amp, "Force to enable external amplifier."); +module_param_array(thinkpad, bool, NULL, 0444); +MODULE_PARM_DESC(thinkpad, "Force to enable Thinkpad's CLKRUN control."); +module_param_array(mmap_valid, bool, NULL, 0444); +MODULE_PARM_DESC(mmap_valid, "Support OSS mmap."); + +static const struct pci_device_id snd_cs46xx_ids[] = { + { PCI_VDEVICE(CIRRUS, 0x6001), 0, }, /* CS4280 */ + { PCI_VDEVICE(CIRRUS, 0x6003), 0, }, /* CS4612 */ + { PCI_VDEVICE(CIRRUS, 0x6004), 0, }, /* CS4615 */ + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, snd_cs46xx_ids); + +static int snd_card_cs46xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_cs46xx *chip; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + err = snd_cs46xx_create(card, pci, + external_amp[dev], thinkpad[dev]); + if (err < 0) + goto error; + card->private_data = chip; + chip->accept_valid = mmap_valid[dev]; + err = snd_cs46xx_pcm(chip, 0); + if (err < 0) + goto error; +#ifdef CONFIG_SND_CS46XX_NEW_DSP + err = snd_cs46xx_pcm_rear(chip, 1); + if (err < 0) + goto error; + err = snd_cs46xx_pcm_iec958(chip, 2); + if (err < 0) + goto error; +#endif + err = snd_cs46xx_mixer(chip, 2); + if (err < 0) + goto error; +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (chip->nr_ac97_codecs ==2) { + err = snd_cs46xx_pcm_center_lfe(chip, 3); + if (err < 0) + goto error; + } +#endif + err = snd_cs46xx_midi(chip, 0); + if (err < 0) + goto error; + err = snd_cs46xx_start_dsp(chip); + if (err < 0) + goto error; + + snd_cs46xx_gameport(chip); + + strcpy(card->driver, "CS46xx"); + strcpy(card->shortname, "Sound Fusion CS46xx"); + sprintf(card->longname, "%s at 0x%lx/0x%lx, irq %i", + card->shortname, + chip->ba0_addr, + chip->ba1_addr, + chip->irq); + + err = snd_card_register(card); + if (err < 0) + goto error; + + pci_set_drvdata(pci, card); + dev++; + return 0; + + error: + snd_card_free(card); + return err; +} + +static struct pci_driver cs46xx_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_cs46xx_ids, + .probe = snd_card_cs46xx_probe, +#ifdef CONFIG_PM_SLEEP + .driver = { + .pm = &snd_cs46xx_pm, + }, +#endif +}; + +module_pci_driver(cs46xx_driver); diff --git a/sound/pci/cs46xx/cs46xx.h b/sound/pci/cs46xx/cs46xx.h new file mode 100644 index 000000000..c4f0a0b94 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx.h @@ -0,0 +1,1732 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_CS46XX_H +#define __SOUND_CS46XX_H + +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz>, + * Cirrus Logic, Inc. + * Definitions for Cirrus Logic CS46xx chips + */ + +#include <sound/pcm.h> +#include <sound/pcm-indirect.h> +#include <sound/rawmidi.h> +#include <sound/ac97_codec.h> +#include "cs46xx_dsp_spos.h" + +/* + * Direct registers + */ + +/* + * The following define the offsets of the registers accessed via base address + * register zero on the CS46xx part. + */ +#define BA0_HISR 0x00000000 +#define BA0_HSR0 0x00000004 +#define BA0_HICR 0x00000008 +#define BA0_DMSR 0x00000100 +#define BA0_HSAR 0x00000110 +#define BA0_HDAR 0x00000114 +#define BA0_HDMR 0x00000118 +#define BA0_HDCR 0x0000011C +#define BA0_PFMC 0x00000200 +#define BA0_PFCV1 0x00000204 +#define BA0_PFCV2 0x00000208 +#define BA0_PCICFG00 0x00000300 +#define BA0_PCICFG04 0x00000304 +#define BA0_PCICFG08 0x00000308 +#define BA0_PCICFG0C 0x0000030C +#define BA0_PCICFG10 0x00000310 +#define BA0_PCICFG14 0x00000314 +#define BA0_PCICFG18 0x00000318 +#define BA0_PCICFG1C 0x0000031C +#define BA0_PCICFG20 0x00000320 +#define BA0_PCICFG24 0x00000324 +#define BA0_PCICFG28 0x00000328 +#define BA0_PCICFG2C 0x0000032C +#define BA0_PCICFG30 0x00000330 +#define BA0_PCICFG34 0x00000334 +#define BA0_PCICFG38 0x00000338 +#define BA0_PCICFG3C 0x0000033C +#define BA0_CLKCR1 0x00000400 +#define BA0_CLKCR2 0x00000404 +#define BA0_PLLM 0x00000408 +#define BA0_PLLCC 0x0000040C +#define BA0_FRR 0x00000410 +#define BA0_CFL1 0x00000414 +#define BA0_CFL2 0x00000418 +#define BA0_SERMC1 0x00000420 +#define BA0_SERMC2 0x00000424 +#define BA0_SERC1 0x00000428 +#define BA0_SERC2 0x0000042C +#define BA0_SERC3 0x00000430 +#define BA0_SERC4 0x00000434 +#define BA0_SERC5 0x00000438 +#define BA0_SERBSP 0x0000043C +#define BA0_SERBST 0x00000440 +#define BA0_SERBCM 0x00000444 +#define BA0_SERBAD 0x00000448 +#define BA0_SERBCF 0x0000044C +#define BA0_SERBWP 0x00000450 +#define BA0_SERBRP 0x00000454 +#ifndef NO_CS4612 +#define BA0_ASER_FADDR 0x00000458 +#endif +#define BA0_ACCTL 0x00000460 +#define BA0_ACSTS 0x00000464 +#define BA0_ACOSV 0x00000468 +#define BA0_ACCAD 0x0000046C +#define BA0_ACCDA 0x00000470 +#define BA0_ACISV 0x00000474 +#define BA0_ACSAD 0x00000478 +#define BA0_ACSDA 0x0000047C +#define BA0_JSPT 0x00000480 +#define BA0_JSCTL 0x00000484 +#define BA0_JSC1 0x00000488 +#define BA0_JSC2 0x0000048C +#define BA0_MIDCR 0x00000490 +#define BA0_MIDSR 0x00000494 +#define BA0_MIDWP 0x00000498 +#define BA0_MIDRP 0x0000049C +#define BA0_JSIO 0x000004A0 +#ifndef NO_CS4612 +#define BA0_ASER_MASTER 0x000004A4 +#endif +#define BA0_CFGI 0x000004B0 +#define BA0_SSVID 0x000004B4 +#define BA0_GPIOR 0x000004B8 +#ifndef NO_CS4612 +#define BA0_EGPIODR 0x000004BC +#define BA0_EGPIOPTR 0x000004C0 +#define BA0_EGPIOTR 0x000004C4 +#define BA0_EGPIOWR 0x000004C8 +#define BA0_EGPIOSR 0x000004CC +#define BA0_SERC6 0x000004D0 +#define BA0_SERC7 0x000004D4 +#define BA0_SERACC 0x000004D8 +#define BA0_ACCTL2 0x000004E0 +#define BA0_ACSTS2 0x000004E4 +#define BA0_ACOSV2 0x000004E8 +#define BA0_ACCAD2 0x000004EC +#define BA0_ACCDA2 0x000004F0 +#define BA0_ACISV2 0x000004F4 +#define BA0_ACSAD2 0x000004F8 +#define BA0_ACSDA2 0x000004FC +#define BA0_IOTAC0 0x00000500 +#define BA0_IOTAC1 0x00000504 +#define BA0_IOTAC2 0x00000508 +#define BA0_IOTAC3 0x0000050C +#define BA0_IOTAC4 0x00000510 +#define BA0_IOTAC5 0x00000514 +#define BA0_IOTAC6 0x00000518 +#define BA0_IOTAC7 0x0000051C +#define BA0_IOTAC8 0x00000520 +#define BA0_IOTAC9 0x00000524 +#define BA0_IOTAC10 0x00000528 +#define BA0_IOTAC11 0x0000052C +#define BA0_IOTFR0 0x00000540 +#define BA0_IOTFR1 0x00000544 +#define BA0_IOTFR2 0x00000548 +#define BA0_IOTFR3 0x0000054C +#define BA0_IOTFR4 0x00000550 +#define BA0_IOTFR5 0x00000554 +#define BA0_IOTFR6 0x00000558 +#define BA0_IOTFR7 0x0000055C +#define BA0_IOTFIFO 0x00000580 +#define BA0_IOTRRD 0x00000584 +#define BA0_IOTFP 0x00000588 +#define BA0_IOTCR 0x0000058C +#define BA0_DPCID 0x00000590 +#define BA0_DPCIA 0x00000594 +#define BA0_DPCIC 0x00000598 +#define BA0_PCPCIR 0x00000600 +#define BA0_PCPCIG 0x00000604 +#define BA0_PCPCIEN 0x00000608 +#define BA0_EPCIPMC 0x00000610 +#endif + +/* + * The following define the offsets of the registers and memories accessed via + * base address register one on the CS46xx part. + */ +#define BA1_SP_DMEM0 0x00000000 +#define BA1_SP_DMEM1 0x00010000 +#define BA1_SP_PMEM 0x00020000 +#define BA1_SP_REG 0x00030000 +#define BA1_SPCR 0x00030000 +#define BA1_DREG 0x00030004 +#define BA1_DSRWP 0x00030008 +#define BA1_TWPR 0x0003000C +#define BA1_SPWR 0x00030010 +#define BA1_SPIR 0x00030014 +#define BA1_FGR1 0x00030020 +#define BA1_SPCS 0x00030028 +#define BA1_SDSR 0x0003002C +#define BA1_FRMT 0x00030030 +#define BA1_FRCC 0x00030034 +#define BA1_FRSC 0x00030038 +#define BA1_OMNI_MEM 0x000E0000 + + +/* + * The following defines are for the flags in the host interrupt status + * register. + */ +#define HISR_VC_MASK 0x0000FFFF +#define HISR_VC0 0x00000001 +#define HISR_VC1 0x00000002 +#define HISR_VC2 0x00000004 +#define HISR_VC3 0x00000008 +#define HISR_VC4 0x00000010 +#define HISR_VC5 0x00000020 +#define HISR_VC6 0x00000040 +#define HISR_VC7 0x00000080 +#define HISR_VC8 0x00000100 +#define HISR_VC9 0x00000200 +#define HISR_VC10 0x00000400 +#define HISR_VC11 0x00000800 +#define HISR_VC12 0x00001000 +#define HISR_VC13 0x00002000 +#define HISR_VC14 0x00004000 +#define HISR_VC15 0x00008000 +#define HISR_INT0 0x00010000 +#define HISR_INT1 0x00020000 +#define HISR_DMAI 0x00040000 +#define HISR_FROVR 0x00080000 +#define HISR_MIDI 0x00100000 +#ifdef NO_CS4612 +#define HISR_RESERVED 0x0FE00000 +#else +#define HISR_SBINT 0x00200000 +#define HISR_RESERVED 0x0FC00000 +#endif +#define HISR_H0P 0x40000000 +#define HISR_INTENA 0x80000000 + +/* + * The following defines are for the flags in the host signal register 0. + */ +#define HSR0_VC_MASK 0xFFFFFFFF +#define HSR0_VC16 0x00000001 +#define HSR0_VC17 0x00000002 +#define HSR0_VC18 0x00000004 +#define HSR0_VC19 0x00000008 +#define HSR0_VC20 0x00000010 +#define HSR0_VC21 0x00000020 +#define HSR0_VC22 0x00000040 +#define HSR0_VC23 0x00000080 +#define HSR0_VC24 0x00000100 +#define HSR0_VC25 0x00000200 +#define HSR0_VC26 0x00000400 +#define HSR0_VC27 0x00000800 +#define HSR0_VC28 0x00001000 +#define HSR0_VC29 0x00002000 +#define HSR0_VC30 0x00004000 +#define HSR0_VC31 0x00008000 +#define HSR0_VC32 0x00010000 +#define HSR0_VC33 0x00020000 +#define HSR0_VC34 0x00040000 +#define HSR0_VC35 0x00080000 +#define HSR0_VC36 0x00100000 +#define HSR0_VC37 0x00200000 +#define HSR0_VC38 0x00400000 +#define HSR0_VC39 0x00800000 +#define HSR0_VC40 0x01000000 +#define HSR0_VC41 0x02000000 +#define HSR0_VC42 0x04000000 +#define HSR0_VC43 0x08000000 +#define HSR0_VC44 0x10000000 +#define HSR0_VC45 0x20000000 +#define HSR0_VC46 0x40000000 +#define HSR0_VC47 0x80000000 + +/* + * The following defines are for the flags in the host interrupt control + * register. + */ +#define HICR_IEV 0x00000001 +#define HICR_CHGM 0x00000002 + +/* + * The following defines are for the flags in the DMA status register. + */ +#define DMSR_HP 0x00000001 +#define DMSR_HR 0x00000002 +#define DMSR_SP 0x00000004 +#define DMSR_SR 0x00000008 + +/* + * The following defines are for the flags in the host DMA source address + * register. + */ +#define HSAR_HOST_ADDR_MASK 0xFFFFFFFF +#define HSAR_DSP_ADDR_MASK 0x0000FFFF +#define HSAR_MEMID_MASK 0x000F0000 +#define HSAR_MEMID_SP_DMEM0 0x00000000 +#define HSAR_MEMID_SP_DMEM1 0x00010000 +#define HSAR_MEMID_SP_PMEM 0x00020000 +#define HSAR_MEMID_SP_DEBUG 0x00030000 +#define HSAR_MEMID_OMNI_MEM 0x000E0000 +#define HSAR_END 0x40000000 +#define HSAR_ERR 0x80000000 + +/* + * The following defines are for the flags in the host DMA destination address + * register. + */ +#define HDAR_HOST_ADDR_MASK 0xFFFFFFFF +#define HDAR_DSP_ADDR_MASK 0x0000FFFF +#define HDAR_MEMID_MASK 0x000F0000 +#define HDAR_MEMID_SP_DMEM0 0x00000000 +#define HDAR_MEMID_SP_DMEM1 0x00010000 +#define HDAR_MEMID_SP_PMEM 0x00020000 +#define HDAR_MEMID_SP_DEBUG 0x00030000 +#define HDAR_MEMID_OMNI_MEM 0x000E0000 +#define HDAR_END 0x40000000 +#define HDAR_ERR 0x80000000 + +/* + * The following defines are for the flags in the host DMA control register. + */ +#define HDMR_AC_MASK 0x0000F000 +#define HDMR_AC_8_16 0x00001000 +#define HDMR_AC_M_S 0x00002000 +#define HDMR_AC_B_L 0x00004000 +#define HDMR_AC_S_U 0x00008000 + +/* + * The following defines are for the flags in the host DMA control register. + */ +#define HDCR_COUNT_MASK 0x000003FF +#define HDCR_DONE 0x00004000 +#define HDCR_OPT 0x00008000 +#define HDCR_WBD 0x00400000 +#define HDCR_WBS 0x00800000 +#define HDCR_DMS_MASK 0x07000000 +#define HDCR_DMS_LINEAR 0x00000000 +#define HDCR_DMS_16_DWORDS 0x01000000 +#define HDCR_DMS_32_DWORDS 0x02000000 +#define HDCR_DMS_64_DWORDS 0x03000000 +#define HDCR_DMS_128_DWORDS 0x04000000 +#define HDCR_DMS_256_DWORDS 0x05000000 +#define HDCR_DMS_512_DWORDS 0x06000000 +#define HDCR_DMS_1024_DWORDS 0x07000000 +#define HDCR_DH 0x08000000 +#define HDCR_SMS_MASK 0x70000000 +#define HDCR_SMS_LINEAR 0x00000000 +#define HDCR_SMS_16_DWORDS 0x10000000 +#define HDCR_SMS_32_DWORDS 0x20000000 +#define HDCR_SMS_64_DWORDS 0x30000000 +#define HDCR_SMS_128_DWORDS 0x40000000 +#define HDCR_SMS_256_DWORDS 0x50000000 +#define HDCR_SMS_512_DWORDS 0x60000000 +#define HDCR_SMS_1024_DWORDS 0x70000000 +#define HDCR_SH 0x80000000 +#define HDCR_COUNT_SHIFT 0 + +/* + * The following defines are for the flags in the performance monitor control + * register. + */ +#define PFMC_C1SS_MASK 0x0000001F +#define PFMC_C1EV 0x00000020 +#define PFMC_C1RS 0x00008000 +#define PFMC_C2SS_MASK 0x001F0000 +#define PFMC_C2EV 0x00200000 +#define PFMC_C2RS 0x80000000 +#define PFMC_C1SS_SHIFT 0 +#define PFMC_C2SS_SHIFT 16 +#define PFMC_BUS_GRANT 0 +#define PFMC_GRANT_AFTER_REQ 1 +#define PFMC_TRANSACTION 2 +#define PFMC_DWORD_TRANSFER 3 +#define PFMC_SLAVE_READ 4 +#define PFMC_SLAVE_WRITE 5 +#define PFMC_PREEMPTION 6 +#define PFMC_DISCONNECT_RETRY 7 +#define PFMC_INTERRUPT 8 +#define PFMC_BUS_OWNERSHIP 9 +#define PFMC_TRANSACTION_LAG 10 +#define PFMC_PCI_CLOCK 11 +#define PFMC_SERIAL_CLOCK 12 +#define PFMC_SP_CLOCK 13 + +/* + * The following defines are for the flags in the performance counter value 1 + * register. + */ +#define PFCV1_PC1V_MASK 0xFFFFFFFF +#define PFCV1_PC1V_SHIFT 0 + +/* + * The following defines are for the flags in the performance counter value 2 + * register. + */ +#define PFCV2_PC2V_MASK 0xFFFFFFFF +#define PFCV2_PC2V_SHIFT 0 + +/* + * The following defines are for the flags in the clock control register 1. + */ +#define CLKCR1_OSCS 0x00000001 +#define CLKCR1_OSCP 0x00000002 +#define CLKCR1_PLLSS_MASK 0x0000000C +#define CLKCR1_PLLSS_SERIAL 0x00000000 +#define CLKCR1_PLLSS_CRYSTAL 0x00000004 +#define CLKCR1_PLLSS_PCI 0x00000008 +#define CLKCR1_PLLSS_RESERVED 0x0000000C +#define CLKCR1_PLLP 0x00000010 +#define CLKCR1_SWCE 0x00000020 +#define CLKCR1_PLLOS 0x00000040 + +/* + * The following defines are for the flags in the clock control register 2. + */ +#define CLKCR2_PDIVS_MASK 0x0000000F +#define CLKCR2_PDIVS_1 0x00000001 +#define CLKCR2_PDIVS_2 0x00000002 +#define CLKCR2_PDIVS_4 0x00000004 +#define CLKCR2_PDIVS_7 0x00000007 +#define CLKCR2_PDIVS_8 0x00000008 +#define CLKCR2_PDIVS_16 0x00000000 + +/* + * The following defines are for the flags in the PLL multiplier register. + */ +#define PLLM_MASK 0x000000FF +#define PLLM_SHIFT 0 + +/* + * The following defines are for the flags in the PLL capacitor coefficient + * register. + */ +#define PLLCC_CDR_MASK 0x00000007 +#ifndef NO_CS4610 +#define PLLCC_CDR_240_350_MHZ 0x00000000 +#define PLLCC_CDR_184_265_MHZ 0x00000001 +#define PLLCC_CDR_144_205_MHZ 0x00000002 +#define PLLCC_CDR_111_160_MHZ 0x00000003 +#define PLLCC_CDR_87_123_MHZ 0x00000004 +#define PLLCC_CDR_67_96_MHZ 0x00000005 +#define PLLCC_CDR_52_74_MHZ 0x00000006 +#define PLLCC_CDR_45_58_MHZ 0x00000007 +#endif +#ifndef NO_CS4612 +#define PLLCC_CDR_271_398_MHZ 0x00000000 +#define PLLCC_CDR_227_330_MHZ 0x00000001 +#define PLLCC_CDR_167_239_MHZ 0x00000002 +#define PLLCC_CDR_150_215_MHZ 0x00000003 +#define PLLCC_CDR_107_154_MHZ 0x00000004 +#define PLLCC_CDR_98_140_MHZ 0x00000005 +#define PLLCC_CDR_73_104_MHZ 0x00000006 +#define PLLCC_CDR_63_90_MHZ 0x00000007 +#endif +#define PLLCC_LPF_MASK 0x000000F8 +#ifndef NO_CS4610 +#define PLLCC_LPF_23850_60000_KHZ 0x00000000 +#define PLLCC_LPF_7960_26290_KHZ 0x00000008 +#define PLLCC_LPF_4160_10980_KHZ 0x00000018 +#define PLLCC_LPF_1740_4580_KHZ 0x00000038 +#define PLLCC_LPF_724_1910_KHZ 0x00000078 +#define PLLCC_LPF_317_798_KHZ 0x000000F8 +#endif +#ifndef NO_CS4612 +#define PLLCC_LPF_25580_64530_KHZ 0x00000000 +#define PLLCC_LPF_14360_37270_KHZ 0x00000008 +#define PLLCC_LPF_6100_16020_KHZ 0x00000018 +#define PLLCC_LPF_2540_6690_KHZ 0x00000038 +#define PLLCC_LPF_1050_2780_KHZ 0x00000078 +#define PLLCC_LPF_450_1160_KHZ 0x000000F8 +#endif + +/* + * The following defines are for the flags in the feature reporting register. + */ +#define FRR_FAB_MASK 0x00000003 +#define FRR_MASK_MASK 0x0000001C +#ifdef NO_CS4612 +#define FRR_CFOP_MASK 0x000000E0 +#else +#define FRR_CFOP_MASK 0x00000FE0 +#endif +#define FRR_CFOP_NOT_DVD 0x00000020 +#define FRR_CFOP_A3D 0x00000040 +#define FRR_CFOP_128_PIN 0x00000080 +#ifndef NO_CS4612 +#define FRR_CFOP_CS4280 0x00000800 +#endif +#define FRR_FAB_SHIFT 0 +#define FRR_MASK_SHIFT 2 +#define FRR_CFOP_SHIFT 5 + +/* + * The following defines are for the flags in the configuration load 1 + * register. + */ +#define CFL1_CLOCK_SOURCE_MASK 0x00000003 +#define CFL1_CLOCK_SOURCE_CS423X 0x00000000 +#define CFL1_CLOCK_SOURCE_AC97 0x00000001 +#define CFL1_CLOCK_SOURCE_CRYSTAL 0x00000002 +#define CFL1_CLOCK_SOURCE_DUAL_AC97 0x00000003 +#define CFL1_VALID_DATA_MASK 0x000000FF + +/* + * The following defines are for the flags in the configuration load 2 + * register. + */ +#define CFL2_VALID_DATA_MASK 0x000000FF + +/* + * The following defines are for the flags in the serial port master control + * register 1. + */ +#define SERMC1_MSPE 0x00000001 +#define SERMC1_PTC_MASK 0x0000000E +#define SERMC1_PTC_CS423X 0x00000000 +#define SERMC1_PTC_AC97 0x00000002 +#define SERMC1_PTC_DAC 0x00000004 +#define SERMC1_PLB 0x00000010 +#define SERMC1_XLB 0x00000020 + +/* + * The following defines are for the flags in the serial port master control + * register 2. + */ +#define SERMC2_LROE 0x00000001 +#define SERMC2_MCOE 0x00000002 +#define SERMC2_MCDIV 0x00000004 + +/* + * The following defines are for the flags in the serial port 1 configuration + * register. + */ +#define SERC1_SO1EN 0x00000001 +#define SERC1_SO1F_MASK 0x0000000E +#define SERC1_SO1F_CS423X 0x00000000 +#define SERC1_SO1F_AC97 0x00000002 +#define SERC1_SO1F_DAC 0x00000004 +#define SERC1_SO1F_SPDIF 0x00000006 + +/* + * The following defines are for the flags in the serial port 2 configuration + * register. + */ +#define SERC2_SI1EN 0x00000001 +#define SERC2_SI1F_MASK 0x0000000E +#define SERC2_SI1F_CS423X 0x00000000 +#define SERC2_SI1F_AC97 0x00000002 +#define SERC2_SI1F_ADC 0x00000004 +#define SERC2_SI1F_SPDIF 0x00000006 + +/* + * The following defines are for the flags in the serial port 3 configuration + * register. + */ +#define SERC3_SO2EN 0x00000001 +#define SERC3_SO2F_MASK 0x00000006 +#define SERC3_SO2F_DAC 0x00000000 +#define SERC3_SO2F_SPDIF 0x00000002 + +/* + * The following defines are for the flags in the serial port 4 configuration + * register. + */ +#define SERC4_SO3EN 0x00000001 +#define SERC4_SO3F_MASK 0x00000006 +#define SERC4_SO3F_DAC 0x00000000 +#define SERC4_SO3F_SPDIF 0x00000002 + +/* + * The following defines are for the flags in the serial port 5 configuration + * register. + */ +#define SERC5_SI2EN 0x00000001 +#define SERC5_SI2F_MASK 0x00000006 +#define SERC5_SI2F_ADC 0x00000000 +#define SERC5_SI2F_SPDIF 0x00000002 + +/* + * The following defines are for the flags in the serial port backdoor sample + * pointer register. + */ +#define SERBSP_FSP_MASK 0x0000000F +#define SERBSP_FSP_SHIFT 0 + +/* + * The following defines are for the flags in the serial port backdoor status + * register. + */ +#define SERBST_RRDY 0x00000001 +#define SERBST_WBSY 0x00000002 + +/* + * The following defines are for the flags in the serial port backdoor command + * register. + */ +#define SERBCM_RDC 0x00000001 +#define SERBCM_WRC 0x00000002 + +/* + * The following defines are for the flags in the serial port backdoor address + * register. + */ +#ifdef NO_CS4612 +#define SERBAD_FAD_MASK 0x000000FF +#else +#define SERBAD_FAD_MASK 0x000001FF +#endif +#define SERBAD_FAD_SHIFT 0 + +/* + * The following defines are for the flags in the serial port backdoor + * configuration register. + */ +#define SERBCF_HBP 0x00000001 + +/* + * The following defines are for the flags in the serial port backdoor write + * port register. + */ +#define SERBWP_FWD_MASK 0x000FFFFF +#define SERBWP_FWD_SHIFT 0 + +/* + * The following defines are for the flags in the serial port backdoor read + * port register. + */ +#define SERBRP_FRD_MASK 0x000FFFFF +#define SERBRP_FRD_SHIFT 0 + +/* + * The following defines are for the flags in the async FIFO address register. + */ +#ifndef NO_CS4612 +#define ASER_FADDR_A1_MASK 0x000001FF +#define ASER_FADDR_EN1 0x00008000 +#define ASER_FADDR_A2_MASK 0x01FF0000 +#define ASER_FADDR_EN2 0x80000000 +#define ASER_FADDR_A1_SHIFT 0 +#define ASER_FADDR_A2_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the AC97 control register. + */ +#define ACCTL_RSTN 0x00000001 +#define ACCTL_ESYN 0x00000002 +#define ACCTL_VFRM 0x00000004 +#define ACCTL_DCV 0x00000008 +#define ACCTL_CRW 0x00000010 +#define ACCTL_ASYN 0x00000020 +#ifndef NO_CS4612 +#define ACCTL_TC 0x00000040 +#endif + +/* + * The following defines are for the flags in the AC97 status register. + */ +#define ACSTS_CRDY 0x00000001 +#define ACSTS_VSTS 0x00000002 +#ifndef NO_CS4612 +#define ACSTS_WKUP 0x00000004 +#endif + +/* + * The following defines are for the flags in the AC97 output slot valid + * register. + */ +#define ACOSV_SLV3 0x00000001 +#define ACOSV_SLV4 0x00000002 +#define ACOSV_SLV5 0x00000004 +#define ACOSV_SLV6 0x00000008 +#define ACOSV_SLV7 0x00000010 +#define ACOSV_SLV8 0x00000020 +#define ACOSV_SLV9 0x00000040 +#define ACOSV_SLV10 0x00000080 +#define ACOSV_SLV11 0x00000100 +#define ACOSV_SLV12 0x00000200 + +/* + * The following defines are for the flags in the AC97 command address + * register. + */ +#define ACCAD_CI_MASK 0x0000007F +#define ACCAD_CI_SHIFT 0 + +/* + * The following defines are for the flags in the AC97 command data register. + */ +#define ACCDA_CD_MASK 0x0000FFFF +#define ACCDA_CD_SHIFT 0 + +/* + * The following defines are for the flags in the AC97 input slot valid + * register. + */ +#define ACISV_ISV3 0x00000001 +#define ACISV_ISV4 0x00000002 +#define ACISV_ISV5 0x00000004 +#define ACISV_ISV6 0x00000008 +#define ACISV_ISV7 0x00000010 +#define ACISV_ISV8 0x00000020 +#define ACISV_ISV9 0x00000040 +#define ACISV_ISV10 0x00000080 +#define ACISV_ISV11 0x00000100 +#define ACISV_ISV12 0x00000200 + +/* + * The following defines are for the flags in the AC97 status address + * register. + */ +#define ACSAD_SI_MASK 0x0000007F +#define ACSAD_SI_SHIFT 0 + +/* + * The following defines are for the flags in the AC97 status data register. + */ +#define ACSDA_SD_MASK 0x0000FFFF +#define ACSDA_SD_SHIFT 0 + +/* + * The following defines are for the flags in the joystick poll/trigger + * register. + */ +#define JSPT_CAX 0x00000001 +#define JSPT_CAY 0x00000002 +#define JSPT_CBX 0x00000004 +#define JSPT_CBY 0x00000008 +#define JSPT_BA1 0x00000010 +#define JSPT_BA2 0x00000020 +#define JSPT_BB1 0x00000040 +#define JSPT_BB2 0x00000080 + +/* + * The following defines are for the flags in the joystick control register. + */ +#define JSCTL_SP_MASK 0x00000003 +#define JSCTL_SP_SLOW 0x00000000 +#define JSCTL_SP_MEDIUM_SLOW 0x00000001 +#define JSCTL_SP_MEDIUM_FAST 0x00000002 +#define JSCTL_SP_FAST 0x00000003 +#define JSCTL_ARE 0x00000004 + +/* + * The following defines are for the flags in the joystick coordinate pair 1 + * readback register. + */ +#define JSC1_Y1V_MASK 0x0000FFFF +#define JSC1_X1V_MASK 0xFFFF0000 +#define JSC1_Y1V_SHIFT 0 +#define JSC1_X1V_SHIFT 16 + +/* + * The following defines are for the flags in the joystick coordinate pair 2 + * readback register. + */ +#define JSC2_Y2V_MASK 0x0000FFFF +#define JSC2_X2V_MASK 0xFFFF0000 +#define JSC2_Y2V_SHIFT 0 +#define JSC2_X2V_SHIFT 16 + +/* + * The following defines are for the flags in the MIDI control register. + */ +#define MIDCR_TXE 0x00000001 /* Enable transmitting. */ +#define MIDCR_RXE 0x00000002 /* Enable receiving. */ +#define MIDCR_RIE 0x00000004 /* Interrupt upon tx ready. */ +#define MIDCR_TIE 0x00000008 /* Interrupt upon rx ready. */ +#define MIDCR_MLB 0x00000010 /* Enable midi loopback. */ +#define MIDCR_MRST 0x00000020 /* Reset interface. */ + +/* + * The following defines are for the flags in the MIDI status register. + */ +#define MIDSR_TBF 0x00000001 /* Tx FIFO is full. */ +#define MIDSR_RBE 0x00000002 /* Rx FIFO is empty. */ + +/* + * The following defines are for the flags in the MIDI write port register. + */ +#define MIDWP_MWD_MASK 0x000000FF +#define MIDWP_MWD_SHIFT 0 + +/* + * The following defines are for the flags in the MIDI read port register. + */ +#define MIDRP_MRD_MASK 0x000000FF +#define MIDRP_MRD_SHIFT 0 + +/* + * The following defines are for the flags in the joystick GPIO register. + */ +#define JSIO_DAX 0x00000001 +#define JSIO_DAY 0x00000002 +#define JSIO_DBX 0x00000004 +#define JSIO_DBY 0x00000008 +#define JSIO_AXOE 0x00000010 +#define JSIO_AYOE 0x00000020 +#define JSIO_BXOE 0x00000040 +#define JSIO_BYOE 0x00000080 + +/* + * The following defines are for the flags in the master async/sync serial + * port enable register. + */ +#ifndef NO_CS4612 +#define ASER_MASTER_ME 0x00000001 +#endif + +/* + * The following defines are for the flags in the configuration interface + * register. + */ +#define CFGI_CLK 0x00000001 +#define CFGI_DOUT 0x00000002 +#define CFGI_DIN_EEN 0x00000004 +#define CFGI_EELD 0x00000008 + +/* + * The following defines are for the flags in the subsystem ID and vendor ID + * register. + */ +#define SSVID_VID_MASK 0x0000FFFF +#define SSVID_SID_MASK 0xFFFF0000 +#define SSVID_VID_SHIFT 0 +#define SSVID_SID_SHIFT 16 + +/* + * The following defines are for the flags in the GPIO pin interface register. + */ +#define GPIOR_VOLDN 0x00000001 +#define GPIOR_VOLUP 0x00000002 +#define GPIOR_SI2D 0x00000004 +#define GPIOR_SI2OE 0x00000008 + +/* + * The following defines are for the flags in the extended GPIO pin direction + * register. + */ +#ifndef NO_CS4612 +#define EGPIODR_GPOE0 0x00000001 +#define EGPIODR_GPOE1 0x00000002 +#define EGPIODR_GPOE2 0x00000004 +#define EGPIODR_GPOE3 0x00000008 +#define EGPIODR_GPOE4 0x00000010 +#define EGPIODR_GPOE5 0x00000020 +#define EGPIODR_GPOE6 0x00000040 +#define EGPIODR_GPOE7 0x00000080 +#define EGPIODR_GPOE8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO pin polarity/ + * type register. + */ +#ifndef NO_CS4612 +#define EGPIOPTR_GPPT0 0x00000001 +#define EGPIOPTR_GPPT1 0x00000002 +#define EGPIOPTR_GPPT2 0x00000004 +#define EGPIOPTR_GPPT3 0x00000008 +#define EGPIOPTR_GPPT4 0x00000010 +#define EGPIOPTR_GPPT5 0x00000020 +#define EGPIOPTR_GPPT6 0x00000040 +#define EGPIOPTR_GPPT7 0x00000080 +#define EGPIOPTR_GPPT8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO pin sticky + * register. + */ +#ifndef NO_CS4612 +#define EGPIOTR_GPS0 0x00000001 +#define EGPIOTR_GPS1 0x00000002 +#define EGPIOTR_GPS2 0x00000004 +#define EGPIOTR_GPS3 0x00000008 +#define EGPIOTR_GPS4 0x00000010 +#define EGPIOTR_GPS5 0x00000020 +#define EGPIOTR_GPS6 0x00000040 +#define EGPIOTR_GPS7 0x00000080 +#define EGPIOTR_GPS8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO ping wakeup + * register. + */ +#ifndef NO_CS4612 +#define EGPIOWR_GPW0 0x00000001 +#define EGPIOWR_GPW1 0x00000002 +#define EGPIOWR_GPW2 0x00000004 +#define EGPIOWR_GPW3 0x00000008 +#define EGPIOWR_GPW4 0x00000010 +#define EGPIOWR_GPW5 0x00000020 +#define EGPIOWR_GPW6 0x00000040 +#define EGPIOWR_GPW7 0x00000080 +#define EGPIOWR_GPW8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO pin status + * register. + */ +#ifndef NO_CS4612 +#define EGPIOSR_GPS0 0x00000001 +#define EGPIOSR_GPS1 0x00000002 +#define EGPIOSR_GPS2 0x00000004 +#define EGPIOSR_GPS3 0x00000008 +#define EGPIOSR_GPS4 0x00000010 +#define EGPIOSR_GPS5 0x00000020 +#define EGPIOSR_GPS6 0x00000040 +#define EGPIOSR_GPS7 0x00000080 +#define EGPIOSR_GPS8 0x00000100 +#endif + +/* + * The following defines are for the flags in the serial port 6 configuration + * register. + */ +#ifndef NO_CS4612 +#define SERC6_ASDO2EN 0x00000001 +#endif + +/* + * The following defines are for the flags in the serial port 7 configuration + * register. + */ +#ifndef NO_CS4612 +#define SERC7_ASDI2EN 0x00000001 +#define SERC7_POSILB 0x00000002 +#define SERC7_SIPOLB 0x00000004 +#define SERC7_SOSILB 0x00000008 +#define SERC7_SISOLB 0x00000010 +#endif + +/* + * The following defines are for the flags in the serial port AC link + * configuration register. + */ +#ifndef NO_CS4612 +#define SERACC_CHIP_TYPE_MASK 0x00000001 +#define SERACC_CHIP_TYPE_1_03 0x00000000 +#define SERACC_CHIP_TYPE_2_0 0x00000001 +#define SERACC_TWO_CODECS 0x00000002 +#define SERACC_MDM 0x00000004 +#define SERACC_HSP 0x00000008 +#define SERACC_ODT 0x00000010 /* only CS4630 */ +#endif + +/* + * The following defines are for the flags in the AC97 control register 2. + */ +#ifndef NO_CS4612 +#define ACCTL2_RSTN 0x00000001 +#define ACCTL2_ESYN 0x00000002 +#define ACCTL2_VFRM 0x00000004 +#define ACCTL2_DCV 0x00000008 +#define ACCTL2_CRW 0x00000010 +#define ACCTL2_ASYN 0x00000020 +#endif + +/* + * The following defines are for the flags in the AC97 status register 2. + */ +#ifndef NO_CS4612 +#define ACSTS2_CRDY 0x00000001 +#define ACSTS2_VSTS 0x00000002 +#endif + +/* + * The following defines are for the flags in the AC97 output slot valid + * register 2. + */ +#ifndef NO_CS4612 +#define ACOSV2_SLV3 0x00000001 +#define ACOSV2_SLV4 0x00000002 +#define ACOSV2_SLV5 0x00000004 +#define ACOSV2_SLV6 0x00000008 +#define ACOSV2_SLV7 0x00000010 +#define ACOSV2_SLV8 0x00000020 +#define ACOSV2_SLV9 0x00000040 +#define ACOSV2_SLV10 0x00000080 +#define ACOSV2_SLV11 0x00000100 +#define ACOSV2_SLV12 0x00000200 +#endif + +/* + * The following defines are for the flags in the AC97 command address + * register 2. + */ +#ifndef NO_CS4612 +#define ACCAD2_CI_MASK 0x0000007F +#define ACCAD2_CI_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the AC97 command data register + * 2. + */ +#ifndef NO_CS4612 +#define ACCDA2_CD_MASK 0x0000FFFF +#define ACCDA2_CD_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the AC97 input slot valid + * register 2. + */ +#ifndef NO_CS4612 +#define ACISV2_ISV3 0x00000001 +#define ACISV2_ISV4 0x00000002 +#define ACISV2_ISV5 0x00000004 +#define ACISV2_ISV6 0x00000008 +#define ACISV2_ISV7 0x00000010 +#define ACISV2_ISV8 0x00000020 +#define ACISV2_ISV9 0x00000040 +#define ACISV2_ISV10 0x00000080 +#define ACISV2_ISV11 0x00000100 +#define ACISV2_ISV12 0x00000200 +#endif + +/* + * The following defines are for the flags in the AC97 status address + * register 2. + */ +#ifndef NO_CS4612 +#define ACSAD2_SI_MASK 0x0000007F +#define ACSAD2_SI_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the AC97 status data register 2. + */ +#ifndef NO_CS4612 +#define ACSDA2_SD_MASK 0x0000FFFF +#define ACSDA2_SD_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the I/O trap address and control + * registers (all 12). + */ +#ifndef NO_CS4612 +#define IOTAC_SA_MASK 0x0000FFFF +#define IOTAC_MSK_MASK 0x000F0000 +#define IOTAC_IODC_MASK 0x06000000 +#define IOTAC_IODC_16_BIT 0x00000000 +#define IOTAC_IODC_10_BIT 0x02000000 +#define IOTAC_IODC_12_BIT 0x04000000 +#define IOTAC_WSPI 0x08000000 +#define IOTAC_RSPI 0x10000000 +#define IOTAC_WSE 0x20000000 +#define IOTAC_WE 0x40000000 +#define IOTAC_RE 0x80000000 +#define IOTAC_SA_SHIFT 0 +#define IOTAC_MSK_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the I/O trap fast read registers + * (all 8). + */ +#ifndef NO_CS4612 +#define IOTFR_D_MASK 0x0000FFFF +#define IOTFR_A_MASK 0x000F0000 +#define IOTFR_R_MASK 0x0F000000 +#define IOTFR_ALL 0x40000000 +#define IOTFR_VL 0x80000000 +#define IOTFR_D_SHIFT 0 +#define IOTFR_A_SHIFT 16 +#define IOTFR_R_SHIFT 24 +#endif + +/* + * The following defines are for the flags in the I/O trap FIFO register. + */ +#ifndef NO_CS4612 +#define IOTFIFO_BA_MASK 0x00003FFF +#define IOTFIFO_S_MASK 0x00FF0000 +#define IOTFIFO_OF 0x40000000 +#define IOTFIFO_SPIOF 0x80000000 +#define IOTFIFO_BA_SHIFT 0 +#define IOTFIFO_S_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the I/O trap retry read data + * register. + */ +#ifndef NO_CS4612 +#define IOTRRD_D_MASK 0x0000FFFF +#define IOTRRD_RDV 0x80000000 +#define IOTRRD_D_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the I/O trap FIFO pointer + * register. + */ +#ifndef NO_CS4612 +#define IOTFP_CA_MASK 0x00003FFF +#define IOTFP_PA_MASK 0x3FFF0000 +#define IOTFP_CA_SHIFT 0 +#define IOTFP_PA_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the I/O trap control register. + */ +#ifndef NO_CS4612 +#define IOTCR_ITD 0x00000001 +#define IOTCR_HRV 0x00000002 +#define IOTCR_SRV 0x00000004 +#define IOTCR_DTI 0x00000008 +#define IOTCR_DFI 0x00000010 +#define IOTCR_DDP 0x00000020 +#define IOTCR_JTE 0x00000040 +#define IOTCR_PPE 0x00000080 +#endif + +/* + * The following defines are for the flags in the direct PCI data register. + */ +#ifndef NO_CS4612 +#define DPCID_D_MASK 0xFFFFFFFF +#define DPCID_D_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the direct PCI address register. + */ +#ifndef NO_CS4612 +#define DPCIA_A_MASK 0xFFFFFFFF +#define DPCIA_A_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the direct PCI command register. + */ +#ifndef NO_CS4612 +#define DPCIC_C_MASK 0x0000000F +#define DPCIC_C_IOREAD 0x00000002 +#define DPCIC_C_IOWRITE 0x00000003 +#define DPCIC_BE_MASK 0x000000F0 +#endif + +/* + * The following defines are for the flags in the PC/PCI request register. + */ +#ifndef NO_CS4612 +#define PCPCIR_RDC_MASK 0x00000007 +#define PCPCIR_C_MASK 0x00007000 +#define PCPCIR_REQ 0x00008000 +#define PCPCIR_RDC_SHIFT 0 +#define PCPCIR_C_SHIFT 12 +#endif + +/* + * The following defines are for the flags in the PC/PCI grant register. + */ +#ifndef NO_CS4612 +#define PCPCIG_GDC_MASK 0x00000007 +#define PCPCIG_VL 0x00008000 +#define PCPCIG_GDC_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the PC/PCI master enable + * register. + */ +#ifndef NO_CS4612 +#define PCPCIEN_EN 0x00000001 +#endif + +/* + * The following defines are for the flags in the extended PCI power + * management control register. + */ +#ifndef NO_CS4612 +#define EPCIPMC_GWU 0x00000001 +#define EPCIPMC_FSPC 0x00000002 +#endif + +/* + * The following defines are for the flags in the SP control register. + */ +#define SPCR_RUN 0x00000001 +#define SPCR_STPFR 0x00000002 +#define SPCR_RUNFR 0x00000004 +#define SPCR_TICK 0x00000008 +#define SPCR_DRQEN 0x00000020 +#define SPCR_RSTSP 0x00000040 +#define SPCR_OREN 0x00000080 +#ifndef NO_CS4612 +#define SPCR_PCIINT 0x00000100 +#define SPCR_OINTD 0x00000200 +#define SPCR_CRE 0x00008000 +#endif + +/* + * The following defines are for the flags in the debug index register. + */ +#define DREG_REGID_MASK 0x0000007F +#define DREG_DEBUG 0x00000080 +#define DREG_RGBK_MASK 0x00000700 +#define DREG_TRAP 0x00000800 +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_TRAPX 0x00001000 +#endif +#endif +#define DREG_REGID_SHIFT 0 +#define DREG_RGBK_SHIFT 8 +#define DREG_RGBK_REGID_MASK 0x0000077F +#define DREG_REGID_R0 0x00000010 +#define DREG_REGID_R1 0x00000011 +#define DREG_REGID_R2 0x00000012 +#define DREG_REGID_R3 0x00000013 +#define DREG_REGID_R4 0x00000014 +#define DREG_REGID_R5 0x00000015 +#define DREG_REGID_R6 0x00000016 +#define DREG_REGID_R7 0x00000017 +#define DREG_REGID_R8 0x00000018 +#define DREG_REGID_R9 0x00000019 +#define DREG_REGID_RA 0x0000001A +#define DREG_REGID_RB 0x0000001B +#define DREG_REGID_RC 0x0000001C +#define DREG_REGID_RD 0x0000001D +#define DREG_REGID_RE 0x0000001E +#define DREG_REGID_RF 0x0000001F +#define DREG_REGID_RA_BUS_LOW 0x00000020 +#define DREG_REGID_RA_BUS_HIGH 0x00000038 +#define DREG_REGID_YBUS_LOW 0x00000050 +#define DREG_REGID_YBUS_HIGH 0x00000058 +#define DREG_REGID_TRAP_0 0x00000100 +#define DREG_REGID_TRAP_1 0x00000101 +#define DREG_REGID_TRAP_2 0x00000102 +#define DREG_REGID_TRAP_3 0x00000103 +#define DREG_REGID_TRAP_4 0x00000104 +#define DREG_REGID_TRAP_5 0x00000105 +#define DREG_REGID_TRAP_6 0x00000106 +#define DREG_REGID_TRAP_7 0x00000107 +#define DREG_REGID_INDIRECT_ADDRESS 0x0000010E +#define DREG_REGID_TOP_OF_STACK 0x0000010F +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_REGID_TRAP_8 0x00000110 +#define DREG_REGID_TRAP_9 0x00000111 +#define DREG_REGID_TRAP_10 0x00000112 +#define DREG_REGID_TRAP_11 0x00000113 +#define DREG_REGID_TRAP_12 0x00000114 +#define DREG_REGID_TRAP_13 0x00000115 +#define DREG_REGID_TRAP_14 0x00000116 +#define DREG_REGID_TRAP_15 0x00000117 +#define DREG_REGID_TRAP_16 0x00000118 +#define DREG_REGID_TRAP_17 0x00000119 +#define DREG_REGID_TRAP_18 0x0000011A +#define DREG_REGID_TRAP_19 0x0000011B +#define DREG_REGID_TRAP_20 0x0000011C +#define DREG_REGID_TRAP_21 0x0000011D +#define DREG_REGID_TRAP_22 0x0000011E +#define DREG_REGID_TRAP_23 0x0000011F +#endif +#endif +#define DREG_REGID_RSA0_LOW 0x00000200 +#define DREG_REGID_RSA0_HIGH 0x00000201 +#define DREG_REGID_RSA1_LOW 0x00000202 +#define DREG_REGID_RSA1_HIGH 0x00000203 +#define DREG_REGID_RSA2 0x00000204 +#define DREG_REGID_RSA3 0x00000205 +#define DREG_REGID_RSI0_LOW 0x00000206 +#define DREG_REGID_RSI0_HIGH 0x00000207 +#define DREG_REGID_RSI1 0x00000208 +#define DREG_REGID_RSI2 0x00000209 +#define DREG_REGID_SAGUSTATUS 0x0000020A +#define DREG_REGID_RSCONFIG01_LOW 0x0000020B +#define DREG_REGID_RSCONFIG01_HIGH 0x0000020C +#define DREG_REGID_RSCONFIG23_LOW 0x0000020D +#define DREG_REGID_RSCONFIG23_HIGH 0x0000020E +#define DREG_REGID_RSDMA01E 0x0000020F +#define DREG_REGID_RSDMA23E 0x00000210 +#define DREG_REGID_RSD0_LOW 0x00000211 +#define DREG_REGID_RSD0_HIGH 0x00000212 +#define DREG_REGID_RSD1_LOW 0x00000213 +#define DREG_REGID_RSD1_HIGH 0x00000214 +#define DREG_REGID_RSD2_LOW 0x00000215 +#define DREG_REGID_RSD2_HIGH 0x00000216 +#define DREG_REGID_RSD3_LOW 0x00000217 +#define DREG_REGID_RSD3_HIGH 0x00000218 +#define DREG_REGID_SRAR_HIGH 0x0000021A +#define DREG_REGID_SRAR_LOW 0x0000021B +#define DREG_REGID_DMA_STATE 0x0000021C +#define DREG_REGID_CURRENT_DMA_STREAM 0x0000021D +#define DREG_REGID_NEXT_DMA_STREAM 0x0000021E +#define DREG_REGID_CPU_STATUS 0x00000300 +#define DREG_REGID_MAC_MODE 0x00000301 +#define DREG_REGID_STACK_AND_REPEAT 0x00000302 +#define DREG_REGID_INDEX0 0x00000304 +#define DREG_REGID_INDEX1 0x00000305 +#define DREG_REGID_DMA_STATE_0_3 0x00000400 +#define DREG_REGID_DMA_STATE_4_7 0x00000404 +#define DREG_REGID_DMA_STATE_8_11 0x00000408 +#define DREG_REGID_DMA_STATE_12_15 0x0000040C +#define DREG_REGID_DMA_STATE_16_19 0x00000410 +#define DREG_REGID_DMA_STATE_20_23 0x00000414 +#define DREG_REGID_DMA_STATE_24_27 0x00000418 +#define DREG_REGID_DMA_STATE_28_31 0x0000041C +#define DREG_REGID_DMA_STATE_32_35 0x00000420 +#define DREG_REGID_DMA_STATE_36_39 0x00000424 +#define DREG_REGID_DMA_STATE_40_43 0x00000428 +#define DREG_REGID_DMA_STATE_44_47 0x0000042C +#define DREG_REGID_DMA_STATE_48_51 0x00000430 +#define DREG_REGID_DMA_STATE_52_55 0x00000434 +#define DREG_REGID_DMA_STATE_56_59 0x00000438 +#define DREG_REGID_DMA_STATE_60_63 0x0000043C +#define DREG_REGID_DMA_STATE_64_67 0x00000440 +#define DREG_REGID_DMA_STATE_68_71 0x00000444 +#define DREG_REGID_DMA_STATE_72_75 0x00000448 +#define DREG_REGID_DMA_STATE_76_79 0x0000044C +#define DREG_REGID_DMA_STATE_80_83 0x00000450 +#define DREG_REGID_DMA_STATE_84_87 0x00000454 +#define DREG_REGID_DMA_STATE_88_91 0x00000458 +#define DREG_REGID_DMA_STATE_92_95 0x0000045C +#define DREG_REGID_TRAP_SELECT 0x00000500 +#define DREG_REGID_TRAP_WRITE_0 0x00000500 +#define DREG_REGID_TRAP_WRITE_1 0x00000501 +#define DREG_REGID_TRAP_WRITE_2 0x00000502 +#define DREG_REGID_TRAP_WRITE_3 0x00000503 +#define DREG_REGID_TRAP_WRITE_4 0x00000504 +#define DREG_REGID_TRAP_WRITE_5 0x00000505 +#define DREG_REGID_TRAP_WRITE_6 0x00000506 +#define DREG_REGID_TRAP_WRITE_7 0x00000507 +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_REGID_TRAP_WRITE_8 0x00000510 +#define DREG_REGID_TRAP_WRITE_9 0x00000511 +#define DREG_REGID_TRAP_WRITE_10 0x00000512 +#define DREG_REGID_TRAP_WRITE_11 0x00000513 +#define DREG_REGID_TRAP_WRITE_12 0x00000514 +#define DREG_REGID_TRAP_WRITE_13 0x00000515 +#define DREG_REGID_TRAP_WRITE_14 0x00000516 +#define DREG_REGID_TRAP_WRITE_15 0x00000517 +#define DREG_REGID_TRAP_WRITE_16 0x00000518 +#define DREG_REGID_TRAP_WRITE_17 0x00000519 +#define DREG_REGID_TRAP_WRITE_18 0x0000051A +#define DREG_REGID_TRAP_WRITE_19 0x0000051B +#define DREG_REGID_TRAP_WRITE_20 0x0000051C +#define DREG_REGID_TRAP_WRITE_21 0x0000051D +#define DREG_REGID_TRAP_WRITE_22 0x0000051E +#define DREG_REGID_TRAP_WRITE_23 0x0000051F +#endif +#endif +#define DREG_REGID_MAC0_ACC0_LOW 0x00000600 +#define DREG_REGID_MAC0_ACC1_LOW 0x00000601 +#define DREG_REGID_MAC0_ACC2_LOW 0x00000602 +#define DREG_REGID_MAC0_ACC3_LOW 0x00000603 +#define DREG_REGID_MAC1_ACC0_LOW 0x00000604 +#define DREG_REGID_MAC1_ACC1_LOW 0x00000605 +#define DREG_REGID_MAC1_ACC2_LOW 0x00000606 +#define DREG_REGID_MAC1_ACC3_LOW 0x00000607 +#define DREG_REGID_MAC0_ACC0_MID 0x00000608 +#define DREG_REGID_MAC0_ACC1_MID 0x00000609 +#define DREG_REGID_MAC0_ACC2_MID 0x0000060A +#define DREG_REGID_MAC0_ACC3_MID 0x0000060B +#define DREG_REGID_MAC1_ACC0_MID 0x0000060C +#define DREG_REGID_MAC1_ACC1_MID 0x0000060D +#define DREG_REGID_MAC1_ACC2_MID 0x0000060E +#define DREG_REGID_MAC1_ACC3_MID 0x0000060F +#define DREG_REGID_MAC0_ACC0_HIGH 0x00000610 +#define DREG_REGID_MAC0_ACC1_HIGH 0x00000611 +#define DREG_REGID_MAC0_ACC2_HIGH 0x00000612 +#define DREG_REGID_MAC0_ACC3_HIGH 0x00000613 +#define DREG_REGID_MAC1_ACC0_HIGH 0x00000614 +#define DREG_REGID_MAC1_ACC1_HIGH 0x00000615 +#define DREG_REGID_MAC1_ACC2_HIGH 0x00000616 +#define DREG_REGID_MAC1_ACC3_HIGH 0x00000617 +#define DREG_REGID_RSHOUT_LOW 0x00000620 +#define DREG_REGID_RSHOUT_MID 0x00000628 +#define DREG_REGID_RSHOUT_HIGH 0x00000630 + +/* + * The following defines are for the flags in the DMA stream requestor write + */ +#define DSRWP_DSR_MASK 0x0000000F +#define DSRWP_DSR_BG_RQ 0x00000001 +#define DSRWP_DSR_PRIORITY_MASK 0x00000006 +#define DSRWP_DSR_PRIORITY_0 0x00000000 +#define DSRWP_DSR_PRIORITY_1 0x00000002 +#define DSRWP_DSR_PRIORITY_2 0x00000004 +#define DSRWP_DSR_PRIORITY_3 0x00000006 +#define DSRWP_DSR_RQ_PENDING 0x00000008 + +/* + * The following defines are for the flags in the trap write port register. + */ +#define TWPR_TW_MASK 0x0000FFFF +#define TWPR_TW_SHIFT 0 + +/* + * The following defines are for the flags in the stack pointer write + * register. + */ +#define SPWR_STKP_MASK 0x0000000F +#define SPWR_STKP_SHIFT 0 + +/* + * The following defines are for the flags in the SP interrupt register. + */ +#define SPIR_FRI 0x00000001 +#define SPIR_DOI 0x00000002 +#define SPIR_GPI2 0x00000004 +#define SPIR_GPI3 0x00000008 +#define SPIR_IP0 0x00000010 +#define SPIR_IP1 0x00000020 +#define SPIR_IP2 0x00000040 +#define SPIR_IP3 0x00000080 + +/* + * The following defines are for the flags in the functional group 1 register. + */ +#define FGR1_F1S_MASK 0x0000FFFF +#define FGR1_F1S_SHIFT 0 + +/* + * The following defines are for the flags in the SP clock status register. + */ +#define SPCS_FRI 0x00000001 +#define SPCS_DOI 0x00000002 +#define SPCS_GPI2 0x00000004 +#define SPCS_GPI3 0x00000008 +#define SPCS_IP0 0x00000010 +#define SPCS_IP1 0x00000020 +#define SPCS_IP2 0x00000040 +#define SPCS_IP3 0x00000080 +#define SPCS_SPRUN 0x00000100 +#define SPCS_SLEEP 0x00000200 +#define SPCS_FG 0x00000400 +#define SPCS_ORUN 0x00000800 +#define SPCS_IRQ 0x00001000 +#define SPCS_FGN_MASK 0x0000E000 +#define SPCS_FGN_SHIFT 13 + +/* + * The following defines are for the flags in the SP DMA requestor status + * register. + */ +#define SDSR_DCS_MASK 0x000000FF +#define SDSR_DCS_SHIFT 0 +#define SDSR_DCS_NONE 0x00000007 + +/* + * The following defines are for the flags in the frame timer register. + */ +#define FRMT_FTV_MASK 0x0000FFFF +#define FRMT_FTV_SHIFT 0 + +/* + * The following defines are for the flags in the frame timer current count + * register. + */ +#define FRCC_FCC_MASK 0x0000FFFF +#define FRCC_FCC_SHIFT 0 + +/* + * The following defines are for the flags in the frame timer save count + * register. + */ +#define FRSC_FCS_MASK 0x0000FFFF +#define FRSC_FCS_SHIFT 0 + +/* + * The following define the various flags stored in the scatter/gather + * descriptors. + */ +#define DMA_SG_NEXT_ENTRY_MASK 0x00000FF8 +#define DMA_SG_SAMPLE_END_MASK 0x0FFF0000 +#define DMA_SG_SAMPLE_END_FLAG 0x10000000 +#define DMA_SG_LOOP_END_FLAG 0x20000000 +#define DMA_SG_SIGNAL_END_FLAG 0x40000000 +#define DMA_SG_SIGNAL_PAGE_FLAG 0x80000000 +#define DMA_SG_NEXT_ENTRY_SHIFT 3 +#define DMA_SG_SAMPLE_END_SHIFT 16 + +/* + * The following define the offsets of the fields within the on-chip generic + * DMA requestor. + */ +#define DMA_RQ_CONTROL1 0x00000000 +#define DMA_RQ_CONTROL2 0x00000004 +#define DMA_RQ_SOURCE_ADDR 0x00000008 +#define DMA_RQ_DESTINATION_ADDR 0x0000000C +#define DMA_RQ_NEXT_PAGE_ADDR 0x00000010 +#define DMA_RQ_NEXT_PAGE_SGDESC 0x00000014 +#define DMA_RQ_LOOP_START_ADDR 0x00000018 +#define DMA_RQ_POST_LOOP_ADDR 0x0000001C +#define DMA_RQ_PAGE_MAP_ADDR 0x00000020 + +/* + * The following defines are for the flags in the first control word of the + * on-chip generic DMA requestor. + */ +#define DMA_RQ_C1_COUNT_MASK 0x000003FF +#define DMA_RQ_C1_DESTINATION_SCATTER 0x00001000 +#define DMA_RQ_C1_SOURCE_GATHER 0x00002000 +#define DMA_RQ_C1_DONE_FLAG 0x00004000 +#define DMA_RQ_C1_OPTIMIZE_STATE 0x00008000 +#define DMA_RQ_C1_SAMPLE_END_STATE_MASK 0x00030000 +#define DMA_RQ_C1_FULL_PAGE 0x00000000 +#define DMA_RQ_C1_BEFORE_SAMPLE_END 0x00010000 +#define DMA_RQ_C1_PAGE_MAP_ERROR 0x00020000 +#define DMA_RQ_C1_AT_SAMPLE_END 0x00030000 +#define DMA_RQ_C1_LOOP_END_STATE_MASK 0x000C0000 +#define DMA_RQ_C1_NOT_LOOP_END 0x00000000 +#define DMA_RQ_C1_BEFORE_LOOP_END 0x00040000 +#define DMA_RQ_C1_2PAGE_LOOP_BEGIN 0x00080000 +#define DMA_RQ_C1_LOOP_BEGIN 0x000C0000 +#define DMA_RQ_C1_PAGE_MAP_MASK 0x00300000 +#define DMA_RQ_C1_PM_NONE_PENDING 0x00000000 +#define DMA_RQ_C1_PM_NEXT_PENDING 0x00100000 +#define DMA_RQ_C1_PM_RESERVED 0x00200000 +#define DMA_RQ_C1_PM_LOOP_NEXT_PENDING 0x00300000 +#define DMA_RQ_C1_WRITEBACK_DEST_FLAG 0x00400000 +#define DMA_RQ_C1_WRITEBACK_SRC_FLAG 0x00800000 +#define DMA_RQ_C1_DEST_SIZE_MASK 0x07000000 +#define DMA_RQ_C1_DEST_LINEAR 0x00000000 +#define DMA_RQ_C1_DEST_MOD16 0x01000000 +#define DMA_RQ_C1_DEST_MOD32 0x02000000 +#define DMA_RQ_C1_DEST_MOD64 0x03000000 +#define DMA_RQ_C1_DEST_MOD128 0x04000000 +#define DMA_RQ_C1_DEST_MOD256 0x05000000 +#define DMA_RQ_C1_DEST_MOD512 0x06000000 +#define DMA_RQ_C1_DEST_MOD1024 0x07000000 +#define DMA_RQ_C1_DEST_ON_HOST 0x08000000 +#define DMA_RQ_C1_SOURCE_SIZE_MASK 0x70000000 +#define DMA_RQ_C1_SOURCE_LINEAR 0x00000000 +#define DMA_RQ_C1_SOURCE_MOD16 0x10000000 +#define DMA_RQ_C1_SOURCE_MOD32 0x20000000 +#define DMA_RQ_C1_SOURCE_MOD64 0x30000000 +#define DMA_RQ_C1_SOURCE_MOD128 0x40000000 +#define DMA_RQ_C1_SOURCE_MOD256 0x50000000 +#define DMA_RQ_C1_SOURCE_MOD512 0x60000000 +#define DMA_RQ_C1_SOURCE_MOD1024 0x70000000 +#define DMA_RQ_C1_SOURCE_ON_HOST 0x80000000 +#define DMA_RQ_C1_COUNT_SHIFT 0 + +/* + * The following defines are for the flags in the second control word of the + * on-chip generic DMA requestor. + */ +#define DMA_RQ_C2_VIRTUAL_CHANNEL_MASK 0x0000003F +#define DMA_RQ_C2_VIRTUAL_SIGNAL_MASK 0x00000300 +#define DMA_RQ_C2_NO_VIRTUAL_SIGNAL 0x00000000 +#define DMA_RQ_C2_SIGNAL_EVERY_DMA 0x00000100 +#define DMA_RQ_C2_SIGNAL_SOURCE_PINGPONG 0x00000200 +#define DMA_RQ_C2_SIGNAL_DEST_PINGPONG 0x00000300 +#define DMA_RQ_C2_AUDIO_CONVERT_MASK 0x0000F000 +#define DMA_RQ_C2_AC_NONE 0x00000000 +#define DMA_RQ_C2_AC_8_TO_16_BIT 0x00001000 +#define DMA_RQ_C2_AC_MONO_TO_STEREO 0x00002000 +#define DMA_RQ_C2_AC_ENDIAN_CONVERT 0x00004000 +#define DMA_RQ_C2_AC_SIGNED_CONVERT 0x00008000 +#define DMA_RQ_C2_LOOP_END_MASK 0x0FFF0000 +#define DMA_RQ_C2_LOOP_MASK 0x30000000 +#define DMA_RQ_C2_NO_LOOP 0x00000000 +#define DMA_RQ_C2_ONE_PAGE_LOOP 0x10000000 +#define DMA_RQ_C2_TWO_PAGE_LOOP 0x20000000 +#define DMA_RQ_C2_MULTI_PAGE_LOOP 0x30000000 +#define DMA_RQ_C2_SIGNAL_LOOP_BACK 0x40000000 +#define DMA_RQ_C2_SIGNAL_POST_BEGIN_PAGE 0x80000000 +#define DMA_RQ_C2_VIRTUAL_CHANNEL_SHIFT 0 +#define DMA_RQ_C2_LOOP_END_SHIFT 16 + +/* + * The following defines are for the flags in the source and destination words + * of the on-chip generic DMA requestor. + */ +#define DMA_RQ_SD_ADDRESS_MASK 0x0000FFFF +#define DMA_RQ_SD_MEMORY_ID_MASK 0x000F0000 +#define DMA_RQ_SD_SP_PARAM_ADDR 0x00000000 +#define DMA_RQ_SD_SP_SAMPLE_ADDR 0x00010000 +#define DMA_RQ_SD_SP_PROGRAM_ADDR 0x00020000 +#define DMA_RQ_SD_SP_DEBUG_ADDR 0x00030000 +#define DMA_RQ_SD_OMNIMEM_ADDR 0x000E0000 +#define DMA_RQ_SD_END_FLAG 0x40000000 +#define DMA_RQ_SD_ERROR_FLAG 0x80000000 +#define DMA_RQ_SD_ADDRESS_SHIFT 0 + +/* + * The following defines are for the flags in the page map address word of the + * on-chip generic DMA requestor. + */ +#define DMA_RQ_PMA_LOOP_THIRD_PAGE_ENTRY_MASK 0x00000FF8 +#define DMA_RQ_PMA_PAGE_TABLE_MASK 0xFFFFF000 +#define DMA_RQ_PMA_LOOP_THIRD_PAGE_ENTRY_SHIFT 3 +#define DMA_RQ_PMA_PAGE_TABLE_SHIFT 12 + +#define BA1_VARIDEC_BUF_1 0x000 + +#define BA1_PDTC 0x0c0 /* BA1_PLAY_DMA_TRANSACTION_COUNT_REG */ +#define BA1_PFIE 0x0c4 /* BA1_PLAY_FORMAT_&_INTERRUPT_ENABLE_REG */ +#define BA1_PBA 0x0c8 /* BA1_PLAY_BUFFER_ADDRESS */ +#define BA1_PVOL 0x0f8 /* BA1_PLAY_VOLUME_REG */ +#define BA1_PSRC 0x288 /* BA1_PLAY_SAMPLE_RATE_CORRECTION_REG */ +#define BA1_PCTL 0x2a4 /* BA1_PLAY_CONTROL_REG */ +#define BA1_PPI 0x2b4 /* BA1_PLAY_PHASE_INCREMENT_REG */ + +#define BA1_CCTL 0x064 /* BA1_CAPTURE_CONTROL_REG */ +#define BA1_CIE 0x104 /* BA1_CAPTURE_INTERRUPT_ENABLE_REG */ +#define BA1_CBA 0x10c /* BA1_CAPTURE_BUFFER_ADDRESS */ +#define BA1_CSRC 0x2c8 /* BA1_CAPTURE_SAMPLE_RATE_CORRECTION_REG */ +#define BA1_CCI 0x2d8 /* BA1_CAPTURE_COEFFICIENT_INCREMENT_REG */ +#define BA1_CD 0x2e0 /* BA1_CAPTURE_DELAY_REG */ +#define BA1_CPI 0x2f4 /* BA1_CAPTURE_PHASE_INCREMENT_REG */ +#define BA1_CVOL 0x2f8 /* BA1_CAPTURE_VOLUME_REG */ + +#define BA1_CFG1 0x134 /* BA1_CAPTURE_FRAME_GROUP_1_REG */ +#define BA1_CFG2 0x138 /* BA1_CAPTURE_FRAME_GROUP_2_REG */ +#define BA1_CCST 0x13c /* BA1_CAPTURE_CONSTANT_REG */ +#define BA1_CSPB 0x340 /* BA1_CAPTURE_SPB_ADDRESS */ + +/* + * + */ + +#define CS46XX_MODE_OUTPUT (1<<0) /* MIDI UART - output */ +#define CS46XX_MODE_INPUT (1<<1) /* MIDI UART - input */ + +/* + * + */ + +#define SAVE_REG_MAX 0x10 +#define POWER_DOWN_ALL 0x7f0f + +/* maxinum number of AC97 codecs connected, AC97 2.0 defined 4 */ +#define MAX_NR_AC97 4 +#define CS46XX_PRIMARY_CODEC_INDEX 0 +#define CS46XX_SECONDARY_CODEC_INDEX 1 +#define CS46XX_SECONDARY_CODEC_OFFSET 0x80 +#define CS46XX_DSP_CAPTURE_CHANNEL 1 + +/* capture */ +#define CS46XX_DSP_CAPTURE_CHANNEL 1 + +/* mixer */ +#define CS46XX_MIXER_SPDIF_INPUT_ELEMENT 1 +#define CS46XX_MIXER_SPDIF_OUTPUT_ELEMENT 2 + + +struct snd_cs46xx_pcm { + struct snd_dma_buffer hw_buf; + + unsigned int ctl; + unsigned int shift; /* Shift count to trasform frames in bytes */ + struct snd_pcm_indirect pcm_rec; + struct snd_pcm_substream *substream; + + struct dsp_pcm_channel_descriptor * pcm_channel; + + int pcm_channel_id; /* Fron Rear, Center Lfe ... */ +}; + +struct snd_cs46xx_region { + char name[24]; + unsigned long base; + void __iomem *remap_addr; + unsigned long size; +}; + +struct snd_cs46xx { + int irq; + unsigned long ba0_addr; + unsigned long ba1_addr; + union { + struct { + struct snd_cs46xx_region ba0; + struct snd_cs46xx_region data0; + struct snd_cs46xx_region data1; + struct snd_cs46xx_region pmem; + struct snd_cs46xx_region reg; + } name; + struct snd_cs46xx_region idx[5]; + } region; + + unsigned int mode; + + struct { + struct snd_dma_buffer hw_buf; + + unsigned int ctl; + unsigned int shift; /* Shift count to trasform frames in bytes */ + struct snd_pcm_indirect pcm_rec; + struct snd_pcm_substream *substream; + } capt; + + + int nr_ac97_codecs; + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97[MAX_NR_AC97]; + + struct pci_dev *pci; + struct snd_card *card; + struct snd_pcm *pcm; + + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *midi_input; + struct snd_rawmidi_substream *midi_output; + + spinlock_t reg_lock; + unsigned int midcr; + unsigned int uartm; + + int amplifier; + void (*amplifier_ctrl)(struct snd_cs46xx *, int); + void (*active_ctrl)(struct snd_cs46xx *, int); + void (*mixer_init)(struct snd_cs46xx *); + + int acpi_port; + struct snd_kcontrol *eapd_switch; /* for amplifier hack */ + int accept_valid; /* accept mmap valid (for OSS) */ + int in_suspend; + + struct gameport *gameport; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + struct mutex spos_mutex; + + struct dsp_spos_instance * dsp_spos_instance; + + struct snd_pcm *pcm_rear; + struct snd_pcm *pcm_center_lfe; + struct snd_pcm *pcm_iec958; + +#define CS46XX_DSP_MODULES 5 + struct dsp_module_desc *modules[CS46XX_DSP_MODULES]; +#else /* for compatibility */ + struct snd_cs46xx_pcm *playback_pcm; + unsigned int play_ctl; + + struct ba1_struct *ba1; +#endif + +#ifdef CONFIG_PM_SLEEP + u32 *saved_regs; +#endif +}; + +int snd_cs46xx_create(struct snd_card *card, + struct pci_dev *pci, + int external_amp, int thinkpad); +extern const struct dev_pm_ops snd_cs46xx_pm; + +int snd_cs46xx_pcm(struct snd_cs46xx *chip, int device); +int snd_cs46xx_pcm_rear(struct snd_cs46xx *chip, int device); +int snd_cs46xx_pcm_iec958(struct snd_cs46xx *chip, int device); +int snd_cs46xx_pcm_center_lfe(struct snd_cs46xx *chip, int device); +int snd_cs46xx_mixer(struct snd_cs46xx *chip, int spdif_device); +int snd_cs46xx_midi(struct snd_cs46xx *chip, int device); +int snd_cs46xx_start_dsp(struct snd_cs46xx *chip); +int snd_cs46xx_gameport(struct snd_cs46xx *chip); + +#endif /* __SOUND_CS46XX_H */ diff --git a/sound/pci/cs46xx/cs46xx_dsp_scb_types.h b/sound/pci/cs46xx/cs46xx_dsp_scb_types.h new file mode 100644 index 000000000..7339c3857 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_dsp_scb_types.h @@ -0,0 +1,1198 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * + * NOTE: comments are copy/paste from cwcemb80.lst + * provided by Tom Woller at Cirrus (my only + * documentation about the SP OS running inside + * the DSP) + */ + +#ifndef __CS46XX_DSP_SCB_TYPES_H__ +#define __CS46XX_DSP_SCB_TYPES_H__ + +#include <asm/byteorder.h> + +#ifndef ___DSP_DUAL_16BIT_ALLOC +#if defined(__LITTLE_ENDIAN) +#define ___DSP_DUAL_16BIT_ALLOC(a,b) u16 a; u16 b; +#elif defined(__BIG_ENDIAN) +#define ___DSP_DUAL_16BIT_ALLOC(a,b) u16 b; u16 a; +#else +#error Not __LITTLE_ENDIAN and not __BIG_ENDIAN, then what ??? +#endif +#endif + +/* This structs are used internally by the SP */ + +struct dsp_basic_dma_req { + /* DMA Requestor Word 0 (DCW) fields: + + 31 [30-28]27 [26:24] 23 22 21 20 [19:18] [17:16] 15 14 13 12 11 10 9 8 7 6 [5:0] + _______________________________________________________________________________________ + |S| SBT |D| DBT |wb|wb| | | LS | SS |Opt|Do|SSG|DSG| | | | | | | Dword | + |H|_____ |H|_________|S_|D |__|__|______|_______|___|ne|__ |__ |__|__|_|_|_|_|_Count -1| + */ + u32 dcw; /* DMA Control Word */ + u32 dmw; /* DMA Mode Word */ + u32 saw; /* Source Address Word */ + u32 daw; /* Destination Address Word */ +}; + +struct dsp_scatter_gather_ext { + u32 npaw; /* Next-Page Address Word */ + + /* DMA Requestor Word 5 (NPCW) fields: + + 31-30 29 28 [27:16] [15:12] [11:3] [2:0] + _________________________________________________________________________________________ + |SV |LE|SE| Sample-end byte offset | | Page-map entry offset for next | | + |page|__|__| ___________________________|_________|__page, if !sample-end___________|____| + */ + u32 npcw; /* Next-Page Control Word */ + u32 lbaw; /* Loop-Begin Address Word */ + u32 nplbaw; /* Next-Page after Loop-Begin Address Word */ + u32 sgaw; /* Scatter/Gather Address Word */ +}; + +struct dsp_volume_control { + ___DSP_DUAL_16BIT_ALLOC( + rightTarg, /* Target volume for left & right channels */ + leftTarg + ) + ___DSP_DUAL_16BIT_ALLOC( + rightVol, /* Current left & right channel volumes */ + leftVol + ) +}; + +/* Generic stream control block (SCB) structure definition */ +struct dsp_generic_scb { + /* For streaming I/O, the DSP should never alter any words in the DMA + requestor or the scatter/gather extension. Only ad hoc DMA request + streams are free to alter the requestor (currently only occur in the + DOS-based MIDI controller and in debugger-inserted code). + + If an SCB does not have any associated DMA requestor, these 9 ints + may be freed for use by other tasks, but the pointer to the SCB must + still be such that the insOrd:nextSCB appear at offset 9 from the + SCB pointer. + + Basic (non scatter/gather) DMA requestor (4 ints) + */ + + /* Initialized by the host, only modified by DMA + R/O for the DSP task */ + struct dsp_basic_dma_req basic_req; /* Optional */ + + /* Scatter/gather DMA requestor extension (5 ints) + Initialized by the host, only modified by DMA + DSP task never needs to even read these. + */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + + /* Sublist pointer & next stream control block (SCB) link. + Initialized & modified by the host R/O for the DSP task + */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + /* Pointer to this tasks parameter block & stream function pointer + Initialized by the host R/O for the DSP task */ + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + /* rsConfig register for stream buffer (rsDMA reg. + is loaded from basicReq.daw for incoming streams, or + basicReq.saw, for outgoing streams) + + 31 30 29 [28:24] [23:16] 15 14 13 12 11 10 9 8 7 6 5 4 [3:0] + ______________________________________________________________________________ + |DMA |D|maxDMAsize| streamNum|dir|p| | | | | | |ds |shr 1|rev Cy | mod | + |prio |_|__________|__________|___|_|__|__|__|__|_|_|___|_____|_______|_______| + 31 30 29 [28:24] [23:16] 15 14 13 12 11 10 9 8 7 6 5 4 [3:0] + + + Initialized by the host R/O for the DSP task + */ + u32 strm_rs_config; /* REQUIRED */ + // + /* On mixer input streams: indicates mixer input stream configuration + On Tees, this is copied from the stream being snooped + + Stream sample pointer & MAC-unit mode for this stream + + Initialized by the host Updated by the DSP task + */ + u32 strm_buf_ptr; /* REQUIRED */ + + /* On mixer input streams: points to next mixer input and is updated by the + mixer subroutine in the "parent" DSP task + (least-significant 16 bits are preserved, unused) + + On Tees, the pointer is copied from the stream being snooped on + initialization, and, subsequently, it is copied into the + stream being snooped. + + On wavetable/3D voices: the strmBufPtr will use all 32 bits to allow for + fractional phase accumulation + + Fractional increment per output sample in the input sample buffer + + (Not used on mixer input streams & redefined on Tees) + On wavetable/3D voices: this 32-bit word specifies the integer.fractional + increment per output sample. + */ + u32 strmPhiIncr; + + + /* Standard stereo volume control + Initialized by the host (host updates target volumes) + + Current volumes update by the DSP task + On mixer input streams: required & updated by the mixer subroutine in the + "parent" DSP task + + On Tees, both current & target volumes are copied up on initialization, + and, subsequently, the target volume is copied up while the current + volume is copied down. + + These two 32-bit words are redefined for wavetable & 3-D voices. + */ + struct dsp_volume_control vol_ctrl_t; /* Optional */ +}; + + +struct dsp_spos_control_block { + /* WARNING: Certain items in this structure are modified by the host + Any dword that can be modified by the host, must not be + modified by the SP as the host can only do atomic dword + writes, and to do otherwise, even a read modify write, + may lead to corrupted data on the SP. + + This rule does not apply to one off boot time initialisation prior to starting the SP + */ + + + ___DSP_DUAL_16BIT_ALLOC( + /* First element on the Hyper forground task tree */ + hfg_tree_root_ptr, /* HOST */ + /* First 3 dwords are written by the host and read-only on the DSP */ + hfg_stack_base /* HOST */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* Point to this data structure to enable easy access */ + spos_cb_ptr, /* SP */ + prev_task_tree_ptr /* SP && HOST */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* Currently Unused */ + xxinterval_timer_period, + /* Enable extension of SPOS data structure */ + HFGSPB_ptr + ) + + + ___DSP_DUAL_16BIT_ALLOC( + xxnum_HFG_ticks_thisInterval, + /* Modified by the DSP */ + xxnum_tntervals + ) + + + /* Set by DSP upon encountering a trap (breakpoint) or a spurious + interrupt. The host must clear this dword after reading it + upon receiving spInt1. */ + ___DSP_DUAL_16BIT_ALLOC( + spurious_int_flag, /* (Host & SP) Nature of the spurious interrupt */ + trap_flag /* (Host & SP) Nature of detected Trap */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused2, + invalid_IP_flag /* (Host & SP ) Indicate detection of invalid instruction pointer */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* pointer to forground task tree header for use in next task search */ + fg_task_tree_hdr_ptr, /* HOST */ + /* Data structure for controlling synchronous link update */ + hfg_sync_update_ptr /* HOST */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + begin_foreground_FCNT, /* SP */ + /* Place holder for holding sleep timing */ + last_FCNT_before_sleep /* SP */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused7, /* SP */ + next_task_treePtr /* SP */ + ) + + u32 unused5; + + ___DSP_DUAL_16BIT_ALLOC( + active_flags, /* SP */ + /* State flags, used to assist control of execution of Hyper Forground */ + HFG_flags /* SP */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused9, + unused8 + ) + + /* Space for saving enough context so that we can set up enough + to save some more context. + */ + u32 rFE_save_for_invalid_IP; + u32 r32_save_for_spurious_int; + u32 r32_save_for_trap; + u32 r32_save_for_HFG; +}; + +/* SPB for MIX_TO_OSTREAM algorithm family */ +struct dsp_mix2_ostream_spb +{ + /* 16b.16b integer.frac approximation to the + number of 3 sample triplets to output each + frame. (approximation must be floor, to + insure that the fractional error is always + positive) + */ + u32 outTripletsPerFrame; + + /* 16b.16b integer.frac accumulated number of + output triplets since the start of group + */ + u32 accumOutTriplets; +}; + +/* SCB for Timing master algorithm */ +struct dsp_timing_master_scb { + /* First 12 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* Initial values are 0000:xxxx */ + reserved, + extra_sample_accum + ) + + + /* Initial values are xxxx:0000 + hi: Current CODEC output FIFO pointer + (0 to 0x0f) + lo: Flag indicating that the CODEC + FIFO is sync'd (host clears to + resynchronize the FIFO pointer + upon start/restart) + */ + ___DSP_DUAL_16BIT_ALLOC( + codec_FIFO_syncd, + codec_FIFO_ptr + ) + + /* Init. 8000:0005 for 44.1k + 8000:0001 for 48k + hi: Fractional sample accumulator 0.16b + lo: Number of frames remaining to be + processed in the current group of + frames + */ + ___DSP_DUAL_16BIT_ALLOC( + frac_samp_accum_qm1, + TM_frms_left_in_group + ) + + /* Init. 0001:0005 for 44.1k + 0000:0001 for 48k + hi: Fractional sample correction factor 0.16b + to be added every frameGroupLength frames + to correct for truncation error in + nsamp_per_frm_q15 + lo: Number of frames in the group + */ + ___DSP_DUAL_16BIT_ALLOC( + frac_samp_correction_qm1, + TM_frm_group_length + ) + + /* Init. 44.1k*65536/8k = 0x00058333 for 44.1k + 48k*65536/8k = 0x00060000 for 48k + 16b.16b integer.frac approximation to the + number of samples to output each frame. + (approximation must be floor, to insure */ + u32 nsamp_per_frm_q15; +}; + +/* SCB for CODEC output algorithm */ +struct dsp_codec_output_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + + u32 strm_buf_ptr; /* REQUIRED */ + + /* NOTE: The CODEC output task reads samples from the first task on its + sublist at the stream buffer pointer (init. to lag DMA destination + address word). After the required number of samples is transferred, + the CODEC output task advances sub_list_ptr->strm_buf_ptr past the samples + consumed. + */ + + /* Init. 0000:0010 for SDout + 0060:0010 for SDout2 + 0080:0010 for SDout3 + hi: Base IO address of FIFO to which + the left-channel samples are to + be written. + lo: Displacement for the base IO + address for left-channel to obtain + the base IO address for the FIFO + to which the right-channel samples + are to be written. + */ + ___DSP_DUAL_16BIT_ALLOC( + left_chan_base_IO_addr, + right_chan_IO_disp + ) + + + /* Init: 0x0080:0004 for non-AC-97 + Init: 0x0080:0000 for AC-97 + hi: Exponential volume change rate + for input stream + lo: Positive shift count to shift the + 16-bit input sample to obtain the + 32-bit output word + */ + ___DSP_DUAL_16BIT_ALLOC( + CO_scale_shift_count, + CO_exp_vol_change_rate + ) + + /* Pointer to SCB at end of input chain */ + ___DSP_DUAL_16BIT_ALLOC( + reserved, + last_sub_ptr + ) +}; + +/* SCB for CODEC input algorithm */ +struct dsp_codec_input_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + u32 strm_buf_ptr; /* REQUIRED */ + + /* NOTE: The CODEC input task reads samples from the hardware FIFO + sublist at the DMA source address word (sub_list_ptr->basic_req.saw). + After the required number of samples is transferred, the CODEC + output task advances sub_list_ptr->basic_req.saw past the samples + consumed. SPuD must initialize the sub_list_ptr->basic_req.saw + to point half-way around from the initial sub_list_ptr->strm_nuf_ptr + to allow for lag/lead. + */ + + /* Init. 0000:0010 for SDout + 0060:0010 for SDout2 + 0080:0010 for SDout3 + hi: Base IO address of FIFO to which + the left-channel samples are to + be written. + lo: Displacement for the base IO + address for left-channel to obtain + the base IO address for the FIFO + to which the right-channel samples + are to be written. + */ + ___DSP_DUAL_16BIT_ALLOC( + rightChanINdisp, + left_chan_base_IN_addr + ) + /* Init. ?:fffc + lo: Negative shift count to shift the + 32-bit input dword to obtain the + 16-bit sample msb-aligned (count + is negative to shift left) + */ + ___DSP_DUAL_16BIT_ALLOC( + scaleShiftCount, + reserver1 + ) + + u32 reserved2; +}; + + +struct dsp_pcm_serial_input_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_buf_ptr; /* REQUIRED */ + u32 strm_rs_config; /* REQUIRED */ + + /* Init. Ptr to CODEC input SCB + hi: Pointer to the SCB containing the + input buffer to which CODEC input + samples are written + lo: Flag indicating the link to the CODEC + input task is to be initialized + */ + ___DSP_DUAL_16BIT_ALLOC( + init_codec_input_link, + codec_input_buf_scb + ) + + /* Initialized by the host (host updates target volumes) */ + struct dsp_volume_control psi_vol_ctrl; + +}; + +struct dsp_src_task_scb { + ___DSP_DUAL_16BIT_ALLOC( + frames_left_in_gof, + gofs_left_in_sec + ) + + ___DSP_DUAL_16BIT_ALLOC( + const2_thirds, + num_extra_tnput_samples + ) + + ___DSP_DUAL_16BIT_ALLOC( + cor_per_gof, + correction_per_sec + ) + + ___DSP_DUAL_16BIT_ALLOC( + output_buf_producer_ptr, + junk_DMA_MID + ) + + ___DSP_DUAL_16BIT_ALLOC( + gof_length, + gofs_per_sec + ) + + u32 input_buf_strm_config; + + ___DSP_DUAL_16BIT_ALLOC( + reserved_for_SRC_use, + input_buf_consumer_ptr + ) + + u32 accum_phi; + + ___DSP_DUAL_16BIT_ALLOC( + exp_src_vol_change_rate, + input_buf_producer_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + src_next_scb, + src_sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + src_entry_point, + src_this_sbp + ) + + u32 src_strm_rs_config; + u32 src_strm_buf_ptr; + + u32 phiIncr6int_26frac; + + struct dsp_volume_control src_vol_ctrl; +}; + +struct dsp_decimate_by_pow2_scb { + /* decimationFactor = 2, 4, or 8 (larger factors waste too much memory + when compared to cascading decimators) + */ + ___DSP_DUAL_16BIT_ALLOC( + dec2_coef_base_ptr, + dec2_coef_increment + ) + + /* coefIncrement = 128 / decimationFactor (for our ROM filter) + coefBasePtr = 0x8000 (for our ROM filter) + */ + ___DSP_DUAL_16BIT_ALLOC( + dec2_in_samples_per_out_triplet, + dec2_extra_in_samples + ) + /* extraInSamples: # of accumulated, unused input samples (init. to 0) + inSamplesPerOutTriplet = 3 * decimationFactor + */ + + ___DSP_DUAL_16BIT_ALLOC( + dec2_const2_thirds, + dec2_half_num_taps_mp5 + ) + /* halfNumTapsM5: (1/2 number of taps in decimation filter) minus 5 + const2thirds: constant 2/3 in 16Q0 format (sign.15) + */ + + ___DSP_DUAL_16BIT_ALLOC( + dec2_output_buf_producer_ptr, + dec2_junkdma_mid + ) + + u32 dec2_reserved2; + + u32 dec2_input_nuf_strm_config; + /* inputBufStrmConfig: rsConfig for the input buffer to the decimator + (buffer size = decimationFactor * 32 dwords) + */ + + ___DSP_DUAL_16BIT_ALLOC( + dec2_phi_incr, + dec2_input_buf_consumer_ptr + ) + /* inputBufConsumerPtr: Input buffer read pointer (into SRC filter) + phiIncr = decimationFactor * 4 + */ + + u32 dec2_reserved3; + + ___DSP_DUAL_16BIT_ALLOC( + dec2_exp_vol_change_rate, + dec2_input_buf_producer_ptr + ) + /* inputBufProducerPtr: Input buffer write pointer + expVolChangeRate: Exponential volume change rate for possible + future mixer on input streams + */ + + ___DSP_DUAL_16BIT_ALLOC( + dec2_next_scb, + dec2_sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + dec2_entry_point, + dec2_this_spb + ) + + u32 dec2_strm_rs_config; + u32 dec2_strm_buf_ptr; + + u32 dec2_reserved4; + + struct dsp_volume_control dec2_vol_ctrl; /* Not used! */ +}; + +struct dsp_vari_decimate_scb { + ___DSP_DUAL_16BIT_ALLOC( + vdec_frames_left_in_gof, + vdec_gofs_left_in_sec + ) + + ___DSP_DUAL_16BIT_ALLOC( + vdec_const2_thirds, + vdec_extra_in_samples + ) + /* extraInSamples: # of accumulated, unused input samples (init. to 0) + const2thirds: constant 2/3 in 16Q0 format (sign.15) */ + + ___DSP_DUAL_16BIT_ALLOC( + vdec_cor_per_gof, + vdec_correction_per_sec + ) + + ___DSP_DUAL_16BIT_ALLOC( + vdec_output_buf_producer_ptr, + vdec_input_buf_consumer_ptr + ) + /* inputBufConsumerPtr: Input buffer read pointer (into SRC filter) */ + ___DSP_DUAL_16BIT_ALLOC( + vdec_gof_length, + vdec_gofs_per_sec + ) + + u32 vdec_input_buf_strm_config; + /* inputBufStrmConfig: rsConfig for the input buffer to the decimator + (buffer size = 64 dwords) */ + u32 vdec_coef_increment; + /* coefIncrement = - 128.0 / decimationFactor (as a 32Q15 number) */ + + u32 vdec_accumphi; + /* accumPhi: accumulated fractional phase increment (6.26) */ + + ___DSP_DUAL_16BIT_ALLOC( + vdec_exp_vol_change_rate, + vdec_input_buf_producer_ptr + ) + /* inputBufProducerPtr: Input buffer write pointer + expVolChangeRate: Exponential volume change rate for possible + future mixer on input streams */ + + ___DSP_DUAL_16BIT_ALLOC( + vdec_next_scb, + vdec_sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + vdec_entry_point, + vdec_this_spb + ) + + u32 vdec_strm_rs_config; + u32 vdec_strm_buf_ptr; + + u32 vdec_phi_incr_6int_26frac; + + struct dsp_volume_control vdec_vol_ctrl; +}; + + +/* SCB for MIX_TO_OSTREAM algorithm family */ +struct dsp_mix2_ostream_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + u32 strm_buf_ptr; /* REQUIRED */ + + + /* hi: Number of mixed-down input triplets + computed since start of group + lo: Number of frames remaining to be + processed in the current group of + frames + */ + ___DSP_DUAL_16BIT_ALLOC( + frames_left_in_group, + accum_input_triplets + ) + + /* hi: Exponential volume change rate + for mixer on input streams + lo: Number of frames in the group + */ + ___DSP_DUAL_16BIT_ALLOC( + frame_group_length, + exp_vol_change_rate + ) + + ___DSP_DUAL_16BIT_ALLOC( + const_FFFF, + const_zero + ) +}; + + +/* SCB for S16_MIX algorithm */ +struct dsp_mix_only_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + u32 strm_buf_ptr; /* REQUIRED */ + + u32 reserved; + struct dsp_volume_control vol_ctrl; +}; + +/* SCB for the async. CODEC input algorithm */ +struct dsp_async_codec_input_scb { + u32 io_free2; + + u32 io_current_total; + u32 io_previous_total; + + u16 io_count; + u16 io_count_limit; + + u16 o_fifo_base_addr; + u16 ost_mo_format; + /* 1 = stereo; 0 = mono + xxx for ASER 1 (not allowed); 118 for ASER2 */ + + u32 ostrm_rs_config; + u32 ostrm_buf_ptr; + + ___DSP_DUAL_16BIT_ALLOC( + io_sclks_per_lr_clk, + io_io_enable + ) + + u32 io_free4; + + ___DSP_DUAL_16BIT_ALLOC( + io_next_scb, + io_sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + io_entry_point, + io_this_spb + ) + + u32 istrm_rs_config; + u32 istrm_buf_ptr; + + /* Init. 0000:8042: for ASER1 + 0000:8044: for ASER2 */ + ___DSP_DUAL_16BIT_ALLOC( + io_stat_reg_addr, + iofifo_pointer + ) + + /* Init 1 stero:100 ASER1 + Init 0 mono:110 ASER2 + */ + ___DSP_DUAL_16BIT_ALLOC( + ififo_base_addr, + ist_mo_format + ) + + u32 i_free; +}; + + +/* SCB for the SP/DIF CODEC input and output */ +struct dsp_spdifiscb { + ___DSP_DUAL_16BIT_ALLOC( + status_ptr, + status_start_ptr + ) + + u32 current_total; + u32 previous_total; + + ___DSP_DUAL_16BIT_ALLOC( + count, + count_limit + ) + + u32 status_data; + + ___DSP_DUAL_16BIT_ALLOC( + status, + free4 + ) + + u32 free3; + + ___DSP_DUAL_16BIT_ALLOC( + free2, + bit_count + ) + + u32 temp_status; + + ___DSP_DUAL_16BIT_ALLOC( + next_SCB, + sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, + this_spb + ) + + u32 strm_rs_config; + u32 strm_buf_ptr; + + ___DSP_DUAL_16BIT_ALLOC( + stat_reg_addr, + fifo_pointer + ) + + ___DSP_DUAL_16BIT_ALLOC( + fifo_base_addr, + st_mo_format + ) + + u32 free1; +}; + + +/* SCB for the SP/DIF CODEC input and output */ +struct dsp_spdifoscb { + + u32 free2; + + u32 free3[4]; + + /* Need to be here for compatibility with AsynchFGTxCode */ + u32 strm_rs_config; + + u32 strm_buf_ptr; + + ___DSP_DUAL_16BIT_ALLOC( + status, + free5 + ) + + u32 free4; + + ___DSP_DUAL_16BIT_ALLOC( + next_scb, + sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, + this_spb + ) + + u32 free6[2]; + + ___DSP_DUAL_16BIT_ALLOC( + stat_reg_addr, + fifo_pointer + ) + + ___DSP_DUAL_16BIT_ALLOC( + fifo_base_addr, + st_mo_format + ) + + u32 free1; +}; + + +struct dsp_asynch_fg_rx_scb { + ___DSP_DUAL_16BIT_ALLOC( + bot_buf_mask, + buf_Mask + ) + + ___DSP_DUAL_16BIT_ALLOC( + max, + min + ) + + ___DSP_DUAL_16BIT_ALLOC( + old_producer_pointer, + hfg_scb_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + delta, + adjust_count + ) + + u32 unused2[5]; + + ___DSP_DUAL_16BIT_ALLOC( + sibling_ptr, + child_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + code_ptr, + this_ptr + ) + + u32 strm_rs_config; + + u32 strm_buf_ptr; + + u32 unused_phi_incr; + + ___DSP_DUAL_16BIT_ALLOC( + right_targ, + left_targ + ) + + ___DSP_DUAL_16BIT_ALLOC( + right_vol, + left_vol + ) +}; + + +struct dsp_asynch_fg_tx_scb { + ___DSP_DUAL_16BIT_ALLOC( + not_buf_mask, + buf_mask + ) + + ___DSP_DUAL_16BIT_ALLOC( + max, + min + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused1, + hfg_scb_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + delta, + adjust_count + ) + + u32 accum_phi; + + ___DSP_DUAL_16BIT_ALLOC( + unused2, + const_one_third + ) + + u32 unused3[3]; + + ___DSP_DUAL_16BIT_ALLOC( + sibling_ptr, + child_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + codePtr, + this_ptr + ) + + u32 strm_rs_config; + + u32 strm_buf_ptr; + + u32 phi_incr; + + ___DSP_DUAL_16BIT_ALLOC( + unused_right_targ, + unused_left_targ + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused_right_vol, + unused_left_vol + ) +}; + + +struct dsp_output_snoop_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + u32 strm_buf_ptr; /* REQUIRED */ + + ___DSP_DUAL_16BIT_ALLOC( + init_snoop_input_link, + snoop_child_input_scb + ) + + u32 snoop_input_buf_ptr; + + ___DSP_DUAL_16BIT_ALLOC( + reserved, + input_scb + ) +}; + +struct dsp_spio_write_scb { + ___DSP_DUAL_16BIT_ALLOC( + address1, + address2 + ) + + u32 data1; + + u32 data2; + + ___DSP_DUAL_16BIT_ALLOC( + address3, + address4 + ) + + u32 data3; + + u32 data4; + + ___DSP_DUAL_16BIT_ALLOC( + unused1, + data_ptr + ) + + u32 unused2[2]; + + ___DSP_DUAL_16BIT_ALLOC( + sibling_ptr, + child_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, + this_ptr + ) + + u32 unused3[5]; +}; + +struct dsp_magic_snoop_task { + u32 i0; + u32 i1; + + u32 strm_buf_ptr1; + + u16 i2; + u16 snoop_scb; + + u32 i3; + u32 i4; + u32 i5; + u32 i6; + + u32 i7; + + ___DSP_DUAL_16BIT_ALLOC( + next_scb, + sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, + this_ptr + ) + + u32 strm_buf_config; + u32 strm_buf_ptr2; + + u32 i8; + + struct dsp_volume_control vdec_vol_ctrl; +}; + + +struct dsp_filter_scb { + ___DSP_DUAL_16BIT_ALLOC( + a0_right, /* 0x00 */ + a0_left + ) + ___DSP_DUAL_16BIT_ALLOC( + a1_right, /* 0x01 */ + a1_left + ) + ___DSP_DUAL_16BIT_ALLOC( + a2_right, /* 0x02 */ + a2_left + ) + ___DSP_DUAL_16BIT_ALLOC( + output_buf_ptr, /* 0x03 */ + init + ) + + ___DSP_DUAL_16BIT_ALLOC( + filter_unused3, /* 0x04 */ + filter_unused2 + ) + + u32 prev_sample_output1; /* 0x05 */ + u32 prev_sample_output2; /* 0x06 */ + u32 prev_sample_input1; /* 0x07 */ + u32 prev_sample_input2; /* 0x08 */ + + ___DSP_DUAL_16BIT_ALLOC( + next_scb_ptr, /* 0x09 */ + sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* 0x0A */ + spb_ptr + ) + + u32 strm_rs_config; /* 0x0B */ + u32 strm_buf_ptr; /* 0x0C */ + + ___DSP_DUAL_16BIT_ALLOC( + b0_right, /* 0x0D */ + b0_left + ) + ___DSP_DUAL_16BIT_ALLOC( + b1_right, /* 0x0E */ + b1_left + ) + ___DSP_DUAL_16BIT_ALLOC( + b2_right, /* 0x0F */ + b2_left + ) +}; +#endif /* __DSP_SCB_TYPES_H__ */ diff --git a/sound/pci/cs46xx/cs46xx_dsp_spos.h b/sound/pci/cs46xx/cs46xx_dsp_spos.h new file mode 100644 index 000000000..2fa9c7d6a --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_dsp_spos.h @@ -0,0 +1,213 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#ifndef __CS46XX_DSP_SPOS_H__ +#define __CS46XX_DSP_SPOS_H__ + +#include "cs46xx_dsp_scb_types.h" +#include "cs46xx_dsp_task_types.h" + +#define SYMBOL_CONSTANT 0x0 +#define SYMBOL_SAMPLE 0x1 +#define SYMBOL_PARAMETER 0x2 +#define SYMBOL_CODE 0x3 + +#define SEGTYPE_SP_PROGRAM 0x00000001 +#define SEGTYPE_SP_PARAMETER 0x00000002 +#define SEGTYPE_SP_SAMPLE 0x00000003 +#define SEGTYPE_SP_COEFFICIENT 0x00000004 + +#define DSP_SPOS_UU 0x0deadul /* unused */ +#define DSP_SPOS_DC 0x0badul /* don't care */ +#define DSP_SPOS_DC_DC 0x0bad0badul /* don't care */ +#define DSP_SPOS_UUUU 0xdeadc0edul /* unused */ +#define DSP_SPOS_UUHI 0xdeadul +#define DSP_SPOS_UULO 0xc0edul +#define DSP_SPOS_DCDC 0x0badf1d0ul /* don't care */ +#define DSP_SPOS_DCDCHI 0x0badul +#define DSP_SPOS_DCDCLO 0xf1d0ul + +#define DSP_MAX_TASK_NAME 60 +#define DSP_MAX_SYMBOL_NAME 100 +#define DSP_MAX_SCB_NAME 60 +#define DSP_MAX_SCB_DESC 200 +#define DSP_MAX_TASK_DESC 50 + +#define DSP_MAX_PCM_CHANNELS 32 +#define DSP_MAX_SRC_NR 14 + +#define DSP_PCM_MAIN_CHANNEL 1 +#define DSP_PCM_REAR_CHANNEL 2 +#define DSP_PCM_CENTER_LFE_CHANNEL 3 +#define DSP_PCM_S71_CHANNEL 4 /* surround 7.1 */ +#define DSP_IEC958_CHANNEL 5 + +#define DSP_SPDIF_STATUS_OUTPUT_ENABLED 1 +#define DSP_SPDIF_STATUS_PLAYBACK_OPEN 2 +#define DSP_SPDIF_STATUS_HW_ENABLED 4 +#define DSP_SPDIF_STATUS_INPUT_CTRL_ENABLED 8 + +struct dsp_symbol_entry { + u32 address; + char symbol_name[DSP_MAX_SYMBOL_NAME]; + int symbol_type; + + /* initialized by driver */ + struct dsp_module_desc * module; + int deleted; +}; + +struct dsp_symbol_desc { + int nsymbols; + + struct dsp_symbol_entry *symbols; + + /* initialized by driver */ + int highest_frag_index; +}; + +struct dsp_segment_desc { + int segment_type; + u32 offset; + u32 size; + u32 * data; +}; + +struct dsp_module_desc { + char * module_name; + struct dsp_symbol_desc symbol_table; + int nsegments; + struct dsp_segment_desc * segments; + + /* initialized by driver */ + u32 overlay_begin_address; + u32 load_address; + int nfixups; +}; + +struct dsp_scb_descriptor { + char scb_name[DSP_MAX_SCB_NAME]; + u32 address; + int index; + u32 *data; + + struct dsp_scb_descriptor * sub_list_ptr; + struct dsp_scb_descriptor * next_scb_ptr; + struct dsp_scb_descriptor * parent_scb_ptr; + + struct dsp_symbol_entry * task_entry; + struct dsp_symbol_entry * scb_symbol; + + struct snd_info_entry *proc_info; + int ref_count; + + u16 volume[2]; + unsigned int deleted :1; + unsigned int updated :1; + unsigned int volume_set :1; +}; + +struct dsp_task_descriptor { + char task_name[DSP_MAX_TASK_NAME]; + int size; + u32 address; + int index; + u32 *data; +}; + +struct dsp_pcm_channel_descriptor { + int active; + int src_slot; + int pcm_slot; + u32 sample_rate; + u32 unlinked; + struct dsp_scb_descriptor * pcm_reader_scb; + struct dsp_scb_descriptor * src_scb; + struct dsp_scb_descriptor * mixer_scb; + + void * private_data; +}; + +struct dsp_spos_instance { + struct dsp_symbol_desc symbol_table; /* currently available loaded symbols in SP */ + + int nmodules; + struct dsp_module_desc * modules; /* modules loaded into SP */ + + struct dsp_segment_desc code; + + /* Main PCM playback mixer */ + struct dsp_scb_descriptor * master_mix_scb; + u16 dac_volume_right; + u16 dac_volume_left; + + /* Rear/surround PCM playback mixer */ + struct dsp_scb_descriptor * rear_mix_scb; + + /* Center/LFE mixer */ + struct dsp_scb_descriptor * center_lfe_mix_scb; + + int npcm_channels; + int nsrc_scb; + struct dsp_pcm_channel_descriptor pcm_channels[DSP_MAX_PCM_CHANNELS]; + int src_scb_slots[DSP_MAX_SRC_NR]; + + /* cache this symbols */ + struct dsp_symbol_entry * null_algorithm; /* used by PCMreaderSCB's */ + struct dsp_symbol_entry * s16_up; /* used by SRCtaskSCB's */ + + /* proc fs */ + struct snd_card *snd_card; + struct snd_info_entry * proc_dsp_dir; + + /* SCB's descriptors */ + int nscb; + int scb_highest_frag_index; + struct dsp_scb_descriptor scbs[DSP_MAX_SCB_DESC]; + struct dsp_scb_descriptor * the_null_scb; + + /* Task's descriptors */ + int ntask; + struct dsp_task_descriptor tasks[DSP_MAX_TASK_DESC]; + + /* SPDIF status */ + int spdif_status_out; + int spdif_status_in; + u16 spdif_input_volume_right; + u16 spdif_input_volume_left; + /* spdif channel status, + left right and user validity bits */ + unsigned int spdif_csuv_default; + unsigned int spdif_csuv_stream; + + /* SPDIF input sample rate converter */ + struct dsp_scb_descriptor * spdif_in_src; + /* SPDIF input asynch. receiver */ + struct dsp_scb_descriptor * asynch_rx_scb; + + /* Capture record mixer SCB */ + struct dsp_scb_descriptor * record_mixer_scb; + + /* CODEC input SCB */ + struct dsp_scb_descriptor * codec_in_scb; + + /* reference snooper */ + struct dsp_scb_descriptor * ref_snoop_scb; + + /* SPDIF output PCM reference */ + struct dsp_scb_descriptor * spdif_pcm_input_scb; + + /* asynch TX task */ + struct dsp_scb_descriptor * asynch_tx_scb; + + /* record sources */ + struct dsp_scb_descriptor * pcm_input; + struct dsp_scb_descriptor * adc_input; + + int spdif_in_sample_rate; +}; + +#endif /* __DSP_SPOS_H__ */ diff --git a/sound/pci/cs46xx/cs46xx_dsp_task_types.h b/sound/pci/cs46xx/cs46xx_dsp_task_types.h new file mode 100644 index 000000000..fcbd31e40 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_dsp_task_types.h @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * + * NOTE: comments are copy/paste from cwcemb80.lst + * provided by Tom Woller at Cirrus (my only + * documentation about the SP OS running inside + * the DSP) + */ + +#ifndef __CS46XX_DSP_TASK_TYPES_H__ +#define __CS46XX_DSP_TASK_TYPES_H__ + +#include "cs46xx_dsp_scb_types.h" + +/********************************************************************************************* +Example hierarchy of stream control blocks in the SP + +hfgTree +Ptr____Call (c) + \ + -------+------ ------------- ------------- ------------- ----- +| SBlaster IF |______\| Foreground |___\| Middlegr'nd |___\| Background |___\| Nul | +| |Goto /| tree header |g /| tree header |g /| tree header |g /| SCB |r + -------------- (g) ------------- ------------- ------------- ----- + |c |c |c |c + | | | | + \/ ------------- ------------- ------------- + | Foreground |_\ | Middlegr'nd |_\ | Background |_\ + | tree |g/ | tree |g/ | tree |g/ + ------------- ------------- ------------- + |c |c |c + | | | + \/ \/ \/ + +*********************************************************************************************/ + +#define HFG_FIRST_EXECUTE_MODE 0x0001 +#define HFG_FIRST_EXECUTE_MODE_BIT 0 +#define HFG_CONTEXT_SWITCH_MODE 0x0002 +#define HFG_CONTEXT_SWITCH_MODE_BIT 1 + +#define MAX_FG_STACK_SIZE 32 /* THESE NEED TO BE COMPUTED PROPERLY */ +#define MAX_MG_STACK_SIZE 16 +#define MAX_BG_STACK_SIZE 9 +#define MAX_HFG_STACK_SIZE 4 + +#define SLEEP_ACTIVE_INCREMENT 0 /* Enable task tree thread to go to sleep + This should only ever be used on the Background thread */ +#define STANDARD_ACTIVE_INCREMENT 1 /* Task tree thread normal operation */ +#define SUSPEND_ACTIVE_INCREMENT 2 /* Cause execution to suspend in the task tree thread + This should only ever be used on the Background thread */ + +#define HOSTFLAGS_DISABLE_BG_SLEEP 0 /* Host-controlled flag that determines whether we go to sleep + at the end of BG */ + +/* Minimal context save area for Hyper Forground */ +struct dsp_hf_save_area { + u32 r10_save; + u32 r54_save; + u32 r98_save; + + ___DSP_DUAL_16BIT_ALLOC( + status_save, + ind_save + ) + + ___DSP_DUAL_16BIT_ALLOC( + rci1_save, + rci0_save + ) + + u32 r32_save; + u32 r76_save; + u32 rsd2_save; + + ___DSP_DUAL_16BIT_ALLOC( + rsi2_save, /* See TaskTreeParameterBlock for + remainder of registers */ + rsa2Save + ) + /* saved as part of HFG context */ +}; + + +/* Task link data structure */ +struct dsp_tree_link { + ___DSP_DUAL_16BIT_ALLOC( + /* Pointer to sibling task control block */ + next_scb, + /* Pointer to child task control block */ + sub_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* Pointer to code entry point */ + entry_point, + /* Pointer to local data */ + this_spb + ) +}; + + +struct dsp_task_tree_data { + ___DSP_DUAL_16BIT_ALLOC( + /* Initial tock count; controls task tree execution rate */ + tock_count_limit, + /* Tock down counter */ + tock_count + ) + + /* Add to ActiveCount when TockCountLimit reached: + Subtract on task tree termination */ + ___DSP_DUAL_16BIT_ALLOC( + active_tncrement, + /* Number of pending activations for task tree */ + active_count + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* BitNumber to enable modification of correct bit in ActiveTaskFlags */ + active_bit, + /* Pointer to OS location for indicating current activity on task level */ + active_task_flags_ptr + ) + + /* Data structure for controlling movement of memory blocks:- + currently unused */ + ___DSP_DUAL_16BIT_ALLOC( + mem_upd_ptr, + /* Data structure for controlling synchronous link update */ + link_upd_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* Save area for remainder of full context. */ + save_area, + /* Address of start of local stack for data storage */ + data_stack_base_ptr + ) + +}; + + +struct dsp_interval_timer_data +{ + /* These data items have the same relative locations to those */ + ___DSP_DUAL_16BIT_ALLOC( + interval_timer_period, + itd_unused + ) + + /* used for this data in the SPOS control block for SPOS 1.0 */ + ___DSP_DUAL_16BIT_ALLOC( + num_FG_ticks_this_interval, + num_intervals + ) +}; + + +/* This structure contains extra storage for the task tree + Currently, this additional data is related only to a full context save */ +struct dsp_task_tree_context_block { + /* Up to 10 values are saved onto the stack. 8 for the task tree, 1 for + The access to the context switch (call or interrupt), and 1 spare that + users should never use. This last may be required by the system */ + ___DSP_DUAL_16BIT_ALLOC( + stack1, + stack0 + ) + ___DSP_DUAL_16BIT_ALLOC( + stack3, + stack2 + ) + ___DSP_DUAL_16BIT_ALLOC( + stack5, + stack4 + ) + ___DSP_DUAL_16BIT_ALLOC( + stack7, + stack6 + ) + ___DSP_DUAL_16BIT_ALLOC( + stack9, + stack8 + ) + + u32 saverfe; + + /* Value may be overwritten by stack save algorithm. + Retain the size of the stack data saved here if used */ + ___DSP_DUAL_16BIT_ALLOC( + reserved1, + stack_size + ) + u32 saverba; /* (HFG) */ + u32 saverdc; + u32 savers_config_23; /* (HFG) */ + u32 savers_DMA23; /* (HFG) */ + u32 saversa0; + u32 saversi0; + u32 saversa1; + u32 saversi1; + u32 saversa3; + u32 saversd0; + u32 saversd1; + u32 saversd3; + u32 savers_config01; + u32 savers_DMA01; + u32 saveacc0hl; + u32 saveacc1hl; + u32 saveacc0xacc1x; + u32 saveacc2hl; + u32 saveacc3hl; + u32 saveacc2xacc3x; + u32 saveaux0hl; + u32 saveaux1hl; + u32 saveaux0xaux1x; + u32 saveaux2hl; + u32 saveaux3hl; + u32 saveaux2xaux3x; + u32 savershouthl; + u32 savershoutxmacmode; +}; + + +struct dsp_task_tree_control_block { + struct dsp_hf_save_area context; + struct dsp_tree_link links; + struct dsp_task_tree_data data; + struct dsp_task_tree_context_block context_blk; + struct dsp_interval_timer_data int_timer; +}; + + +#endif /* __DSP_TASK_TYPES_H__ */ diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c new file mode 100644 index 000000000..62f45847b --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_lib.c @@ -0,0 +1,3967 @@ +// 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; +} + +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; + strcpy(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; + strcpy(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; + strcpy(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; + strcpy(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, + .get = snd_cs46xx_iec958_get, + .put = snd_cs46xx_iec958_put, + .private_value = CS46XX_MIXER_SPDIF_INPUT_ELEMENT, +}, +#if 0 +/* Input IEC958 volume does not work for the moment. (Benny) */ +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("Input ",NONE,VOLUME), + .info = snd_cs46xx_vol_info, + .get = snd_cs46xx_vol_iec958_get, + .put = snd_cs46xx_vol_iec958_put, + .private_value = (ASYNCRX_SCB_ADDR + 0xE) << 2, +}, +#endif +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = snd_cs46xx_spdif_info, + .get = snd_cs46xx_spdif_default_get, + .put = snd_cs46xx_spdif_default_put, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .info = snd_cs46xx_spdif_info, + .get = snd_cs46xx_spdif_mask_get, + .access = SNDRV_CTL_ELEM_ACCESS_READ +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM), + .info = snd_cs46xx_spdif_info, + .get = snd_cs46xx_spdif_stream_get, + .put = snd_cs46xx_spdif_stream_put +}, + +#endif +}; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +/* set primary cs4294 codec into Extended Audio Mode */ +static int snd_cs46xx_front_dup_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + unsigned short val; + val = snd_ac97_read(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX], AC97_CSR_ACMODE); + ucontrol->value.integer.value[0] = (val & 0x200) ? 0 : 1; + return 0; +} + +static int snd_cs46xx_front_dup_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + return snd_ac97_update_bits(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX], + AC97_CSR_ACMODE, 0x200, + ucontrol->value.integer.value[0] ? 0 : 0x200); +} + +static const struct snd_kcontrol_new snd_cs46xx_front_dup_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Duplicate Front", + .info = snd_mixer_boolean_info, + .get = snd_cs46xx_front_dup_get, + .put = snd_cs46xx_front_dup_put, +}; +#endif + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +/* Only available on the Hercules Game Theater XP soundcard */ +static const struct snd_kcontrol_new snd_hercules_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Optical/Coaxial SPDIF Input Switch", + .info = snd_mixer_boolean_info, + .get = snd_herc_spdif_select_get, + .put = snd_herc_spdif_select_put, +}, +}; + + +static void snd_cs46xx_codec_reset (struct snd_ac97 * ac97) +{ + unsigned long end_time; + int err; + + /* reset to defaults */ + snd_ac97_write(ac97, AC97_RESET, 0); + + /* set the desired CODEC mode */ + if (ac97->num == CS46XX_PRIMARY_CODEC_INDEX) { + dev_dbg(ac97->bus->card->dev, "CODEC1 mode %04x\n", 0x0); + snd_cs46xx_ac97_write(ac97, AC97_CSR_ACMODE, 0x0); + } else if (ac97->num == CS46XX_SECONDARY_CODEC_INDEX) { + dev_dbg(ac97->bus->card->dev, "CODEC2 mode %04x\n", 0x3); + snd_cs46xx_ac97_write(ac97, AC97_CSR_ACMODE, 0x3); + } else { + snd_BUG(); /* should never happen ... */ + } + + udelay(50); + + /* it's necessary to wait awhile until registers are accessible after RESET */ + /* because the PCM or MASTER volume registers can be modified, */ + /* the REC_GAIN register is used for tests */ + end_time = jiffies + HZ; + do { + unsigned short ext_mid; + + /* use preliminary reads to settle the communication */ + snd_ac97_read(ac97, AC97_RESET); + snd_ac97_read(ac97, AC97_VENDOR_ID1); + snd_ac97_read(ac97, AC97_VENDOR_ID2); + /* modem? */ + ext_mid = snd_ac97_read(ac97, AC97_EXTENDED_MID); + if (ext_mid != 0xffff && (ext_mid & 1) != 0) + return; + + /* test if we can write to the record gain volume register */ + snd_ac97_write(ac97, AC97_REC_GAIN, 0x8a05); + err = snd_ac97_read(ac97, AC97_REC_GAIN); + if (err == 0x8a05) + return; + + msleep(10); + } while (time_after_eq(end_time, jiffies)); + + dev_err(ac97->bus->card->dev, + "CS46xx secondary codec doesn't respond!\n"); +} +#endif + +static int cs46xx_detect_codec(struct snd_cs46xx *chip, int codec) +{ + int idx, err; + struct snd_ac97_template ac97; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.private_free = snd_cs46xx_mixer_free_ac97; + ac97.num = codec; + if (chip->amplifier_ctrl == amp_voyetra) + ac97.scaps = AC97_SCAP_INV_EAPD; + + if (codec == CS46XX_SECONDARY_CODEC_INDEX) { + snd_cs46xx_codec_write(chip, AC97_RESET, 0, codec); + udelay(10); + if (snd_cs46xx_codec_read(chip, AC97_RESET, codec) & 0x8000) { + dev_dbg(chip->card->dev, + "secondary codec not present\n"); + return -ENXIO; + } + } + + snd_cs46xx_codec_write(chip, AC97_MASTER, 0x8000, codec); + for (idx = 0; idx < 100; ++idx) { + if (snd_cs46xx_codec_read(chip, AC97_MASTER, codec) == 0x8000) { + err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97[codec]); + return err; + } + msleep(10); + } + dev_dbg(chip->card->dev, "codec %d detection timeout\n", codec); + return -ENXIO; +} + +int snd_cs46xx_mixer(struct snd_cs46xx *chip, int spdif_device) +{ + struct snd_card *card = chip->card; + struct snd_ctl_elem_id id; + int err; + unsigned int idx; + static const struct snd_ac97_bus_ops ops = { +#ifdef CONFIG_SND_CS46XX_NEW_DSP + .reset = snd_cs46xx_codec_reset, +#endif + .write = snd_cs46xx_ac97_write, + .read = snd_cs46xx_ac97_read, + }; + + /* detect primary codec */ + chip->nr_ac97_codecs = 0; + dev_dbg(chip->card->dev, "detecting primary codec\n"); + err = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus); + if (err < 0) + return err; + + if (cs46xx_detect_codec(chip, CS46XX_PRIMARY_CODEC_INDEX) < 0) + return -ENXIO; + chip->nr_ac97_codecs = 1; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + dev_dbg(chip->card->dev, "detecting secondary codec\n"); + /* try detect a secondary codec */ + if (! cs46xx_detect_codec(chip, CS46XX_SECONDARY_CODEC_INDEX)) + chip->nr_ac97_codecs = 2; +#endif /* CONFIG_SND_CS46XX_NEW_DSP */ + + /* add cs4630 mixer controls */ + for (idx = 0; idx < ARRAY_SIZE(snd_cs46xx_controls); idx++) { + struct snd_kcontrol *kctl; + kctl = snd_ctl_new1(&snd_cs46xx_controls[idx], chip); + if (kctl && kctl->id.iface == SNDRV_CTL_ELEM_IFACE_PCM) + kctl->id.device = spdif_device; + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + + /* get EAPD mixer switch (for voyetra hack) */ + memset(&id, 0, sizeof(id)); + id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strcpy(id.name, "External Amplifier"); + chip->eapd_switch = snd_ctl_find_id(chip->card, &id); + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (chip->nr_ac97_codecs == 1) { + unsigned int id2 = chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]->id & 0xffff; + if ((id2 & 0xfff0) == 0x5920) { /* CS4294 and CS4298 */ + err = snd_ctl_add(card, snd_ctl_new1(&snd_cs46xx_front_dup_ctl, chip)); + if (err < 0) + return err; + snd_ac97_write_cache(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX], + AC97_CSR_ACMODE, 0x200); + } + } + /* do soundcard specific mixer setup */ + if (chip->mixer_init) { + dev_dbg(chip->card->dev, "calling chip->mixer_init(chip);\n"); + chip->mixer_init(chip); + } +#endif + + /* turn on amplifier */ + chip->amplifier_ctrl(chip, 1); + + return 0; +} + +/* + * RawMIDI interface + */ + +static void snd_cs46xx_midi_reset(struct snd_cs46xx *chip) +{ + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, MIDCR_MRST); + udelay(100); + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); +} + +static int snd_cs46xx_midi_input_open(struct snd_rawmidi_substream *substream) +{ + struct snd_cs46xx *chip = substream->rmidi->private_data; + + chip->active_ctrl(chip, 1); + spin_lock_irq(&chip->reg_lock); + chip->uartm |= CS46XX_MODE_INPUT; + chip->midcr |= MIDCR_RXE; + chip->midi_input = substream; + if (!(chip->uartm & CS46XX_MODE_OUTPUT)) { + snd_cs46xx_midi_reset(chip); + } else { + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + spin_unlock_irq(&chip->reg_lock); + return 0; +} + +static int snd_cs46xx_midi_input_close(struct snd_rawmidi_substream *substream) +{ + struct snd_cs46xx *chip = substream->rmidi->private_data; + + spin_lock_irq(&chip->reg_lock); + chip->midcr &= ~(MIDCR_RXE | MIDCR_RIE); + chip->midi_input = NULL; + if (!(chip->uartm & CS46XX_MODE_OUTPUT)) { + snd_cs46xx_midi_reset(chip); + } else { + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + chip->uartm &= ~CS46XX_MODE_INPUT; + spin_unlock_irq(&chip->reg_lock); + chip->active_ctrl(chip, -1); + return 0; +} + +static int snd_cs46xx_midi_output_open(struct snd_rawmidi_substream *substream) +{ + struct snd_cs46xx *chip = substream->rmidi->private_data; + + chip->active_ctrl(chip, 1); + + spin_lock_irq(&chip->reg_lock); + chip->uartm |= CS46XX_MODE_OUTPUT; + chip->midcr |= MIDCR_TXE; + chip->midi_output = substream; + if (!(chip->uartm & CS46XX_MODE_INPUT)) { + snd_cs46xx_midi_reset(chip); + } else { + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + spin_unlock_irq(&chip->reg_lock); + return 0; +} + +static int snd_cs46xx_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct snd_cs46xx *chip = substream->rmidi->private_data; + + spin_lock_irq(&chip->reg_lock); + chip->midcr &= ~(MIDCR_TXE | MIDCR_TIE); + chip->midi_output = NULL; + if (!(chip->uartm & CS46XX_MODE_INPUT)) { + snd_cs46xx_midi_reset(chip); + } else { + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + chip->uartm &= ~CS46XX_MODE_OUTPUT; + spin_unlock_irq(&chip->reg_lock); + chip->active_ctrl(chip, -1); + return 0; +} + +static void snd_cs46xx_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + unsigned long flags; + struct snd_cs46xx *chip = substream->rmidi->private_data; + + spin_lock_irqsave(&chip->reg_lock, flags); + if (up) { + if ((chip->midcr & MIDCR_RIE) == 0) { + chip->midcr |= MIDCR_RIE; + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + } else { + if (chip->midcr & MIDCR_RIE) { + chip->midcr &= ~MIDCR_RIE; + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + } + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static void snd_cs46xx_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + unsigned long flags; + struct snd_cs46xx *chip = substream->rmidi->private_data; + unsigned char byte; + + spin_lock_irqsave(&chip->reg_lock, flags); + if (up) { + if ((chip->midcr & MIDCR_TIE) == 0) { + chip->midcr |= MIDCR_TIE; + /* fill UART FIFO buffer at first, and turn Tx interrupts only if necessary */ + while ((chip->midcr & MIDCR_TIE) && + (snd_cs46xx_peekBA0(chip, BA0_MIDSR) & MIDSR_TBF) == 0) { + if (snd_rawmidi_transmit(substream, &byte, 1) != 1) { + chip->midcr &= ~MIDCR_TIE; + } else { + snd_cs46xx_pokeBA0(chip, BA0_MIDWP, byte); + } + } + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + } else { + if (chip->midcr & MIDCR_TIE) { + chip->midcr &= ~MIDCR_TIE; + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + } + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static const struct snd_rawmidi_ops snd_cs46xx_midi_output = +{ + .open = snd_cs46xx_midi_output_open, + .close = snd_cs46xx_midi_output_close, + .trigger = snd_cs46xx_midi_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_cs46xx_midi_input = +{ + .open = snd_cs46xx_midi_input_open, + .close = snd_cs46xx_midi_input_close, + .trigger = snd_cs46xx_midi_input_trigger, +}; + +int snd_cs46xx_midi(struct snd_cs46xx *chip, int device) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(chip->card, "CS46XX", device, 1, 1, &rmidi); + if (err < 0) + return err; + strcpy(rmidi->name, "CS46XX"); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_cs46xx_midi_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_cs46xx_midi_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = chip; + chip->rmidi = rmidi; + return 0; +} + + +/* + * gameport interface + */ + +#if IS_REACHABLE(CONFIG_GAMEPORT) + +static void snd_cs46xx_gameport_trigger(struct gameport *gameport) +{ + struct snd_cs46xx *chip = gameport_get_port_data(gameport); + + if (snd_BUG_ON(!chip)) + return; + snd_cs46xx_pokeBA0(chip, BA0_JSPT, 0xFF); //outb(gameport->io, 0xFF); +} + +static unsigned char snd_cs46xx_gameport_read(struct gameport *gameport) +{ + struct snd_cs46xx *chip = gameport_get_port_data(gameport); + + if (snd_BUG_ON(!chip)) + return 0; + return snd_cs46xx_peekBA0(chip, BA0_JSPT); //inb(gameport->io); +} + +static int snd_cs46xx_gameport_cooked_read(struct gameport *gameport, int *axes, int *buttons) +{ + struct snd_cs46xx *chip = gameport_get_port_data(gameport); + unsigned js1, js2, jst; + + if (snd_BUG_ON(!chip)) + return 0; + + js1 = snd_cs46xx_peekBA0(chip, BA0_JSC1); + js2 = snd_cs46xx_peekBA0(chip, BA0_JSC2); + jst = snd_cs46xx_peekBA0(chip, BA0_JSPT); + + *buttons = (~jst >> 4) & 0x0F; + + axes[0] = ((js1 & JSC1_Y1V_MASK) >> JSC1_Y1V_SHIFT) & 0xFFFF; + axes[1] = ((js1 & JSC1_X1V_MASK) >> JSC1_X1V_SHIFT) & 0xFFFF; + axes[2] = ((js2 & JSC2_Y2V_MASK) >> JSC2_Y2V_SHIFT) & 0xFFFF; + axes[3] = ((js2 & JSC2_X2V_MASK) >> JSC2_X2V_SHIFT) & 0xFFFF; + + for(jst=0;jst<4;++jst) + if(axes[jst]==0xFFFF) axes[jst] = -1; + return 0; +} + +static int snd_cs46xx_gameport_open(struct gameport *gameport, int mode) +{ + switch (mode) { + case GAMEPORT_MODE_COOKED: + return 0; + case GAMEPORT_MODE_RAW: + return 0; + default: + return -1; + } + return 0; +} + +int snd_cs46xx_gameport(struct snd_cs46xx *chip) +{ + struct gameport *gp; + + chip->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(chip->card->dev, + "cannot allocate memory for gameport\n"); + return -ENOMEM; + } + + gameport_set_name(gp, "CS46xx Gameport"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci)); + gameport_set_dev_parent(gp, &chip->pci->dev); + gameport_set_port_data(gp, chip); + + gp->open = snd_cs46xx_gameport_open; + gp->read = snd_cs46xx_gameport_read; + gp->trigger = snd_cs46xx_gameport_trigger; + gp->cooked_read = snd_cs46xx_gameport_cooked_read; + + snd_cs46xx_pokeBA0(chip, BA0_JSIO, 0xFF); // ? + snd_cs46xx_pokeBA0(chip, BA0_JSCTL, JSCTL_SP_MEDIUM_SLOW); + + gameport_register_port(gp); + + return 0; +} + +static inline void snd_cs46xx_remove_gameport(struct snd_cs46xx *chip) +{ + if (chip->gameport) { + gameport_unregister_port(chip->gameport); + chip->gameport = NULL; + } +} +#else +int snd_cs46xx_gameport(struct snd_cs46xx *chip) { return -ENOSYS; } +static inline void snd_cs46xx_remove_gameport(struct snd_cs46xx *chip) { } +#endif /* CONFIG_GAMEPORT */ + +#ifdef CONFIG_SND_PROC_FS +/* + * proc interface + */ + +static ssize_t snd_cs46xx_io_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) +{ + struct snd_cs46xx_region *region = entry->private_data; + + if (copy_to_user_fromio(buf, region->remap_addr + pos, count)) + return -EFAULT; + return count; +} + +static const struct snd_info_entry_ops snd_cs46xx_proc_io_ops = { + .read = snd_cs46xx_io_read, +}; + +static int snd_cs46xx_proc_init(struct snd_card *card, struct snd_cs46xx *chip) +{ + struct snd_info_entry *entry; + int idx; + + for (idx = 0; idx < 5; idx++) { + struct snd_cs46xx_region *region = &chip->region.idx[idx]; + if (! snd_card_proc_new(card, region->name, &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = chip; + entry->c.ops = &snd_cs46xx_proc_io_ops; + entry->size = region->size; + entry->mode = S_IFREG | 0400; + } + } +#ifdef CONFIG_SND_CS46XX_NEW_DSP + cs46xx_dsp_proc_init(card, chip); +#endif + return 0; +} + +static int snd_cs46xx_proc_done(struct snd_cs46xx *chip) +{ +#ifdef CONFIG_SND_CS46XX_NEW_DSP + cs46xx_dsp_proc_done(chip); +#endif + return 0; +} +#else /* !CONFIG_SND_PROC_FS */ +#define snd_cs46xx_proc_init(card, chip) +#define snd_cs46xx_proc_done(chip) +#endif + +/* + * stop the h/w + */ +static void snd_cs46xx_hw_stop(struct snd_cs46xx *chip) +{ + unsigned int tmp; + + tmp = snd_cs46xx_peek(chip, BA1_PFIE); + tmp &= ~0x0000f03f; + tmp |= 0x00000010; + snd_cs46xx_poke(chip, BA1_PFIE, tmp); /* playback interrupt disable */ + + tmp = snd_cs46xx_peek(chip, BA1_CIE); + tmp &= ~0x0000003f; + tmp |= 0x00000011; + snd_cs46xx_poke(chip, BA1_CIE, tmp); /* capture interrupt disable */ + + /* + * Stop playback DMA. + */ + tmp = snd_cs46xx_peek(chip, BA1_PCTL); + snd_cs46xx_poke(chip, BA1_PCTL, tmp & 0x0000ffff); + + /* + * Stop capture DMA. + */ + tmp = snd_cs46xx_peek(chip, BA1_CCTL); + snd_cs46xx_poke(chip, BA1_CCTL, tmp & 0xffff0000); + + /* + * Reset the processor. + */ + snd_cs46xx_reset(chip); + + snd_cs46xx_proc_stop(chip); + + /* + * Power down the PLL. + */ + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, 0); + + /* + * Turn off the Processor by turning off the software clock enable flag in + * the clock control register. + */ + tmp = snd_cs46xx_peekBA0(chip, BA0_CLKCR1) & ~CLKCR1_SWCE; + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp); +} + + +static void snd_cs46xx_free(struct snd_card *card) +{ + struct snd_cs46xx *chip = card->private_data; +#ifdef CONFIG_SND_CS46XX_NEW_DSP + int idx; +#endif + + if (chip->active_ctrl) + chip->active_ctrl(chip, 1); + + snd_cs46xx_remove_gameport(chip); + + if (chip->amplifier_ctrl) + chip->amplifier_ctrl(chip, -chip->amplifier); /* force to off */ + + snd_cs46xx_proc_done(chip); + + snd_cs46xx_hw_stop(chip); + + if (chip->active_ctrl) + chip->active_ctrl(chip, -chip->amplifier); + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (chip->dsp_spos_instance) { + cs46xx_dsp_spos_destroy(chip); + chip->dsp_spos_instance = NULL; + } + for (idx = 0; idx < CS46XX_DSP_MODULES; idx++) + free_module_desc(chip->modules[idx]); +#else + vfree(chip->ba1); +#endif +} + +/* + * initialize chip + */ +static int snd_cs46xx_chip_init(struct snd_cs46xx *chip) +{ + int timeout; + + /* + * First, blast the clock control register to zero so that the PLL starts + * out in a known state, and blast the master serial port control register + * to zero so that the serial ports also start out in a known state. + */ + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, 0); + snd_cs46xx_pokeBA0(chip, BA0_SERMC1, 0); + + /* + * If we are in AC97 mode, then we must set the part to a host controlled + * AC-link. Otherwise, we won't be able to bring up the link. + */ +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_cs46xx_pokeBA0(chip, BA0_SERACC, SERACC_HSP | SERACC_CHIP_TYPE_2_0 | + SERACC_TWO_CODECS); /* 2.00 dual codecs */ + /* snd_cs46xx_pokeBA0(chip, BA0_SERACC, SERACC_HSP | SERACC_CHIP_TYPE_2_0); */ /* 2.00 codec */ +#else + snd_cs46xx_pokeBA0(chip, BA0_SERACC, SERACC_HSP | SERACC_CHIP_TYPE_1_03); /* 1.03 codec */ +#endif + + /* + * Drive the ARST# pin low for a minimum of 1uS (as defined in the AC97 + * spec) and then drive it high. This is done for non AC97 modes since + * there might be logic external to the CS461x that uses the ARST# line + * for a reset. + */ + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, 0); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_cs46xx_pokeBA0(chip, BA0_ACCTL2, 0); +#endif + udelay(50); + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_RSTN); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_cs46xx_pokeBA0(chip, BA0_ACCTL2, ACCTL_RSTN); +#endif + + /* + * The first thing we do here is to enable sync generation. As soon + * as we start receiving bit clock, we'll start producing the SYNC + * signal. + */ + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_ESYN | ACCTL_RSTN); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_cs46xx_pokeBA0(chip, BA0_ACCTL2, ACCTL_ESYN | ACCTL_RSTN); +#endif + + /* + * Now wait for a short while to allow the AC97 part to start + * generating bit clock (so we don't try to start the PLL without an + * input clock). + */ + mdelay(10); + + /* + * Set the serial port timing configuration, so that + * the clock control circuit gets its clock from the correct place. + */ + snd_cs46xx_pokeBA0(chip, BA0_SERMC1, SERMC1_PTC_AC97); + + /* + * Write the selected clock control setup to the hardware. Do not turn on + * SWCE yet (if requested), so that the devices clocked by the output of + * PLL are not clocked until the PLL is stable. + */ + snd_cs46xx_pokeBA0(chip, BA0_PLLCC, PLLCC_LPF_1050_2780_KHZ | PLLCC_CDR_73_104_MHZ); + snd_cs46xx_pokeBA0(chip, BA0_PLLM, 0x3a); + snd_cs46xx_pokeBA0(chip, BA0_CLKCR2, CLKCR2_PDIVS_8); + + /* + * Power up the PLL. + */ + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, CLKCR1_PLLP); + + /* + * Wait until the PLL has stabilized. + */ + msleep(100); + + /* + * Turn on clocking of the core so that we can setup the serial ports. + */ + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, CLKCR1_PLLP | CLKCR1_SWCE); + + /* + * Enable FIFO Host Bypass + */ + snd_cs46xx_pokeBA0(chip, BA0_SERBCF, SERBCF_HBP); + + /* + * Fill the serial port FIFOs with silence. + */ + snd_cs46xx_clear_serial_FIFOs(chip); + + /* + * Set the serial port FIFO pointer to the first sample in the FIFO. + */ + /* snd_cs46xx_pokeBA0(chip, BA0_SERBSP, 0); */ + + /* + * Write the serial port configuration to the part. The master + * enable bit is not set until all other values have been written. + */ + snd_cs46xx_pokeBA0(chip, BA0_SERC1, SERC1_SO1F_AC97 | SERC1_SO1EN); + snd_cs46xx_pokeBA0(chip, BA0_SERC2, SERC2_SI1F_AC97 | SERC1_SO1EN); + snd_cs46xx_pokeBA0(chip, BA0_SERMC1, SERMC1_PTC_AC97 | SERMC1_MSPE); + + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_cs46xx_pokeBA0(chip, BA0_SERC7, SERC7_ASDI2EN); + snd_cs46xx_pokeBA0(chip, BA0_SERC3, 0); + snd_cs46xx_pokeBA0(chip, BA0_SERC4, 0); + snd_cs46xx_pokeBA0(chip, BA0_SERC5, 0); + snd_cs46xx_pokeBA0(chip, BA0_SERC6, 1); +#endif + + mdelay(5); + + + /* + * Wait for the codec ready signal from the AC97 codec. + */ + timeout = 150; + while (timeout-- > 0) { + /* + * Read the AC97 status register to see if we've seen a CODEC READY + * signal from the AC97 codec. + */ + if (snd_cs46xx_peekBA0(chip, BA0_ACSTS) & ACSTS_CRDY) + goto ok1; + msleep(10); + } + + + dev_err(chip->card->dev, + "create - never read codec ready from AC'97\n"); + dev_err(chip->card->dev, + "it is not probably bug, try to use CS4236 driver\n"); + return -EIO; + ok1: +#ifdef CONFIG_SND_CS46XX_NEW_DSP + { + int count; + for (count = 0; count < 150; count++) { + /* First, we want to wait for a short time. */ + udelay(25); + + if (snd_cs46xx_peekBA0(chip, BA0_ACSTS2) & ACSTS_CRDY) + break; + } + + /* + * Make sure CODEC is READY. + */ + if (!(snd_cs46xx_peekBA0(chip, BA0_ACSTS2) & ACSTS_CRDY)) + dev_dbg(chip->card->dev, + "never read card ready from secondary AC'97\n"); + } +#endif + + /* + * Assert the vaid frame signal so that we can start sending commands + * to the AC97 codec. + */ + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_cs46xx_pokeBA0(chip, BA0_ACCTL2, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); +#endif + + + /* + * Wait until we've sampled input slots 3 and 4 as valid, meaning that + * the codec is pumping ADC data across the AC-link. + */ + timeout = 150; + while (timeout-- > 0) { + /* + * Read the input slot valid register and see if input slots 3 and + * 4 are valid yet. + */ + if ((snd_cs46xx_peekBA0(chip, BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) == (ACISV_ISV3 | ACISV_ISV4)) + goto ok2; + msleep(10); + } + +#ifndef CONFIG_SND_CS46XX_NEW_DSP + dev_err(chip->card->dev, + "create - never read ISV3 & ISV4 from AC'97\n"); + return -EIO; +#else + /* This may happen on a cold boot with a Terratec SiXPack 5.1. + Reloading the driver may help, if there's other soundcards + with the same problem I would like to know. (Benny) */ + + dev_err(chip->card->dev, "never read ISV3 & ISV4 from AC'97\n"); + dev_err(chip->card->dev, + "Try reloading the ALSA driver, if you find something\n"); + dev_err(chip->card->dev, + "broken or not working on your soundcard upon\n"); + dev_err(chip->card->dev, + "this message please report to alsa-devel@alsa-project.org\n"); + + return -EIO; +#endif + ok2: + + /* + * Now, assert valid frame and the slot 3 and 4 valid bits. This will + * commense the transfer of digital audio data to the AC97 codec. + */ + + snd_cs46xx_pokeBA0(chip, BA0_ACOSV, ACOSV_SLV3 | ACOSV_SLV4); + + + /* + * Power down the DAC and ADC. We will power them up (if) when we need + * them. + */ + /* snd_cs46xx_pokeBA0(chip, BA0_AC97_POWERDOWN, 0x300); */ + + /* + * Turn off the Processor by turning off the software clock enable flag in + * the clock control register. + */ + /* tmp = snd_cs46xx_peekBA0(chip, BA0_CLKCR1) & ~CLKCR1_SWCE; */ + /* snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp); */ + + return 0; +} + +/* + * start and load DSP + */ + +static void cs46xx_enable_stream_irqs(struct snd_cs46xx *chip) +{ + unsigned int tmp; + + snd_cs46xx_pokeBA0(chip, BA0_HICR, HICR_IEV | HICR_CHGM); + + tmp = snd_cs46xx_peek(chip, BA1_PFIE); + tmp &= ~0x0000f03f; + snd_cs46xx_poke(chip, BA1_PFIE, tmp); /* playback interrupt enable */ + + tmp = snd_cs46xx_peek(chip, BA1_CIE); + tmp &= ~0x0000003f; + tmp |= 0x00000001; + snd_cs46xx_poke(chip, BA1_CIE, tmp); /* capture interrupt enable */ +} + +int snd_cs46xx_start_dsp(struct snd_cs46xx *chip) +{ + unsigned int tmp; +#ifdef CONFIG_SND_CS46XX_NEW_DSP + int i; +#endif + int err; + + /* + * Reset the processor. + */ + snd_cs46xx_reset(chip); + /* + * Download the image to the processor. + */ +#ifdef CONFIG_SND_CS46XX_NEW_DSP + for (i = 0; i < CS46XX_DSP_MODULES; i++) { + err = load_firmware(chip, &chip->modules[i], module_names[i]); + if (err < 0) { + dev_err(chip->card->dev, "firmware load error [%s]\n", + module_names[i]); + return err; + } + err = cs46xx_dsp_load_module(chip, chip->modules[i]); + if (err < 0) { + dev_err(chip->card->dev, "image download error [%s]\n", + module_names[i]); + return err; + } + } + + if (cs46xx_dsp_scb_and_task_init(chip) < 0) + return -EIO; +#else + err = load_firmware(chip); + if (err < 0) + return err; + + /* old image */ + err = snd_cs46xx_download_image(chip); + if (err < 0) { + dev_err(chip->card->dev, "image download error\n"); + return err; + } + + /* + * Stop playback DMA. + */ + tmp = snd_cs46xx_peek(chip, BA1_PCTL); + chip->play_ctl = tmp & 0xffff0000; + snd_cs46xx_poke(chip, BA1_PCTL, tmp & 0x0000ffff); +#endif + + /* + * Stop capture DMA. + */ + tmp = snd_cs46xx_peek(chip, BA1_CCTL); + chip->capt.ctl = tmp & 0x0000ffff; + snd_cs46xx_poke(chip, BA1_CCTL, tmp & 0xffff0000); + + mdelay(5); + + snd_cs46xx_set_play_sample_rate(chip, 8000); + snd_cs46xx_set_capture_sample_rate(chip, 8000); + + snd_cs46xx_proc_start(chip); + + cs46xx_enable_stream_irqs(chip); + +#ifndef CONFIG_SND_CS46XX_NEW_DSP + /* set the attenuation to 0dB */ + snd_cs46xx_poke(chip, BA1_PVOL, 0x80008000); + snd_cs46xx_poke(chip, BA1_CVOL, 0x80008000); +#endif + + return 0; +} + + +/* + * AMP control - null AMP + */ + +static void amp_none(struct snd_cs46xx *chip, int change) +{ +} + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +static int voyetra_setup_eapd_slot(struct snd_cs46xx *chip) +{ + + u32 idx, valid_slots,tmp,powerdown = 0; + u16 modem_power,pin_config,logic_type; + + dev_dbg(chip->card->dev, "cs46xx_setup_eapd_slot()+\n"); + + /* + * 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; + } + + /* + * Clear PRA. The Bonzo chip will be used for GPIO not for modem + * stuff. + */ + if(chip->nr_ac97_codecs != 2) { + dev_err(chip->card->dev, + "cs46xx_setup_eapd_slot() - no secondary codec configured\n"); + return -EINVAL; + } + + modem_power = snd_cs46xx_codec_read (chip, + AC97_EXTENDED_MSTATUS, + CS46XX_SECONDARY_CODEC_INDEX); + modem_power &=0xFEFF; + + snd_cs46xx_codec_write(chip, + AC97_EXTENDED_MSTATUS, modem_power, + CS46XX_SECONDARY_CODEC_INDEX); + + /* + * Set GPIO pin's 7 and 8 so that they are configured for output. + */ + pin_config = snd_cs46xx_codec_read (chip, + AC97_GPIO_CFG, + CS46XX_SECONDARY_CODEC_INDEX); + pin_config &=0x27F; + + snd_cs46xx_codec_write(chip, + AC97_GPIO_CFG, pin_config, + CS46XX_SECONDARY_CODEC_INDEX); + + /* + * Set GPIO pin's 7 and 8 so that they are compatible with CMOS logic. + */ + + logic_type = snd_cs46xx_codec_read(chip, AC97_GPIO_POLARITY, + CS46XX_SECONDARY_CODEC_INDEX); + logic_type &=0x27F; + + snd_cs46xx_codec_write (chip, AC97_GPIO_POLARITY, logic_type, + CS46XX_SECONDARY_CODEC_INDEX); + + valid_slots = snd_cs46xx_peekBA0(chip, BA0_ACOSV); + valid_slots |= 0x200; + snd_cs46xx_pokeBA0(chip, BA0_ACOSV, valid_slots); + + if ( cs46xx_wait_for_fifo(chip,1) ) { + dev_dbg(chip->card->dev, "FIFO is busy\n"); + + return -EINVAL; + } + + /* + * Fill slots 12 with the correct value for the GPIO pins. + */ + for(idx = 0x90; idx <= 0x9F; idx++) { + /* + * Initialize the fifo so that bits 7 and 8 are on. + * + * Remember that the GPIO pins in bonzo are shifted by 4 bits to + * the left. 0x1800 corresponds to bits 7 and 8. + */ + snd_cs46xx_pokeBA0(chip, BA0_SERBWP, 0x1800); + + /* + * Wait for command to complete + */ + if ( cs46xx_wait_for_fifo(chip,200) ) { + dev_dbg(chip->card->dev, + "failed waiting for FIFO at addr (%02X)\n", + idx); + + return -EINVAL; + } + + /* + * 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); + } + + /* wait for last command to complete */ + cs46xx_wait_for_fifo(chip,200); + + /* + * 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); + + return 0; +} +#endif + +/* + * Crystal EAPD mode + */ + +static void amp_voyetra(struct snd_cs46xx *chip, int change) +{ + /* Manage the EAPD bit on the Crystal 4297 + and the Analog AD1885 */ + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + int old = chip->amplifier; +#endif + int oval, val; + + chip->amplifier += change; + oval = snd_cs46xx_codec_read(chip, AC97_POWERDOWN, + CS46XX_PRIMARY_CODEC_INDEX); + val = oval; + if (chip->amplifier) { + /* Turn the EAPD amp on */ + val |= 0x8000; + } else { + /* Turn the EAPD amp off */ + val &= ~0x8000; + } + if (val != oval) { + snd_cs46xx_codec_write(chip, AC97_POWERDOWN, val, + CS46XX_PRIMARY_CODEC_INDEX); + if (chip->eapd_switch) + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->eapd_switch->id); + } + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (chip->amplifier && !old) { + voyetra_setup_eapd_slot(chip); + } +#endif +} + +static void hercules_init(struct snd_cs46xx *chip) +{ + /* default: AMP off, and SPDIF input optical */ + snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, EGPIODR_GPOE0); + snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, EGPIODR_GPOE0); +} + + +/* + * Game Theatre XP card - EGPIO[2] is used to enable the external amp. + */ +static void amp_hercules(struct snd_cs46xx *chip, int change) +{ + int old = chip->amplifier; + int val1 = snd_cs46xx_peekBA0(chip, BA0_EGPIODR); + int val2 = snd_cs46xx_peekBA0(chip, BA0_EGPIOPTR); + + chip->amplifier += change; + if (chip->amplifier && !old) { + dev_dbg(chip->card->dev, "Hercules amplifier ON\n"); + + snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, + EGPIODR_GPOE2 | val1); /* enable EGPIO2 output */ + snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, + EGPIOPTR_GPPT2 | val2); /* open-drain on output */ + } else if (old && !chip->amplifier) { + dev_dbg(chip->card->dev, "Hercules amplifier OFF\n"); + snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, val1 & ~EGPIODR_GPOE2); /* disable */ + snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, val2 & ~EGPIOPTR_GPPT2); /* disable */ + } +} + +static void voyetra_mixer_init (struct snd_cs46xx *chip) +{ + dev_dbg(chip->card->dev, "initializing Voyetra mixer\n"); + + /* Enable SPDIF out */ + snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, EGPIODR_GPOE0); + snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, EGPIODR_GPOE0); +} + +static void hercules_mixer_init (struct snd_cs46xx *chip) +{ +#ifdef CONFIG_SND_CS46XX_NEW_DSP + unsigned int idx; + int err; + struct snd_card *card = chip->card; +#endif + + /* set EGPIO to default */ + hercules_init(chip); + + dev_dbg(chip->card->dev, "initializing Hercules mixer\n"); + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (chip->in_suspend) + return; + + for (idx = 0 ; idx < ARRAY_SIZE(snd_hercules_controls); idx++) { + struct snd_kcontrol *kctl; + + kctl = snd_ctl_new1(&snd_hercules_controls[idx], chip); + err = snd_ctl_add(card, kctl); + if (err < 0) { + dev_err(card->dev, + "failed to initialize Hercules mixer (%d)\n", + err); + break; + } + } +#endif +} + + +#if 0 +/* + * Untested + */ + +static void amp_voyetra_4294(struct snd_cs46xx *chip, int change) +{ + chip->amplifier += change; + + if (chip->amplifier) { + /* Switch the GPIO pins 7 and 8 to open drain */ + snd_cs46xx_codec_write(chip, 0x4C, + snd_cs46xx_codec_read(chip, 0x4C) & 0xFE7F); + snd_cs46xx_codec_write(chip, 0x4E, + snd_cs46xx_codec_read(chip, 0x4E) | 0x0180); + /* Now wake the AMP (this might be backwards) */ + snd_cs46xx_codec_write(chip, 0x54, + snd_cs46xx_codec_read(chip, 0x54) & ~0x0180); + } else { + snd_cs46xx_codec_write(chip, 0x54, + snd_cs46xx_codec_read(chip, 0x54) | 0x0180); + } +} +#endif + + +/* + * Handle the CLKRUN on a thinkpad. We must disable CLKRUN support + * whenever we need to beat on the chip. + * + * The original idea and code for this hack comes from David Kaiser at + * Linuxcare. Perhaps one day Crystal will document their chips well + * enough to make them useful. + */ + +static void clkrun_hack(struct snd_cs46xx *chip, int change) +{ + u16 control, nval; + + if (!chip->acpi_port) + return; + + chip->amplifier += change; + + /* Read ACPI port */ + nval = control = inw(chip->acpi_port + 0x10); + + /* Flip CLKRUN off while running */ + if (! chip->amplifier) + nval |= 0x2000; + else + nval &= ~0x2000; + if (nval != control) + outw(nval, chip->acpi_port + 0x10); +} + + +/* + * detect intel piix4 + */ +static void clkrun_init(struct snd_cs46xx *chip) +{ + struct pci_dev *pdev; + u8 pp; + + chip->acpi_port = 0; + + pdev = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_82371AB_3, NULL); + if (pdev == NULL) + return; /* Not a thinkpad thats for sure */ + + /* Find the control port */ + pci_read_config_byte(pdev, 0x41, &pp); + chip->acpi_port = pp << 8; + pci_dev_put(pdev); +} + + +/* + * Card subid table + */ + +struct cs_card_type +{ + u16 vendor; + u16 id; + char *name; + void (*init)(struct snd_cs46xx *); + void (*amp)(struct snd_cs46xx *, int); + void (*active)(struct snd_cs46xx *, int); + void (*mixer_init)(struct snd_cs46xx *); +}; + +static struct cs_card_type cards[] = { + { + .vendor = 0x1489, + .id = 0x7001, + .name = "Genius Soundmaker 128 value", + /* nothing special */ + }, + { + .vendor = 0x5053, + .id = 0x3357, + .name = "Voyetra", + .amp = amp_voyetra, + .mixer_init = voyetra_mixer_init, + }, + { + .vendor = 0x1071, + .id = 0x6003, + .name = "Mitac MI6020/21", + .amp = amp_voyetra, + }, + /* Hercules Game Theatre XP */ + { + .vendor = 0x14af, /* Guillemot Corporation */ + .id = 0x0050, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + .mixer_init = hercules_mixer_init, + }, + { + .vendor = 0x1681, + .id = 0x0050, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + .mixer_init = hercules_mixer_init, + }, + { + .vendor = 0x1681, + .id = 0x0051, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + .mixer_init = hercules_mixer_init, + + }, + { + .vendor = 0x1681, + .id = 0x0052, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + .mixer_init = hercules_mixer_init, + }, + { + .vendor = 0x1681, + .id = 0x0053, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + .mixer_init = hercules_mixer_init, + }, + { + .vendor = 0x1681, + .id = 0x0054, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + .mixer_init = hercules_mixer_init, + }, + /* Herculess Fortissimo */ + { + .vendor = 0x1681, + .id = 0xa010, + .name = "Hercules Gamesurround Fortissimo II", + }, + { + .vendor = 0x1681, + .id = 0xa011, + .name = "Hercules Gamesurround Fortissimo III 7.1", + }, + /* Teratec */ + { + .vendor = 0x153b, + .id = 0x112e, + .name = "Terratec DMX XFire 1024", + }, + { + .vendor = 0x153b, + .id = 0x1136, + .name = "Terratec SiXPack 5.1", + }, + /* Not sure if the 570 needs the clkrun hack */ + { + .vendor = PCI_VENDOR_ID_IBM, + .id = 0x0132, + .name = "Thinkpad 570", + .init = clkrun_init, + .active = clkrun_hack, + }, + { + .vendor = PCI_VENDOR_ID_IBM, + .id = 0x0153, + .name = "Thinkpad 600X/A20/T20", + .init = clkrun_init, + .active = clkrun_hack, + }, + { + .vendor = PCI_VENDOR_ID_IBM, + .id = 0x1010, + .name = "Thinkpad 600E (unsupported)", + }, + {} /* terminator */ +}; + + +/* + * APM support + */ +#ifdef CONFIG_PM_SLEEP +static const unsigned int saved_regs[] = { + BA0_ACOSV, + /*BA0_ASER_FADDR,*/ + BA0_ASER_MASTER, + BA1_PVOL, + BA1_CVOL, +}; + +static int snd_cs46xx_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_cs46xx *chip = card->private_data; + int i, amp_saved; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + chip->in_suspend = 1; + // chip->ac97_powerdown = snd_cs46xx_codec_read(chip, AC97_POWER_CONTROL); + // chip->ac97_general_purpose = snd_cs46xx_codec_read(chip, BA0_AC97_GENERAL_PURPOSE); + + snd_ac97_suspend(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]); + snd_ac97_suspend(chip->ac97[CS46XX_SECONDARY_CODEC_INDEX]); + + /* save some registers */ + for (i = 0; i < ARRAY_SIZE(saved_regs); i++) + chip->saved_regs[i] = snd_cs46xx_peekBA0(chip, saved_regs[i]); + + amp_saved = chip->amplifier; + /* turn off amp */ + chip->amplifier_ctrl(chip, -chip->amplifier); + snd_cs46xx_hw_stop(chip); + /* disable CLKRUN */ + chip->active_ctrl(chip, -chip->amplifier); + chip->amplifier = amp_saved; /* restore the status */ + return 0; +} + +static int snd_cs46xx_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_cs46xx *chip = card->private_data; + int amp_saved; +#ifdef CONFIG_SND_CS46XX_NEW_DSP + int i; +#endif + unsigned int tmp; + + amp_saved = chip->amplifier; + chip->amplifier = 0; + chip->active_ctrl(chip, 1); /* force to on */ + + snd_cs46xx_chip_init(chip); + + snd_cs46xx_reset(chip); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + cs46xx_dsp_resume(chip); + /* restore some registers */ + for (i = 0; i < ARRAY_SIZE(saved_regs); i++) + snd_cs46xx_pokeBA0(chip, saved_regs[i], chip->saved_regs[i]); +#else + snd_cs46xx_download_image(chip); +#endif + +#if 0 + snd_cs46xx_codec_write(chip, BA0_AC97_GENERAL_PURPOSE, + chip->ac97_general_purpose); + snd_cs46xx_codec_write(chip, AC97_POWER_CONTROL, + chip->ac97_powerdown); + mdelay(10); + snd_cs46xx_codec_write(chip, BA0_AC97_POWERDOWN, + chip->ac97_powerdown); + mdelay(5); +#endif + + snd_ac97_resume(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]); + snd_ac97_resume(chip->ac97[CS46XX_SECONDARY_CODEC_INDEX]); + + /* + * Stop capture DMA. + */ + tmp = snd_cs46xx_peek(chip, BA1_CCTL); + chip->capt.ctl = tmp & 0x0000ffff; + snd_cs46xx_poke(chip, BA1_CCTL, tmp & 0xffff0000); + + mdelay(5); + + /* reset playback/capture */ + snd_cs46xx_set_play_sample_rate(chip, 8000); + snd_cs46xx_set_capture_sample_rate(chip, 8000); + snd_cs46xx_proc_start(chip); + + cs46xx_enable_stream_irqs(chip); + + if (amp_saved) + chip->amplifier_ctrl(chip, 1); /* turn amp on */ + else + chip->active_ctrl(chip, -1); /* disable CLKRUN */ + chip->amplifier = amp_saved; + chip->in_suspend = 0; + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +SIMPLE_DEV_PM_OPS(snd_cs46xx_pm, snd_cs46xx_suspend, snd_cs46xx_resume); +#endif /* CONFIG_PM_SLEEP */ + + +/* + */ + +int snd_cs46xx_create(struct snd_card *card, + struct pci_dev *pci, + int external_amp, int thinkpad) +{ + struct snd_cs46xx *chip = card->private_data; + int err, idx; + struct snd_cs46xx_region *region; + struct cs_card_type *cp; + u16 ss_card, ss_vendor; + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + + spin_lock_init(&chip->reg_lock); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + mutex_init(&chip->spos_mutex); +#endif + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + err = pci_request_regions(pci, "CS46xx"); + if (err < 0) + return err; + chip->ba0_addr = pci_resource_start(pci, 0); + chip->ba1_addr = pci_resource_start(pci, 1); + if (chip->ba0_addr == 0 || chip->ba0_addr == (unsigned long)~0 || + chip->ba1_addr == 0 || chip->ba1_addr == (unsigned long)~0) { + dev_err(chip->card->dev, + "wrong address(es) - ba0 = 0x%lx, ba1 = 0x%lx\n", + chip->ba0_addr, chip->ba1_addr); + return -ENOMEM; + } + + region = &chip->region.name.ba0; + strcpy(region->name, "CS46xx_BA0"); + region->base = chip->ba0_addr; + region->size = CS46XX_BA0_SIZE; + + region = &chip->region.name.data0; + strcpy(region->name, "CS46xx_BA1_data0"); + region->base = chip->ba1_addr + BA1_SP_DMEM0; + region->size = CS46XX_BA1_DATA0_SIZE; + + region = &chip->region.name.data1; + strcpy(region->name, "CS46xx_BA1_data1"); + region->base = chip->ba1_addr + BA1_SP_DMEM1; + region->size = CS46XX_BA1_DATA1_SIZE; + + region = &chip->region.name.pmem; + strcpy(region->name, "CS46xx_BA1_pmem"); + region->base = chip->ba1_addr + BA1_SP_PMEM; + region->size = CS46XX_BA1_PRG_SIZE; + + region = &chip->region.name.reg; + strcpy(region->name, "CS46xx_BA1_reg"); + region->base = chip->ba1_addr + BA1_SP_REG; + region->size = CS46XX_BA1_REG_SIZE; + + /* set up amp and clkrun hack */ + pci_read_config_word(pci, PCI_SUBSYSTEM_VENDOR_ID, &ss_vendor); + pci_read_config_word(pci, PCI_SUBSYSTEM_ID, &ss_card); + + for (cp = &cards[0]; cp->name; cp++) { + if (cp->vendor == ss_vendor && cp->id == ss_card) { + dev_dbg(chip->card->dev, "hack for %s enabled\n", + cp->name); + + chip->amplifier_ctrl = cp->amp; + chip->active_ctrl = cp->active; + chip->mixer_init = cp->mixer_init; + + if (cp->init) + cp->init(chip); + break; + } + } + + if (external_amp) { + dev_info(chip->card->dev, + "Crystal EAPD support forced on.\n"); + chip->amplifier_ctrl = amp_voyetra; + } + + if (thinkpad) { + dev_info(chip->card->dev, + "Activating CLKRUN hack for Thinkpad.\n"); + chip->active_ctrl = clkrun_hack; + clkrun_init(chip); + } + + if (chip->amplifier_ctrl == NULL) + chip->amplifier_ctrl = amp_none; + if (chip->active_ctrl == NULL) + chip->active_ctrl = amp_none; + + chip->active_ctrl(chip, 1); /* enable CLKRUN */ + + pci_set_master(pci); + + for (idx = 0; idx < 5; idx++) { + region = &chip->region.idx[idx]; + region->remap_addr = devm_ioremap(&pci->dev, region->base, + region->size); + if (region->remap_addr == NULL) { + dev_err(chip->card->dev, + "%s ioremap problem\n", region->name); + return -ENOMEM; + } + } + + if (devm_request_irq(&pci->dev, pci->irq, snd_cs46xx_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(chip->card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_cs46xx_free; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + chip->dsp_spos_instance = cs46xx_dsp_spos_create(chip); + if (!chip->dsp_spos_instance) + return -ENOMEM; +#endif + + err = snd_cs46xx_chip_init(chip); + if (err < 0) + return err; + + snd_cs46xx_proc_init(card, chip); + +#ifdef CONFIG_PM_SLEEP + chip->saved_regs = devm_kmalloc_array(&pci->dev, + ARRAY_SIZE(saved_regs), + sizeof(*chip->saved_regs), + GFP_KERNEL); + if (!chip->saved_regs) + return -ENOMEM; +#endif + + chip->active_ctrl(chip, -1); /* disable CLKRUN */ + return 0; +} diff --git a/sound/pci/cs46xx/cs46xx_lib.h b/sound/pci/cs46xx/cs46xx_lib.h new file mode 100644 index 000000000..6bcf2b636 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_lib.h @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#ifndef __CS46XX_LIB_H__ +#define __CS46XX_LIB_H__ + +/* + * constants + */ + +#define CS46XX_BA0_SIZE 0x1000 +#define CS46XX_BA1_DATA0_SIZE 0x3000 +#define CS46XX_BA1_DATA1_SIZE 0x3800 +#define CS46XX_BA1_PRG_SIZE 0x7000 +#define CS46XX_BA1_REG_SIZE 0x0100 + + + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +#define CS46XX_MIN_PERIOD_SIZE 64 +#define CS46XX_MAX_PERIOD_SIZE 1024*1024 +#else +#define CS46XX_MIN_PERIOD_SIZE 2048 +#define CS46XX_MAX_PERIOD_SIZE 2048 +#endif + +#define CS46XX_FRAGS 2 +/* #define CS46XX_BUFFER_SIZE CS46XX_MAX_PERIOD_SIZE * CS46XX_FRAGS */ + +#define SCB_NO_PARENT 0 +#define SCB_ON_PARENT_NEXT_SCB 1 +#define SCB_ON_PARENT_SUBLIST_SCB 2 + +/* 3*1024 parameter, 3.5*1024 sample, 2*3.5*1024 code */ +#define BA1_DWORD_SIZE (13 * 1024 + 512) +#define BA1_MEMORY_COUNT 3 + +/* + * common I/O routines + */ + +static inline void snd_cs46xx_poke(struct snd_cs46xx *chip, unsigned long reg, unsigned int val) +{ + unsigned int bank = reg >> 16; + unsigned int offset = reg & 0xffff; + + /* + if (bank == 0) + printk(KERN_DEBUG "snd_cs46xx_poke: %04X - %08X\n", + reg >> 2,val); + */ + writel(val, chip->region.idx[bank+1].remap_addr + offset); +} + +static inline unsigned int snd_cs46xx_peek(struct snd_cs46xx *chip, unsigned long reg) +{ + unsigned int bank = reg >> 16; + unsigned int offset = reg & 0xffff; + return readl(chip->region.idx[bank+1].remap_addr + offset); +} + +static inline void snd_cs46xx_pokeBA0(struct snd_cs46xx *chip, unsigned long offset, unsigned int val) +{ + writel(val, chip->region.name.ba0.remap_addr + offset); +} + +static inline unsigned int snd_cs46xx_peekBA0(struct snd_cs46xx *chip, unsigned long offset) +{ + return readl(chip->region.name.ba0.remap_addr + offset); +} + +struct dsp_spos_instance *cs46xx_dsp_spos_create (struct snd_cs46xx * chip); +void cs46xx_dsp_spos_destroy (struct snd_cs46xx * chip); +int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * module); +#ifdef CONFIG_PM_SLEEP +int cs46xx_dsp_resume(struct snd_cs46xx * chip); +#endif +struct dsp_symbol_entry *cs46xx_dsp_lookup_symbol (struct snd_cs46xx * chip, char * symbol_name, + int symbol_type); +#ifdef CONFIG_SND_PROC_FS +int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip); +int cs46xx_dsp_proc_done (struct snd_cs46xx *chip); +#else +#define cs46xx_dsp_proc_init(card, chip) +#define cs46xx_dsp_proc_done(chip) +#endif +int cs46xx_dsp_scb_and_task_init (struct snd_cs46xx *chip); +int snd_cs46xx_download (struct snd_cs46xx *chip, u32 *src, unsigned long offset, + unsigned long len); +int snd_cs46xx_clear_BA1(struct snd_cs46xx *chip, unsigned long offset, unsigned long len); +int cs46xx_dsp_enable_spdif_out (struct snd_cs46xx *chip); +int cs46xx_dsp_enable_spdif_hw (struct snd_cs46xx *chip); +int cs46xx_dsp_disable_spdif_out (struct snd_cs46xx *chip); +int cs46xx_dsp_enable_spdif_in (struct snd_cs46xx *chip); +int cs46xx_dsp_disable_spdif_in (struct snd_cs46xx *chip); +int cs46xx_dsp_enable_pcm_capture (struct snd_cs46xx *chip); +int cs46xx_dsp_disable_pcm_capture (struct snd_cs46xx *chip); +int cs46xx_dsp_enable_adc_capture (struct snd_cs46xx *chip); +int cs46xx_dsp_disable_adc_capture (struct snd_cs46xx *chip); +int cs46xx_poke_via_dsp (struct snd_cs46xx *chip, u32 address, u32 data); +struct dsp_scb_descriptor * cs46xx_dsp_create_scb (struct snd_cs46xx *chip, char * name, + u32 * scb_data, u32 dest); +#ifdef CONFIG_SND_PROC_FS +void cs46xx_dsp_proc_free_scb_desc (struct dsp_scb_descriptor * scb); +void cs46xx_dsp_proc_register_scb_desc (struct snd_cs46xx *chip, + struct dsp_scb_descriptor * scb); +#else +#define cs46xx_dsp_proc_free_scb_desc(scb) +#define cs46xx_dsp_proc_register_scb_desc(chip, scb) +#endif +struct dsp_scb_descriptor * cs46xx_dsp_create_timing_master_scb (struct snd_cs46xx *chip); +struct dsp_scb_descriptor * +cs46xx_dsp_create_codec_out_scb(struct snd_cs46xx * chip, + char * codec_name, u16 channel_disp, u16 fifo_addr, + u16 child_scb_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_scb_descriptor * +cs46xx_dsp_create_codec_in_scb(struct snd_cs46xx * chip, char * codec_name, + u16 channel_disp, u16 fifo_addr, + u16 sample_buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +void cs46xx_dsp_remove_scb (struct snd_cs46xx *chip, + struct dsp_scb_descriptor * scb); +struct dsp_scb_descriptor * +cs46xx_dsp_create_codec_in_scb(struct snd_cs46xx * chip, char * codec_name, + u16 channel_disp, u16 fifo_addr, + u16 sample_buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_scb_descriptor * +cs46xx_dsp_create_src_task_scb(struct snd_cs46xx * chip, char * scb_name, + int sample_rate, u16 src_buffer_addr, + u16 src_delay_buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type, int pass_through); +struct dsp_scb_descriptor * +cs46xx_dsp_create_mix_only_scb(struct snd_cs46xx * chip, char * scb_name, + u16 mix_buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); + +struct dsp_scb_descriptor * +cs46xx_dsp_create_vari_decimate_scb(struct snd_cs46xx * chip, char * scb_name, + u16 vari_buffer_addr0, u16 vari_buffer_addr1, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_scb_descriptor * +cs46xx_dsp_create_asynch_fg_rx_scb(struct snd_cs46xx * chip, char * scb_name, + u32 dest, u16 hfg_scb_address, u16 asynch_buffer_address, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_scb_descriptor * +cs46xx_dsp_create_spio_write_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_scb_descriptor * +cs46xx_dsp_create_mix_to_ostream_scb(struct snd_cs46xx * chip, char * scb_name, + u16 mix_buffer_addr, u16 writeback_spb, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_scb_descriptor * +cs46xx_dsp_create_magic_snoop_scb(struct snd_cs46xx * chip, char * scb_name, + u32 dest, u16 snoop_buffer_address, + struct dsp_scb_descriptor * snoop_scb, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_pcm_channel_descriptor * +cs46xx_dsp_create_pcm_channel (struct snd_cs46xx * chip, u32 sample_rate, + void * private_data, u32 hw_dma_addr, + int pcm_channel_id); +void cs46xx_dsp_destroy_pcm_channel (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel); +int cs46xx_dsp_pcm_unlink (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel); +int cs46xx_dsp_pcm_link (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel); +struct dsp_scb_descriptor * +cs46xx_add_record_source (struct snd_cs46xx *chip, struct dsp_scb_descriptor * source, + u16 addr, char * scb_name); +int cs46xx_src_unlink(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src); +int cs46xx_src_link(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src); +int cs46xx_iec958_pre_open (struct snd_cs46xx *chip); +int cs46xx_iec958_post_close (struct snd_cs46xx *chip); +int cs46xx_dsp_pcm_channel_set_period (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel, + int period_size); +int cs46xx_dsp_pcm_ostream_set_period (struct snd_cs46xx * chip, int period_size); +int cs46xx_dsp_set_dac_volume (struct snd_cs46xx * chip, u16 left, u16 right); +int cs46xx_dsp_set_iec958_volume (struct snd_cs46xx * chip, u16 left, u16 right); +#endif /* __CS46XX_LIB_H__ */ diff --git a/sound/pci/cs46xx/dsp_spos.c b/sound/pci/cs46xx/dsp_spos.c new file mode 100644 index 000000000..1db6bc58d --- /dev/null +++ b/sound/pci/cs46xx/dsp_spos.c @@ -0,0 +1,1983 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +/* + * 2002-07 Benny Sjostrand benny@hostmobility.com + */ + + +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/mutex.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/info.h> +#include <sound/asoundef.h> +#include "cs46xx.h" + +#include "cs46xx_lib.h" +#include "dsp_spos.h" + +static int cs46xx_dsp_async_init (struct snd_cs46xx *chip, + struct dsp_scb_descriptor * fg_entry); + +static const enum wide_opcode wide_opcodes[] = { + WIDE_FOR_BEGIN_LOOP, + WIDE_FOR_BEGIN_LOOP2, + WIDE_COND_GOTO_ADDR, + WIDE_COND_GOTO_CALL, + WIDE_TBEQ_COND_GOTO_ADDR, + WIDE_TBEQ_COND_CALL_ADDR, + WIDE_TBEQ_NCOND_GOTO_ADDR, + WIDE_TBEQ_NCOND_CALL_ADDR, + WIDE_TBEQ_COND_GOTO1_ADDR, + WIDE_TBEQ_COND_CALL1_ADDR, + WIDE_TBEQ_NCOND_GOTOI_ADDR, + WIDE_TBEQ_NCOND_CALL1_ADDR +}; + +static int shadow_and_reallocate_code (struct snd_cs46xx * chip, u32 * data, u32 size, + u32 overlay_begin_address) +{ + unsigned int i = 0, j, nreallocated = 0; + u32 hival,loval,address; + u32 mop_operands,mop_type,wide_op; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(size %2)) + return -EINVAL; + + while (i < size) { + loval = data[i++]; + hival = data[i++]; + + if (ins->code.offset > 0) { + mop_operands = (hival >> 6) & 0x03fff; + mop_type = mop_operands >> 10; + + /* check for wide type instruction */ + if (mop_type == 0 && + (mop_operands & WIDE_LADD_INSTR_MASK) == 0 && + (mop_operands & WIDE_INSTR_MASK) != 0) { + wide_op = loval & 0x7f; + for (j = 0;j < ARRAY_SIZE(wide_opcodes); ++j) { + if (wide_opcodes[j] == wide_op) { + /* need to reallocate instruction */ + address = (hival & 0x00FFF) << 5; + address |= loval >> 15; + + dev_dbg(chip->card->dev, + "handle_wideop[1]: %05x:%05x addr %04x\n", + hival, loval, address); + + if ( !(address & 0x8000) ) { + address += (ins->code.offset / 2) - overlay_begin_address; + } else { + dev_dbg(chip->card->dev, + "handle_wideop[1]: ROM symbol not reallocated\n"); + } + + hival &= 0xFF000; + loval &= 0x07FFF; + + hival |= ( (address >> 5) & 0x00FFF); + loval |= ( (address << 15) & 0xF8000); + + address = (hival & 0x00FFF) << 5; + address |= loval >> 15; + + dev_dbg(chip->card->dev, + "handle_wideop:[2] %05x:%05x addr %04x\n", + hival, loval, address); + nreallocated++; + } /* wide_opcodes[j] == wide_op */ + } /* for */ + } /* mod_type == 0 ... */ + } /* ins->code.offset > 0 */ + + ins->code.data[ins->code.size++] = loval; + ins->code.data[ins->code.size++] = hival; + } + + dev_dbg(chip->card->dev, + "dsp_spos: %d instructions reallocated\n", nreallocated); + return nreallocated; +} + +static struct dsp_segment_desc * get_segment_desc (struct dsp_module_desc * module, int seg_type) +{ + int i; + for (i = 0;i < module->nsegments; ++i) { + if (module->segments[i].segment_type == seg_type) { + return (module->segments + i); + } + } + + return NULL; +}; + +static int find_free_symbol_index (struct dsp_spos_instance * ins) +{ + int index = ins->symbol_table.nsymbols,i; + + for (i = ins->symbol_table.highest_frag_index; i < ins->symbol_table.nsymbols; ++i) { + if (ins->symbol_table.symbols[i].deleted) { + index = i; + break; + } + } + + return index; +} + +static int add_symbols (struct snd_cs46xx * chip, struct dsp_module_desc * module) +{ + int i; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (module->symbol_table.nsymbols > 0) { + if (!strcmp(module->symbol_table.symbols[0].symbol_name, "OVERLAYBEGINADDRESS") && + module->symbol_table.symbols[0].symbol_type == SYMBOL_CONSTANT ) { + module->overlay_begin_address = module->symbol_table.symbols[0].address; + } + } + + for (i = 0;i < module->symbol_table.nsymbols; ++i) { + if (ins->symbol_table.nsymbols == (DSP_MAX_SYMBOLS - 1)) { + dev_err(chip->card->dev, + "dsp_spos: symbol table is full\n"); + return -ENOMEM; + } + + + if (cs46xx_dsp_lookup_symbol(chip, + module->symbol_table.symbols[i].symbol_name, + module->symbol_table.symbols[i].symbol_type) == NULL) { + + ins->symbol_table.symbols[ins->symbol_table.nsymbols] = module->symbol_table.symbols[i]; + ins->symbol_table.symbols[ins->symbol_table.nsymbols].address += ((ins->code.offset / 2) - module->overlay_begin_address); + ins->symbol_table.symbols[ins->symbol_table.nsymbols].module = module; + ins->symbol_table.symbols[ins->symbol_table.nsymbols].deleted = 0; + + if (ins->symbol_table.nsymbols > ins->symbol_table.highest_frag_index) + ins->symbol_table.highest_frag_index = ins->symbol_table.nsymbols; + + ins->symbol_table.nsymbols++; + } else { +#if 0 + dev_dbg(chip->card->dev, + "dsp_spos: symbol <%s> duplicated, probably nothing wrong with that (Cirrus?)\n", + module->symbol_table.symbols[i].symbol_name); */ +#endif + } + } + + return 0; +} + +static struct dsp_symbol_entry * +add_symbol (struct snd_cs46xx * chip, char * symbol_name, u32 address, int type) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_symbol_entry * symbol = NULL; + int index; + + if (ins->symbol_table.nsymbols == (DSP_MAX_SYMBOLS - 1)) { + dev_err(chip->card->dev, "dsp_spos: symbol table is full\n"); + return NULL; + } + + if (cs46xx_dsp_lookup_symbol(chip, + symbol_name, + type) != NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol <%s> duplicated\n", symbol_name); + return NULL; + } + + index = find_free_symbol_index (ins); + + strcpy (ins->symbol_table.symbols[index].symbol_name, symbol_name); + ins->symbol_table.symbols[index].address = address; + ins->symbol_table.symbols[index].symbol_type = type; + ins->symbol_table.symbols[index].module = NULL; + ins->symbol_table.symbols[index].deleted = 0; + symbol = (ins->symbol_table.symbols + index); + + if (index > ins->symbol_table.highest_frag_index) + ins->symbol_table.highest_frag_index = index; + + if (index == ins->symbol_table.nsymbols) + ins->symbol_table.nsymbols++; /* no frag. in list */ + + return symbol; +} + +struct dsp_spos_instance *cs46xx_dsp_spos_create (struct snd_cs46xx * chip) +{ + struct dsp_spos_instance * ins = kzalloc(sizeof(struct dsp_spos_instance), GFP_KERNEL); + + if (ins == NULL) + return NULL; + + /* better to use vmalloc for this big table */ + ins->symbol_table.symbols = + vmalloc(array_size(DSP_MAX_SYMBOLS, + sizeof(struct dsp_symbol_entry))); + ins->code.data = kmalloc(DSP_CODE_BYTE_SIZE, GFP_KERNEL); + ins->modules = kmalloc_array(DSP_MAX_MODULES, + sizeof(struct dsp_module_desc), + GFP_KERNEL); + if (!ins->symbol_table.symbols || !ins->code.data || !ins->modules) { + cs46xx_dsp_spos_destroy(chip); + goto error; + } + ins->symbol_table.nsymbols = 0; + ins->symbol_table.highest_frag_index = 0; + ins->code.offset = 0; + ins->code.size = 0; + ins->nscb = 0; + ins->ntask = 0; + ins->nmodules = 0; + + /* default SPDIF input sample rate + to 48000 khz */ + ins->spdif_in_sample_rate = 48000; + + /* maximize volume */ + ins->dac_volume_right = 0x8000; + ins->dac_volume_left = 0x8000; + ins->spdif_input_volume_right = 0x8000; + ins->spdif_input_volume_left = 0x8000; + + /* set left and right validity bits and + default channel status */ + ins->spdif_csuv_default = + ins->spdif_csuv_stream = + /* byte 0 */ ((unsigned int)_wrap_all_bits( (SNDRV_PCM_DEFAULT_CON_SPDIF & 0xff)) << 24) | + /* byte 1 */ ((unsigned int)_wrap_all_bits( ((SNDRV_PCM_DEFAULT_CON_SPDIF >> 8) & 0xff)) << 16) | + /* byte 3 */ (unsigned int)_wrap_all_bits( (SNDRV_PCM_DEFAULT_CON_SPDIF >> 24) & 0xff) | + /* left and right validity bits */ (1 << 13) | (1 << 12); + + return ins; + +error: + kfree(ins->modules); + kfree(ins->code.data); + vfree(ins->symbol_table.symbols); + kfree(ins); + return NULL; +} + +void cs46xx_dsp_spos_destroy (struct snd_cs46xx * chip) +{ + int i; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(!ins)) + return; + + mutex_lock(&chip->spos_mutex); + for (i = 0; i < ins->nscb; ++i) { + if (ins->scbs[i].deleted) continue; + + cs46xx_dsp_proc_free_scb_desc ( (ins->scbs + i) ); +#ifdef CONFIG_PM_SLEEP + kfree(ins->scbs[i].data); +#endif + } + + kfree(ins->code.data); + vfree(ins->symbol_table.symbols); + kfree(ins->modules); + kfree(ins); + mutex_unlock(&chip->spos_mutex); +} + +static int dsp_load_parameter(struct snd_cs46xx *chip, + struct dsp_segment_desc *parameter) +{ + u32 doffset, dsize; + + if (!parameter) { + dev_dbg(chip->card->dev, + "dsp_spos: module got no parameter segment\n"); + return 0; + } + + doffset = (parameter->offset * 4 + DSP_PARAMETER_BYTE_OFFSET); + dsize = parameter->size * 4; + + dev_dbg(chip->card->dev, + "dsp_spos: downloading parameter data to chip (%08x-%08x)\n", + doffset,doffset + dsize); + if (snd_cs46xx_download (chip, parameter->data, doffset, dsize)) { + dev_err(chip->card->dev, + "dsp_spos: failed to download parameter data to DSP\n"); + return -EINVAL; + } + return 0; +} + +static int dsp_load_sample(struct snd_cs46xx *chip, + struct dsp_segment_desc *sample) +{ + u32 doffset, dsize; + + if (!sample) { + dev_dbg(chip->card->dev, + "dsp_spos: module got no sample segment\n"); + return 0; + } + + doffset = (sample->offset * 4 + DSP_SAMPLE_BYTE_OFFSET); + dsize = sample->size * 4; + + dev_dbg(chip->card->dev, + "dsp_spos: downloading sample data to chip (%08x-%08x)\n", + doffset,doffset + dsize); + + if (snd_cs46xx_download (chip,sample->data,doffset,dsize)) { + dev_err(chip->card->dev, + "dsp_spos: failed to sample data to DSP\n"); + return -EINVAL; + } + return 0; +} + +int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * module) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_segment_desc * code = get_segment_desc (module,SEGTYPE_SP_PROGRAM); + u32 doffset, dsize; + int err; + + if (ins->nmodules == DSP_MAX_MODULES - 1) { + dev_err(chip->card->dev, + "dsp_spos: to many modules loaded into DSP\n"); + return -ENOMEM; + } + + dev_dbg(chip->card->dev, + "dsp_spos: loading module %s into DSP\n", module->module_name); + + if (ins->nmodules == 0) { + dev_dbg(chip->card->dev, "dsp_spos: clearing parameter area\n"); + snd_cs46xx_clear_BA1(chip, DSP_PARAMETER_BYTE_OFFSET, DSP_PARAMETER_BYTE_SIZE); + } + + err = dsp_load_parameter(chip, get_segment_desc(module, + SEGTYPE_SP_PARAMETER)); + if (err < 0) + return err; + + if (ins->nmodules == 0) { + dev_dbg(chip->card->dev, "dsp_spos: clearing sample area\n"); + snd_cs46xx_clear_BA1(chip, DSP_SAMPLE_BYTE_OFFSET, DSP_SAMPLE_BYTE_SIZE); + } + + err = dsp_load_sample(chip, get_segment_desc(module, + SEGTYPE_SP_SAMPLE)); + if (err < 0) + return err; + + if (ins->nmodules == 0) { + dev_dbg(chip->card->dev, "dsp_spos: clearing code area\n"); + snd_cs46xx_clear_BA1(chip, DSP_CODE_BYTE_OFFSET, DSP_CODE_BYTE_SIZE); + } + + if (code == NULL) { + dev_dbg(chip->card->dev, + "dsp_spos: module got no code segment\n"); + } else { + if (ins->code.offset + code->size > DSP_CODE_BYTE_SIZE) { + dev_err(chip->card->dev, + "dsp_spos: no space available in DSP\n"); + return -ENOMEM; + } + + module->load_address = ins->code.offset; + module->overlay_begin_address = 0x000; + + /* if module has a code segment it must have + symbol table */ + if (snd_BUG_ON(!module->symbol_table.symbols)) + return -ENOMEM; + if (add_symbols(chip,module)) { + dev_err(chip->card->dev, + "dsp_spos: failed to load symbol table\n"); + return -ENOMEM; + } + + doffset = (code->offset * 4 + ins->code.offset * 4 + DSP_CODE_BYTE_OFFSET); + dsize = code->size * 4; + dev_dbg(chip->card->dev, + "dsp_spos: downloading code to chip (%08x-%08x)\n", + doffset,doffset + dsize); + + module->nfixups = shadow_and_reallocate_code(chip,code->data,code->size,module->overlay_begin_address); + + if (snd_cs46xx_download (chip,(ins->code.data + ins->code.offset),doffset,dsize)) { + dev_err(chip->card->dev, + "dsp_spos: failed to download code to DSP\n"); + return -EINVAL; + } + + ins->code.offset += code->size; + } + + /* NOTE: module segments and symbol table must be + statically allocated. Case that module data is + not generated by the ospparser */ + ins->modules[ins->nmodules] = *module; + ins->nmodules++; + + return 0; +} + +struct dsp_symbol_entry * +cs46xx_dsp_lookup_symbol (struct snd_cs46xx * chip, char * symbol_name, int symbol_type) +{ + int i; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + for ( i = 0; i < ins->symbol_table.nsymbols; ++i ) { + + if (ins->symbol_table.symbols[i].deleted) + continue; + + if (!strcmp(ins->symbol_table.symbols[i].symbol_name,symbol_name) && + ins->symbol_table.symbols[i].symbol_type == symbol_type) { + return (ins->symbol_table.symbols + i); + } + } + +#if 0 + dev_err(chip->card->dev, "dsp_spos: symbol <%s> type %02x not found\n", + symbol_name,symbol_type); +#endif + + return NULL; +} + + +#ifdef CONFIG_SND_PROC_FS +static struct dsp_symbol_entry * +cs46xx_dsp_lookup_symbol_addr (struct snd_cs46xx * chip, u32 address, int symbol_type) +{ + int i; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + for ( i = 0; i < ins->symbol_table.nsymbols; ++i ) { + + if (ins->symbol_table.symbols[i].deleted) + continue; + + if (ins->symbol_table.symbols[i].address == address && + ins->symbol_table.symbols[i].symbol_type == symbol_type) { + return (ins->symbol_table.symbols + i); + } + } + + + return NULL; +} + + +static void cs46xx_dsp_proc_symbol_table_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_cs46xx *chip = entry->private_data; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i; + + snd_iprintf(buffer, "SYMBOLS:\n"); + for ( i = 0; i < ins->symbol_table.nsymbols; ++i ) { + char *module_str = "system"; + + if (ins->symbol_table.symbols[i].deleted) + continue; + + if (ins->symbol_table.symbols[i].module != NULL) { + module_str = ins->symbol_table.symbols[i].module->module_name; + } + + + snd_iprintf(buffer, "%04X <%02X> %s [%s]\n", + ins->symbol_table.symbols[i].address, + ins->symbol_table.symbols[i].symbol_type, + ins->symbol_table.symbols[i].symbol_name, + module_str); + } +} + + +static void cs46xx_dsp_proc_modules_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_cs46xx *chip = entry->private_data; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i,j; + + mutex_lock(&chip->spos_mutex); + snd_iprintf(buffer, "MODULES:\n"); + for ( i = 0; i < ins->nmodules; ++i ) { + snd_iprintf(buffer, "\n%s:\n", ins->modules[i].module_name); + snd_iprintf(buffer, " %d symbols\n", ins->modules[i].symbol_table.nsymbols); + snd_iprintf(buffer, " %d fixups\n", ins->modules[i].nfixups); + + for (j = 0; j < ins->modules[i].nsegments; ++ j) { + struct dsp_segment_desc * desc = (ins->modules[i].segments + j); + snd_iprintf(buffer, " segment %02x offset %08x size %08x\n", + desc->segment_type,desc->offset, desc->size); + } + } + mutex_unlock(&chip->spos_mutex); +} + +static void cs46xx_dsp_proc_task_tree_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_cs46xx *chip = entry->private_data; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i, j, col; + void __iomem *dst = chip->region.idx[1].remap_addr + DSP_PARAMETER_BYTE_OFFSET; + + mutex_lock(&chip->spos_mutex); + snd_iprintf(buffer, "TASK TREES:\n"); + for ( i = 0; i < ins->ntask; ++i) { + snd_iprintf(buffer,"\n%04x %s:\n",ins->tasks[i].address,ins->tasks[i].task_name); + + for (col = 0,j = 0;j < ins->tasks[i].size; j++,col++) { + u32 val; + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + val = readl(dst + (ins->tasks[i].address + j) * sizeof(u32)); + snd_iprintf(buffer,"%08x ",val); + } + } + + snd_iprintf(buffer,"\n"); + mutex_unlock(&chip->spos_mutex); +} + +static void cs46xx_dsp_proc_scb_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_cs46xx *chip = entry->private_data; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i; + + mutex_lock(&chip->spos_mutex); + snd_iprintf(buffer, "SCB's:\n"); + for ( i = 0; i < ins->nscb; ++i) { + if (ins->scbs[i].deleted) + continue; + snd_iprintf(buffer,"\n%04x %s:\n\n",ins->scbs[i].address,ins->scbs[i].scb_name); + + if (ins->scbs[i].parent_scb_ptr != NULL) { + snd_iprintf(buffer,"parent [%s:%04x] ", + ins->scbs[i].parent_scb_ptr->scb_name, + ins->scbs[i].parent_scb_ptr->address); + } else snd_iprintf(buffer,"parent [none] "); + + snd_iprintf(buffer,"sub_list_ptr [%s:%04x]\nnext_scb_ptr [%s:%04x] task_entry [%s:%04x]\n", + ins->scbs[i].sub_list_ptr->scb_name, + ins->scbs[i].sub_list_ptr->address, + ins->scbs[i].next_scb_ptr->scb_name, + ins->scbs[i].next_scb_ptr->address, + ins->scbs[i].task_entry->symbol_name, + ins->scbs[i].task_entry->address); + } + + snd_iprintf(buffer,"\n"); + mutex_unlock(&chip->spos_mutex); +} + +static void cs46xx_dsp_proc_parameter_dump_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_cs46xx *chip = entry->private_data; + /*struct dsp_spos_instance * ins = chip->dsp_spos_instance; */ + unsigned int i, col = 0; + void __iomem *dst = chip->region.idx[1].remap_addr + DSP_PARAMETER_BYTE_OFFSET; + struct dsp_symbol_entry * symbol; + + for (i = 0;i < DSP_PARAMETER_BYTE_SIZE; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + symbol = cs46xx_dsp_lookup_symbol_addr(chip, i / sizeof(u32), SYMBOL_PARAMETER); + if (symbol) { + col = 0; + snd_iprintf (buffer,"\n%s:\n",symbol->symbol_name); + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ", i / (unsigned int)sizeof(u32)); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } +} + +static void cs46xx_dsp_proc_sample_dump_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_cs46xx *chip = entry->private_data; + int i,col = 0; + void __iomem *dst = chip->region.idx[2].remap_addr; + + snd_iprintf(buffer,"PCMREADER:\n"); + for (i = PCM_READER_BUF1;i < PCM_READER_BUF1 + 0x30; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + + snd_iprintf(buffer,"\nMIX_SAMPLE_BUF1:\n"); + + col = 0; + for (i = MIX_SAMPLE_BUF1;i < MIX_SAMPLE_BUF1 + 0x40; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + + snd_iprintf(buffer,"\nSRC_TASK_SCB1:\n"); + col = 0; + for (i = 0x2480 ; i < 0x2480 + 0x40 ; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + + + snd_iprintf(buffer,"\nSPDIFO_BUFFER:\n"); + col = 0; + for (i = SPDIFO_IP_OUTPUT_BUFFER1;i < SPDIFO_IP_OUTPUT_BUFFER1 + 0x30; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + + snd_iprintf(buffer,"\n...\n"); + col = 0; + + for (i = SPDIFO_IP_OUTPUT_BUFFER1+0xD0;i < SPDIFO_IP_OUTPUT_BUFFER1 + 0x110; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + + + snd_iprintf(buffer,"\nOUTPUT_SNOOP:\n"); + col = 0; + for (i = OUTPUT_SNOOP_BUFFER;i < OUTPUT_SNOOP_BUFFER + 0x40; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + + snd_iprintf(buffer,"\nCODEC_INPUT_BUF1: \n"); + col = 0; + for (i = CODEC_INPUT_BUF1;i < CODEC_INPUT_BUF1 + 0x40; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } +#if 0 + snd_iprintf(buffer,"\nWRITE_BACK_BUF1: \n"); + col = 0; + for (i = WRITE_BACK_BUF1;i < WRITE_BACK_BUF1 + 0x40; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } +#endif + + snd_iprintf(buffer,"\nSPDIFI_IP_OUTPUT_BUFFER1: \n"); + col = 0; + for (i = SPDIFI_IP_OUTPUT_BUFFER1;i < SPDIFI_IP_OUTPUT_BUFFER1 + 0x80; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + snd_iprintf(buffer,"\n"); +} + +int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip) +{ + struct snd_info_entry *entry; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i; + + ins->snd_card = card; + + entry = snd_info_create_card_entry(card, "dsp", card->proc_root); + if (entry) + entry->mode = S_IFDIR | 0555; + ins->proc_dsp_dir = entry; + + if (!ins->proc_dsp_dir) + return -ENOMEM; + + entry = snd_info_create_card_entry(card, "spos_symbols", + ins->proc_dsp_dir); + if (entry) + snd_info_set_text_ops(entry, chip, + cs46xx_dsp_proc_symbol_table_read); + + entry = snd_info_create_card_entry(card, "spos_modules", + ins->proc_dsp_dir); + if (entry) + snd_info_set_text_ops(entry, chip, + cs46xx_dsp_proc_modules_read); + + entry = snd_info_create_card_entry(card, "parameter", + ins->proc_dsp_dir); + if (entry) + snd_info_set_text_ops(entry, chip, + cs46xx_dsp_proc_parameter_dump_read); + + entry = snd_info_create_card_entry(card, "sample", + ins->proc_dsp_dir); + if (entry) + snd_info_set_text_ops(entry, chip, + cs46xx_dsp_proc_sample_dump_read); + + entry = snd_info_create_card_entry(card, "task_tree", + ins->proc_dsp_dir); + if (entry) + snd_info_set_text_ops(entry, chip, + cs46xx_dsp_proc_task_tree_read); + + entry = snd_info_create_card_entry(card, "scb_info", + ins->proc_dsp_dir); + if (entry) + snd_info_set_text_ops(entry, chip, + cs46xx_dsp_proc_scb_read); + + mutex_lock(&chip->spos_mutex); + /* register/update SCB's entries on proc */ + for (i = 0; i < ins->nscb; ++i) { + if (ins->scbs[i].deleted) continue; + + cs46xx_dsp_proc_register_scb_desc (chip, (ins->scbs + i)); + } + mutex_unlock(&chip->spos_mutex); + + return 0; +} + +int cs46xx_dsp_proc_done (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i; + + if (!ins) + return 0; + + mutex_lock(&chip->spos_mutex); + for (i = 0; i < ins->nscb; ++i) { + if (ins->scbs[i].deleted) continue; + cs46xx_dsp_proc_free_scb_desc ( (ins->scbs + i) ); + } + mutex_unlock(&chip->spos_mutex); + + snd_info_free_entry(ins->proc_dsp_dir); + ins->proc_dsp_dir = NULL; + + return 0; +} +#endif /* CONFIG_SND_PROC_FS */ + +static void _dsp_create_task_tree (struct snd_cs46xx *chip, u32 * task_data, + u32 dest, int size) +{ + void __iomem *spdst = chip->region.idx[1].remap_addr + + DSP_PARAMETER_BYTE_OFFSET + dest * sizeof(u32); + int i; + + for (i = 0; i < size; ++i) { + dev_dbg(chip->card->dev, "addr %p, val %08x\n", + spdst, task_data[i]); + writel(task_data[i],spdst); + spdst += sizeof(u32); + } +} + +static void _dsp_create_scb (struct snd_cs46xx *chip, u32 * scb_data, u32 dest) +{ + void __iomem *spdst = chip->region.idx[1].remap_addr + + DSP_PARAMETER_BYTE_OFFSET + dest * sizeof(u32); + int i; + + for (i = 0; i < 0x10; ++i) { + dev_dbg(chip->card->dev, "addr %p, val %08x\n", + spdst, scb_data[i]); + writel(scb_data[i],spdst); + spdst += sizeof(u32); + } +} + +static int find_free_scb_index (struct dsp_spos_instance * ins) +{ + int index = ins->nscb, i; + + for (i = ins->scb_highest_frag_index; i < ins->nscb; ++i) { + if (ins->scbs[i].deleted) { + index = i; + break; + } + } + + return index; +} + +static struct dsp_scb_descriptor * _map_scb (struct snd_cs46xx *chip, char * name, u32 dest) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * desc = NULL; + int index; + + if (ins->nscb == DSP_MAX_SCB_DESC - 1) { + dev_err(chip->card->dev, + "dsp_spos: got no place for other SCB\n"); + return NULL; + } + + index = find_free_scb_index (ins); + + memset(&ins->scbs[index], 0, sizeof(ins->scbs[index])); + strcpy(ins->scbs[index].scb_name, name); + ins->scbs[index].address = dest; + ins->scbs[index].index = index; + ins->scbs[index].ref_count = 1; + + desc = (ins->scbs + index); + ins->scbs[index].scb_symbol = add_symbol (chip, name, dest, SYMBOL_PARAMETER); + + if (index > ins->scb_highest_frag_index) + ins->scb_highest_frag_index = index; + + if (index == ins->nscb) + ins->nscb++; + + return desc; +} + +static struct dsp_task_descriptor * +_map_task_tree (struct snd_cs46xx *chip, char * name, u32 dest, u32 size) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_task_descriptor * desc = NULL; + + if (ins->ntask == DSP_MAX_TASK_DESC - 1) { + dev_err(chip->card->dev, + "dsp_spos: got no place for other TASK\n"); + return NULL; + } + + if (name) + strcpy(ins->tasks[ins->ntask].task_name, name); + else + strcpy(ins->tasks[ins->ntask].task_name, "(NULL)"); + ins->tasks[ins->ntask].address = dest; + ins->tasks[ins->ntask].size = size; + + /* quick find in list */ + ins->tasks[ins->ntask].index = ins->ntask; + desc = (ins->tasks + ins->ntask); + ins->ntask++; + + if (name) + add_symbol (chip,name,dest,SYMBOL_PARAMETER); + return desc; +} + +#define SCB_BYTES (0x10 * 4) + +struct dsp_scb_descriptor * +cs46xx_dsp_create_scb (struct snd_cs46xx *chip, char * name, u32 * scb_data, u32 dest) +{ + struct dsp_scb_descriptor * desc; + +#ifdef CONFIG_PM_SLEEP + /* copy the data for resume */ + scb_data = kmemdup(scb_data, SCB_BYTES, GFP_KERNEL); + if (!scb_data) + return NULL; +#endif + + desc = _map_scb (chip,name,dest); + if (desc) { + desc->data = scb_data; + _dsp_create_scb(chip,scb_data,dest); + } else { + dev_err(chip->card->dev, "dsp_spos: failed to map SCB\n"); +#ifdef CONFIG_PM_SLEEP + kfree(scb_data); +#endif + } + + return desc; +} + + +static struct dsp_task_descriptor * +cs46xx_dsp_create_task_tree (struct snd_cs46xx *chip, char * name, u32 * task_data, + u32 dest, int size) +{ + struct dsp_task_descriptor * desc; + + desc = _map_task_tree (chip,name,dest,size); + if (desc) { + desc->data = task_data; + _dsp_create_task_tree(chip,task_data,dest,size); + } else { + dev_err(chip->card->dev, "dsp_spos: failed to map TASK\n"); + } + + return desc; +} + +int cs46xx_dsp_scb_and_task_init (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_symbol_entry * fg_task_tree_header_code; + struct dsp_symbol_entry * task_tree_header_code; + struct dsp_symbol_entry * task_tree_thread; + struct dsp_symbol_entry * null_algorithm; + struct dsp_symbol_entry * magic_snoop_task; + + struct dsp_scb_descriptor * timing_master_scb; + struct dsp_scb_descriptor * codec_out_scb; + struct dsp_scb_descriptor * codec_in_scb; + struct dsp_scb_descriptor * src_task_scb; + struct dsp_scb_descriptor * master_mix_scb; + struct dsp_scb_descriptor * rear_mix_scb; + struct dsp_scb_descriptor * record_mix_scb; + struct dsp_scb_descriptor * write_back_scb; + struct dsp_scb_descriptor * vari_decimate_scb; + struct dsp_scb_descriptor * rear_codec_out_scb; + struct dsp_scb_descriptor * clfe_codec_out_scb; + struct dsp_scb_descriptor * magic_snoop_scb; + + int fifo_addr, fifo_span, valid_slots; + + static const struct dsp_spos_control_block sposcb = { + /* 0 */ HFG_TREE_SCB,HFG_STACK, + /* 1 */ SPOSCB_ADDR,BG_TREE_SCB_ADDR, + /* 2 */ DSP_SPOS_DC,0, + /* 3 */ DSP_SPOS_DC,DSP_SPOS_DC, + /* 4 */ 0,0, + /* 5 */ DSP_SPOS_UU,0, + /* 6 */ FG_TASK_HEADER_ADDR,0, + /* 7 */ 0,0, + /* 8 */ DSP_SPOS_UU,DSP_SPOS_DC, + /* 9 */ 0, + /* A */ 0,HFG_FIRST_EXECUTE_MODE, + /* B */ DSP_SPOS_UU,DSP_SPOS_UU, + /* C */ DSP_SPOS_DC_DC, + /* D */ DSP_SPOS_DC_DC, + /* E */ DSP_SPOS_DC_DC, + /* F */ DSP_SPOS_DC_DC + }; + + cs46xx_dsp_create_task_tree(chip, "sposCB", (u32 *)&sposcb, SPOSCB_ADDR, 0x10); + + null_algorithm = cs46xx_dsp_lookup_symbol(chip, "NULLALGORITHM", SYMBOL_CODE); + if (null_algorithm == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol NULLALGORITHM not found\n"); + return -EIO; + } + + fg_task_tree_header_code = cs46xx_dsp_lookup_symbol(chip, "FGTASKTREEHEADERCODE", SYMBOL_CODE); + if (fg_task_tree_header_code == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol FGTASKTREEHEADERCODE not found\n"); + return -EIO; + } + + task_tree_header_code = cs46xx_dsp_lookup_symbol(chip, "TASKTREEHEADERCODE", SYMBOL_CODE); + if (task_tree_header_code == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol TASKTREEHEADERCODE not found\n"); + return -EIO; + } + + task_tree_thread = cs46xx_dsp_lookup_symbol(chip, "TASKTREETHREAD", SYMBOL_CODE); + if (task_tree_thread == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol TASKTREETHREAD not found\n"); + return -EIO; + } + + magic_snoop_task = cs46xx_dsp_lookup_symbol(chip, "MAGICSNOOPTASK", SYMBOL_CODE); + if (magic_snoop_task == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol MAGICSNOOPTASK not found\n"); + return -EIO; + } + + { + /* create the null SCB */ + static struct dsp_generic_scb null_scb = { + { 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + NULL_SCB_ADDR, NULL_SCB_ADDR, + 0, 0, 0, 0, 0, + { + 0,0, + 0,0, + } + }; + + null_scb.entry_point = null_algorithm->address; + ins->the_null_scb = cs46xx_dsp_create_scb(chip, "nullSCB", (u32 *)&null_scb, NULL_SCB_ADDR); + ins->the_null_scb->task_entry = null_algorithm; + ins->the_null_scb->sub_list_ptr = ins->the_null_scb; + ins->the_null_scb->next_scb_ptr = ins->the_null_scb; + ins->the_null_scb->parent_scb_ptr = NULL; + cs46xx_dsp_proc_register_scb_desc (chip,ins->the_null_scb); + } + + { + /* setup foreground task tree */ + static struct dsp_task_tree_control_block fg_task_tree_hdr = { + { FG_TASK_HEADER_ADDR | (DSP_SPOS_DC << 0x10), + DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + 0x0000,DSP_SPOS_DC, + DSP_SPOS_DC, DSP_SPOS_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC,DSP_SPOS_DC }, + + { + BG_TREE_SCB_ADDR,TIMINGMASTER_SCB_ADDR, + 0, + FG_TASK_HEADER_ADDR + TCBData, + }, + + { + 4,0, + 1,0, + 2,SPOSCB_ADDR + HFGFlags, + 0,0, + FG_TASK_HEADER_ADDR + TCBContextBlk,FG_STACK + }, + + { + DSP_SPOS_DC,0, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DCDC, + DSP_SPOS_UU,1, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC + }, + { + FG_INTERVAL_TIMER_PERIOD,DSP_SPOS_UU, + 0,0 + } + }; + + fg_task_tree_hdr.links.entry_point = fg_task_tree_header_code->address; + fg_task_tree_hdr.context_blk.stack0 = task_tree_thread->address; + cs46xx_dsp_create_task_tree(chip,"FGtaskTreeHdr",(u32 *)&fg_task_tree_hdr,FG_TASK_HEADER_ADDR,0x35); + } + + + { + /* setup foreground task tree */ + static struct dsp_task_tree_control_block bg_task_tree_hdr = { + { DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC, DSP_SPOS_DC, + DSP_SPOS_DC, DSP_SPOS_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC,DSP_SPOS_DC }, + + { + NULL_SCB_ADDR,NULL_SCB_ADDR, /* Set up the background to do nothing */ + 0, + BG_TREE_SCB_ADDR + TCBData, + }, + + { + 9999,0, + 0,1, + 0,SPOSCB_ADDR + HFGFlags, + 0,0, + BG_TREE_SCB_ADDR + TCBContextBlk,BG_STACK + }, + + { + DSP_SPOS_DC,0, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DCDC, + DSP_SPOS_UU,1, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC + }, + { + BG_INTERVAL_TIMER_PERIOD,DSP_SPOS_UU, + 0,0 + } + }; + + bg_task_tree_hdr.links.entry_point = task_tree_header_code->address; + bg_task_tree_hdr.context_blk.stack0 = task_tree_thread->address; + cs46xx_dsp_create_task_tree(chip,"BGtaskTreeHdr",(u32 *)&bg_task_tree_hdr,BG_TREE_SCB_ADDR,0x35); + } + + /* create timing master SCB */ + timing_master_scb = cs46xx_dsp_create_timing_master_scb(chip); + + /* create the CODEC output task */ + codec_out_scb = cs46xx_dsp_create_codec_out_scb(chip,"CodecOutSCB_I",0x0010,0x0000, + MASTERMIX_SCB_ADDR, + CODECOUT_SCB_ADDR,timing_master_scb, + SCB_ON_PARENT_SUBLIST_SCB); + + if (!codec_out_scb) goto _fail_end; + /* create the master mix SCB */ + master_mix_scb = cs46xx_dsp_create_mix_only_scb(chip,"MasterMixSCB", + MIX_SAMPLE_BUF1,MASTERMIX_SCB_ADDR, + codec_out_scb, + SCB_ON_PARENT_SUBLIST_SCB); + ins->master_mix_scb = master_mix_scb; + + if (!master_mix_scb) goto _fail_end; + + /* create codec in */ + codec_in_scb = cs46xx_dsp_create_codec_in_scb(chip,"CodecInSCB",0x0010,0x00A0, + CODEC_INPUT_BUF1, + CODECIN_SCB_ADDR,codec_out_scb, + SCB_ON_PARENT_NEXT_SCB); + if (!codec_in_scb) goto _fail_end; + ins->codec_in_scb = codec_in_scb; + + /* create write back scb */ + write_back_scb = cs46xx_dsp_create_mix_to_ostream_scb(chip,"WriteBackSCB", + WRITE_BACK_BUF1,WRITE_BACK_SPB, + WRITEBACK_SCB_ADDR, + timing_master_scb, + SCB_ON_PARENT_NEXT_SCB); + if (!write_back_scb) goto _fail_end; + + { + static struct dsp_mix2_ostream_spb mix2_ostream_spb = { + 0x00020000, + 0x0000ffff + }; + + if (!cs46xx_dsp_create_task_tree(chip, NULL, + (u32 *)&mix2_ostream_spb, + WRITE_BACK_SPB, 2)) + goto _fail_end; + } + + /* input sample converter */ + vari_decimate_scb = cs46xx_dsp_create_vari_decimate_scb(chip,"VariDecimateSCB", + VARI_DECIMATE_BUF0, + VARI_DECIMATE_BUF1, + VARIDECIMATE_SCB_ADDR, + write_back_scb, + SCB_ON_PARENT_SUBLIST_SCB); + if (!vari_decimate_scb) goto _fail_end; + + /* create the record mixer SCB */ + record_mix_scb = cs46xx_dsp_create_mix_only_scb(chip,"RecordMixerSCB", + MIX_SAMPLE_BUF2, + RECORD_MIXER_SCB_ADDR, + vari_decimate_scb, + SCB_ON_PARENT_SUBLIST_SCB); + ins->record_mixer_scb = record_mix_scb; + + if (!record_mix_scb) goto _fail_end; + + valid_slots = snd_cs46xx_peekBA0(chip, BA0_ACOSV); + + if (snd_BUG_ON(chip->nr_ac97_codecs != 1 && chip->nr_ac97_codecs != 2)) + goto _fail_end; + + if (chip->nr_ac97_codecs == 1) { + /* output on slot 5 and 11 + on primary CODEC */ + fifo_addr = 0x20; + fifo_span = 0x60; + + /* enable slot 5 and 11 */ + valid_slots |= ACOSV_SLV5 | ACOSV_SLV11; + } else { + /* output on slot 7 and 8 + on secondary CODEC */ + fifo_addr = 0x40; + fifo_span = 0x10; + + /* enable slot 7 and 8 */ + valid_slots |= ACOSV_SLV7 | ACOSV_SLV8; + } + /* create CODEC tasklet for rear speakers output*/ + rear_codec_out_scb = cs46xx_dsp_create_codec_out_scb(chip,"CodecOutSCB_Rear",fifo_span,fifo_addr, + REAR_MIXER_SCB_ADDR, + REAR_CODECOUT_SCB_ADDR,codec_in_scb, + SCB_ON_PARENT_NEXT_SCB); + if (!rear_codec_out_scb) goto _fail_end; + + + /* create the rear PCM channel mixer SCB */ + rear_mix_scb = cs46xx_dsp_create_mix_only_scb(chip,"RearMixerSCB", + MIX_SAMPLE_BUF3, + REAR_MIXER_SCB_ADDR, + rear_codec_out_scb, + SCB_ON_PARENT_SUBLIST_SCB); + ins->rear_mix_scb = rear_mix_scb; + if (!rear_mix_scb) goto _fail_end; + + if (chip->nr_ac97_codecs == 2) { + /* create CODEC tasklet for rear Center/LFE output + slot 6 and 9 on secondary CODEC */ + clfe_codec_out_scb = cs46xx_dsp_create_codec_out_scb(chip,"CodecOutSCB_CLFE",0x0030,0x0030, + CLFE_MIXER_SCB_ADDR, + CLFE_CODEC_SCB_ADDR, + rear_codec_out_scb, + SCB_ON_PARENT_NEXT_SCB); + if (!clfe_codec_out_scb) goto _fail_end; + + + /* create the rear PCM channel mixer SCB */ + ins->center_lfe_mix_scb = cs46xx_dsp_create_mix_only_scb(chip,"CLFEMixerSCB", + MIX_SAMPLE_BUF4, + CLFE_MIXER_SCB_ADDR, + clfe_codec_out_scb, + SCB_ON_PARENT_SUBLIST_SCB); + if (!ins->center_lfe_mix_scb) goto _fail_end; + + /* enable slot 6 and 9 */ + valid_slots |= ACOSV_SLV6 | ACOSV_SLV9; + } else { + clfe_codec_out_scb = rear_codec_out_scb; + ins->center_lfe_mix_scb = rear_mix_scb; + } + + /* enable slots depending on CODEC configuration */ + snd_cs46xx_pokeBA0(chip, BA0_ACOSV, valid_slots); + + /* the magic snooper */ + magic_snoop_scb = cs46xx_dsp_create_magic_snoop_scb (chip,"MagicSnoopSCB_I",OUTPUTSNOOP_SCB_ADDR, + OUTPUT_SNOOP_BUFFER, + codec_out_scb, + clfe_codec_out_scb, + SCB_ON_PARENT_NEXT_SCB); + + + if (!magic_snoop_scb) goto _fail_end; + ins->ref_snoop_scb = magic_snoop_scb; + + /* SP IO access */ + if (!cs46xx_dsp_create_spio_write_scb(chip,"SPIOWriteSCB",SPIOWRITE_SCB_ADDR, + magic_snoop_scb, + SCB_ON_PARENT_NEXT_SCB)) + goto _fail_end; + + /* SPDIF input sampel rate converter */ + src_task_scb = cs46xx_dsp_create_src_task_scb(chip,"SrcTaskSCB_SPDIFI", + ins->spdif_in_sample_rate, + SRC_OUTPUT_BUF1, + SRC_DELAY_BUF1,SRCTASK_SCB_ADDR, + master_mix_scb, + SCB_ON_PARENT_SUBLIST_SCB,1); + + if (!src_task_scb) goto _fail_end; + cs46xx_src_unlink(chip,src_task_scb); + + /* NOTE: when we now how to detect the SPDIF input + sample rate we will use this SRC to adjust it */ + ins->spdif_in_src = src_task_scb; + + cs46xx_dsp_async_init(chip,timing_master_scb); + return 0; + + _fail_end: + dev_err(chip->card->dev, "dsp_spos: failed to setup SCB's in DSP\n"); + return -EINVAL; +} + +static int cs46xx_dsp_async_init (struct snd_cs46xx *chip, + struct dsp_scb_descriptor * fg_entry) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_symbol_entry * s16_async_codec_input_task; + struct dsp_symbol_entry * spdifo_task; + struct dsp_symbol_entry * spdifi_task; + struct dsp_scb_descriptor * spdifi_scb_desc, * spdifo_scb_desc, * async_codec_scb_desc; + + s16_async_codec_input_task = cs46xx_dsp_lookup_symbol(chip, "S16_ASYNCCODECINPUTTASK", SYMBOL_CODE); + if (s16_async_codec_input_task == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol S16_ASYNCCODECINPUTTASK not found\n"); + return -EIO; + } + spdifo_task = cs46xx_dsp_lookup_symbol(chip, "SPDIFOTASK", SYMBOL_CODE); + if (spdifo_task == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol SPDIFOTASK not found\n"); + return -EIO; + } + + spdifi_task = cs46xx_dsp_lookup_symbol(chip, "SPDIFITASK", SYMBOL_CODE); + if (spdifi_task == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol SPDIFITASK not found\n"); + return -EIO; + } + + { + /* 0xBC0 */ + struct dsp_spdifoscb spdifo_scb = { + /* 0 */ DSP_SPOS_UUUU, + { + /* 1 */ 0xb0, + /* 2 */ 0, + /* 3 */ 0, + /* 4 */ 0, + }, + /* NOTE: the SPDIF output task read samples in mono + format, the AsynchFGTxSCB task writes to buffer + in stereo format + */ + /* 5 */ RSCONFIG_SAMPLE_16MONO + RSCONFIG_MODULO_256, + /* 6 */ ( SPDIFO_IP_OUTPUT_BUFFER1 << 0x10 ) | 0xFFFC, + /* 7 */ 0,0, + /* 8 */ 0, + /* 9 */ FG_TASK_HEADER_ADDR, NULL_SCB_ADDR, + /* A */ spdifo_task->address, + SPDIFO_SCB_INST + SPDIFOFIFOPointer, + { + /* B */ 0x0040, /*DSP_SPOS_UUUU,*/ + /* C */ 0x20ff, /*DSP_SPOS_UUUU,*/ + }, + /* D */ 0x804c,0, /* SPDIFOFIFOPointer:SPDIFOStatRegAddr; */ + /* E */ 0x0108,0x0001, /* SPDIFOStMoFormat:SPDIFOFIFOBaseAddr; */ + /* F */ DSP_SPOS_UUUU /* SPDIFOFree; */ + }; + + /* 0xBB0 */ + struct dsp_spdifiscb spdifi_scb = { + /* 0 */ DSP_SPOS_UULO,DSP_SPOS_UUHI, + /* 1 */ 0, + /* 2 */ 0, + /* 3 */ 1,4000, /* SPDIFICountLimit SPDIFICount */ + /* 4 */ DSP_SPOS_UUUU, /* SPDIFIStatusData */ + /* 5 */ 0,DSP_SPOS_UUHI, /* StatusData, Free4 */ + /* 6 */ DSP_SPOS_UUUU, /* Free3 */ + /* 7 */ DSP_SPOS_UU,DSP_SPOS_DC, /* Free2 BitCount*/ + /* 8 */ DSP_SPOS_UUUU, /* TempStatus */ + /* 9 */ SPDIFO_SCB_INST, NULL_SCB_ADDR, + /* A */ spdifi_task->address, + SPDIFI_SCB_INST + SPDIFIFIFOPointer, + /* NOTE: The SPDIF input task write the sample in mono + format from the HW FIFO, the AsynchFGRxSCB task reads + them in stereo + */ + /* B */ RSCONFIG_SAMPLE_16MONO + RSCONFIG_MODULO_128, + /* C */ (SPDIFI_IP_OUTPUT_BUFFER1 << 0x10) | 0xFFFC, + /* D */ 0x8048,0, + /* E */ 0x01f0,0x0001, + /* F */ DSP_SPOS_UUUU /* SPDIN_STATUS monitor */ + }; + + /* 0xBA0 */ + struct dsp_async_codec_input_scb async_codec_input_scb = { + /* 0 */ DSP_SPOS_UUUU, + /* 1 */ 0, + /* 2 */ 0, + /* 3 */ 1,4000, + /* 4 */ 0x0118,0x0001, + /* 5 */ RSCONFIG_SAMPLE_16MONO + RSCONFIG_MODULO_64, + /* 6 */ (ASYNC_IP_OUTPUT_BUFFER1 << 0x10) | 0xFFFC, + /* 7 */ DSP_SPOS_UU,0x3, + /* 8 */ DSP_SPOS_UUUU, + /* 9 */ SPDIFI_SCB_INST,NULL_SCB_ADDR, + /* A */ s16_async_codec_input_task->address, + HFG_TREE_SCB + AsyncCIOFIFOPointer, + + /* B */ RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_64, + /* C */ (ASYNC_IP_OUTPUT_BUFFER1 << 0x10), /*(ASYNC_IP_OUTPUT_BUFFER1 << 0x10) | 0xFFFC,*/ + +#ifdef UseASER1Input + /* short AsyncCIFIFOPointer:AsyncCIStatRegAddr; + Init. 0000:8042: for ASER1 + 0000:8044: for ASER2 */ + /* D */ 0x8042,0, + + /* short AsyncCIStMoFormat:AsyncCIFIFOBaseAddr; + Init 1 stero:8050 ASER1 + Init 0 mono:8070 ASER2 + Init 1 Stereo : 0100 ASER1 (Set by script) */ + /* E */ 0x0100,0x0001, + +#endif + +#ifdef UseASER2Input + /* short AsyncCIFIFOPointer:AsyncCIStatRegAddr; + Init. 0000:8042: for ASER1 + 0000:8044: for ASER2 */ + /* D */ 0x8044,0, + + /* short AsyncCIStMoFormat:AsyncCIFIFOBaseAddr; + Init 1 stero:8050 ASER1 + Init 0 mono:8070 ASER2 + Init 1 Stereo : 0100 ASER1 (Set by script) */ + /* E */ 0x0110,0x0001, + +#endif + + /* short AsyncCIOutputBufModulo:AsyncCIFree; + AsyncCIOutputBufModulo: The modulo size for + the output buffer of this task */ + /* F */ 0, /* DSP_SPOS_UUUU */ + }; + + spdifo_scb_desc = cs46xx_dsp_create_scb(chip,"SPDIFOSCB",(u32 *)&spdifo_scb,SPDIFO_SCB_INST); + + if (snd_BUG_ON(!spdifo_scb_desc)) + return -EIO; + spdifi_scb_desc = cs46xx_dsp_create_scb(chip,"SPDIFISCB",(u32 *)&spdifi_scb,SPDIFI_SCB_INST); + if (snd_BUG_ON(!spdifi_scb_desc)) + return -EIO; + async_codec_scb_desc = cs46xx_dsp_create_scb(chip,"AsynCodecInputSCB",(u32 *)&async_codec_input_scb, HFG_TREE_SCB); + if (snd_BUG_ON(!async_codec_scb_desc)) + return -EIO; + + async_codec_scb_desc->parent_scb_ptr = NULL; + async_codec_scb_desc->next_scb_ptr = spdifi_scb_desc; + async_codec_scb_desc->sub_list_ptr = ins->the_null_scb; + async_codec_scb_desc->task_entry = s16_async_codec_input_task; + + spdifi_scb_desc->parent_scb_ptr = async_codec_scb_desc; + spdifi_scb_desc->next_scb_ptr = spdifo_scb_desc; + spdifi_scb_desc->sub_list_ptr = ins->the_null_scb; + spdifi_scb_desc->task_entry = spdifi_task; + + spdifo_scb_desc->parent_scb_ptr = spdifi_scb_desc; + spdifo_scb_desc->next_scb_ptr = fg_entry; + spdifo_scb_desc->sub_list_ptr = ins->the_null_scb; + spdifo_scb_desc->task_entry = spdifo_task; + + /* this one is faked, as the parnet of SPDIFO task + is the FG task tree */ + fg_entry->parent_scb_ptr = spdifo_scb_desc; + + /* for proc fs */ + cs46xx_dsp_proc_register_scb_desc (chip,spdifo_scb_desc); + cs46xx_dsp_proc_register_scb_desc (chip,spdifi_scb_desc); + cs46xx_dsp_proc_register_scb_desc (chip,async_codec_scb_desc); + + /* Async MASTER ENABLE, affects both SPDIF input and output */ + snd_cs46xx_pokeBA0(chip, BA0_ASER_MASTER, 0x1 ); + } + + return 0; +} + +static void cs46xx_dsp_disable_spdif_hw (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + /* set SPDIF output FIFO slot */ + snd_cs46xx_pokeBA0(chip, BA0_ASER_FADDR, 0); + + /* SPDIF output MASTER ENABLE */ + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CONTROL, 0); + + /* right and left validate bit */ + /*cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, ins->spdif_csuv_default);*/ + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, 0x0); + + /* clear fifo pointer */ + cs46xx_poke_via_dsp (chip,SP_SPDIN_FIFOPTR, 0x0); + + /* monitor state */ + ins->spdif_status_out &= ~DSP_SPDIF_STATUS_HW_ENABLED; +} + +int cs46xx_dsp_enable_spdif_hw (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + /* if hw-ctrl already enabled, turn off to reset logic ... */ + cs46xx_dsp_disable_spdif_hw (chip); + udelay(50); + + /* set SPDIF output FIFO slot */ + snd_cs46xx_pokeBA0(chip, BA0_ASER_FADDR, ( 0x8000 | ((SP_SPDOUT_FIFO >> 4) << 4) )); + + /* SPDIF output MASTER ENABLE */ + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CONTROL, 0x80000000); + + /* right and left validate bit */ + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, ins->spdif_csuv_default); + + /* monitor state */ + ins->spdif_status_out |= DSP_SPDIF_STATUS_HW_ENABLED; + + return 0; +} + +int cs46xx_dsp_enable_spdif_in (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + /* turn on amplifier */ + chip->active_ctrl(chip, 1); + chip->amplifier_ctrl(chip, 1); + + if (snd_BUG_ON(ins->asynch_rx_scb)) + return -EINVAL; + if (snd_BUG_ON(!ins->spdif_in_src)) + return -EINVAL; + + mutex_lock(&chip->spos_mutex); + + if ( ! (ins->spdif_status_out & DSP_SPDIF_STATUS_INPUT_CTRL_ENABLED) ) { + /* time countdown enable */ + cs46xx_poke_via_dsp (chip,SP_ASER_COUNTDOWN, 0x80000005); + /* NOTE: 80000005 value is just magic. With all values + that I've tested this one seem to give the best result. + Got no explication why. (Benny) */ + + /* SPDIF input MASTER ENABLE */ + cs46xx_poke_via_dsp (chip,SP_SPDIN_CONTROL, 0x800003ff); + + ins->spdif_status_out |= DSP_SPDIF_STATUS_INPUT_CTRL_ENABLED; + } + + /* create and start the asynchronous receiver SCB */ + ins->asynch_rx_scb = cs46xx_dsp_create_asynch_fg_rx_scb(chip,"AsynchFGRxSCB", + ASYNCRX_SCB_ADDR, + SPDIFI_SCB_INST, + SPDIFI_IP_OUTPUT_BUFFER1, + ins->spdif_in_src, + SCB_ON_PARENT_SUBLIST_SCB); + + spin_lock_irq(&chip->reg_lock); + + /* reset SPDIF input sample buffer pointer */ + /*snd_cs46xx_poke (chip, (SPDIFI_SCB_INST + 0x0c) << 2, + (SPDIFI_IP_OUTPUT_BUFFER1 << 0x10) | 0xFFFC);*/ + + /* reset FIFO ptr */ + /*cs46xx_poke_via_dsp (chip,SP_SPDIN_FIFOPTR, 0x0);*/ + cs46xx_src_link(chip,ins->spdif_in_src); + + /* unmute SRC volume */ + cs46xx_dsp_scb_set_volume (chip,ins->spdif_in_src,0x7fff,0x7fff); + + spin_unlock_irq(&chip->reg_lock); + + /* set SPDIF input sample rate and unmute + NOTE: only 48khz support for SPDIF input this time */ + /* cs46xx_dsp_set_src_sample_rate(chip,ins->spdif_in_src,48000); */ + + /* monitor state */ + ins->spdif_status_in = 1; + mutex_unlock(&chip->spos_mutex); + + return 0; +} + +int cs46xx_dsp_disable_spdif_in (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(!ins->asynch_rx_scb)) + return -EINVAL; + if (snd_BUG_ON(!ins->spdif_in_src)) + return -EINVAL; + + mutex_lock(&chip->spos_mutex); + + /* Remove the asynchronous receiver SCB */ + cs46xx_dsp_remove_scb (chip,ins->asynch_rx_scb); + ins->asynch_rx_scb = NULL; + + cs46xx_src_unlink(chip,ins->spdif_in_src); + + /* monitor state */ + ins->spdif_status_in = 0; + mutex_unlock(&chip->spos_mutex); + + /* restore amplifier */ + chip->active_ctrl(chip, -1); + chip->amplifier_ctrl(chip, -1); + + return 0; +} + +int cs46xx_dsp_enable_pcm_capture (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(ins->pcm_input)) + return -EINVAL; + if (snd_BUG_ON(!ins->ref_snoop_scb)) + return -EINVAL; + + mutex_lock(&chip->spos_mutex); + ins->pcm_input = cs46xx_add_record_source(chip,ins->ref_snoop_scb,PCMSERIALIN_PCM_SCB_ADDR, + "PCMSerialInput_Wave"); + mutex_unlock(&chip->spos_mutex); + + return 0; +} + +int cs46xx_dsp_disable_pcm_capture (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(!ins->pcm_input)) + return -EINVAL; + + mutex_lock(&chip->spos_mutex); + cs46xx_dsp_remove_scb (chip,ins->pcm_input); + ins->pcm_input = NULL; + mutex_unlock(&chip->spos_mutex); + + return 0; +} + +int cs46xx_dsp_enable_adc_capture (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(ins->adc_input)) + return -EINVAL; + if (snd_BUG_ON(!ins->codec_in_scb)) + return -EINVAL; + + mutex_lock(&chip->spos_mutex); + ins->adc_input = cs46xx_add_record_source(chip,ins->codec_in_scb,PCMSERIALIN_SCB_ADDR, + "PCMSerialInput_ADC"); + mutex_unlock(&chip->spos_mutex); + + return 0; +} + +int cs46xx_dsp_disable_adc_capture (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(!ins->adc_input)) + return -EINVAL; + + mutex_lock(&chip->spos_mutex); + cs46xx_dsp_remove_scb (chip,ins->adc_input); + ins->adc_input = NULL; + mutex_unlock(&chip->spos_mutex); + + return 0; +} + +int cs46xx_poke_via_dsp (struct snd_cs46xx *chip, u32 address, u32 data) +{ + u32 temp; + int i; + + /* santiy check the parameters. (These numbers are not 100% correct. They are + a rough guess from looking at the controller spec.) */ + if (address < 0x8000 || address >= 0x9000) + return -EINVAL; + + /* initialize the SP_IO_WRITE SCB with the data. */ + temp = ( address << 16 ) | ( address & 0x0000FFFF); /* offset 0 <-- address2 : address1 */ + + snd_cs46xx_poke(chip,( SPIOWRITE_SCB_ADDR << 2), temp); + snd_cs46xx_poke(chip,((SPIOWRITE_SCB_ADDR + 1) << 2), data); /* offset 1 <-- data1 */ + snd_cs46xx_poke(chip,((SPIOWRITE_SCB_ADDR + 2) << 2), data); /* offset 1 <-- data2 */ + + /* Poke this location to tell the task to start */ + snd_cs46xx_poke(chip,((SPIOWRITE_SCB_ADDR + 6) << 2), SPIOWRITE_SCB_ADDR << 0x10); + + /* Verify that the task ran */ + for (i=0; i<25; i++) { + udelay(125); + + temp = snd_cs46xx_peek(chip,((SPIOWRITE_SCB_ADDR + 6) << 2)); + if (temp == 0x00000000) + break; + } + + if (i == 25) { + dev_err(chip->card->dev, + "dsp_spos: SPIOWriteTask not responding\n"); + return -EBUSY; + } + + return 0; +} + +int cs46xx_dsp_set_dac_volume (struct snd_cs46xx * chip, u16 left, u16 right) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * scb; + + mutex_lock(&chip->spos_mutex); + + /* main output */ + scb = ins->master_mix_scb->sub_list_ptr; + while (scb != ins->the_null_scb) { + cs46xx_dsp_scb_set_volume (chip,scb,left,right); + scb = scb->next_scb_ptr; + } + + /* rear output */ + scb = ins->rear_mix_scb->sub_list_ptr; + while (scb != ins->the_null_scb) { + cs46xx_dsp_scb_set_volume (chip,scb,left,right); + scb = scb->next_scb_ptr; + } + + ins->dac_volume_left = left; + ins->dac_volume_right = right; + + mutex_unlock(&chip->spos_mutex); + + return 0; +} + +int cs46xx_dsp_set_iec958_volume (struct snd_cs46xx * chip, u16 left, u16 right) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + mutex_lock(&chip->spos_mutex); + + if (ins->asynch_rx_scb != NULL) + cs46xx_dsp_scb_set_volume (chip,ins->asynch_rx_scb, + left,right); + + ins->spdif_input_volume_left = left; + ins->spdif_input_volume_right = right; + + mutex_unlock(&chip->spos_mutex); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +int cs46xx_dsp_resume(struct snd_cs46xx * chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i, err; + + /* clear parameter, sample and code areas */ + snd_cs46xx_clear_BA1(chip, DSP_PARAMETER_BYTE_OFFSET, + DSP_PARAMETER_BYTE_SIZE); + snd_cs46xx_clear_BA1(chip, DSP_SAMPLE_BYTE_OFFSET, + DSP_SAMPLE_BYTE_SIZE); + snd_cs46xx_clear_BA1(chip, DSP_CODE_BYTE_OFFSET, DSP_CODE_BYTE_SIZE); + + for (i = 0; i < ins->nmodules; i++) { + struct dsp_module_desc *module = &ins->modules[i]; + struct dsp_segment_desc *seg; + u32 doffset, dsize; + + seg = get_segment_desc(module, SEGTYPE_SP_PARAMETER); + err = dsp_load_parameter(chip, seg); + if (err < 0) + return err; + + seg = get_segment_desc(module, SEGTYPE_SP_SAMPLE); + err = dsp_load_sample(chip, seg); + if (err < 0) + return err; + + seg = get_segment_desc(module, SEGTYPE_SP_PROGRAM); + if (!seg) + continue; + + doffset = seg->offset * 4 + module->load_address * 4 + + DSP_CODE_BYTE_OFFSET; + dsize = seg->size * 4; + err = snd_cs46xx_download(chip, + ins->code.data + module->load_address, + doffset, dsize); + if (err < 0) + return err; + } + + for (i = 0; i < ins->ntask; i++) { + struct dsp_task_descriptor *t = &ins->tasks[i]; + _dsp_create_task_tree(chip, t->data, t->address, t->size); + } + + for (i = 0; i < ins->nscb; i++) { + struct dsp_scb_descriptor *s = &ins->scbs[i]; + if (s->deleted) + continue; + _dsp_create_scb(chip, s->data, s->address); + } + for (i = 0; i < ins->nscb; i++) { + struct dsp_scb_descriptor *s = &ins->scbs[i]; + if (s->deleted) + continue; + if (s->updated) + cs46xx_dsp_spos_update_scb(chip, s); + if (s->volume_set) + cs46xx_dsp_scb_set_volume(chip, s, + s->volume[0], s->volume[1]); + } + if (ins->spdif_status_out & DSP_SPDIF_STATUS_HW_ENABLED) { + cs46xx_dsp_enable_spdif_hw(chip); + snd_cs46xx_poke(chip, (ins->ref_snoop_scb->address + 2) << 2, + (OUTPUT_SNOOP_BUFFER + 0x10) << 0x10); + if (ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN) + cs46xx_poke_via_dsp(chip, SP_SPDOUT_CSUV, + ins->spdif_csuv_stream); + } + if (chip->dsp_spos_instance->spdif_status_in) { + cs46xx_poke_via_dsp(chip, SP_ASER_COUNTDOWN, 0x80000005); + cs46xx_poke_via_dsp(chip, SP_SPDIN_CONTROL, 0x800003ff); + } + return 0; +} +#endif diff --git a/sound/pci/cs46xx/dsp_spos.h b/sound/pci/cs46xx/dsp_spos.h new file mode 100644 index 000000000..a4853c748 --- /dev/null +++ b/sound/pci/cs46xx/dsp_spos.h @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +/* + * 2002-07 Benny Sjostrand benny@hostmobility.com + */ + +#ifdef CONFIG_SND_CS46XX_NEW_DSP /* hack ... */ +#ifndef __DSP_SPOS_H__ +#define __DSP_SPOS_H__ + +#define DSP_MAX_SYMBOLS 1024 +#define DSP_MAX_MODULES 64 + +#define DSP_CODE_BYTE_SIZE 0x00007000UL +#define DSP_PARAMETER_BYTE_SIZE 0x00003000UL +#define DSP_SAMPLE_BYTE_SIZE 0x00003800UL +#define DSP_PARAMETER_BYTE_OFFSET 0x00000000UL +#define DSP_SAMPLE_BYTE_OFFSET 0x00010000UL +#define DSP_CODE_BYTE_OFFSET 0x00020000UL + +#define WIDE_INSTR_MASK 0x0040 +#define WIDE_LADD_INSTR_MASK 0x0380 + +/* this instruction types + needs to be reallocated when load + code into DSP */ +enum wide_opcode { + WIDE_FOR_BEGIN_LOOP = 0x20, + WIDE_FOR_BEGIN_LOOP2, + + WIDE_COND_GOTO_ADDR = 0x30, + WIDE_COND_GOTO_CALL, + + WIDE_TBEQ_COND_GOTO_ADDR = 0x70, + WIDE_TBEQ_COND_CALL_ADDR, + WIDE_TBEQ_NCOND_GOTO_ADDR, + WIDE_TBEQ_NCOND_CALL_ADDR, + WIDE_TBEQ_COND_GOTO1_ADDR, + WIDE_TBEQ_COND_CALL1_ADDR, + WIDE_TBEQ_NCOND_GOTOI_ADDR, + WIDE_TBEQ_NCOND_CALL1_ADDR, +}; + +/* SAMPLE segment */ +#define VARI_DECIMATE_BUF1 0x0000 +#define WRITE_BACK_BUF1 0x0400 +#define CODEC_INPUT_BUF1 0x0500 +#define PCM_READER_BUF1 0x0600 +#define SRC_DELAY_BUF1 0x0680 +#define VARI_DECIMATE_BUF0 0x0780 +#define SRC_OUTPUT_BUF1 0x07A0 +#define ASYNC_IP_OUTPUT_BUFFER1 0x0A00 +#define OUTPUT_SNOOP_BUFFER 0x0B00 +#define SPDIFI_IP_OUTPUT_BUFFER1 0x0E00 +#define SPDIFO_IP_OUTPUT_BUFFER1 0x1000 +#define MIX_SAMPLE_BUF1 0x1400 +#define MIX_SAMPLE_BUF2 0x2E80 +#define MIX_SAMPLE_BUF3 0x2F00 +#define MIX_SAMPLE_BUF4 0x2F80 +#define MIX_SAMPLE_BUF5 0x3000 + +/* Task stack address */ +#define HFG_STACK 0x066A +#define FG_STACK 0x066E +#define BG_STACK 0x068E + +/* SCB's addresses */ +#define SPOSCB_ADDR 0x070 +#define BG_TREE_SCB_ADDR 0x635 +#define NULL_SCB_ADDR 0x000 +#define TIMINGMASTER_SCB_ADDR 0x010 +#define CODECOUT_SCB_ADDR 0x020 +#define PCMREADER_SCB_ADDR 0x030 +#define WRITEBACK_SCB_ADDR 0x040 +#define CODECIN_SCB_ADDR 0x080 +#define MASTERMIX_SCB_ADDR 0x090 +#define SRCTASK_SCB_ADDR 0x0A0 +#define VARIDECIMATE_SCB_ADDR 0x0B0 +#define PCMSERIALIN_SCB_ADDR 0x0C0 +#define FG_TASK_HEADER_ADDR 0x600 +#define ASYNCTX_SCB_ADDR 0x0E0 +#define ASYNCRX_SCB_ADDR 0x0F0 +#define SRCTASKII_SCB_ADDR 0x100 +#define OUTPUTSNOOP_SCB_ADDR 0x110 +#define PCMSERIALINII_SCB_ADDR 0x120 +#define SPIOWRITE_SCB_ADDR 0x130 +#define REAR_CODECOUT_SCB_ADDR 0x140 +#define OUTPUTSNOOPII_SCB_ADDR 0x150 +#define PCMSERIALIN_PCM_SCB_ADDR 0x160 +#define RECORD_MIXER_SCB_ADDR 0x170 +#define REAR_MIXER_SCB_ADDR 0x180 +#define CLFE_MIXER_SCB_ADDR 0x190 +#define CLFE_CODEC_SCB_ADDR 0x1A0 + +/* hyperforground SCB's*/ +#define HFG_TREE_SCB 0xBA0 +#define SPDIFI_SCB_INST 0xBB0 +#define SPDIFO_SCB_INST 0xBC0 +#define WRITE_BACK_SPB 0x0D0 + +/* offsets */ +#define AsyncCIOFIFOPointer 0xd +#define SPDIFOFIFOPointer 0xd +#define SPDIFIFIFOPointer 0xd +#define TCBData 0xb +#define HFGFlags 0xa +#define TCBContextBlk 0x10 +#define AFGTxAccumPhi 0x4 +#define SCBsubListPtr 0x9 +#define SCBfuncEntryPtr 0xA +#define SRCCorPerGof 0x2 +#define SRCPhiIncr6Int26Frac 0xd +#define SCBVolumeCtrl 0xe + +/* conf */ +#define UseASER1Input 1 + + + +/* + * The following defines are for the flags in the rsConfig01/23 registers of + * the SP. + */ + +#define RSCONFIG_MODULO_SIZE_MASK 0x0000000FL +#define RSCONFIG_MODULO_16 0x00000001L +#define RSCONFIG_MODULO_32 0x00000002L +#define RSCONFIG_MODULO_64 0x00000003L +#define RSCONFIG_MODULO_128 0x00000004L +#define RSCONFIG_MODULO_256 0x00000005L +#define RSCONFIG_MODULO_512 0x00000006L +#define RSCONFIG_MODULO_1024 0x00000007L +#define RSCONFIG_MODULO_4 0x00000008L +#define RSCONFIG_MODULO_8 0x00000009L +#define RSCONFIG_SAMPLE_SIZE_MASK 0x000000C0L +#define RSCONFIG_SAMPLE_8MONO 0x00000000L +#define RSCONFIG_SAMPLE_8STEREO 0x00000040L +#define RSCONFIG_SAMPLE_16MONO 0x00000080L +#define RSCONFIG_SAMPLE_16STEREO 0x000000C0L +#define RSCONFIG_UNDERRUN_ZERO 0x00004000L +#define RSCONFIG_DMA_TO_HOST 0x00008000L +#define RSCONFIG_STREAM_NUM_MASK 0x00FF0000L +#define RSCONFIG_MAX_DMA_SIZE_MASK 0x1F000000L +#define RSCONFIG_DMA_ENABLE 0x20000000L +#define RSCONFIG_PRIORITY_MASK 0xC0000000L +#define RSCONFIG_PRIORITY_HIGH 0x00000000L +#define RSCONFIG_PRIORITY_MEDIUM_HIGH 0x40000000L +#define RSCONFIG_PRIORITY_MEDIUM_LOW 0x80000000L +#define RSCONFIG_PRIORITY_LOW 0xC0000000L +#define RSCONFIG_STREAM_NUM_SHIFT 16L +#define RSCONFIG_MAX_DMA_SIZE_SHIFT 24L + +/* SP constants */ +#define FG_INTERVAL_TIMER_PERIOD 0x0051 +#define BG_INTERVAL_TIMER_PERIOD 0x0100 + + +/* Only SP accessible registers */ +#define SP_ASER_COUNTDOWN 0x8040 +#define SP_SPDOUT_FIFO 0x0108 +#define SP_SPDIN_MI_FIFO 0x01E0 +#define SP_SPDIN_D_FIFO 0x01F0 +#define SP_SPDIN_STATUS 0x8048 +#define SP_SPDIN_CONTROL 0x8049 +#define SP_SPDIN_FIFOPTR 0x804A +#define SP_SPDOUT_STATUS 0x804C +#define SP_SPDOUT_CONTROL 0x804D +#define SP_SPDOUT_CSUV 0x808E + +static inline u8 _wrap_all_bits (u8 val) +{ + u8 wrapped; + + /* wrap all 8 bits */ + wrapped = + ((val & 0x1 ) << 7) | + ((val & 0x2 ) << 5) | + ((val & 0x4 ) << 3) | + ((val & 0x8 ) << 1) | + ((val & 0x10) >> 1) | + ((val & 0x20) >> 3) | + ((val & 0x40) >> 5) | + ((val & 0x80) >> 7); + + return wrapped; +} + +static inline void cs46xx_dsp_spos_update_scb (struct snd_cs46xx * chip, + struct dsp_scb_descriptor * scb) +{ + /* update nextSCB and subListPtr in SCB */ + snd_cs46xx_poke(chip, + (scb->address + SCBsubListPtr) << 2, + (scb->sub_list_ptr->address << 0x10) | + (scb->next_scb_ptr->address)); + scb->updated = 1; +} + +static inline void cs46xx_dsp_scb_set_volume (struct snd_cs46xx * chip, + struct dsp_scb_descriptor * scb, + u16 left, u16 right) +{ + unsigned int val = ((0xffff - left) << 16 | (0xffff - right)); + + snd_cs46xx_poke(chip, (scb->address + SCBVolumeCtrl) << 2, val); + snd_cs46xx_poke(chip, (scb->address + SCBVolumeCtrl + 1) << 2, val); + scb->volume_set = 1; + scb->volume[0] = left; + scb->volume[1] = right; +} +#endif /* __DSP_SPOS_H__ */ +#endif /* CONFIG_SND_CS46XX_NEW_DSP */ diff --git a/sound/pci/cs46xx/dsp_spos_scb_lib.c b/sound/pci/cs46xx/dsp_spos_scb_lib.c new file mode 100644 index 000000000..1f90ca723 --- /dev/null +++ b/sound/pci/cs46xx/dsp_spos_scb_lib.c @@ -0,0 +1,1777 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +/* + * 2002-07 Benny Sjostrand benny@hostmobility.com + */ + + +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/mutex.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/info.h> +#include "cs46xx.h" + +#include "cs46xx_lib.h" +#include "dsp_spos.h" + +struct proc_scb_info { + struct dsp_scb_descriptor * scb_desc; + struct snd_cs46xx *chip; +}; + +static void remove_symbol (struct snd_cs46xx * chip, struct dsp_symbol_entry * symbol) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int symbol_index = (int)(symbol - ins->symbol_table.symbols); + + if (snd_BUG_ON(ins->symbol_table.nsymbols <= 0)) + return; + if (snd_BUG_ON(symbol_index < 0 || + symbol_index >= ins->symbol_table.nsymbols)) + return; + + ins->symbol_table.symbols[symbol_index].deleted = 1; + + if (symbol_index < ins->symbol_table.highest_frag_index) { + ins->symbol_table.highest_frag_index = symbol_index; + } + + if (symbol_index == ins->symbol_table.nsymbols - 1) + ins->symbol_table.nsymbols --; + + if (ins->symbol_table.highest_frag_index > ins->symbol_table.nsymbols) { + ins->symbol_table.highest_frag_index = ins->symbol_table.nsymbols; + } + +} + +#ifdef CONFIG_SND_PROC_FS +static void cs46xx_dsp_proc_scb_info_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct proc_scb_info * scb_info = entry->private_data; + struct dsp_scb_descriptor * scb = scb_info->scb_desc; + struct snd_cs46xx *chip = scb_info->chip; + int j,col; + void __iomem *dst = chip->region.idx[1].remap_addr + DSP_PARAMETER_BYTE_OFFSET; + + mutex_lock(&chip->spos_mutex); + snd_iprintf(buffer,"%04x %s:\n",scb->address,scb->scb_name); + + for (col = 0,j = 0;j < 0x10; j++,col++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + snd_iprintf(buffer,"%08x ",readl(dst + (scb->address + j) * sizeof(u32))); + } + + snd_iprintf(buffer,"\n"); + + if (scb->parent_scb_ptr != NULL) { + snd_iprintf(buffer,"parent [%s:%04x] ", + scb->parent_scb_ptr->scb_name, + scb->parent_scb_ptr->address); + } else snd_iprintf(buffer,"parent [none] "); + + snd_iprintf(buffer,"sub_list_ptr [%s:%04x]\nnext_scb_ptr [%s:%04x] task_entry [%s:%04x]\n", + scb->sub_list_ptr->scb_name, + scb->sub_list_ptr->address, + scb->next_scb_ptr->scb_name, + scb->next_scb_ptr->address, + scb->task_entry->symbol_name, + scb->task_entry->address); + + snd_iprintf(buffer,"index [%d] ref_count [%d]\n",scb->index,scb->ref_count); + mutex_unlock(&chip->spos_mutex); +} +#endif + +static void _dsp_unlink_scb (struct snd_cs46xx *chip, struct dsp_scb_descriptor * scb) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if ( scb->parent_scb_ptr ) { + /* unlink parent SCB */ + if (snd_BUG_ON(scb->parent_scb_ptr->sub_list_ptr != scb && + scb->parent_scb_ptr->next_scb_ptr != scb)) + return; + + if (scb->parent_scb_ptr->sub_list_ptr == scb) { + + if (scb->next_scb_ptr == ins->the_null_scb) { + /* last and only node in parent sublist */ + scb->parent_scb_ptr->sub_list_ptr = scb->sub_list_ptr; + + if (scb->sub_list_ptr != ins->the_null_scb) { + scb->sub_list_ptr->parent_scb_ptr = scb->parent_scb_ptr; + } + scb->sub_list_ptr = ins->the_null_scb; + } else { + /* first node in parent sublist */ + scb->parent_scb_ptr->sub_list_ptr = scb->next_scb_ptr; + + if (scb->next_scb_ptr != ins->the_null_scb) { + /* update next node parent ptr. */ + scb->next_scb_ptr->parent_scb_ptr = scb->parent_scb_ptr; + } + scb->next_scb_ptr = ins->the_null_scb; + } + } else { + scb->parent_scb_ptr->next_scb_ptr = scb->next_scb_ptr; + + if (scb->next_scb_ptr != ins->the_null_scb) { + /* update next node parent ptr. */ + scb->next_scb_ptr->parent_scb_ptr = scb->parent_scb_ptr; + } + scb->next_scb_ptr = ins->the_null_scb; + } + + /* update parent first entry in DSP RAM */ + cs46xx_dsp_spos_update_scb(chip,scb->parent_scb_ptr); + + /* then update entry in DSP RAM */ + cs46xx_dsp_spos_update_scb(chip,scb); + + scb->parent_scb_ptr = NULL; + } +} + +static void _dsp_clear_sample_buffer (struct snd_cs46xx *chip, u32 sample_buffer_addr, + int dword_count) +{ + void __iomem *dst = chip->region.idx[2].remap_addr + sample_buffer_addr; + int i; + + for (i = 0; i < dword_count ; ++i ) { + writel(0, dst); + dst += 4; + } +} + +void cs46xx_dsp_remove_scb (struct snd_cs46xx *chip, struct dsp_scb_descriptor * scb) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + unsigned long flags; + + /* check integrety */ + if (snd_BUG_ON(scb->index < 0 || + scb->index >= ins->nscb || + (ins->scbs + scb->index) != scb)) + return; + +#if 0 + /* can't remove a SCB with childs before + removing childs first */ + if (snd_BUG_ON(scb->sub_list_ptr != ins->the_null_scb || + scb->next_scb_ptr != ins->the_null_scb)) + goto _end; +#endif + + spin_lock_irqsave(&chip->reg_lock, flags); + _dsp_unlink_scb (chip,scb); + spin_unlock_irqrestore(&chip->reg_lock, flags); + + cs46xx_dsp_proc_free_scb_desc(scb); + if (snd_BUG_ON(!scb->scb_symbol)) + return; + remove_symbol (chip,scb->scb_symbol); + + ins->scbs[scb->index].deleted = 1; +#ifdef CONFIG_PM_SLEEP + kfree(ins->scbs[scb->index].data); + ins->scbs[scb->index].data = NULL; +#endif + + if (scb->index < ins->scb_highest_frag_index) + ins->scb_highest_frag_index = scb->index; + + if (scb->index == ins->nscb - 1) { + ins->nscb --; + } + + if (ins->scb_highest_frag_index > ins->nscb) { + ins->scb_highest_frag_index = ins->nscb; + } + +#if 0 + /* !!!! THIS IS A PIECE OF SHIT MADE BY ME !!! */ + for(i = scb->index + 1;i < ins->nscb; ++i) { + ins->scbs[i - 1].index = i - 1; + } +#endif +} + + +#ifdef CONFIG_SND_PROC_FS +void cs46xx_dsp_proc_free_scb_desc (struct dsp_scb_descriptor * scb) +{ + if (scb->proc_info) { + struct proc_scb_info * scb_info = scb->proc_info->private_data; + struct snd_cs46xx *chip = scb_info->chip; + + dev_dbg(chip->card->dev, + "cs46xx_dsp_proc_free_scb_desc: freeing %s\n", + scb->scb_name); + + snd_info_free_entry(scb->proc_info); + scb->proc_info = NULL; + + kfree (scb_info); + } +} + +void cs46xx_dsp_proc_register_scb_desc (struct snd_cs46xx *chip, + struct dsp_scb_descriptor * scb) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct snd_info_entry * entry; + struct proc_scb_info * scb_info; + + /* register to proc */ + if (ins->snd_card != NULL && ins->proc_dsp_dir != NULL && + scb->proc_info == NULL) { + + entry = snd_info_create_card_entry(ins->snd_card, scb->scb_name, + ins->proc_dsp_dir); + if (entry) { + scb_info = kmalloc(sizeof(struct proc_scb_info), GFP_KERNEL); + if (!scb_info) { + snd_info_free_entry(entry); + entry = NULL; + goto out; + } + + scb_info->chip = chip; + scb_info->scb_desc = scb; + snd_info_set_text_ops(entry, scb_info, + cs46xx_dsp_proc_scb_info_read); + } +out: + scb->proc_info = entry; + } +} +#endif /* CONFIG_SND_PROC_FS */ + +static struct dsp_scb_descriptor * +_dsp_create_generic_scb (struct snd_cs46xx *chip, char * name, u32 * scb_data, u32 dest, + struct dsp_symbol_entry * task_entry, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * scb; + + unsigned long flags; + + if (snd_BUG_ON(!ins->the_null_scb)) + return NULL; + + /* fill the data that will be wroten to DSP */ + scb_data[SCBsubListPtr] = + (ins->the_null_scb->address << 0x10) | ins->the_null_scb->address; + + scb_data[SCBfuncEntryPtr] &= 0xFFFF0000; + scb_data[SCBfuncEntryPtr] |= task_entry->address; + + dev_dbg(chip->card->dev, "dsp_spos: creating SCB <%s>\n", name); + + scb = cs46xx_dsp_create_scb(chip,name,scb_data,dest); + + + scb->sub_list_ptr = ins->the_null_scb; + scb->next_scb_ptr = ins->the_null_scb; + + scb->parent_scb_ptr = parent_scb; + scb->task_entry = task_entry; + + + /* update parent SCB */ + if (scb->parent_scb_ptr) { +#if 0 + dev_dbg(chip->card->dev, + "scb->parent_scb_ptr = %s\n", + scb->parent_scb_ptr->scb_name); + dev_dbg(chip->card->dev, + "scb->parent_scb_ptr->next_scb_ptr = %s\n", + scb->parent_scb_ptr->next_scb_ptr->scb_name); + dev_dbg(chip->card->dev, + "scb->parent_scb_ptr->sub_list_ptr = %s\n", + scb->parent_scb_ptr->sub_list_ptr->scb_name); +#endif + /* link to parent SCB */ + if (scb_child_type == SCB_ON_PARENT_NEXT_SCB) { + if (snd_BUG_ON(scb->parent_scb_ptr->next_scb_ptr != + ins->the_null_scb)) + return NULL; + + scb->parent_scb_ptr->next_scb_ptr = scb; + + } else if (scb_child_type == SCB_ON_PARENT_SUBLIST_SCB) { + if (snd_BUG_ON(scb->parent_scb_ptr->sub_list_ptr != + ins->the_null_scb)) + return NULL; + + scb->parent_scb_ptr->sub_list_ptr = scb; + } else { + snd_BUG(); + } + + spin_lock_irqsave(&chip->reg_lock, flags); + + /* update entry in DSP RAM */ + cs46xx_dsp_spos_update_scb(chip,scb->parent_scb_ptr); + + spin_unlock_irqrestore(&chip->reg_lock, flags); + } + + + cs46xx_dsp_proc_register_scb_desc (chip,scb); + + return scb; +} + +static struct dsp_scb_descriptor * +cs46xx_dsp_create_generic_scb (struct snd_cs46xx *chip, char * name, u32 * scb_data, + u32 dest, char * task_entry_name, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_symbol_entry * task_entry; + + task_entry = cs46xx_dsp_lookup_symbol (chip,task_entry_name, + SYMBOL_CODE); + + if (task_entry == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol %s not found\n", task_entry_name); + return NULL; + } + + return _dsp_create_generic_scb (chip,name,scb_data,dest,task_entry, + parent_scb,scb_child_type); +} + +struct dsp_scb_descriptor * +cs46xx_dsp_create_timing_master_scb (struct snd_cs46xx *chip) +{ + struct dsp_scb_descriptor * scb; + + struct dsp_timing_master_scb timing_master_scb = { + { 0, + 0, + 0, + 0 + }, + { 0, + 0, + 0, + 0, + 0 + }, + 0,0, + 0,NULL_SCB_ADDR, + 0,0, /* extraSampleAccum:TMreserved */ + 0,0, /* codecFIFOptr:codecFIFOsyncd */ + 0x0001,0x8000, /* fracSampAccumQm1:TMfrmsLeftInGroup */ + 0x0001,0x0000, /* fracSampCorrectionQm1:TMfrmGroupLength */ + 0x00060000 /* nSampPerFrmQ15 */ + }; + + scb = cs46xx_dsp_create_generic_scb(chip,"TimingMasterSCBInst",(u32 *)&timing_master_scb, + TIMINGMASTER_SCB_ADDR, + "TIMINGMASTER",NULL,SCB_NO_PARENT); + + return scb; +} + + +struct dsp_scb_descriptor * +cs46xx_dsp_create_codec_out_scb(struct snd_cs46xx * chip, char * codec_name, + u16 channel_disp, u16 fifo_addr, u16 child_scb_addr, + u32 dest, struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_scb_descriptor * scb; + + struct dsp_codec_output_scb codec_out_scb = { + { 0, + 0, + 0, + 0 + }, + { + 0, + 0, + 0, + 0, + 0 + }, + 0,0, + 0,NULL_SCB_ADDR, + 0, /* COstrmRsConfig */ + 0, /* COstrmBufPtr */ + channel_disp,fifo_addr, /* leftChanBaseIOaddr:rightChanIOdisp */ + 0x0000,0x0080, /* (!AC97!) COexpVolChangeRate:COscaleShiftCount */ + 0,child_scb_addr /* COreserved - need child scb to work with rom code */ + }; + + + scb = cs46xx_dsp_create_generic_scb(chip,codec_name,(u32 *)&codec_out_scb, + dest,"S16_CODECOUTPUTTASK",parent_scb, + scb_child_type); + + return scb; +} + +struct dsp_scb_descriptor * +cs46xx_dsp_create_codec_in_scb(struct snd_cs46xx * chip, char * codec_name, + u16 channel_disp, u16 fifo_addr, u16 sample_buffer_addr, + u32 dest, struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + + struct dsp_scb_descriptor * scb; + struct dsp_codec_input_scb codec_input_scb = { + { 0, + 0, + 0, + 0 + }, + { + 0, + 0, + 0, + 0, + 0 + }, + +#if 0 /* cs4620 */ + SyncIOSCB,NULL_SCB_ADDR +#else + 0 , 0, +#endif + 0,0, + + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_64, /* strmRsConfig */ + sample_buffer_addr << 0x10, /* strmBufPtr; defined as a dword ptr, used as a byte ptr */ + channel_disp,fifo_addr, /* (!AC97!) leftChanBaseINaddr=AC97primary + link input slot 3 :rightChanINdisp=""slot 4 */ + 0x0000,0x0000, /* (!AC97!) ????:scaleShiftCount; no shift needed + because AC97 is already 20 bits */ + 0x80008000 /* ??clw cwcgame.scb has 0 */ + }; + + scb = cs46xx_dsp_create_generic_scb(chip,codec_name,(u32 *)&codec_input_scb, + dest,"S16_CODECINPUTTASK",parent_scb, + scb_child_type); + return scb; +} + + +static struct dsp_scb_descriptor * +cs46xx_dsp_create_pcm_reader_scb(struct snd_cs46xx * chip, char * scb_name, + u16 sample_buffer_addr, u32 dest, + int virtual_channel, u32 playback_hw_addr, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * scb; + + struct dsp_generic_scb pcm_reader_scb = { + + /* + Play DMA Task xfers data from host buffer to SP buffer + init/runtime variables: + PlayAC: Play Audio Data Conversion - SCB loc: 2nd dword, mask: 0x0000F000L + DATA_FMT_16BIT_ST_LTLEND(0x00000000L) from 16-bit stereo, little-endian + DATA_FMT_8_BIT_ST_SIGNED(0x00001000L) from 8-bit stereo, signed + DATA_FMT_16BIT_MN_LTLEND(0x00002000L) from 16-bit mono, little-endian + DATA_FMT_8_BIT_MN_SIGNED(0x00003000L) from 8-bit mono, signed + DATA_FMT_16BIT_ST_BIGEND(0x00004000L) from 16-bit stereo, big-endian + DATA_FMT_16BIT_MN_BIGEND(0x00006000L) from 16-bit mono, big-endian + DATA_FMT_8_BIT_ST_UNSIGNED(0x00009000L) from 8-bit stereo, unsigned + DATA_FMT_8_BIT_MN_UNSIGNED(0x0000b000L) from 8-bit mono, unsigned + ? Other combinations possible from: + DMA_RQ_C2_AUDIO_CONVERT_MASK 0x0000F000L + DMA_RQ_C2_AC_NONE 0x00000000L + DMA_RQ_C2_AC_8_TO_16_BIT 0x00001000L + DMA_RQ_C2_AC_MONO_TO_STEREO 0x00002000L + DMA_RQ_C2_AC_ENDIAN_CONVERT 0x00004000L + DMA_RQ_C2_AC_SIGNED_CONVERT 0x00008000L + + HostBuffAddr: Host Buffer Physical Byte Address - SCB loc:3rd dword, Mask: 0xFFFFFFFFL + aligned to dword boundary + */ + /* Basic (non scatter/gather) DMA requestor (4 ints) */ + { DMA_RQ_C1_SOURCE_ON_HOST + /* source buffer is on the host */ + DMA_RQ_C1_SOURCE_MOD1024 + /* source buffer is 1024 dwords (4096 bytes) */ + DMA_RQ_C1_DEST_MOD32 + /* dest buffer(PCMreaderBuf) is 32 dwords*/ + DMA_RQ_C1_WRITEBACK_SRC_FLAG + /* ?? */ + DMA_RQ_C1_WRITEBACK_DEST_FLAG + /* ?? */ + 15, /* DwordCount-1: picked 16 for DwordCount because Jim */ + /* Barnette said that is what we should use since */ + /* we are not running in optimized mode? */ + DMA_RQ_C2_AC_NONE + + DMA_RQ_C2_SIGNAL_SOURCE_PINGPONG + /* set play interrupt (bit0) in HISR when source */ + /* buffer (on host) crosses half-way point */ + virtual_channel, /* Play DMA channel arbitrarily set to 0 */ + playback_hw_addr, /* HostBuffAddr (source) */ + DMA_RQ_SD_SP_SAMPLE_ADDR + /* destination buffer is in SP Sample Memory */ + sample_buffer_addr /* SP Buffer Address (destination) */ + }, + /* Scatter/gather DMA requestor extension (5 ints) */ + { + 0, + 0, + 0, + 0, + 0 + }, + /* Sublist pointer & next stream control block (SCB) link. */ + NULL_SCB_ADDR,NULL_SCB_ADDR, + /* Pointer to this tasks parameter block & stream function pointer */ + 0,NULL_SCB_ADDR, + /* rsConfig register for stream buffer (rsDMA reg. is loaded from basicReq.daw */ + /* for incoming streams, or basicReq.saw, for outgoing streams) */ + RSCONFIG_DMA_ENABLE + /* enable DMA */ + (19 << RSCONFIG_MAX_DMA_SIZE_SHIFT) + /* MAX_DMA_SIZE picked to be 19 since SPUD */ + /* uses it for some reason */ + ((dest >> 4) << RSCONFIG_STREAM_NUM_SHIFT) + /* stream number = SCBaddr/16 */ + RSCONFIG_SAMPLE_16STEREO + + RSCONFIG_MODULO_32, /* dest buffer(PCMreaderBuf) is 32 dwords (256 bytes) */ + /* Stream sample pointer & MAC-unit mode for this stream */ + (sample_buffer_addr << 0x10), + /* Fractional increment per output sample in the input sample buffer */ + 0, + { + /* Standard stereo volume control + default muted */ + 0xffff,0xffff, + 0xffff,0xffff + } + }; + + if (ins->null_algorithm == NULL) { + ins->null_algorithm = cs46xx_dsp_lookup_symbol (chip,"NULLALGORITHM", + SYMBOL_CODE); + + if (ins->null_algorithm == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol NULLALGORITHM not found\n"); + return NULL; + } + } + + scb = _dsp_create_generic_scb(chip,scb_name,(u32 *)&pcm_reader_scb, + dest,ins->null_algorithm,parent_scb, + scb_child_type); + + return scb; +} + +#define GOF_PER_SEC 200 + +struct dsp_scb_descriptor * +cs46xx_dsp_create_src_task_scb(struct snd_cs46xx * chip, char * scb_name, + int rate, + u16 src_buffer_addr, + u16 src_delay_buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type, + int pass_through) +{ + + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * scb; + unsigned int tmp1, tmp2; + unsigned int phiIncr; + unsigned int correctionPerGOF, correctionPerSec; + + dev_dbg(chip->card->dev, "dsp_spos: setting %s rate to %u\n", + scb_name, rate); + + /* + * 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; + + { + struct dsp_src_task_scb src_task_scb = { + 0x0028,0x00c8, + 0x5555,0x0000, + 0x0000,0x0000, + src_buffer_addr,1, + correctionPerGOF,correctionPerSec, + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_32, + 0x0000,src_delay_buffer_addr, + 0x0, + 0x080,(src_delay_buffer_addr + (24 * 4)), + 0,0, /* next_scb, sub_list_ptr */ + 0,0, /* entry, this_spb */ + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_8, + src_buffer_addr << 0x10, + phiIncr, + { + 0xffff - ins->dac_volume_right,0xffff - ins->dac_volume_left, + 0xffff - ins->dac_volume_right,0xffff - ins->dac_volume_left + } + }; + + if (ins->s16_up == NULL) { + ins->s16_up = cs46xx_dsp_lookup_symbol (chip,"S16_UPSRC", + SYMBOL_CODE); + + if (ins->s16_up == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol S16_UPSRC not found\n"); + return NULL; + } + } + + /* clear buffers */ + _dsp_clear_sample_buffer (chip,src_buffer_addr,8); + _dsp_clear_sample_buffer (chip,src_delay_buffer_addr,32); + + if (pass_through) { + /* wont work with any other rate than + the native DSP rate */ + snd_BUG_ON(rate != 48000); + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&src_task_scb, + dest,"DMAREADER",parent_scb, + scb_child_type); + } else { + scb = _dsp_create_generic_scb(chip,scb_name,(u32 *)&src_task_scb, + dest,ins->s16_up,parent_scb, + scb_child_type); + } + + + } + + return scb; +} + +#if 0 /* not used */ +struct dsp_scb_descriptor * +cs46xx_dsp_create_filter_scb(struct snd_cs46xx * chip, char * scb_name, + u16 buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) { + struct dsp_scb_descriptor * scb; + + struct dsp_filter_scb filter_scb = { + .a0_right = 0x41a9, + .a0_left = 0x41a9, + .a1_right = 0xb8e4, + .a1_left = 0xb8e4, + .a2_right = 0x3e55, + .a2_left = 0x3e55, + + .filter_unused3 = 0x0000, + .filter_unused2 = 0x0000, + + .output_buf_ptr = buffer_addr, + .init = 0x000, + + .prev_sample_output1 = 0x00000000, + .prev_sample_output2 = 0x00000000, + + .prev_sample_input1 = 0x00000000, + .prev_sample_input2 = 0x00000000, + + .next_scb_ptr = 0x0000, + .sub_list_ptr = 0x0000, + + .entry_point = 0x0000, + .spb_ptr = 0x0000, + + .b0_right = 0x0e38, + .b0_left = 0x0e38, + .b1_right = 0x1c71, + .b1_left = 0x1c71, + .b2_right = 0x0e38, + .b2_left = 0x0e38, + }; + + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&filter_scb, + dest,"FILTERTASK",parent_scb, + scb_child_type); + + return scb; +} +#endif /* not used */ + +struct dsp_scb_descriptor * +cs46xx_dsp_create_mix_only_scb(struct snd_cs46xx * chip, char * scb_name, + u16 mix_buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_scb_descriptor * scb; + + struct dsp_mix_only_scb master_mix_scb = { + /* 0 */ { 0, + /* 1 */ 0, + /* 2 */ mix_buffer_addr, + /* 3 */ 0 + /* */ }, + { + /* 4 */ 0, + /* 5 */ 0, + /* 6 */ 0, + /* 7 */ 0, + /* 8 */ 0x00000080 + }, + /* 9 */ 0,0, + /* A */ 0,0, + /* B */ RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_32, + /* C */ (mix_buffer_addr + (16 * 4)) << 0x10, + /* D */ 0, + { + /* E */ 0x8000,0x8000, + /* F */ 0x8000,0x8000 + } + }; + + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&master_mix_scb, + dest,"S16_MIX",parent_scb, + scb_child_type); + return scb; +} + + +struct dsp_scb_descriptor * +cs46xx_dsp_create_mix_to_ostream_scb(struct snd_cs46xx * chip, char * scb_name, + u16 mix_buffer_addr, u16 writeback_spb, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_scb_descriptor * scb; + + struct dsp_mix2_ostream_scb mix2_ostream_scb = { + /* Basic (non scatter/gather) DMA requestor (4 ints) */ + { + DMA_RQ_C1_SOURCE_MOD64 + + DMA_RQ_C1_DEST_ON_HOST + + DMA_RQ_C1_DEST_MOD1024 + + DMA_RQ_C1_WRITEBACK_SRC_FLAG + + DMA_RQ_C1_WRITEBACK_DEST_FLAG + + 15, + + DMA_RQ_C2_AC_NONE + + DMA_RQ_C2_SIGNAL_DEST_PINGPONG + + + CS46XX_DSP_CAPTURE_CHANNEL, + DMA_RQ_SD_SP_SAMPLE_ADDR + + mix_buffer_addr, + 0x0 + }, + + { 0, 0, 0, 0, 0, }, + 0,0, + 0,writeback_spb, + + RSCONFIG_DMA_ENABLE + + (19 << RSCONFIG_MAX_DMA_SIZE_SHIFT) + + + ((dest >> 4) << RSCONFIG_STREAM_NUM_SHIFT) + + RSCONFIG_DMA_TO_HOST + + RSCONFIG_SAMPLE_16STEREO + + RSCONFIG_MODULO_64, + (mix_buffer_addr + (32 * 4)) << 0x10, + 1,0, + 0x0001,0x0080, + 0xFFFF,0 + }; + + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&mix2_ostream_scb, + + dest,"S16_MIX_TO_OSTREAM",parent_scb, + scb_child_type); + + return scb; +} + + +struct dsp_scb_descriptor * +cs46xx_dsp_create_vari_decimate_scb(struct snd_cs46xx * chip,char * scb_name, + u16 vari_buffer_addr0, + u16 vari_buffer_addr1, + u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + + struct dsp_scb_descriptor * scb; + + struct dsp_vari_decimate_scb vari_decimate_scb = { + 0x0028,0x00c8, + 0x5555,0x0000, + 0x0000,0x0000, + vari_buffer_addr0,vari_buffer_addr1, + + 0x0028,0x00c8, + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_256, + + 0xFF800000, + 0, + 0x0080,vari_buffer_addr1 + (25 * 4), + + 0,0, + 0,0, + + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_8, + vari_buffer_addr0 << 0x10, + 0x04000000, + { + 0x8000,0x8000, + 0xFFFF,0xFFFF + } + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&vari_decimate_scb, + dest,"VARIDECIMATE",parent_scb, + scb_child_type); + + return scb; +} + + +static struct dsp_scb_descriptor * +cs46xx_dsp_create_pcm_serial_input_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + struct dsp_scb_descriptor * input_scb, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + + struct dsp_scb_descriptor * scb; + + + struct dsp_pcm_serial_input_scb pcm_serial_input_scb = { + { 0, + 0, + 0, + 0 + }, + { + 0, + 0, + 0, + 0, + 0 + }, + + 0,0, + 0,0, + + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_16, + 0, + /* 0xD */ 0,input_scb->address, + { + /* 0xE */ 0x8000,0x8000, + /* 0xF */ 0x8000,0x8000 + } + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&pcm_serial_input_scb, + dest,"PCMSERIALINPUTTASK",parent_scb, + scb_child_type); + return scb; +} + + +static struct dsp_scb_descriptor * +cs46xx_dsp_create_asynch_fg_tx_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + u16 hfg_scb_address, + u16 asynch_buffer_address, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + + struct dsp_scb_descriptor * scb; + + struct dsp_asynch_fg_tx_scb asynch_fg_tx_scb = { + 0xfc00,0x03ff, /* Prototype sample buffer size of 256 dwords */ + 0x0058,0x0028, /* Min Delta 7 dwords == 28 bytes */ + /* : Max delta 25 dwords == 100 bytes */ + 0,hfg_scb_address, /* Point to HFG task SCB */ + 0,0, /* Initialize current Delta and Consumer ptr adjustment count */ + 0, /* Initialize accumulated Phi to 0 */ + 0,0x2aab, /* Const 1/3 */ + + { + 0, /* Define the unused elements */ + 0, + 0 + }, + + 0,0, + 0,dest + AFGTxAccumPhi, + + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_256, /* Stereo, 256 dword */ + (asynch_buffer_address) << 0x10, /* This should be automagically synchronized + to the producer pointer */ + + /* There is no correct initial value, it will depend upon the detected + rate etc */ + 0x18000000, /* Phi increment for approx 32k operation */ + 0x8000,0x8000, /* Volume controls are unused at this time */ + 0x8000,0x8000 + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&asynch_fg_tx_scb, + dest,"ASYNCHFGTXCODE",parent_scb, + scb_child_type); + + return scb; +} + + +struct dsp_scb_descriptor * +cs46xx_dsp_create_asynch_fg_rx_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + u16 hfg_scb_address, + u16 asynch_buffer_address, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * scb; + + struct dsp_asynch_fg_rx_scb asynch_fg_rx_scb = { + 0xfe00,0x01ff, /* Prototype sample buffer size of 128 dwords */ + 0x0064,0x001c, /* Min Delta 7 dwords == 28 bytes */ + /* : Max delta 25 dwords == 100 bytes */ + 0,hfg_scb_address, /* Point to HFG task SCB */ + 0,0, /* Initialize current Delta and Consumer ptr adjustment count */ + { + 0, /* Define the unused elements */ + 0, + 0, + 0, + 0 + }, + + 0,0, + 0,dest, + + RSCONFIG_MODULO_128 | + RSCONFIG_SAMPLE_16STEREO, /* Stereo, 128 dword */ + ( (asynch_buffer_address + (16 * 4)) << 0x10), /* This should be automagically + synchrinized to the producer pointer */ + + /* There is no correct initial value, it will depend upon the detected + rate etc */ + 0x18000000, + + /* Set IEC958 input volume */ + 0xffff - ins->spdif_input_volume_right,0xffff - ins->spdif_input_volume_left, + 0xffff - ins->spdif_input_volume_right,0xffff - ins->spdif_input_volume_left, + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&asynch_fg_rx_scb, + dest,"ASYNCHFGRXCODE",parent_scb, + scb_child_type); + + return scb; +} + + +#if 0 /* not used */ +struct dsp_scb_descriptor * +cs46xx_dsp_create_output_snoop_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + u16 snoop_buffer_address, + struct dsp_scb_descriptor * snoop_scb, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + + struct dsp_scb_descriptor * scb; + + struct dsp_output_snoop_scb output_snoop_scb = { + { 0, /* not used. Zero */ + 0, + 0, + 0, + }, + { + 0, /* not used. Zero */ + 0, + 0, + 0, + 0 + }, + + 0,0, + 0,0, + + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_64, + snoop_buffer_address << 0x10, + 0,0, + 0, + 0,snoop_scb->address + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&output_snoop_scb, + dest,"OUTPUTSNOOP",parent_scb, + scb_child_type); + return scb; +} +#endif /* not used */ + + +struct dsp_scb_descriptor * +cs46xx_dsp_create_spio_write_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_scb_descriptor * scb; + + struct dsp_spio_write_scb spio_write_scb = { + 0,0, /* SPIOWAddress2:SPIOWAddress1; */ + 0, /* SPIOWData1; */ + 0, /* SPIOWData2; */ + 0,0, /* SPIOWAddress4:SPIOWAddress3; */ + 0, /* SPIOWData3; */ + 0, /* SPIOWData4; */ + 0,0, /* SPIOWDataPtr:Unused1; */ + { 0,0 }, /* Unused2[2]; */ + + 0,0, /* SPIOWChildPtr:SPIOWSiblingPtr; */ + 0,0, /* SPIOWThisPtr:SPIOWEntryPoint; */ + + { + 0, + 0, + 0, + 0, + 0 /* Unused3[5]; */ + } + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&spio_write_scb, + dest,"SPIOWRITE",parent_scb, + scb_child_type); + + return scb; +} + +struct dsp_scb_descriptor * +cs46xx_dsp_create_magic_snoop_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + u16 snoop_buffer_address, + struct dsp_scb_descriptor * snoop_scb, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_scb_descriptor * scb; + + struct dsp_magic_snoop_task magic_snoop_scb = { + /* 0 */ 0, /* i0 */ + /* 1 */ 0, /* i1 */ + /* 2 */ snoop_buffer_address << 0x10, + /* 3 */ 0,snoop_scb->address, + /* 4 */ 0, /* i3 */ + /* 5 */ 0, /* i4 */ + /* 6 */ 0, /* i5 */ + /* 7 */ 0, /* i6 */ + /* 8 */ 0, /* i7 */ + /* 9 */ 0,0, /* next_scb, sub_list_ptr */ + /* A */ 0,0, /* entry_point, this_ptr */ + /* B */ RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_64, + /* C */ snoop_buffer_address << 0x10, + /* D */ 0, + /* E */ { 0x8000,0x8000, + /* F */ 0xffff,0xffff + } + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&magic_snoop_scb, + dest,"MAGICSNOOPTASK",parent_scb, + scb_child_type); + + return scb; +} + +static struct dsp_scb_descriptor * +find_next_free_scb (struct snd_cs46xx * chip, struct dsp_scb_descriptor * from) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * scb = from; + + while (scb->next_scb_ptr != ins->the_null_scb) { + if (snd_BUG_ON(!scb->next_scb_ptr)) + return NULL; + + scb = scb->next_scb_ptr; + } + + return scb; +} + +static const u32 pcm_reader_buffer_addr[DSP_MAX_PCM_CHANNELS] = { + 0x0600, /* 1 */ + 0x1500, /* 2 */ + 0x1580, /* 3 */ + 0x1600, /* 4 */ + 0x1680, /* 5 */ + 0x1700, /* 6 */ + 0x1780, /* 7 */ + 0x1800, /* 8 */ + 0x1880, /* 9 */ + 0x1900, /* 10 */ + 0x1980, /* 11 */ + 0x1A00, /* 12 */ + 0x1A80, /* 13 */ + 0x1B00, /* 14 */ + 0x1B80, /* 15 */ + 0x1C00, /* 16 */ + 0x1C80, /* 17 */ + 0x1D00, /* 18 */ + 0x1D80, /* 19 */ + 0x1E00, /* 20 */ + 0x1E80, /* 21 */ + 0x1F00, /* 22 */ + 0x1F80, /* 23 */ + 0x2000, /* 24 */ + 0x2080, /* 25 */ + 0x2100, /* 26 */ + 0x2180, /* 27 */ + 0x2200, /* 28 */ + 0x2280, /* 29 */ + 0x2300, /* 30 */ + 0x2380, /* 31 */ + 0x2400, /* 32 */ +}; + +static const u32 src_output_buffer_addr[DSP_MAX_SRC_NR] = { + 0x2B80, + 0x2BA0, + 0x2BC0, + 0x2BE0, + 0x2D00, + 0x2D20, + 0x2D40, + 0x2D60, + 0x2D80, + 0x2DA0, + 0x2DC0, + 0x2DE0, + 0x2E00, + 0x2E20 +}; + +static const u32 src_delay_buffer_addr[DSP_MAX_SRC_NR] = { + 0x2480, + 0x2500, + 0x2580, + 0x2600, + 0x2680, + 0x2700, + 0x2780, + 0x2800, + 0x2880, + 0x2900, + 0x2980, + 0x2A00, + 0x2A80, + 0x2B00 +}; + +struct dsp_pcm_channel_descriptor * +cs46xx_dsp_create_pcm_channel (struct snd_cs46xx * chip, + u32 sample_rate, void * private_data, + u32 hw_dma_addr, + int pcm_channel_id) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * src_scb = NULL, * pcm_scb, * mixer_scb = NULL; + struct dsp_scb_descriptor * src_parent_scb = NULL; + + /* struct dsp_scb_descriptor * pcm_parent_scb; */ + char scb_name[DSP_MAX_SCB_NAME]; + int i, pcm_index = -1, insert_point, src_index = -1, pass_through = 0; + unsigned long flags; + + switch (pcm_channel_id) { + case DSP_PCM_MAIN_CHANNEL: + mixer_scb = ins->master_mix_scb; + break; + case DSP_PCM_REAR_CHANNEL: + mixer_scb = ins->rear_mix_scb; + break; + case DSP_PCM_CENTER_LFE_CHANNEL: + mixer_scb = ins->center_lfe_mix_scb; + break; + case DSP_PCM_S71_CHANNEL: + /* TODO */ + snd_BUG(); + break; + case DSP_IEC958_CHANNEL: + if (snd_BUG_ON(!ins->asynch_tx_scb)) + return NULL; + mixer_scb = ins->asynch_tx_scb; + + /* if sample rate is set to 48khz we pass + the Sample Rate Converted (which could + alter the raw data stream ...) */ + if (sample_rate == 48000) { + dev_dbg(chip->card->dev, "IEC958 pass through\n"); + /* Hack to bypass creating a new SRC */ + pass_through = 1; + } + break; + default: + snd_BUG(); + return NULL; + } + /* default sample rate is 44100 */ + if (!sample_rate) sample_rate = 44100; + + /* search for a already created SRC SCB with the same sample rate */ + for (i = 0; i < DSP_MAX_PCM_CHANNELS && + (pcm_index == -1 || src_scb == NULL); ++i) { + + /* virtual channel reserved + for capture */ + if (i == CS46XX_DSP_CAPTURE_CHANNEL) continue; + + if (ins->pcm_channels[i].active) { + if (!src_scb && + ins->pcm_channels[i].sample_rate == sample_rate && + ins->pcm_channels[i].mixer_scb == mixer_scb) { + src_scb = ins->pcm_channels[i].src_scb; + ins->pcm_channels[i].src_scb->ref_count ++; + src_index = ins->pcm_channels[i].src_slot; + } + } else if (pcm_index == -1) { + pcm_index = i; + } + } + + if (pcm_index == -1) { + dev_err(chip->card->dev, "dsp_spos: no free PCM channel\n"); + return NULL; + } + + if (src_scb == NULL) { + if (ins->nsrc_scb >= DSP_MAX_SRC_NR) { + dev_err(chip->card->dev, + "dsp_spos: too many SRC instances\n!"); + return NULL; + } + + /* find a free slot */ + for (i = 0; i < DSP_MAX_SRC_NR; ++i) { + if (ins->src_scb_slots[i] == 0) { + src_index = i; + ins->src_scb_slots[i] = 1; + break; + } + } + if (snd_BUG_ON(src_index == -1)) + return NULL; + + /* we need to create a new SRC SCB */ + if (mixer_scb->sub_list_ptr == ins->the_null_scb) { + src_parent_scb = mixer_scb; + insert_point = SCB_ON_PARENT_SUBLIST_SCB; + } else { + src_parent_scb = find_next_free_scb(chip,mixer_scb->sub_list_ptr); + insert_point = SCB_ON_PARENT_NEXT_SCB; + } + + snprintf (scb_name,DSP_MAX_SCB_NAME,"SrcTask_SCB%d",src_index); + + dev_dbg(chip->card->dev, + "dsp_spos: creating SRC \"%s\"\n", scb_name); + src_scb = cs46xx_dsp_create_src_task_scb(chip,scb_name, + sample_rate, + src_output_buffer_addr[src_index], + src_delay_buffer_addr[src_index], + /* 0x400 - 0x600 source SCBs */ + 0x400 + (src_index * 0x10) , + src_parent_scb, + insert_point, + pass_through); + + if (!src_scb) { + dev_err(chip->card->dev, + "dsp_spos: failed to create SRCtaskSCB\n"); + return NULL; + } + + /* cs46xx_dsp_set_src_sample_rate(chip,src_scb,sample_rate); */ + + ins->nsrc_scb ++; + } + + + snprintf (scb_name,DSP_MAX_SCB_NAME,"PCMReader_SCB%d",pcm_index); + + dev_dbg(chip->card->dev, "dsp_spos: creating PCM \"%s\" (%d)\n", + scb_name, pcm_channel_id); + + pcm_scb = cs46xx_dsp_create_pcm_reader_scb(chip,scb_name, + pcm_reader_buffer_addr[pcm_index], + /* 0x200 - 400 PCMreader SCBs */ + (pcm_index * 0x10) + 0x200, + pcm_index, /* virtual channel 0-31 */ + hw_dma_addr, /* pcm hw addr */ + NULL, /* parent SCB ptr */ + 0 /* insert point */ + ); + + if (!pcm_scb) { + dev_err(chip->card->dev, + "dsp_spos: failed to create PCMreaderSCB\n"); + return NULL; + } + + spin_lock_irqsave(&chip->reg_lock, flags); + ins->pcm_channels[pcm_index].sample_rate = sample_rate; + ins->pcm_channels[pcm_index].pcm_reader_scb = pcm_scb; + ins->pcm_channels[pcm_index].src_scb = src_scb; + ins->pcm_channels[pcm_index].unlinked = 1; + ins->pcm_channels[pcm_index].private_data = private_data; + ins->pcm_channels[pcm_index].src_slot = src_index; + ins->pcm_channels[pcm_index].active = 1; + ins->pcm_channels[pcm_index].pcm_slot = pcm_index; + ins->pcm_channels[pcm_index].mixer_scb = mixer_scb; + ins->npcm_channels ++; + spin_unlock_irqrestore(&chip->reg_lock, flags); + + return (ins->pcm_channels + pcm_index); +} + +int cs46xx_dsp_pcm_channel_set_period (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel, + int period_size) +{ + u32 temp = snd_cs46xx_peek (chip,pcm_channel->pcm_reader_scb->address << 2); + temp &= ~DMA_RQ_C1_SOURCE_SIZE_MASK; + + switch (period_size) { + case 2048: + temp |= DMA_RQ_C1_SOURCE_MOD1024; + break; + case 1024: + temp |= DMA_RQ_C1_SOURCE_MOD512; + break; + case 512: + temp |= DMA_RQ_C1_SOURCE_MOD256; + break; + case 256: + temp |= DMA_RQ_C1_SOURCE_MOD128; + break; + case 128: + temp |= DMA_RQ_C1_SOURCE_MOD64; + break; + case 64: + temp |= DMA_RQ_C1_SOURCE_MOD32; + break; + case 32: + temp |= DMA_RQ_C1_SOURCE_MOD16; + break; + default: + dev_dbg(chip->card->dev, + "period size (%d) not supported by HW\n", period_size); + return -EINVAL; + } + + snd_cs46xx_poke (chip,pcm_channel->pcm_reader_scb->address << 2,temp); + + return 0; +} + +int cs46xx_dsp_pcm_ostream_set_period (struct snd_cs46xx * chip, + int period_size) +{ + u32 temp = snd_cs46xx_peek (chip,WRITEBACK_SCB_ADDR << 2); + temp &= ~DMA_RQ_C1_DEST_SIZE_MASK; + + switch (period_size) { + case 2048: + temp |= DMA_RQ_C1_DEST_MOD1024; + break; + case 1024: + temp |= DMA_RQ_C1_DEST_MOD512; + break; + case 512: + temp |= DMA_RQ_C1_DEST_MOD256; + break; + case 256: + temp |= DMA_RQ_C1_DEST_MOD128; + break; + case 128: + temp |= DMA_RQ_C1_DEST_MOD64; + break; + case 64: + temp |= DMA_RQ_C1_DEST_MOD32; + break; + case 32: + temp |= DMA_RQ_C1_DEST_MOD16; + break; + default: + dev_dbg(chip->card->dev, + "period size (%d) not supported by HW\n", period_size); + return -EINVAL; + } + + snd_cs46xx_poke (chip,WRITEBACK_SCB_ADDR << 2,temp); + + return 0; +} + +void cs46xx_dsp_destroy_pcm_channel (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + unsigned long flags; + + if (snd_BUG_ON(!pcm_channel->active || + ins->npcm_channels <= 0 || + pcm_channel->src_scb->ref_count <= 0)) + return; + + spin_lock_irqsave(&chip->reg_lock, flags); + pcm_channel->unlinked = 1; + pcm_channel->active = 0; + pcm_channel->private_data = NULL; + pcm_channel->src_scb->ref_count --; + ins->npcm_channels --; + spin_unlock_irqrestore(&chip->reg_lock, flags); + + cs46xx_dsp_remove_scb(chip,pcm_channel->pcm_reader_scb); + + if (!pcm_channel->src_scb->ref_count) { + cs46xx_dsp_remove_scb(chip,pcm_channel->src_scb); + + if (snd_BUG_ON(pcm_channel->src_slot < 0 || + pcm_channel->src_slot >= DSP_MAX_SRC_NR)) + return; + + ins->src_scb_slots[pcm_channel->src_slot] = 0; + ins->nsrc_scb --; + } +} + +int cs46xx_dsp_pcm_unlink (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel) +{ + unsigned long flags; + + if (snd_BUG_ON(!pcm_channel->active || + chip->dsp_spos_instance->npcm_channels <= 0)) + return -EIO; + + spin_lock_irqsave(&chip->reg_lock, flags); + if (pcm_channel->unlinked) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + return -EIO; + } + + pcm_channel->unlinked = 1; + + _dsp_unlink_scb (chip,pcm_channel->pcm_reader_scb); + spin_unlock_irqrestore(&chip->reg_lock, flags); + + return 0; +} + +int cs46xx_dsp_pcm_link (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * parent_scb; + struct dsp_scb_descriptor * src_scb = pcm_channel->src_scb; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + + if (pcm_channel->unlinked == 0) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + return -EIO; + } + + parent_scb = src_scb; + + if (src_scb->sub_list_ptr != ins->the_null_scb) { + src_scb->sub_list_ptr->parent_scb_ptr = pcm_channel->pcm_reader_scb; + pcm_channel->pcm_reader_scb->next_scb_ptr = src_scb->sub_list_ptr; + } + + src_scb->sub_list_ptr = pcm_channel->pcm_reader_scb; + + snd_BUG_ON(pcm_channel->pcm_reader_scb->parent_scb_ptr); + pcm_channel->pcm_reader_scb->parent_scb_ptr = parent_scb; + + /* update SCB entry in DSP RAM */ + cs46xx_dsp_spos_update_scb(chip,pcm_channel->pcm_reader_scb); + + /* update parent SCB entry */ + cs46xx_dsp_spos_update_scb(chip,parent_scb); + + pcm_channel->unlinked = 0; + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +struct dsp_scb_descriptor * +cs46xx_add_record_source (struct snd_cs46xx *chip, struct dsp_scb_descriptor * source, + u16 addr, char * scb_name) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * parent; + struct dsp_scb_descriptor * pcm_input; + int insert_point; + + if (snd_BUG_ON(!ins->record_mixer_scb)) + return NULL; + + if (ins->record_mixer_scb->sub_list_ptr != ins->the_null_scb) { + parent = find_next_free_scb (chip,ins->record_mixer_scb->sub_list_ptr); + insert_point = SCB_ON_PARENT_NEXT_SCB; + } else { + parent = ins->record_mixer_scb; + insert_point = SCB_ON_PARENT_SUBLIST_SCB; + } + + pcm_input = cs46xx_dsp_create_pcm_serial_input_scb(chip,scb_name,addr, + source, parent, + insert_point); + + return pcm_input; +} + +int cs46xx_src_unlink(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src) +{ + unsigned long flags; + + if (snd_BUG_ON(!src->parent_scb_ptr)) + return -EINVAL; + + /* mute SCB */ + cs46xx_dsp_scb_set_volume (chip,src,0,0); + + spin_lock_irqsave(&chip->reg_lock, flags); + _dsp_unlink_scb (chip,src); + spin_unlock_irqrestore(&chip->reg_lock, flags); + + return 0; +} + +int cs46xx_src_link(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * parent_scb; + + if (snd_BUG_ON(src->parent_scb_ptr)) + return -EINVAL; + if (snd_BUG_ON(!ins->master_mix_scb)) + return -EINVAL; + + if (ins->master_mix_scb->sub_list_ptr != ins->the_null_scb) { + parent_scb = find_next_free_scb (chip,ins->master_mix_scb->sub_list_ptr); + parent_scb->next_scb_ptr = src; + } else { + parent_scb = ins->master_mix_scb; + parent_scb->sub_list_ptr = src; + } + + src->parent_scb_ptr = parent_scb; + + /* update entry in DSP RAM */ + cs46xx_dsp_spos_update_scb(chip,parent_scb); + + return 0; +} + +int cs46xx_dsp_enable_spdif_out (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if ( ! (ins->spdif_status_out & DSP_SPDIF_STATUS_HW_ENABLED) ) { + cs46xx_dsp_enable_spdif_hw (chip); + } + + /* dont touch anything if SPDIF is open */ + if ( ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN) { + /* when cs46xx_iec958_post_close(...) is called it + will call this function if necessary depending on + this bit */ + ins->spdif_status_out |= DSP_SPDIF_STATUS_OUTPUT_ENABLED; + + return -EBUSY; + } + + if (snd_BUG_ON(ins->asynch_tx_scb)) + return -EINVAL; + if (snd_BUG_ON(ins->master_mix_scb->next_scb_ptr != + ins->the_null_scb)) + return -EINVAL; + + /* reset output snooper sample buffer pointer */ + snd_cs46xx_poke (chip, (ins->ref_snoop_scb->address + 2) << 2, + (OUTPUT_SNOOP_BUFFER + 0x10) << 0x10 ); + + /* The asynch. transfer task */ + ins->asynch_tx_scb = cs46xx_dsp_create_asynch_fg_tx_scb(chip,"AsynchFGTxSCB",ASYNCTX_SCB_ADDR, + SPDIFO_SCB_INST, + SPDIFO_IP_OUTPUT_BUFFER1, + ins->master_mix_scb, + SCB_ON_PARENT_NEXT_SCB); + if (!ins->asynch_tx_scb) return -ENOMEM; + + ins->spdif_pcm_input_scb = cs46xx_dsp_create_pcm_serial_input_scb(chip,"PCMSerialInput_II", + PCMSERIALINII_SCB_ADDR, + ins->ref_snoop_scb, + ins->asynch_tx_scb, + SCB_ON_PARENT_SUBLIST_SCB); + + + if (!ins->spdif_pcm_input_scb) return -ENOMEM; + + /* monitor state */ + ins->spdif_status_out |= DSP_SPDIF_STATUS_OUTPUT_ENABLED; + + return 0; +} + +int cs46xx_dsp_disable_spdif_out (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + /* dont touch anything if SPDIF is open */ + if ( ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN) { + ins->spdif_status_out &= ~DSP_SPDIF_STATUS_OUTPUT_ENABLED; + return -EBUSY; + } + + /* check integrety */ + if (snd_BUG_ON(!ins->asynch_tx_scb)) + return -EINVAL; + if (snd_BUG_ON(!ins->spdif_pcm_input_scb)) + return -EINVAL; + if (snd_BUG_ON(ins->master_mix_scb->next_scb_ptr != ins->asynch_tx_scb)) + return -EINVAL; + if (snd_BUG_ON(ins->asynch_tx_scb->parent_scb_ptr != + ins->master_mix_scb)) + return -EINVAL; + + cs46xx_dsp_remove_scb (chip,ins->spdif_pcm_input_scb); + cs46xx_dsp_remove_scb (chip,ins->asynch_tx_scb); + + ins->spdif_pcm_input_scb = NULL; + ins->asynch_tx_scb = NULL; + + /* clear buffer to prevent any undesired noise */ + _dsp_clear_sample_buffer(chip,SPDIFO_IP_OUTPUT_BUFFER1,256); + + /* monitor state */ + ins->spdif_status_out &= ~DSP_SPDIF_STATUS_OUTPUT_ENABLED; + + + return 0; +} + +int cs46xx_iec958_pre_open (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if ( ins->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED ) { + /* remove AsynchFGTxSCB and PCMSerialInput_II */ + cs46xx_dsp_disable_spdif_out (chip); + + /* save state */ + ins->spdif_status_out |= DSP_SPDIF_STATUS_OUTPUT_ENABLED; + } + + /* if not enabled already */ + if ( !(ins->spdif_status_out & DSP_SPDIF_STATUS_HW_ENABLED) ) { + cs46xx_dsp_enable_spdif_hw (chip); + } + + /* Create the asynch. transfer task for playback */ + ins->asynch_tx_scb = cs46xx_dsp_create_asynch_fg_tx_scb(chip,"AsynchFGTxSCB",ASYNCTX_SCB_ADDR, + SPDIFO_SCB_INST, + SPDIFO_IP_OUTPUT_BUFFER1, + ins->master_mix_scb, + SCB_ON_PARENT_NEXT_SCB); + + + /* set spdif channel status value for streaming */ + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, ins->spdif_csuv_stream); + + ins->spdif_status_out |= DSP_SPDIF_STATUS_PLAYBACK_OPEN; + + return 0; +} + +int cs46xx_iec958_post_close (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(!ins->asynch_tx_scb)) + return -EINVAL; + + ins->spdif_status_out &= ~DSP_SPDIF_STATUS_PLAYBACK_OPEN; + + /* restore settings */ + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, ins->spdif_csuv_default); + + /* deallocate stuff */ + if (ins->spdif_pcm_input_scb != NULL) { + cs46xx_dsp_remove_scb (chip,ins->spdif_pcm_input_scb); + ins->spdif_pcm_input_scb = NULL; + } + + cs46xx_dsp_remove_scb (chip,ins->asynch_tx_scb); + ins->asynch_tx_scb = NULL; + + /* clear buffer to prevent any undesired noise */ + _dsp_clear_sample_buffer(chip,SPDIFO_IP_OUTPUT_BUFFER1,256); + + /* restore state */ + if ( ins->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED ) { + cs46xx_dsp_enable_spdif_out (chip); + } + + return 0; +} |