# --- T2-COPYRIGHT-NOTE-BEGIN --- # T2 SDE: architecture/mips64/package/*/5403_ip30-snd-rad1.patch # Copyright (C) 2024 The T2 SDE Project # # This Copyright note is generated by scripts/Create-CopyPatch, # more information can be found in the files COPYING and README. # # This patch file is dual-licensed. It is available under the license the # patched project is licensed under, as long as it is an OpenSource license # as defined at http://www.opensource.org/ (e.g. BSD, X11) or under the terms # of the GNU General Public License version 2 as used by the T2 SDE. # --- T2-COPYRIGHT-NOTE-END --- --- linux-6.9/include/linux/pci_ids.h.vanilla 2024-06-23 14:45:08.881974819 +0200 +++ linux-6.9/include/linux/pci_ids.h 2024-06-23 14:45:45.908972100 +0200 @@ -1108,8 +1108,9 @@ #define PCI_VENDOR_ID_SGI 0x10a9 #define PCI_DEVICE_ID_SGI_IOC3 0x0003 +#define PCI_DEVICE_ID_SGI_RAD1 0x0005 #define PCI_DEVICE_ID_SGI_LITHIUM 0x1002 #define PCI_VENDOR_ID_WINBOND 0x10ad #define PCI_DEVICE_ID_WINBOND_82C105 0x0105 #define PCI_DEVICE_ID_WINBOND_83C553 0x0565 diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 4105d9f653d9..f2ddbdb2d4dc 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -907,6 +907,12 @@ config SND_YMFPCI To compile this driver as a module, choose M here: the module will be called snd-ymfpci. +config SND_RAD1 + tristate "SGI RAD1" + depends on SND && SGI_IP30 + help + Say 'Y' or 'M' to include support for SGI RAD1 Pro Audio in Octane. + endif # SND_PCI source "sound/pci/hda/Kconfig" --- linux-6.9/arch/mips/pci/pci-xtalk-bridge.c.vanilla 2024-06-23 14:48:27.655960221 +0200 +++ linux-6.9/arch/mips/pci/pci-xtalk-bridge.c 2024-06-23 14:49:02.163957687 +0200 @@ -105,6 +105,8 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_SGI, PCI_DEVICE_ID_SGI_IOC3, bridge_disable_swapping); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SGI, PCI_DEVICE_ID_SGI_RAD1, + bridge_disable_swapping); /* * The Bridge ASIC supports both type 0 and type 1 access. Type 1 is diff --git a/sound/pci/Makefile b/sound/pci/Makefile index 04cac7469139..9186ff609192 100644 --- a/sound/pci/Makefile +++ b/sound/pci/Makefile @@ -22,6 +22,7 @@ snd-intel8x0-y := intel8x0.o snd-intel8x0m-y := intel8x0m.o snd-maestro3-y := maestro3.o +snd-rad1-objs := rad1.o snd-rme32-y := rme32.o snd-rme96-y := rme96.o snd-sis7019-y := sis7019.o @@ -48,6 +49,7 @@ obj-$(CONFIG_SND_FM801) += snd-fm801.o obj-$(CONFIG_SND_INTEL8X0) += snd-intel8x0.o obj-$(CONFIG_SND_INTEL8X0M) += snd-intel8x0m.o obj-$(CONFIG_SND_MAESTRO3) += snd-maestro3.o +obj-$(CONFIG_SND_RAD1) += snd-rad1.o obj-$(CONFIG_SND_RME32) += snd-rme32.o obj-$(CONFIG_SND_RME96) += snd-rme96.o obj-$(CONFIG_SND_SIS7019) += snd-sis7019.o diff --git a/include/sound/rad1.h b/include/sound/rad1.h new file mode 100644 index 000000000000..3acbb6cd36f2 --- /dev/null +++ b/include/sound/rad1.h @@ -0,0 +1,120 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2004-2007 Stanislaw Skowronek + * Copyright (C) 2018-2024 René Rebe + */ + +#ifndef _SOUND_RAD1_H +#define _SOUND_RAD1_H + +#include + +#define RAD1_ADATRX 0 +#define RAD1_AESRX 1 +#define RAD1_ADC 2 +#define RAD1_ADATSUBRX 3 +#define RAD1_AESSUBRX 4 +#define RAD1_ADATTX 5 +#define RAD1_AESTX 6 +#define RAD1_DAC 7 +#define RAD1_STATUS 8 + +struct rad1regs { + u32 pci_status; /* 0x00000000 */ + u32 adat_rx_msc_ust; /* 0x00000004 */ + u32 adat_rx_msc0_submsc; /* 0x00000008 */ + u32 aes_rx_msc_ust; /* 0x0000000c */ + u32 aes_rx_msc0_submsc; /* 0x00000010 */ + u32 adc_msc_ust; /* 0x00000014 */ + u32 adc_msc0_submsc; /* 0x00000018 */ + u32 adat_tx_msc_ust; /* 0x0000001c */ + u32 adat_tx_msc0_submsc; /* 0x00000020 */ + u32 aes_tx_msc_ust; /* 0x00000024 */ + u32 aes_tx_msc0_submsc; /* 0x00000028 */ + u32 dac_msc_ust; /* 0x0000002c */ + u32 ust_register; /* 0x00000030 */ + u32 gpio_status; /* 0x00000034 */ + u32 chip_status1; /* 0x00000038 */ + u32 chip_status0; /* 0x0000003c */ + + u32 ust_clock_control; /* 0x00000040 */ + u32 adat_rx_control; /* 0x00000044 */ + u32 aes_rx_control; /* 0x00000048 */ + u32 adc_control; /* 0x0000004c */ + u32 adat_tx_control; /* 0x00000050 */ + u32 aes_tx_control; /* 0x00000054 */ + u32 dac_control; /* 0x00000058 */ + u32 status_timer; /* 0x0000005c */ + + u32 _pad70[4]; + + u32 misc_control; /* 0x00000070 */ + u32 pci_holdoff; /* 0x00000074 */ + u32 pci_arb_control; /* 0x00000078 */ + + u32 volume_control; /* 0x0000007c */ + + u32 reset; /* 0x00000080 */ + + u32 gpio0; /* 0x00000084 */ + u32 gpio1; /* 0x00000088 */ + u32 gpio2; /* 0x0000008c */ + u32 gpio3; /* 0x00000090 */ + + u32 _pada0[3]; + + u32 clockgen_ictl; /* 0x000000a0 */ + u32 clockgen_rem; /* 0x000000a4 */ + u32 freq_synth_mux_sel[4]; /* 0x000000a8 */ + u32 mpll0_lock_control; /* 0x000000b8 */ + u32 mpll1_lock_control; /* 0x000000bc */ + + u32 _pad400[208]; + + /* descriptor RAM */ + struct { + u32 loadr; /* 0x00000400 + 12*idx */ + u32 hiadr; /* 0x00000404 + 12*idx */ + u32 control; /* 0x00000408 + 12*idx */ + } pci_descr[16]; + + /* running descriptors */ + struct { + u32 loadr; + u32 control; + } pci_lc[9]; + u32 pci_hiadr[9]; + + u32 _pad1000[693]; + + u32 adat_subcode_txa_u[24]; /* 0x00001000 */ + u32 adat_subcode_txa_unused; /* 0x00001060 */ + + u32 _pad1080[7]; + + u32 adat_subcode_txb_u[24]; /* 0x00001080 */ + u32 adat_subcode_txb_unused; /* 0x000010e0 */ + + u32 _pad1100[7]; + + u32 aes_subcode_txa_lu[6]; /* 0x00001100 */ + u32 aes_subcode_txa_lc[6]; /* 0x00001118 */ + u32 aes_subcode_txa_lv[6]; /* 0x00001130 */ + u32 aes_subcode_txa_ru[6]; /* 0x00001148 */ + u32 aes_subcode_txa_rc[6]; /* 0x00001160 */ + u32 aes_subcode_txa_rv0[2]; /* 0x00001178 */ + u32 aes_subcode_txb_lu[6]; /* 0x00001180 */ + u32 aes_subcode_txb_lc[6]; /* 0x00001198 */ + u32 aes_subcode_txb_lv[6]; /* 0x000011b0 */ + u32 aes_subcode_txb_ru[6]; /* 0x000011c8 */ + u32 aes_subcode_txb_rc[6]; /* 0x000011e0 */ + u32 aes_subcode_txb_rv0[2]; /* 0x000011f8 */ + u32 aes_subcode_txa_rv2[4]; /* 0x00001200 */ + u32 aes_subcode_txb_rv2[4]; /* 0x00001210 */ + u32 aes_subcode_tx_unused; /* 0x00001220 */ +}; + +#endif /* _SOUND_RAD1_H */ diff --git a/sound/pci/rad1.c b/sound/pci/rad1.c new file mode 100644 index 000000000000..24318b1c9187 --- /dev/null +++ b/sound/pci/rad1.c @@ -0,0 +1,1528 @@ +/* + * rad1.c - ALSA driver for SGI RAD1 (as found in Octane and Octane2) + * Copyright (C) 2004-2007 by Stanislaw Skowronek + * Copyright (C) 2018-2024 René Rebe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +struct snd_rad1_pipe { + unsigned long pma; /* physical addr of the ring */ + int *vma; /* virtual addr of the ring */ + struct snd_pcm_substream *subs; /* ALSA substream */ + struct snd_pcm *pcm; + unsigned int pnum; /* number of periods */ + unsigned int plen; /* length of period (in bytes) */ + unsigned int hptr; /* hardware pointer */ + int adma; /* DMA active flag */ + unsigned int qptr; /* queue pointer */ +}; + +struct snd_rad1 { + spinlock_t lock; + struct snd_card *card; + struct pci_dev *pci; + unsigned long mmio_phys; + volatile struct rad1regs *mmio; + int timer_active; + struct timer_list timer; + struct snd_rad1_pipe pipes[9]; + + /* random stuff */ + int last_aesrx_rate; + + /* card controls */ + unsigned int attctrl; /* Attenuation control */ + unsigned int rt_adc; /* ADC routing */ + unsigned int rt_aesr; /* AES Rx routing */ + unsigned int rt_adat; /* ADAT Rx routing */ + unsigned int rt_opto; /* Optical Out routing */ +}; + +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* Hardware initialization, DMa, Pipe setup */ +static void +snd_rad1_hw_init(struct snd_rad1 *chip) +{ + /* The hex values listed here are, for the most part, unknown values + * determined by running portions of the IRIX kernel inside of Linux + * as a userland application and then extracting the run-time info. + * + * We could define them via macros if we wanted, but the macro names + * would be no more intelligible as RAD1_ANCIENT_MU_* than they are + * simple hex numbers, so until the purpose of each value is known, + * they shall remain as simple hex numbers. + * + * The same applies for pretty much any other hex number found in + * driver that isn't a bit mask or some sort. One day, we may figure + * it all out, and create an appropriate header file to define them + * all as intelligible macros. + */ + volatile struct rad1regs *mmio = chip->mmio; + + mmio->reset = 0xffffffff; + udelay(1000); + mmio->reset = 0xffe3cffe; + mmio->pci_holdoff = 0x08000010; + mmio->pci_arb_control = 0x00fac688; + + /* I/O routing */ + /* Mike 03000000; LineIn 05000000 */ + mmio->adc_control = 0x03000000; + chip->rt_adc = 0x03000000; + + /* Default */ + mmio->dac_control = 0x20000000; + + /* Optical In 00000018; AES In 00000010 */ + mmio->aes_rx_control = 0x00000018; + chip->rt_aesr = 0x00000018; + + /* Default */ + mmio->aes_tx_control = 0x40000000; + + /* Disabled a0000000; Optical In a0000018 */ + mmio->adat_rx_control = 0xa0000000; + chip->rt_adat = 0xa0000000; + + /* Default */ + mmio->adat_tx_control = 0x20000000; + mmio->gpio3 = 0x00000002; + mmio->misc_control = 0x00001500; + mmio->mpll0_lock_control = 0x9fffffff; + mmio->mpll1_lock_control = 0x9fffffff; + mmio->reset = 0xffe3c0fe; + udelay(1000); + mmio->clockgen_ictl = 0x02000001; + mmio->reset = 0xffe24070; + udelay(1000); + mmio->reset = 0xffe20000; + mmio->gpio2 = 0x00000002; + mmio->volume_control = 0xd6d6d6d6; + chip->attctrl = 0xd6d6d6d6; + udelay(1000); + + /* AES-Optical Out 00001040; AES-AES Out 00001440 */ + mmio->misc_control = 0x00001040; + chip->rt_opto = 0x00001040; + mmio->reset = 0xffe20100; + mmio->freq_synth_mux_sel[3] = 0x00000001; + mmio->clockgen_rem = 0x0000ffff; + mmio->clockgen_ictl = 0x10000603; + mmio->reset = 0xffe20000; + mmio->reset = 0xffe20200; + mmio->freq_synth_mux_sel[2] = 0x00000001; + mmio->clockgen_rem = 0x0000ffff; + mmio->clockgen_ictl = 0x20000603; + mmio->reset = 0xffe20000; + mmio->reset = 0xffe20400; + mmio->freq_synth_mux_sel[1] = 0x00000001; + mmio->clockgen_rem = 0x0000ffff; + mmio->clockgen_ictl = 0x40000603; + mmio->reset = 0xffe20000; + mmio->reset = 0xffe20800; + mmio->freq_synth_mux_sel[0] = 0x00000001; + mmio->clockgen_rem = 0x0000ffff; + mmio->clockgen_ictl = 0x80000603; + mmio->reset = 0xffe20000; + mmio->gpio1 = 0x00000003; + udelay(10000); +} + +static void +snd_rad1_setup_dma_pipe(struct snd_rad1 *chip, int pidx) +{ + struct snd_rad1_pipe *pipe = (chip->pipes + pidx); + volatile struct rad1regs *mmio = chip->mmio; + + if ((-pipe->pnum * pipe->plen) & 0x7f) + printk(KERN_WARNING "rad1: pipe %d has unaligned size %d\n", + pidx, (pipe->pnum * pipe->plen)); + + mmio->pci_descr[pidx].hiadr = (pipe->pma >> 32); + mmio->pci_descr[pidx].loadr = (pipe->pma & 0xffffffff); + mmio->pci_descr[pidx].control = (((-pipe->pnum * pipe->plen) & + 0xffffff80) | (pidx << 3)); + + mmio->pci_hiadr[pidx] = (pipe->pma >> 32); + mmio->pci_lc[pidx].loadr = (pipe->pma & 0xffffffff); + mmio->pci_lc[pidx].control = (((-pipe->pnum * pipe->plen) & + 0xffffff80) | (pidx << 3)); +} + +static void +snd_rad1_activate_timer(struct snd_rad1 *chip) +{ + if (!chip->timer_active) { + chip->timer.expires = (jiffies + 1); + add_timer(&chip->timer); + chip->timer_active = 1; + } +} + +static void +snd_rad1_run_pipe(struct snd_rad1 *chip, int pidx, int adma) +{ + struct snd_rad1_pipe *pipe = (chip->pipes + pidx); + volatile struct rad1regs *mmio = chip->mmio; + + if (pipe->adma != adma) { + pipe->adma = adma; + + switch (pidx) { + case RAD1_ADC: + mmio->adc_control = (chip->rt_adc | adma); + break; + case RAD1_DAC: + mmio->dac_control = (0x20000000 | adma); + break; + case RAD1_AESRX: + mmio->aes_rx_control = (chip->rt_aesr | adma); + break; + case RAD1_AESTX: + mmio->aes_tx_control = (0x40000000 | adma); + break; + } + } + + if (adma) + snd_rad1_activate_timer(chip); +} + +static void +snd_rad1_poll_pipe(struct snd_rad1 *chip, int pidx, int is_tx) +{ + struct snd_rad1_pipe *pipe = (chip->pipes + pidx); + unsigned long flags; + unsigned int hptr = (pipe->pnum * pipe->plen) + + (chip->mmio->pci_lc[pidx].control & 0xffffff80); + + spin_lock_irqsave(&chip->lock, flags); + if (pipe->adma && pipe->subs) { + /* use hardware pointer to detect period crossing */ + if ((hptr / pipe->plen) != (pipe->hptr / pipe->plen)) { + if (is_tx) + pipe->qptr = (hptr / 8); + else + pipe->qptr = (pipe->hptr / 8); + spin_unlock_irqrestore(&chip->lock, flags); + snd_pcm_period_elapsed(pipe->subs); + spin_lock_irqsave(&chip->lock, flags); + } + pipe->hptr = hptr; + } + spin_unlock_irqrestore(&chip->lock, flags); +} + +static void +snd_rad1_poll_timer(struct timer_list *chip_virt) +{ + struct snd_rad1 *chip = from_timer(chip, chip_virt, timer); + int adma = 0; + + if (chip->pipes[RAD1_ADC].adma) { + snd_rad1_poll_pipe(chip, RAD1_ADC, 0); + adma = 1; + } + + if (chip->pipes[RAD1_DAC].adma) { + snd_rad1_poll_pipe(chip, RAD1_DAC, 1); + adma = 1; + } + + if (chip->pipes[RAD1_AESRX].adma) { + snd_rad1_poll_pipe(chip, RAD1_AESRX, 0); + adma = 1; + } + + if (chip->pipes[RAD1_AESTX].adma) { + snd_rad1_poll_pipe(chip, RAD1_AESTX, 1); + adma = 1; + } + + if (adma) { + chip->timer.expires = (jiffies + 1); + add_timer(&chip->timer); + } else + chip->timer_active = 0; +} + +static int +snd_rad1_free_pipe(struct snd_pcm_substream *substream, int pidx) +{ + struct snd_rad1 *chip = snd_pcm_substream_chip(substream); + struct snd_rad1_pipe *pipe = (chip->pipes + pidx); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + snd_rad1_run_pipe(chip, pidx, 0); + pipe->subs = NULL; + spin_unlock_irqrestore(&chip->lock, flags); + + return snd_pcm_lib_free_pages(substream); +} + +static inline long +snd_rad1_gcd(long x, long y) +{ + long t; + + if (x < y) { + t = x; + x = y; + y = t; + } + + while (y) { + y = x % (t = y); + x = t; + } + + return x; +} + +static void +snd_rad1_red_frac(long *n, long *d, long max) +{ + long gcd = snd_rad1_gcd(*n, *d); + + if (unlikely(!gcd)) + return; + + *n /= gcd; + *d /= gcd; + + /* XXX: Lose precision [sic] */ + while ((*n > max) || (*d > max)) { + *n >>= 1; + *d >>= 1; + } +} + +static void +snd_rad1_set_aestx_subcode(struct snd_rad1 *chip, unsigned char *sub_lc, + unsigned char *sub_rc) +{ + volatile struct rad1regs *mmio = chip->mmio; + unsigned int i, j, lc[6], rc[6]; + + for (i = 0; i < 6; i++) { + lc[i] = 0; + rc[i] = 0; + for (j = 0; j < 4; j++) { + lc[i] |= (sub_lc[i * 4 + j] << (j << 3)); + rc[i] |= (sub_rc[i * 4 + j] << (j << 3)); + } + } + + for (i = 0; i < 6; i++) { + mmio->aes_subcode_txa_lu[i] = 0x00000000; + mmio->aes_subcode_txa_lc[i] = lc[i]; + mmio->aes_subcode_txa_lv[i] = 0x00000000; + mmio->aes_subcode_txa_ru[i] = 0x00000000; + mmio->aes_subcode_txa_rc[i] = rc[i]; + mmio->aes_subcode_txb_lu[i] = 0x00000000; + mmio->aes_subcode_txb_lc[i] = lc[i]; + mmio->aes_subcode_txb_lv[i] = 0x00000000; + mmio->aes_subcode_txb_ru[i] = 0x00000000; + mmio->aes_subcode_txb_rc[i] = rc[i]; + } + + for (i = 0; i < 2; i++) { + mmio->aes_subcode_txa_rv0[i] = 0x00000000; + mmio->aes_subcode_txb_rv0[i] = 0x00000000; + } + + for (i = 0; i < 4; i++) { + mmio->aes_subcode_txa_rv2[i] = 0x00000000; + mmio->aes_subcode_txb_rv2[i] = 0x00000000; + } +} + +static void +snd_rad1_genset_aestx_subcode(struct snd_rad1 *chip, int rate) +{ + int i; + unsigned char lc[24], rc[24]; + + for (i = 0; i < 24; i++) { + lc[i] = 0x00; + rc[i] = 0x00; + } + + lc[0] = 0x04; /* PRO=0, !AUDIO=0, COPY=1, PRE=000, MODE=00 */ + rc[0] = 0x04; + lc[1] = 0x01; /* Laser Optical, CD IEC-908 */ + rc[1] = 0x01; + lc[2] = 0x10; /* SOURCE=0000, CHANNEL=0001 */ + rc[2] = 0x20; /* SOURCE=0000, CHANNEL=0010 */ + + /* + * RAD1 systems have generally decent clock sources, + * so we mark them as Level I + */ + switch (rate) { + case 32000: + lc[3] = 0x0c; /* Level I, 32 kHz */ + rc[3] = 0x0c; + break; + case 44100: + lc[3] = 0x00; /* Level I, 44.1 kHz */ + rc[3] = 0x00; + break; + case 48000: + lc[3] = 0x04; /* Level I, 48 kHz */ + rc[3] = 0x04; + break; + default: + /* Not a valid IEC-958 sample rate */ + lc[3] = 0x10; /* Level III, 44.1 kHz */ + rc[3] = 0x10; + } + snd_rad1_set_aestx_subcode(chip, lc, rc); +} + +static void +snd_rad1_set_cg(struct snd_rad1 *chip, int cg, long rate, + long base, unsigned muxsel) +{ + volatile struct rad1regs *mmio = chip->mmio; + long div, rem; + unsigned flags; + + snd_rad1_red_frac(&base, &rate, 0xffff); + div = (base / rate); + rem = (base % rate); + snd_rad1_red_frac(&rem, &rate, 0xffff); + flags = (((rem * 2) < rate) ? 0x600 : 0x200); + + mmio->reset = 0xffe20000 | (0x100 << cg); + mmio->freq_synth_mux_sel[3 - cg] = muxsel; + mmio->clockgen_rem = (rem << 16) | (0x10000 - rate); + mmio->clockgen_ictl = (flags | (0x10000000 << cg) | (div - 1)); + mmio->reset = 0xffe20000; +} + +/* Select best master clock source for low jitter */ +static inline void +snd_rad1_set_cgms(struct snd_rad1 *chip, int cg, long rate) +{ + if (!(176400 % rate)) + snd_rad1_set_cg(chip, cg, rate, 176400, 0); + else + snd_rad1_set_cg(chip, cg, rate, 192000, 1); +} + +static inline void +snd_rad1_setrate_pipe(struct snd_rad1 *chip, int pidx, int rate) +{ + if (pidx == RAD1_ADC) + snd_rad1_set_cgms(chip, 0, rate); + + if (pidx == RAD1_DAC) + snd_rad1_set_cgms(chip, 1, rate); + + if (pidx == RAD1_AESTX) { + snd_rad1_set_cgms(chip, 2, rate); + snd_rad1_genset_aestx_subcode(chip, rate); + } +} + +static int +snd_rad1_prepare_pipe(struct snd_pcm_substream *substream, int pidx) +{ + struct snd_rad1 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_rad1_pipe *pipe = (chip->pipes + pidx); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + snd_rad1_run_pipe(chip, pidx, 0); + spin_unlock_irqrestore(&chip->lock, flags); + + pipe->subs = substream; + pipe->vma = (int *)runtime->dma_area; + pipe->pma = runtime->dma_addr; + pipe->pnum = runtime->periods; + pipe->plen = frames_to_bytes(runtime, runtime->period_size); + + snd_rad1_setrate_pipe(chip, pidx, runtime->rate); + + pipe->hptr = 0; + pipe->qptr = 0; + snd_rad1_setup_dma_pipe(chip, pidx); + + return 0; +} + +static void +snd_rad1_detect_aesrx_rate(struct snd_rad1 *chip, struct snd_pcm_hardware *hw) +{ + int rate; + unsigned sc = (((chip->mmio->chip_status0) >> 24) & 7); + static int rates[8] = {0, 48000, 44100, 32000, 48000, + 44100, 44056, 32000}; + + if (!rates[sc]) { + printk(KERN_INFO "rad1: Warning: Recording from an unlocked " + "IEC958 source. Assuming sample rate: %d.\n", + chip->last_aesrx_rate); + rate = chip->last_aesrx_rate; + } else + rate = rates[sc]; + + chip->last_aesrx_rate = rate; + hw->rate_min = rate; + hw->rate_max = rate; + + switch (rate) { + case 32000: + hw->rates = SNDRV_PCM_RATE_32000; + break; + case 44056: + hw->rates = SNDRV_PCM_RATE_CONTINUOUS; + break; + case 48000: + hw->rates = SNDRV_PCM_RATE_48000; + break; + case 44100: + default: + hw->rates = SNDRV_PCM_RATE_44100; + break; + } +} + +static int +snd_rad1_trigger_pipe(struct snd_pcm_substream *substream, int pidx, int cmd) +{ + struct snd_rad1 *chip = snd_pcm_substream_chip(substream); + int result = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + snd_rad1_run_pipe(chip, pidx, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + snd_rad1_run_pipe(chip, pidx, 0); + break; + default: + result = -EINVAL; + } + + return result; +} + +static snd_pcm_uframes_t +snd_rad1_ptr_pipe(struct snd_pcm_substream *substream, int pidx) +{ + struct snd_rad1 *chip = snd_pcm_substream_chip(substream); + + return chip->pipes[pidx].qptr; +} + +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* ADC pipe */ +static struct snd_pcm_hardware +snd_rad1_adc_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .formats = SNDRV_PCM_FMTBIT_S24_BE, + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 1048576, + .period_bytes_min = 4096, + .period_bytes_max = 4096, + .periods_min = 4, + .periods_max = 256, +}; + +static int +snd_rad1_adc_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw = snd_rad1_adc_hw; + + return 0; +} + +static int +snd_rad1_adc_close(struct snd_pcm_substream *substream) +{ + struct snd_rad1 *chip = snd_pcm_substream_chip(substream); + + snd_rad1_run_pipe(chip, RAD1_ADC, 0); + + return 0; +} + +static int +snd_rad1_adc_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int +snd_rad1_adc_free(struct snd_pcm_substream *substream) +{ + return snd_rad1_free_pipe(substream, RAD1_ADC); +} + +static int +snd_rad1_adc_prepare(struct snd_pcm_substream *substream) +{ + return snd_rad1_prepare_pipe(substream, RAD1_ADC); +} + +static int +snd_rad1_adc_trigger(struct snd_pcm_substream *substream, int cmd) +{ + return snd_rad1_trigger_pipe(substream, RAD1_ADC, cmd); +} + +static snd_pcm_uframes_t +snd_rad1_adc_ptr(struct snd_pcm_substream *substream) +{ + return snd_rad1_ptr_pipe(substream, RAD1_ADC); +} + +static struct snd_pcm_ops +snd_rad1_adc_ops = { + .open = snd_rad1_adc_open, + .close = snd_rad1_adc_close, + .hw_params = snd_rad1_adc_params, + .hw_free = snd_rad1_adc_free, + .prepare = snd_rad1_adc_prepare, + .trigger = snd_rad1_adc_trigger, + .pointer = snd_rad1_adc_ptr, + .ioctl = snd_pcm_lib_ioctl, +}; + +static void +snd_rad1_adc_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int +snd_rad1_new_adc(struct snd_rad1 *chip, int dev) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "RAD1 ADC", dev, 0, 1, &pcm); + if (unlikely(err < 0)) + return err; + + pcm->private_data = chip; + pcm->private_free = snd_rad1_adc_pcm_free; + strscpy(pcm->name, "RAD1 ADC", sizeof(pcm->name)); + chip->pipes[RAD1_ADC].pcm = pcm; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_rad1_adc_ops); + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + 64*1024, 64*1024); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* DAC pipe */ +static struct +snd_pcm_hardware snd_rad1_dac_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .formats = SNDRV_PCM_FMTBIT_S24_BE, + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 1048576, + .period_bytes_min = 4096, + .period_bytes_max = 4096, + .periods_min = 4, + .periods_max = 256, +}; + +static int +snd_rad1_dac_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw = snd_rad1_dac_hw; + + return 0; +} + +static int +snd_rad1_dac_close(struct snd_pcm_substream *substream) +{ + struct snd_rad1 *chip = snd_pcm_substream_chip(substream); + + snd_rad1_run_pipe(chip, RAD1_DAC, 0); + + return 0; +} + +static int +snd_rad1_dac_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int +snd_rad1_dac_free(struct snd_pcm_substream *substream) +{ + return snd_rad1_free_pipe(substream, RAD1_DAC); +} + +static int +snd_rad1_dac_prepare(struct snd_pcm_substream *substream) +{ + return snd_rad1_prepare_pipe(substream, RAD1_DAC); +} + +static int +snd_rad1_dac_trigger(struct snd_pcm_substream *substream, int cmd) +{ + return snd_rad1_trigger_pipe(substream, RAD1_DAC, cmd); +} + +static snd_pcm_uframes_t +snd_rad1_dac_ptr(struct snd_pcm_substream *substream) +{ + return snd_rad1_ptr_pipe(substream, RAD1_DAC); +} + +static struct snd_pcm_ops +snd_rad1_dac_ops = { + .open = snd_rad1_dac_open, + .close = snd_rad1_dac_close, + .hw_params = snd_rad1_dac_params, + .hw_free = snd_rad1_dac_free, + .prepare = snd_rad1_dac_prepare, + .trigger = snd_rad1_dac_trigger, + .pointer = snd_rad1_dac_ptr, + .ioctl = snd_pcm_lib_ioctl, +}; + +static void +snd_rad1_dac_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int +snd_rad1_new_dac(struct snd_rad1 *chip, int dev) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "RAD1 DAC", dev, 1, 0, &pcm); + if (unlikely(err < 0)) + return err; + + pcm->private_data = chip; + pcm->private_free = snd_rad1_dac_pcm_free; + strscpy(pcm->name, "RAD1 DAC", sizeof(pcm->name)); + chip->pipes[RAD1_DAC].pcm = pcm; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_rad1_dac_ops); + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + 64*1024, 64*1024); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* AESRX pipe */ +static struct snd_pcm_hardware +snd_rad1_aesrx_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .formats = SNDRV_PCM_FMTBIT_S24_BE, + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 1048576, + .period_bytes_min = 4096, + .period_bytes_max = 4096, + .periods_min = 4, + .periods_max = 256, +}; + +static int +snd_rad1_aesrx_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_rad1 *chip = snd_pcm_substream_chip(substream); + + runtime->hw = snd_rad1_aesrx_hw; + snd_rad1_detect_aesrx_rate(chip, &runtime->hw); + + return 0; +} + +static int +snd_rad1_aesrx_close(struct snd_pcm_substream *substream) +{ + struct snd_rad1 *chip = snd_pcm_substream_chip(substream); + + snd_rad1_run_pipe(chip, RAD1_AESRX, 0); + + return 0; +} + +static int +snd_rad1_aesrx_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int +snd_rad1_aesrx_free(struct snd_pcm_substream *substream) +{ + return snd_rad1_free_pipe(substream, RAD1_AESRX); +} + +static int +snd_rad1_aesrx_prepare(struct snd_pcm_substream *substream) +{ + return snd_rad1_prepare_pipe(substream, RAD1_AESRX); +} + +static int +snd_rad1_aesrx_trigger(struct snd_pcm_substream *substream, int cmd) +{ + return snd_rad1_trigger_pipe(substream, RAD1_AESRX, cmd); +} + +static snd_pcm_uframes_t +snd_rad1_aesrx_ptr(struct snd_pcm_substream *substream) +{ + return snd_rad1_ptr_pipe(substream, RAD1_AESRX); +} + +static struct snd_pcm_ops +snd_rad1_aesrx_ops = { + .open = snd_rad1_aesrx_open, + .close = snd_rad1_aesrx_close, + .hw_params = snd_rad1_aesrx_params, + .hw_free = snd_rad1_aesrx_free, + .prepare = snd_rad1_aesrx_prepare, + .trigger = snd_rad1_aesrx_trigger, + .pointer = snd_rad1_aesrx_ptr, + .ioctl = snd_pcm_lib_ioctl, +}; + +static void +snd_rad1_aesrx_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int +snd_rad1_new_aesrx(struct snd_rad1 *chip, int dev) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "RAD1 AES Rx", dev, 0, 1, &pcm); + if (unlikely(err < 0)) + return err; + + pcm->private_data = chip; + pcm->private_free = snd_rad1_aesrx_pcm_free; + strscpy(pcm->name, "RAD1 AES Rx", sizeof(pcm->name)); + chip->pipes[RAD1_AESRX].pcm = pcm; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_rad1_aesrx_ops); + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + 64*1024, 64*1024); + chip->last_aesrx_rate = 44100; + + return 0; +} + +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* AESTX pipe */ +static struct snd_pcm_hardware +snd_rad1_aestx_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .formats = SNDRV_PCM_FMTBIT_S24_BE, + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 1048576, + .period_bytes_min = 4096, + .period_bytes_max = 4096, + .periods_min = 4, + .periods_max = 256, +}; + +static int +snd_rad1_aestx_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw = snd_rad1_aestx_hw; + + return 0; +} + +static int +snd_rad1_aestx_close(struct snd_pcm_substream *substream) +{ + struct snd_rad1 *chip = snd_pcm_substream_chip(substream); + + snd_rad1_run_pipe(chip, RAD1_AESTX, 0); + + return 0; +} + +static int +snd_rad1_aestx_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int +snd_rad1_aestx_free(struct snd_pcm_substream *substream) +{ + return snd_rad1_free_pipe(substream, RAD1_AESTX); +} + +static int +snd_rad1_aestx_prepare(struct snd_pcm_substream *substream) +{ + return snd_rad1_prepare_pipe(substream, RAD1_AESTX); +} + +static int +snd_rad1_aestx_trigger(struct snd_pcm_substream *substream, int cmd) +{ + return snd_rad1_trigger_pipe(substream, RAD1_AESTX, cmd); +} + +static snd_pcm_uframes_t +snd_rad1_aestx_ptr(struct snd_pcm_substream *substream) +{ + return snd_rad1_ptr_pipe(substream, RAD1_AESTX); +} + +static struct snd_pcm_ops +snd_rad1_aestx_ops = { + .open = snd_rad1_aestx_open, + .close = snd_rad1_aestx_close, + .hw_params = snd_rad1_aestx_params, + .hw_free = snd_rad1_aestx_free, + .prepare = snd_rad1_aestx_prepare, + .trigger = snd_rad1_aestx_trigger, + .pointer = snd_rad1_aestx_ptr, + .ioctl = snd_pcm_lib_ioctl, +}; + +static void +snd_rad1_aestx_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int +snd_rad1_new_aestx(struct snd_rad1 *chip, int dev) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "RAD1 AES Tx", dev, 1, 0, &pcm); + if (unlikely(err < 0)) + return err; + + pcm->private_data = chip; + pcm->private_free = snd_rad1_aestx_pcm_free; + strscpy(pcm->name, "RAD1 AES Tx", sizeof(pcm->name)); + chip->pipes[RAD1_AESTX].pcm = pcm; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_rad1_aestx_ops); + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + 64*1024, 64*1024); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* Volume control */ +static int +snd_rad1_control_pv_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 = 255; + + return 0; +} + +static int +snd_rad1_control_pv_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + int shift = (kcontrol->private_value * 16); + unsigned long flags; + struct snd_rad1 *chip = snd_kcontrol_chip(kcontrol); + + spin_lock_irqsave(&chip->lock, flags); + u->value.integer.value[0] = ((chip->attctrl >> shift) & 0xff); + u->value.integer.value[1] = ((chip->attctrl >> (8 + shift)) & 0xff); + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static int +snd_rad1_control_pv_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + int change = 0; + int shift = (kcontrol->private_value * 16); + unsigned long flags; + struct snd_rad1 *chip = snd_kcontrol_chip(kcontrol); + + spin_lock_irqsave(&chip->lock, flags); + if (u->value.integer.value[0] != ((chip->attctrl >> shift) & 0xff)) + change = 1; + + if (u->value.integer.value[1] != ((chip->attctrl >> (8 + shift)) & 0xff)) + change = 1; + + if (change) { + chip->attctrl &= (0xffff << (16 - shift)); + chip->attctrl |= (u->value.integer.value[0] << shift); + chip->attctrl |= (u->value.integer.value[1] << (8 + shift)); + chip->mmio->volume_control = chip->attctrl; + } + spin_unlock_irqrestore(&chip->lock, flags); + + return change; +} + +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* AES Tx route control */ +static int +snd_rad1_control_tr_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static char *rts[2] = {"Optical", "Coaxial"}; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 2; + + if (uinfo->value.enumerated.item > 1) + uinfo->value.enumerated.item = 1; + + strscpy(uinfo->value.enumerated.name, + rts[uinfo->value.enumerated.item], + sizeof(uinfo->value.enumerated.name)); + + return 0; +} + +static int +snd_rad1_control_tr_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + unsigned long flags; + struct snd_rad1 *chip = snd_kcontrol_chip(kcontrol); + + spin_lock_irqsave(&chip->lock, flags); + if (chip->rt_opto == 0x00001440) + u->value.enumerated.item[0] = 1; + else + u->value.enumerated.item[0] = 0; + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static int +snd_rad1_control_tr_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + int change = 0; + unsigned long flags; + struct snd_rad1 *chip = snd_kcontrol_chip(kcontrol); + + spin_lock_irqsave(&chip->lock, flags); + if (u->value.enumerated.item[0] && (chip->rt_opto != 0x00001440)) + change = 1; + + if (!u->value.enumerated.item[0] && (chip->rt_opto == 0x00001440)) + change = 1; + + if (change) { + if (u->value.enumerated.item[0]) + chip->rt_opto = 0x00001440; + else + chip->rt_opto = 0x00001040; + chip->mmio->misc_control = chip->rt_opto; + } + spin_unlock_irqrestore(&chip->lock, flags); + + return change; +} + +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* AES Rx route control */ +static int +snd_rad1_control_rr_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + struct snd_rad1 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + if (chip->rt_aesr == 0x00000010) + u->value.enumerated.item[0] = 1; + else + u->value.enumerated.item[0] = 0; + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static int +snd_rad1_control_rr_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + struct snd_rad1 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change = 0; + + spin_lock_irqsave(&chip->lock, flags); + if (u->value.enumerated.item[0] && (chip->rt_aesr != 0x00000010)) + change = 1; + + if (!u->value.enumerated.item[0] && (chip->rt_aesr == 0x00000010)) + change = 1; + + if (change) { + if (u->value.enumerated.item[0]) { + chip->rt_aesr = 0x00000010; + chip->rt_adat = 0xa0000018; + } else { + chip->rt_aesr = 0x00000018; + chip->rt_adat = 0xa0000000; + } + chip->mmio->aes_rx_control = (chip->rt_aesr | + chip->pipes[RAD1_AESRX].adma); + chip->mmio->adat_rx_control = (chip->rt_adat | + chip->pipes[RAD1_ADATRX].adma); + } + spin_unlock_irqrestore(&chip->lock, flags); + + return change; +} + + +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* ADC route control */ +static int +snd_rad1_control_ar_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static char *rts[2] = {"Mic", "Line"}; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 2; + + if (uinfo->value.enumerated.item > 1) + uinfo->value.enumerated.item = 1; + + strscpy(uinfo->value.enumerated.name, + rts[uinfo->value.enumerated.item], + sizeof(uinfo->value.enumerated.name)); + + return 0; +} + +static int +snd_rad1_control_ar_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + struct snd_rad1 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + if (chip->rt_adc == 0x05000000) + u->value.enumerated.item[0] = 1; + else + u->value.enumerated.item[0] = 0; + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static int +snd_rad1_control_ar_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + struct snd_rad1 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change = 0; + + spin_lock_irqsave(&chip->lock, flags); + if (u->value.enumerated.item[0] && (chip->rt_adc != 0x05000000)) + change = 1; + + if (!u->value.enumerated.item[0] && (chip->rt_adc == 0x05000000)) + change = 1; + + if (change) { + if (u->value.enumerated.item[0]) + chip->rt_adc = 0x05000000; + else + chip->rt_adc = 0x03000000; + chip->mmio->adc_control = (chip->rt_adc | + chip->pipes[RAD1_ADC].adma); + } + spin_unlock_irqrestore(&chip->lock, flags); + + return change; +} + +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* Controls */ +static struct snd_kcontrol_new +snd_rad1_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .info = snd_rad1_control_pv_info, + .get = snd_rad1_control_pv_get, + .put = snd_rad1_control_pv_put, + .private_value = 1 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Capture Volume", + .info = snd_rad1_control_pv_info, + .get = snd_rad1_control_pv_get, + .put = snd_rad1_control_pv_put, + .private_value = 0 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Playback Routing", + .info = snd_rad1_control_tr_info, + .get = snd_rad1_control_tr_get, + .put = snd_rad1_control_tr_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Capture Routing", + .info = snd_rad1_control_tr_info, /* clone */ + .get = snd_rad1_control_rr_get, + .put = snd_rad1_control_rr_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Capture Routing", + .info = snd_rad1_control_ar_info, + .get = snd_rad1_control_ar_get, + .put = snd_rad1_control_ar_put + }, +}; + +static int +snd_rad1_add_controls(struct snd_rad1 *chip) +{ + int idx, err; + + for (idx = 0; idx < 5; idx++) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(&snd_rad1_controls[idx], chip)); + if (unlikely(err < 0)) + return err; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* Create/Probe/Free/Module */ + +static int +snd_rad1_free(struct snd_rad1 *chip) +{ + if (chip->mmio) { + iounmap((void *)(chip->mmio)); + chip->mmio = NULL; + } + kfree(chip); + + return 0; +} + +static int +snd_rad1_dev_free(struct snd_device *device) +{ + struct snd_rad1 *chip = device->device_data; + + return snd_rad1_free(chip); +} + +static int +snd_rad1_create(struct snd_card *card, struct pci_dev *pci, + struct snd_rad1 **rchip) +{ + int error = -ENODEV; + struct snd_rad1 *chip; + static struct snd_device_ops ops = { + .dev_free = snd_rad1_dev_free, + }; + + *rchip = NULL; + + if (unlikely(pci_enable_device(pci))) + goto out; + + if (unlikely(pci_request_regions(pci, "rad1"))) + goto out_disable_dev; + + pci_write_config_byte(pci, PCI_LATENCY_TIMER, 64); + pci_set_master(pci); + + chip = kzalloc(sizeof(struct snd_rad1), GFP_KERNEL); + if (unlikely(!chip)) { + error = -ENOMEM; + goto out_rel_region; + } + + timer_setup(&chip->timer, snd_rad1_poll_timer, 0); + + chip->card = card; + chip->pci = pci; + + spin_lock_init(&chip->lock); + + chip->mmio_phys = pci_resource_start(pci, 0); + chip->mmio = ioremap(chip->mmio_phys, + pci_resource_len(pci, 0)); + + snd_rad1_hw_init(chip); + + if (snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) + goto out_rad1_free; + + snd_card_set_dev(card, &pci->dev); + *rchip = chip; + + return 0; + +out_rad1_free: + snd_rad1_free(chip); +out_rel_region: + pci_release_regions(pci); +out_disable_dev: + pci_disable_device(pci); +out: + return error; +} + +static struct pci_device_id +snd_rad1_ids[] = { + { PCI_VENDOR_ID_SGI, PCI_DEVICE_ID_SGI_RAD1, PCI_ANY_ID, PCI_ANY_ID, }, + { }, +}; + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static int ndev; + +static int +snd_rad1_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + struct snd_card *card; + struct snd_rad1 *chip; + int err; + + if (unlikely(ndev >= SNDRV_CARDS)) + return -ENODEV; + + if (unlikely(!enable[ndev])) { + ndev++; + return -ENOENT; + } + + err = snd_card_new(&pci->dev, index[ndev], id[ndev], THIS_MODULE, 0, &card); + if (unlikely(err < 0)) + return err; + + err = snd_rad1_create(card, pci, &chip); + if (unlikely(err < 0)) { + snd_card_free(card); + return err; + } + + strscpy(card->driver, "RAD1", sizeof(card->driver)); + strscpy(card->shortname, "RADAudio", sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "SGI RAD Audio at 0x%lx", chip->mmio_phys); + + /* create pipes */ + snd_rad1_new_dac(chip, 0); + snd_rad1_new_adc(chip, 1); + snd_rad1_new_aestx(chip, 2); + snd_rad1_new_aesrx(chip, 3); + snd_rad1_add_controls(chip); + + err = snd_card_register(card); + if (unlikely(err < 0)) { + snd_card_free(card); + return err; + } + + pci_set_drvdata(pci, card); + ndev++; + + return 0; +} + +static void +snd_rad1_remove(struct pci_dev *pci) +{ + snd_card_free(pci_get_drvdata(pci)); + pci_set_drvdata(pci, NULL); +} + +MODULE_DEVICE_TABLE(pci, snd_rad1_ids); + +static struct pci_driver +rad1_driver = { + .name = "rad1", + .id_table = snd_rad1_ids, + .probe = snd_rad1_probe, + .remove = snd_rad1_remove, +}; + +static int __init +alsa_card_rad1_init(void) +{ + return pci_register_driver(&rad1_driver); +} + +static void __exit +alsa_card_rad1_exit(void) +{ + pci_unregister_driver(&rad1_driver); +} + +MODULE_AUTHOR("Stanislaw Skowronek "); +MODULE_AUTHOR("René Rebe