--- linux-2.6.9/sound/pci/azx/azx.c.orig 2005-07-17 10:33:35.818824449 -0400 +++ linux-2.6.9/sound/pci/azx/azx.c 2005-07-17 10:25:03.512297693 -0400 @@ -1,1401 +0,0 @@ -/* - * - * azx.c - Implementation of primary alsa driver code base for Intel HD Audio. - * - * Copyright(c) 2004 Intel Corporation. All rights reserved. - * - * Copyright (c) 2004 Takashi Iwai - * PeiSen Hou - * - * 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, MA 02111-1307, USA. - * - * CONTACTS: - * - * Matt Jared matt.jared@intel.com - * Andy Kopp andy.kopp@intel.com - * Dan Kogan dan.d.kogan@intel.com - * - * CHANGES: - * - * 2004.12.01 Major rewrite by tiwai, merged the work of pshou - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "hda_codec.h" - - -static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; -static int index_num; -static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; -static int enable_num; -static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; -static int id_num; -static char *model[SNDRV_CARDS]; -static int model_num; - -module_param_array(index, int, index_num, 0444); -MODULE_PARM_DESC(index, "Index value for Intel HD audio interface."); -module_param_array(id, charp, id_num, 0444); -MODULE_PARM_DESC(id, "ID string for Intel HD audio interface."); -module_param_array(enable, bool, enable_num, 0444); -MODULE_PARM_DESC(enable, "Enable Intel HD audio interface."); -module_param_array(model, charp, model_num, 0444); -MODULE_PARM_DESC(model, "Use the given board model."); - -MODULE_LICENSE("GPL"); -MODULE_SUPPORTED_DEVICE("{{Intel, ICH6}," - "{Intel, ICH6M}," - "{Intel, ICH7}}"); -MODULE_DESCRIPTION("Intel HDA driver"); - -#define SFX "azx: " - -/* - * registers - */ -#define ICH6_REG_GCAP 0x00 -#define ICH6_REG_VMIN 0x02 -#define ICH6_REG_VMAJ 0x03 -#define ICH6_REG_OUTPAY 0x04 -#define ICH6_REG_INPAY 0x06 -#define ICH6_REG_GCTL 0x08 -#define ICH6_REG_WAKEEN 0x0c -#define ICH6_REG_STATESTS 0x0e -#define ICH6_REG_GSTS 0x10 -#define ICH6_REG_INTCTL 0x20 -#define ICH6_REG_INTSTS 0x24 -#define ICH6_REG_WALCLK 0x30 -#define ICH6_REG_SYNC 0x34 -#define ICH6_REG_CORBLBASE 0x40 -#define ICH6_REG_CORBUBASE 0x44 -#define ICH6_REG_CORBWP 0x48 -#define ICH6_REG_CORBRP 0x4A -#define ICH6_REG_CORBCTL 0x4c -#define ICH6_REG_CORBSTS 0x4d -#define ICH6_REG_CORBSIZE 0x4e - -#define ICH6_REG_RIRBLBASE 0x50 -#define ICH6_REG_RIRBUBASE 0x54 -#define ICH6_REG_RIRBWP 0x58 -#define ICH6_REG_RINTCNT 0x5a -#define ICH6_REG_RIRBCTL 0x5c -#define ICH6_REG_RIRBSTS 0x5d -#define ICH6_REG_RIRBSIZE 0x5e - -#define ICH6_REG_IC 0x60 -#define ICH6_REG_IR 0x64 -#define ICH6_REG_IRS 0x68 -#define ICH6_IRS_VALID (1<<1) -#define ICH6_IRS_BUSY (1<<0) - -#define ICH6_REG_DPLBASE 0x70 -#define ICH6_REG_DPUBASE 0x74 -#define ICH6_DPLBASE_ENABLE 0x1 /* Enable position buffer */ - -/* SD offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */ -enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; - -/* stream register offsets from stream base */ -#define ICH6_REG_SD_CTL 0x00 -#define ICH6_REG_SD_STS 0x03 -#define ICH6_REG_SD_LPIB 0x04 -#define ICH6_REG_SD_CBL 0x08 -#define ICH6_REG_SD_LVI 0x0c -#define ICH6_REG_SD_FIFOW 0x0e -#define ICH6_REG_SD_FIFOSIZE 0x10 -#define ICH6_REG_SD_FORMAT 0x12 -#define ICH6_REG_SD_BDLPL 0x18 -#define ICH6_REG_SD_BDLPU 0x1c - -/* PCI space */ -#define ICH6_PCIREG_TCSEL 0x44 - -/* - * other constants - */ - -/* max number of SDs */ -#define MAX_ICH6_DEV 8 -/* max number of fragments - we may use more if allocating more pages for BDL */ -#define AZX_MAX_FRAG (PAGE_SIZE / (MAX_ICH6_DEV * 16)) -/* max buffer size - no h/w limit, you can increase as you like */ -#define AZX_MAX_BUF_SIZE (1024*1024*1024) -/* max number of PCM devics per card */ -#define AZX_MAX_PCMS 8 - -/* RIRB int mask: overrun[2], response[0] */ -#define RIRB_INT_MASK 0x5 - -/* STATESTS int mask: SD2,SD1,SD0 */ -#define STATESTS_INT_MASK 0x07 -#define AZX_MAX_CODECS 3 - -/* SD_CTL bits */ -#define SD_CTL_STREAM_RESET 0x01 /* stream reset bit */ -#define SD_CTL_DMA_START 0x02 /* stream DMA start bit */ -#define SD_CTL_STREAM_TAG_MASK (0xf << 20) -#define SD_CTL_STREAM_TAG_SHIFT 20 - -/* SD_CTL and SD_STS */ -#define SD_INT_DESC_ERR 0x10 /* descriptor error interrupt */ -#define SD_INT_FIFO_ERR 0x08 /* FIFO error interrupt */ -#define SD_INT_COMPLETE 0x04 /* completion interrupt */ -#define SD_INT_MASK (SD_INT_DESC_ERR|SD_INT_FIFO_ERR|SD_INT_COMPLETE) - -/* SD_STS */ -#define SD_STS_FIFO_READY 0x20 /* FIFO ready */ - -/* INTCTL and INTSTS */ -#define ICH6_INT_ALL_STREAM 0xff /* all stream interrupts */ -#define ICH6_INT_CTRL_EN 0x40000000 /* controller interrupt enable bit */ -#define ICH6_INT_GLOBAL_EN 0x80000000 /* global interrupt enable bit */ - -/* GCTL reset bit */ -#define ICH6_GCTL_RESET (1<<0) - -/* CORB/RIRB control, read/write pointer */ -#define ICH6_RBCTL_DMA_EN 0x02 /* enable DMA */ -#define ICH6_RBRWP_CLR 0x8000 /* read/write pointer clear */ -/* below are so far hardcoded - should read registers in future */ -#define ICH6_MAX_CORB_ENTRIES 256 -#define ICH6_MAX_RIRB_ENTRIES 256 - - -/* - * Use CORB/RIRB for communication from/to codecs. - * This is the way recommended by Intel (see below). - */ -#define USE_CORB_RIRB - -/* - * Define this if use the position buffer instead of reading SD_LPIB - * It's not used as default since SD_LPIB seems to give more accurate position - */ -/* #define USE_POSBUF */ - -/* - */ - -typedef struct snd_azx azx_t; -typedef struct snd_azx_rb azx_rb_t; -typedef struct snd_azx_dev azx_dev_t; - -struct snd_azx_dev { - u32 *bdl; /* virtual address of the BDL */ - dma_addr_t bdl_addr; /* physical address of the BDL */ - volatile u32 *posbuf; /* position buffer pointer */ - - unsigned int bufsize; /* size of the play buffer in bytes */ - unsigned int fragsize; /* size of each period in bytes */ - unsigned int frags; /* number for period in the play buffer */ - unsigned int fifo_size; /* FIFO size */ - - void __iomem *sd_addr; /* stream descriptor pointer */ - - u32 sd_int_sta_mask; /* stream int status mask */ - - /* pcm support */ - snd_pcm_substream_t *substream; /* assigned substream, set in PCM open */ - unsigned int format_val; /* format value to be set in the controller and the codec */ - unsigned char stream_tag; /* assigned stream */ - unsigned char index; /* stream index */ - - unsigned int opened: 1; - unsigned int running: 1; -}; - -/* CORB/RIRB */ -struct snd_azx_rb { - u32 *buf; - dma_addr_t addr; -}; - -struct snd_azx { - snd_card_t *card; - struct pci_dev *pci; - - /* pci resources */ - unsigned long addr; - void __iomem *remap_addr; - int irq; - - /* locks */ - spinlock_t reg_lock; - struct semaphore open_mutex; - - /* streams */ - azx_dev_t azx_dev[MAX_ICH6_DEV]; - - /* PCM */ - unsigned int pcm_devs; - snd_pcm_t *pcm[AZX_MAX_PCMS]; - - /* HD codec */ - unsigned short codec_mask; - struct hda_bus *bus; - - /* CORB/RIRB */ - azx_rb_t corb; - azx_rb_t rirb; - - /* BDL, CORB/RIRB and position buffers */ - struct snd_dma_buffer bdl; - struct snd_dma_buffer rb; - struct snd_dma_buffer posbuf; -}; - -/* - * macros for easy use - */ -#define azx_writel(chip,reg,value) \ - writel(value, (chip)->remap_addr + ICH6_REG_##reg) -#define azx_readl(chip,reg) \ - readl((chip)->remap_addr + ICH6_REG_##reg) -#define azx_writew(chip,reg,value) \ - writew(value, (chip)->remap_addr + ICH6_REG_##reg) -#define azx_readw(chip,reg) \ - readw((chip)->remap_addr + ICH6_REG_##reg) -#define azx_writeb(chip,reg,value) \ - writeb(value, (chip)->remap_addr + ICH6_REG_##reg) -#define azx_readb(chip,reg) \ - readb((chip)->remap_addr + ICH6_REG_##reg) - -#define azx_sd_writel(dev,reg,value) \ - writel(value, (dev)->sd_addr + ICH6_REG_##reg) -#define azx_sd_readl(dev,reg) \ - readl((dev)->sd_addr + ICH6_REG_##reg) -#define azx_sd_writew(dev,reg,value) \ - writew(value, (dev)->sd_addr + ICH6_REG_##reg) -#define azx_sd_readw(dev,reg) \ - readw((dev)->sd_addr + ICH6_REG_##reg) -#define azx_sd_writeb(dev,reg,value) \ - writeb(value, (dev)->sd_addr + ICH6_REG_##reg) -#define azx_sd_readb(dev,reg) \ - readb((dev)->sd_addr + ICH6_REG_##reg) - -/* for pcm support */ -#define get_azx_dev(substream) (azx_dev_t*)(substream->runtime->private_data) - -/* Get the upper 32bit of the given dma_addr_t - * Compiler should optimize and eliminate the code if dma_addr_t is 32bit - */ -#define upper_32bit(addr) (sizeof(addr) > 4 ? (u32)((addr) >> 32) : (u32)0) - - -/* - * Interface for HD codec - */ - -#ifdef USE_CORB_RIRB -/* - * CORB / RIRB interface - */ -static int azx_alloc_cmd_io(azx_t *chip) -{ - int err; - - /* single page (at least 4096 bytes) must suffice for both ringbuffes */ - err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci), - PAGE_SIZE, &chip->rb); - if (err < 0) { - snd_printk(KERN_ERR SFX "cannot allocate CORB/RIRB\n"); - return err; - } - return 0; -} - -static void azx_init_cmd_io(azx_t *chip) -{ - /* CORB set up */ - chip->corb.addr = chip->rb.addr; - chip->corb.buf = (u32 *)chip->rb.area; - azx_writel(chip, CORBLBASE, (u32)chip->corb.addr); - azx_writel(chip, CORBUBASE, upper_32bit(chip->corb.addr)); - - /* set the corb write pointer to 0 */ - azx_writew(chip, CORBWP, 0); - /* reset the corb hw read pointer */ - azx_writew(chip, CORBRP, ICH6_RBRWP_CLR); - /* enable corb dma */ - azx_writeb(chip, CORBCTL, ICH6_RBCTL_DMA_EN); - - /* RIRB set up */ - chip->rirb.addr = chip->rb.addr + 2048; - chip->rirb.buf = (u32 *)(chip->rb.area + 2048); - azx_writel(chip, RIRBLBASE, (u32)chip->rirb.addr); - azx_writel(chip, RIRBUBASE, upper_32bit(chip->rirb.addr)); - - /* reset the rirb hw write pointer */ - azx_writew(chip, RIRBWP, ICH6_RBRWP_CLR); - /* enable rirb dma */ - azx_writeb(chip, RIRBCTL, ICH6_RBCTL_DMA_EN); -} - -static void azx_free_cmd_io(azx_t *chip) -{ - /* disable ringbuffer DMAs */ - azx_writeb(chip, RIRBCTL, 0); - azx_writeb(chip, CORBCTL, 0); -} - -/* send a command */ -static int azx_send_cmd(struct hda_codec *codec, hda_nid_t nid, int direct, - unsigned int verb, unsigned int para) -{ - azx_t *chip = codec->bus->private_data; - unsigned int wp; - u32 val; - - val = (u32)(codec->addr & 0x0f) << 28; - val |= (u32)direct << 27; - val |= (u32)nid << 20; - val |= verb << 8; - val |= para; - - /* add command to corb */ - wp = azx_readb(chip, CORBWP); - wp++; - wp %= ICH6_MAX_CORB_ENTRIES; - - chip->corb.buf[wp] = cpu_to_le32(val); - azx_writel(chip, CORBWP, wp); - - return 0; -} - -/* receive a response */ -static unsigned int azx_get_response(struct hda_codec *codec) -{ - azx_t *chip = codec->bus->private_data; - unsigned int wp, corbwp; - int timeout = 2; - - corbwp = azx_readb(chip, CORBWP); - while ((wp = azx_readb(chip, RIRBWP)) != corbwp) { - if (! --timeout) { - snd_printk(KERN_ERR "azx_get_response timeout\n"); - break; - } - msleep(1); - } - wp <<= 1; /* an RIRB entry is 8-bytes */ - /* read the low value only */ - return le32_to_cpu(chip->rirb.buf[wp]); -} - -#else -/* - * Use the single immediate command instead of CORB/RIRB for simplicity - * - * Note: according to Intel, this is not preferred use. The command was - * intended for the BIOS only, and may get confused with unsolicited - * responses. So, we shouldn't use it for normal operation from the - * driver. - * I left the codes, however, for debugging/testing purposes. - */ - -#define azx_alloc_cmd_io(chip) 0 -#define azx_init_cmd_io(chip) -#define azx_free_cmd_io(chip) - -/* send a command */ -static int azx_send_cmd(struct hda_codec *codec, hda_nid_t nid, int direct, - unsigned int verb, unsigned int para) -{ - azx_t *chip = codec->bus->private_data; - u32 val; - int timeout = 50; - - val = (u32)(codec->addr & 0x0f) << 28; - val |= (u32)direct << 27; - val |= (u32)nid << 20; - val |= verb << 8; - val |= para; - - while (timeout--) { - /* check ICB busy bit */ - if (! (azx_readw(chip, IRS) & ICH6_IRS_BUSY)) { - /* Clear IRV valid bit */ - azx_writew(chip, IRS, azx_readw(chip, IRS) | ICH6_IRS_VALID); - azx_writel(chip, IC, val); - azx_writew(chip, IRS, azx_readw(chip, IRS) | ICH6_IRS_BUSY); - return 0; - } - udelay(1); - } - snd_printd(SFX "send_cmd timeout: IRS=0x%x, val=0x%x\n", azx_readw(chip, IRS), val); - return -EIO; -} - -/* receive a response */ -static unsigned int azx_get_response(struct hda_codec *codec) -{ - azx_t *chip = codec->bus->private_data; - int timeout = 50; - - while (timeout--) { - /* check IRV busy bit */ - if (azx_readw(chip, IRS) & ICH6_IRS_VALID) - return azx_readl(chip, IR); - udelay(1); - } - snd_printd(SFX "get_response timeout: IRS=0x%x\n", azx_readw(chip, IRS)); - return (unsigned int)-1; -} - -#endif /* USE_CORB_RIRB */ - -/* reset codec link */ -static int azx_reset(azx_t *chip) -{ - int count; - - /* reset controller */ - azx_writel(chip, GCTL, azx_readl(chip, GCTL) & ~ICH6_GCTL_RESET); - - count = 50; - while (azx_readb(chip, GCTL) && --count) - msleep(1); - - /* delay for >= 100us for codec PLL to settle per spec - * Rev 0.9 section 5.5.1 - */ - msleep(1); - - /* Bring controller out of reset */ - azx_writeb(chip, GCTL, azx_readb(chip, GCTL) | ICH6_GCTL_RESET); - - count = 50; - while (! azx_readb(chip, GCTL) && --count) - msleep(1); - - /* Brent Chartrand said to wait >= 540us for codecs to intialize */ - msleep(1); - - /* check to see if controller is ready */ - if (! azx_readb(chip, GCTL)) { - snd_printd("azx_reset: controller not ready!\n"); - return -EBUSY; - } - - /* detect codecs */ - if (! chip->codec_mask) { - chip->codec_mask = azx_readw(chip, STATESTS); - snd_printdd("codec_mask = 0x%x\n", chip->codec_mask); - } - - return 0; -} - - -/* - * Lowlevel interface - */ - -/* enable interrupts */ -static void azx_int_enable(azx_t *chip) -{ - /* enable controller CIE and GIE */ - azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) | - ICH6_INT_CTRL_EN | ICH6_INT_GLOBAL_EN); -} - -/* disable interrupts */ -static void azx_int_disable(azx_t *chip) -{ - int i; - - /* disable interrupts in stream descriptor */ - for (i = 0; i < MAX_ICH6_DEV; i++) { - azx_dev_t *azx_dev = &chip->azx_dev[i]; - azx_sd_writeb(azx_dev, SD_CTL, - azx_sd_readb(azx_dev, SD_CTL) & ~SD_INT_MASK); - } - - /* disable SIE for all streams */ - azx_writeb(chip, INTCTL, 0); - - /* disable controller CIE and GIE */ - azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) & - ~(ICH6_INT_CTRL_EN | ICH6_INT_GLOBAL_EN)); -} - -/* clear interrupts */ -static void azx_int_clear(azx_t *chip) -{ - int i; - - /* clear stream status */ - for (i = 0; i < MAX_ICH6_DEV; i++) { - azx_dev_t *azx_dev = &chip->azx_dev[i]; - azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK); - } - - /* clear STATESTS */ - azx_writeb(chip, STATESTS, STATESTS_INT_MASK); - - /* clear rirb status */ - azx_writeb(chip, RIRBSTS, RIRB_INT_MASK); - - /* clear int status */ - azx_writel(chip, INTSTS, ICH6_INT_CTRL_EN | ICH6_INT_ALL_STREAM); -} - -/* start a stream */ -static void azx_stream_start(azx_t *chip, azx_dev_t *azx_dev) -{ - /* enable SIE */ - azx_writeb(chip, INTCTL, - azx_readb(chip, INTCTL) | (1 << azx_dev->index)); - /* set DMA start and interrupt mask */ - azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) | - SD_CTL_DMA_START | SD_INT_MASK); -} - -/* stop a stream */ -static void azx_stream_stop(azx_t *chip, azx_dev_t *azx_dev) -{ - /* stop DMA */ - azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) & - ~(SD_CTL_DMA_START | SD_INT_MASK)); - azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ - /* disable SIE */ - azx_writeb(chip, INTCTL, - azx_readb(chip, INTCTL) & ~(1 << azx_dev->index)); -} - - -/* - * initialize the chip - */ -static void azx_init_chip(azx_t *chip) -{ - unsigned char tcsel_reg; - - /* Clear bits 0-2 of PCI register TCSEL (at offset 0x44) - * TCSEL == Traffic Class Select Register, which sets PCI express QOS - * Ensuring these bits are 0 clears playback static on some HD Audio codecs - */ - pci_read_config_byte (chip->pci, ICH6_PCIREG_TCSEL, &tcsel_reg); - pci_write_config_byte(chip->pci, ICH6_PCIREG_TCSEL, tcsel_reg & 0xf8); - - /* reset controller */ - azx_reset(chip); - - /* initialize interrupts */ - azx_int_clear(chip); - azx_int_enable(chip); - - /* initialize the codec command I/O */ - azx_init_cmd_io(chip); - -#ifdef USE_POSBUF - /* program the position buffer */ - azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr); - azx_writel(chip, DPUBASE, upper_32bit(chip->posbuf.addr)); -#endif -} - - -/* - * interrupt handler - */ -static irqreturn_t azx_interrupt(int irq, void* dev_id, struct pt_regs *regs) -{ - azx_t *chip = dev_id; - azx_dev_t *azx_dev; - u32 status; - int i; - - spin_lock(&chip->reg_lock); - - status = azx_readl(chip, INTSTS); - if (status == 0) { - spin_unlock(&chip->reg_lock); - return IRQ_NONE; - } - - for (i = 0; i < MAX_ICH6_DEV; i++) { - azx_dev = &chip->azx_dev[i]; - if (status & azx_dev->sd_int_sta_mask) { - azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK); - if (azx_dev->substream && azx_dev->running) { - spin_unlock(&chip->reg_lock); - snd_pcm_period_elapsed(azx_dev->substream); - spin_lock(&chip->reg_lock); - } - } - } - - /* clear rirb int */ - if (azx_readb(chip, RIRBSTS) & RIRB_INT_MASK) - azx_writeb(chip, RIRBSTS, RIRB_INT_MASK); - -#if 0 - /* clear state status int */ - if (azx_readb(chip, STATESTS) & 0x04) - azx_writeb(chip, STATESTS, 0x04); -#endif - spin_unlock(&chip->reg_lock); - - return IRQ_HANDLED; -} - - -/* - * set up BDL entries - */ -static void azx_setup_periods(azx_dev_t *azx_dev) -{ - u32 *bdl = azx_dev->bdl; - dma_addr_t dma_addr = azx_dev->substream->runtime->dma_addr; - int idx; - - /* reset BDL address */ - azx_sd_writel(azx_dev, SD_BDLPL, 0); - azx_sd_writel(azx_dev, SD_BDLPU, 0); - - /* program the initial BDL entries */ - for (idx = 0; idx < azx_dev->frags; idx++) { - unsigned int off = idx << 2; /* 4 dword step */ - dma_addr_t addr = dma_addr + idx * azx_dev->fragsize; - /* program the address field of the BDL entry */ - bdl[off] = cpu_to_le32((u32)addr); - bdl[off+1] = cpu_to_le32(upper_32bit(addr)); - - /* program the size field of the BDL entry */ - bdl[off+2] = cpu_to_le32(azx_dev->fragsize); - - /* program the IOC to enable interrupt when buffer completes */ - bdl[off+3] = cpu_to_le32(0x01); - } -} - -/* - * set up the SD for streaming - */ -static int azx_setup_controller(azx_t *chip, azx_dev_t *azx_dev) -{ - unsigned char val; - int timeout; - - /* make sure the run bit is zero for SD */ - azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) & ~SD_CTL_DMA_START); - /* reset stream */ - azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) | SD_CTL_STREAM_RESET); - udelay(3); - timeout = 300; - while (!((val = azx_sd_readb(azx_dev, SD_CTL)) & SD_CTL_STREAM_RESET) && - --timeout) - ; - val &= ~SD_CTL_STREAM_RESET; - azx_sd_writeb(azx_dev, SD_CTL, val); - udelay(3); - - timeout = 300; - /* waiting for hardware to report that the stream is out of reset */ - while (((val = azx_sd_readb(azx_dev, SD_CTL)) & SD_CTL_STREAM_RESET) && - --timeout) - ; - - /* program the stream_tag */ - azx_sd_writel(azx_dev, SD_CTL, - (azx_sd_readl(azx_dev, SD_CTL) & ~SD_CTL_STREAM_TAG_MASK) | - (azx_dev->stream_tag << SD_CTL_STREAM_TAG_SHIFT)); - - /* program the length of samples in cyclic buffer */ - azx_sd_writel(azx_dev, SD_CBL, azx_dev->bufsize); - - /* program the stream format */ - /* this value needs to be the same as the one programmed */ - azx_sd_writew(azx_dev, SD_FORMAT, azx_dev->format_val); - - /* program the stream LVI (last valid index) of the BDL */ - azx_sd_writew(azx_dev, SD_LVI, azx_dev->frags - 1); - - /* program the BDL address */ - /* lower BDL address */ - azx_sd_writel(azx_dev, SD_BDLPL, (u32)azx_dev->bdl_addr); - /* upper BDL address */ - azx_sd_writel(azx_dev, SD_BDLPU, upper_32bit(azx_dev->bdl_addr)); - -#ifdef USE_POSBUF - /* enable the position buffer */ - if (! (azx_readl(chip, DPLBASE) & ICH6_DPLBASE_ENABLE)) - azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr | ICH6_DPLBASE_ENABLE); -#endif - /* set the interrupt enable bits in the descriptor control register */ - azx_sd_writel(azx_dev, SD_CTL, azx_sd_readl(azx_dev, SD_CTL) | SD_INT_MASK); - - return 0; -} - - -/* - * Codec initialization - */ - -static int __devinit azx_codec_create(azx_t *chip, const char *model) -{ - struct hda_bus_template bus_temp; - int c, codecs, err; - - memset(&bus_temp, 0, sizeof(bus_temp)); - bus_temp.private_data = chip; - bus_temp.modelname = model; - bus_temp.pci = chip->pci; - bus_temp.ops.command = azx_send_cmd; - bus_temp.ops.get_response = azx_get_response; - - if ((err = snd_hda_bus_new(chip->card, &bus_temp, &chip->bus)) < 0) - return err; - - codecs = 0; - for (c = 0; c < AZX_MAX_CODECS; c++) { - if (chip->codec_mask & (1 << c)) { - err = snd_hda_codec_new(chip->bus, c, NULL); - if (err < 0) - continue; - codecs++; - } - } - if (! codecs) { - snd_printk(KERN_ERR SFX "no codecs initialized\n"); - return -ENXIO; - } - - return 0; -} - - -/* - * PCM support - */ - -/* assign a stream for the PCM */ -static inline azx_dev_t *azx_assign_device(azx_t *chip, int stream) -{ - int dev, i; - dev = stream == SNDRV_PCM_STREAM_PLAYBACK ? 4 : 0; - for (i = 0; i < 4; i++, dev++) - if (! chip->azx_dev[dev].opened) { - chip->azx_dev[dev].opened = 1; - return &chip->azx_dev[dev]; - } - return NULL; -} - -/* release the assigned stream */ -static inline void azx_release_device(azx_dev_t *azx_dev) -{ - azx_dev->opened = 0; -} - -static snd_pcm_hardware_t azx_pcm_hw = { - .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_RESUME), - .formats = SNDRV_PCM_FMTBIT_S16_LE, - .rates = SNDRV_PCM_RATE_48000, - .rate_min = 48000, - .rate_max = 48000, - .channels_min = 2, - .channels_max = 2, - .buffer_bytes_max = AZX_MAX_BUF_SIZE, - .period_bytes_min = 128, - .period_bytes_max = AZX_MAX_BUF_SIZE / 2, - .periods_min = 2, - .periods_max = AZX_MAX_FRAG, - .fifo_size = 0, -}; - -struct azx_pcm { - azx_t *chip; - struct hda_codec *codec; - struct hda_pcm_stream *hinfo[2]; -}; - -static int azx_pcm_open(snd_pcm_substream_t *substream) -{ - struct azx_pcm *apcm = snd_pcm_substream_chip(substream); - struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; - azx_t *chip = apcm->chip; - azx_dev_t *azx_dev; - snd_pcm_runtime_t *runtime = substream->runtime; - unsigned long flags; - int err; - - down(&chip->open_mutex); - azx_dev = azx_assign_device(chip, substream->stream); - if (azx_dev == NULL) { - up(&chip->open_mutex); - return -EBUSY; - } - runtime->hw = azx_pcm_hw; - runtime->hw.channels_min = hinfo->channels_min; - runtime->hw.channels_max = hinfo->channels_max; - runtime->hw.formats = hinfo->formats; - runtime->hw.rates = hinfo->rates; - snd_pcm_limit_hw_rates(runtime); - snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if ((err = hinfo->ops.open(hinfo, apcm->codec, substream)) < 0) { - azx_release_device(azx_dev); - up(&chip->open_mutex); - return err; - } - spin_lock_irqsave(&chip->reg_lock, flags); - azx_dev->substream = substream; - azx_dev->running = 0; - spin_unlock_irqrestore(&chip->reg_lock, flags); - - runtime->private_data = azx_dev; - up(&chip->open_mutex); - return 0; -} - -static int azx_pcm_close(snd_pcm_substream_t *substream) -{ - struct azx_pcm *apcm = snd_pcm_substream_chip(substream); - struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; - azx_t *chip = apcm->chip; - azx_dev_t *azx_dev = get_azx_dev(substream); - unsigned long flags; - - down(&chip->open_mutex); - spin_lock_irqsave(&chip->reg_lock, flags); - azx_dev->substream = NULL; - azx_dev->running = 0; - spin_unlock_irqrestore(&chip->reg_lock, flags); - azx_release_device(azx_dev); - hinfo->ops.close(hinfo, apcm->codec, substream); - up(&chip->open_mutex); - return 0; -} - -static int azx_pcm_hw_params(snd_pcm_substream_t *substream, snd_pcm_hw_params_t *hw_params) -{ - return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); -} - -static int azx_pcm_hw_free(snd_pcm_substream_t *substream) -{ - struct azx_pcm *apcm = snd_pcm_substream_chip(substream); - azx_dev_t *azx_dev = get_azx_dev(substream); - struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; - - /* reset BDL address */ - azx_sd_writel(azx_dev, SD_BDLPL, 0); - azx_sd_writel(azx_dev, SD_BDLPU, 0); - azx_sd_writel(azx_dev, SD_CTL, 0); - - hinfo->ops.cleanup(hinfo, apcm->codec, substream); - - return snd_pcm_lib_free_pages(substream); -} - -static int azx_pcm_prepare(snd_pcm_substream_t *substream) -{ - struct azx_pcm *apcm = snd_pcm_substream_chip(substream); - azx_t *chip = apcm->chip; - azx_dev_t *azx_dev = get_azx_dev(substream); - struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; - snd_pcm_runtime_t *runtime = substream->runtime; - - azx_dev->bufsize = snd_pcm_lib_buffer_bytes(substream); - azx_dev->fragsize = snd_pcm_lib_period_bytes(substream); - azx_dev->frags = azx_dev->bufsize / azx_dev->fragsize; - azx_dev->format_val = snd_hda_calc_stream_format(runtime->rate, - runtime->channels, - runtime->format, - hinfo->maxbps); - if (! azx_dev->format_val) { - snd_printk(KERN_ERR SFX "invalid format_val, rate=%d, ch=%d, format=%d\n", - runtime->rate, runtime->channels, runtime->format); - return -EINVAL; - } - - snd_printdd("azx_pcm_prepare: bufsize=0x%x, fragsize=0x%x, format=0x%x\n", - azx_dev->bufsize, azx_dev->fragsize, azx_dev->format_val); - azx_setup_periods(azx_dev); - azx_setup_controller(chip, azx_dev); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - azx_dev->fifo_size = azx_sd_readw(azx_dev, SD_FIFOSIZE) + 1; - else - azx_dev->fifo_size = 0; - - return hinfo->ops.prepare(hinfo, apcm->codec, azx_dev->stream_tag, - azx_dev->format_val, substream); -} - -static int azx_pcm_trigger(snd_pcm_substream_t *substream, int cmd) -{ - struct azx_pcm *apcm = snd_pcm_substream_chip(substream); - azx_dev_t *azx_dev = get_azx_dev(substream); - azx_t *chip = apcm->chip; - int err = 0; - - spin_lock(&chip->reg_lock); - switch (cmd) { - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - case SNDRV_PCM_TRIGGER_RESUME: - case SNDRV_PCM_TRIGGER_START: - azx_stream_start(chip, azx_dev); - azx_dev->running = 1; - break; - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - case SNDRV_PCM_TRIGGER_STOP: - azx_stream_stop(chip, azx_dev); - azx_dev->running = 0; - break; - default: - err = -EINVAL; - } - spin_unlock(&chip->reg_lock); - if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH || - cmd == SNDRV_PCM_TRIGGER_STOP) { - int timeout = 5000; - while (azx_sd_readb(azx_dev, SD_CTL) & SD_CTL_DMA_START && --timeout) - ; - } - return err; -} - -static snd_pcm_uframes_t azx_pcm_pointer(snd_pcm_substream_t *substream) -{ - azx_dev_t *azx_dev = get_azx_dev(substream); - unsigned int pos; - -#ifdef USE_POSBUF - /* use the position buffer */ - pos = *azx_dev->posbuf; -#else - /* read LPIB */ - pos = azx_sd_readl(azx_dev, SD_LPIB) + azx_dev->fifo_size; -#endif - if (pos >= azx_dev->bufsize) - pos = 0; - return bytes_to_frames(substream->runtime, pos); -} - -static snd_pcm_ops_t azx_pcm_ops = { - .open = azx_pcm_open, - .close = azx_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = azx_pcm_hw_params, - .hw_free = azx_pcm_hw_free, - .prepare = azx_pcm_prepare, - .trigger = azx_pcm_trigger, - .pointer = azx_pcm_pointer, -}; - -static void azx_pcm_free(snd_pcm_t *pcm) -{ - kfree(pcm->private_data); -} - -static int __devinit create_codec_pcm(azx_t *chip, struct hda_codec *codec, - struct hda_pcm *cpcm, int pcm_dev) -{ - int err; - snd_pcm_t *pcm; - struct azx_pcm *apcm; - - snd_assert(cpcm->stream[0].substreams || cpcm->stream[1].substreams, return -EINVAL); - snd_assert(cpcm->name, return -EINVAL); - - err = snd_pcm_new(chip->card, cpcm->name, pcm_dev, - cpcm->stream[0].substreams, cpcm->stream[1].substreams, - &pcm); - if (err < 0) - return err; - strcpy(pcm->name, cpcm->name); - apcm = kmalloc(sizeof(*apcm), GFP_KERNEL); - if (apcm == NULL) - return -ENOMEM; - apcm->chip = chip; - apcm->codec = codec; - apcm->hinfo[0] = &cpcm->stream[0]; - apcm->hinfo[1] = &cpcm->stream[1]; - pcm->private_data = apcm; - pcm->private_free = azx_pcm_free; - if (cpcm->stream[0].substreams) - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &azx_pcm_ops); - if (cpcm->stream[1].substreams) - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &azx_pcm_ops); - snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, - snd_dma_pci_data(chip->pci), - 1024 * 64, 1024 * 128); - chip->pcm[pcm_dev] = pcm; - - return 0; -} - -static int __devinit azx_pcm_create(azx_t *chip) -{ - struct list_head *p; - struct hda_codec *codec; - int c, err; - int pcm_dev; - - if ((err = snd_hda_build_pcms(chip->bus)) < 0) - return err; - - pcm_dev = 0; - list_for_each(p, &chip->bus->codec_list) { - codec = list_entry(p, struct hda_codec, list); - for (c = 0; c < codec->num_pcms; c++) { - if (pcm_dev >= AZX_MAX_PCMS) { - snd_printk(KERN_ERR SFX "Too many PCMs\n"); - return -EINVAL; - } - err = create_codec_pcm(chip, codec, &codec->pcm_info[c], pcm_dev); - if (err < 0) - return err; - pcm_dev++; - } - } - return 0; -} - -/* - * mixer creation - all stuff is implemented in hda module - */ -static int __devinit azx_mixer_create(azx_t *chip) -{ - return snd_hda_build_controls(chip->bus); -} - - -/* - * initialize SD streams - */ -static int __devinit azx_init_stream(azx_t *chip) -{ - int i; - - /* initialize each stream (aka device) - * assign the starting bdl address to each stream (device) and initialize - */ - for (i = 0; i < MAX_ICH6_DEV; i++) { - unsigned int off = sizeof(u32) * (i * AZX_MAX_FRAG * 4); - azx_dev_t *azx_dev = &chip->azx_dev[i]; - azx_dev->bdl = (u32 *)(chip->bdl.area + off); - azx_dev->bdl_addr = chip->bdl.addr + off; -#ifdef USE_POSBUF - azx_dev->posbuf = (volatile u32 *)(chip->posbuf.area + i * 8); -#endif - /* offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */ - azx_dev->sd_addr = chip->remap_addr + (0x20 * i + 0x80); - /* int mask: SDI0=0x01, SDI1=0x02, ... SDO3=0x80 */ - azx_dev->sd_int_sta_mask = 1 << i; - /* stream tag: must be non-zero and unique */ - azx_dev->index = i; - azx_dev->stream_tag = i + 1; - } - - return 0; -} - - -#ifdef CONFIG_PM -/* - * power management - */ -static int azx_suspend(snd_card_t *card, unsigned int state) -{ - azx_t *chip = card->pm_private_data; - int i; - - for (i = 0; i < chip->pcm_devs; i++) - if (chip->pcm[i]) - snd_pcm_suspend_all(chip->pcm[i]); - snd_hda_suspend(chip->bus, state); - azx_free_cmd_io(chip); - pci_disable_device(chip->pci); - return 0; -} - -static int azx_resume(snd_card_t *card, unsigned int state) -{ - azx_t *chip = card->pm_private_data; - - pci_enable_device(chip->pci); - pci_set_master(chip->pci); - azx_init_chip(chip); - snd_hda_resume(chip->bus, state); - return 0; -} -#endif /* CONFIG_PM */ - - -/* - * destructor - */ -static int azx_free(azx_t *chip) -{ - if (chip->remap_addr) { - int i; - - for (i = 0; i < MAX_ICH6_DEV; i++) - azx_stream_stop(chip, &chip->azx_dev[i]); - - /* disable interrupts */ - azx_int_disable(chip); - azx_int_clear(chip); - - /* disable CORB/RIRB */ - azx_free_cmd_io(chip); - - /* disable position buffer */ - azx_writel(chip, DPLBASE, 0); - azx_writel(chip, DPUBASE, 0); - - /* wait a little for interrupts to finish */ - msleep(1); - - iounmap(chip->remap_addr); - } - - if (chip->irq >= 0) - free_irq(chip->irq, (void*)chip); - - if (chip->bdl.area) - snd_dma_free_pages(&chip->bdl); - if (chip->rb.area) - snd_dma_free_pages(&chip->rb); -#ifdef USE_POSBUF - if (chip->posbuf.area) - snd_dma_free_pages(&chip->posbuf); -#endif - pci_release_regions(chip->pci); - pci_disable_device(chip->pci); - kfree(chip); - - return 0; -} - -static int azx_dev_free(snd_device_t *device) -{ - return azx_free(device->device_data); -} - -/* - * constructor - */ -static int __devinit azx_create(snd_card_t *card, struct pci_dev *pci, azx_t **rchip) -{ - azx_t *chip; - int err = 0; - static snd_device_ops_t ops = { - .dev_free = azx_dev_free, - }; - - *rchip = NULL; - - if ((err = pci_enable_device(pci)) < 0) - return err; - - chip = kcalloc(1, sizeof(*chip), GFP_KERNEL); - - if (NULL == chip) { - snd_printk(KERN_ERR SFX "cannot allocate chip\n"); - pci_disable_device(pci); - return -ENOMEM; - } - - spin_lock_init(&chip->reg_lock); - init_MUTEX(&chip->open_mutex); - chip->card = card; - chip->pci = pci; - chip->irq = -1; - - if ((err = pci_request_regions(pci, "ICH HD audio")) < 0) { - kfree(chip); - pci_disable_device(pci); - return err; - } - - chip->addr = pci_resource_start(pci,0); - chip->remap_addr = ioremap_nocache(chip->addr, pci_resource_len(pci,0)); - if (chip->remap_addr == NULL) { - snd_printk(KERN_ERR SFX "ioremap error\n"); - err = -ENXIO; - goto errout; - } - - if (request_irq(pci->irq, azx_interrupt, SA_INTERRUPT|SA_SHIRQ, - "Intel HDA", (void*)chip)) { - snd_printk(KERN_ERR SFX "unable to grab IRQ %d\n", pci->irq); - err = -EBUSY; - goto errout; - } - chip->irq = pci->irq; - - pci_set_master(pci); - synchronize_irq(chip->irq); - - /* allocate memory for the BDL for each stream */ - if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci), - PAGE_SIZE, &chip->bdl)) < 0) { - snd_printk(KERN_ERR SFX "cannot allocate BDL\n"); - goto errout; - } -#ifdef USE_POSBUF - /* allocate memory for the position buffer */ - if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci), - MAX_ICH6_DEV * 8, &chip->posbuf)) < 0) { - snd_printk(KERN_ERR SFX "cannot allocate posbuf\n"); - goto errout; - } -#endif - /* allocate CORB/RIRB */ - if ((err = azx_alloc_cmd_io(chip)) < 0) - goto errout; - - /* initialize streams */ - azx_init_stream(chip); - - /* initialize chip */ - azx_init_chip(chip); - - /* codec detection */ - if (! chip->codec_mask) { - snd_printk(KERN_ERR SFX "no codecs found!\n"); - err = -ENODEV; - goto errout; - } - - if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) <0) { - snd_printk(KERN_ERR SFX "Error creating device [card]!\n"); - goto errout; - } - - *rchip = chip; - return 0; - - errout: - azx_free(chip); - return err; -} - -static int __devinit azx_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - static int dev; - snd_card_t *card; - azx_t *chip; - int err = 0; - - if (dev >= SNDRV_CARDS) - return -ENODEV; - if (! enable[dev]) { - dev++; - return -ENOENT; - } - - card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); - if (NULL == card) { - snd_printk(KERN_ERR SFX "Error creating card!\n"); - return -ENOMEM; - } - - if ((err = azx_create(card, pci, &chip)) < 0) { - snd_card_free(card); - return err; - } - - strcpy(card->driver, "Azalia"); - strcpy(card->shortname, "Intel HDA"); - sprintf(card->longname, "%s at 0x%lx irq %i", card->shortname, chip->addr, chip->irq); - - /* create codec instances */ - if ((err = azx_codec_create(chip, model[dev])) < 0) { - snd_card_free(card); - return err; - } - - /* create PCM streams */ - if ((err = azx_pcm_create(chip)) < 0) { - snd_card_free(card); - return err; - } - - /* create mixer controls */ - if ((err = azx_mixer_create(chip)) < 0) { - snd_card_free(card); - return err; - } - - snd_card_set_pm_callback(card, azx_suspend, azx_resume, chip); - snd_card_set_dev(card, &pci->dev); - - if ((err = snd_card_register(card)) < 0) { - snd_card_free(card); - return err; - } - - pci_set_drvdata(pci, card); - dev++; - - return err; -} - -static void __devexit azx_remove(struct pci_dev *pci) -{ - snd_card_free(pci_get_drvdata(pci)); - pci_set_drvdata(pci, NULL); -} - -/* PCI IDs */ -static struct pci_device_id azx_ids[] = { - { 0x8086, 0x2668, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* ICH6 */ - { 0x8086, 0x27d8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* ICH7 */ - { 0, } -}; -MODULE_DEVICE_TABLE(pci, azx_ids); - -/* pci_driver definition */ -static struct pci_driver driver = { - .name = "Intel HDA", - .id_table = azx_ids, - .probe = azx_probe, - .remove = __devexit_p(azx_remove), - SND_PCI_PM_CALLBACKS -}; - -static int __init alsa_card_azx_init(void) -{ - return pci_module_init(&driver); -} - -static void __exit alsa_card_azx_exit(void) -{ - pci_unregister_driver(&driver); -} - -module_init(alsa_card_azx_init) -module_exit(alsa_card_azx_exit) --- linux-2.6.9/sound/pci/azx/hda_intel.c.orig 2005-07-17 10:33:59.618644419 -0400 +++ linux-2.6.9/sound/pci/azx/hda_intel.c 2005-07-17 10:25:03.522296356 -0400 @@ -0,0 +1,1492 @@ +/* + * + * hda_intel.c - Implementation of primary alsa driver code base for Intel HD Audio. + * + * Copyright(c) 2004 Intel Corporation. All rights reserved. + * + * Copyright (c) 2004 Takashi Iwai + * PeiSen Hou + * + * 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, MA 02111-1307, USA. + * + * CONTACTS: + * + * Matt Jared matt.jared@intel.com + * Andy Kopp andy.kopp@intel.com + * Dan Kogan dan.d.kogan@intel.com + * + * CHANGES: + * + * 2004.12.01 Major rewrite by tiwai, merged the work of pshou + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hda_codec.h" + + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static char *model[SNDRV_CARDS]; +static int position_fix[SNDRV_CARDS]; + +static int num_index = 0; +static int num_id = 0; +static int num_enable = 0; +static int num_model = 0; +static int num_position_fix = 0; + +module_param_array(index, int, num_index, 0444); +MODULE_PARM_DESC(index, "Index value for Intel HD audio interface."); +module_param_array(id, charp, num_id, 0444); +MODULE_PARM_DESC(id, "ID string for Intel HD audio interface."); +module_param_array(enable, bool, num_enable, 0444); +MODULE_PARM_DESC(enable, "Enable Intel HD audio interface."); +module_param_array(model, charp, num_model, 0444); +MODULE_PARM_DESC(model, "Use the given board model."); +module_param_array(position_fix, int, num_position_fix, 0444); +MODULE_PARM_DESC(position_fix, "Fix DMA pointer (0 = FIFO size, 1 = none, 2 = POSBUF)."); + +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Intel, ICH6}," + "{Intel, ICH6M}," + "{Intel, ICH7}," + "{Intel, ESB2}," + "{ATI, SB450}," + "{VIA, VT8251}," + "{VIA, VT8237A}}"); +MODULE_DESCRIPTION("Intel HDA driver"); + +#define SFX "hda-intel: " + +/* + * registers + */ +#define ICH6_REG_GCAP 0x00 +#define ICH6_REG_VMIN 0x02 +#define ICH6_REG_VMAJ 0x03 +#define ICH6_REG_OUTPAY 0x04 +#define ICH6_REG_INPAY 0x06 +#define ICH6_REG_GCTL 0x08 +#define ICH6_REG_WAKEEN 0x0c +#define ICH6_REG_STATESTS 0x0e +#define ICH6_REG_GSTS 0x10 +#define ICH6_REG_INTCTL 0x20 +#define ICH6_REG_INTSTS 0x24 +#define ICH6_REG_WALCLK 0x30 +#define ICH6_REG_SYNC 0x34 +#define ICH6_REG_CORBLBASE 0x40 +#define ICH6_REG_CORBUBASE 0x44 +#define ICH6_REG_CORBWP 0x48 +#define ICH6_REG_CORBRP 0x4A +#define ICH6_REG_CORBCTL 0x4c +#define ICH6_REG_CORBSTS 0x4d +#define ICH6_REG_CORBSIZE 0x4e + +#define ICH6_REG_RIRBLBASE 0x50 +#define ICH6_REG_RIRBUBASE 0x54 +#define ICH6_REG_RIRBWP 0x58 +#define ICH6_REG_RINTCNT 0x5a +#define ICH6_REG_RIRBCTL 0x5c +#define ICH6_REG_RIRBSTS 0x5d +#define ICH6_REG_RIRBSIZE 0x5e + +#define ICH6_REG_IC 0x60 +#define ICH6_REG_IR 0x64 +#define ICH6_REG_IRS 0x68 +#define ICH6_IRS_VALID (1<<1) +#define ICH6_IRS_BUSY (1<<0) + +#define ICH6_REG_DPLBASE 0x70 +#define ICH6_REG_DPUBASE 0x74 +#define ICH6_DPLBASE_ENABLE 0x1 /* Enable position buffer */ + +/* SD offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */ +enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; + +/* stream register offsets from stream base */ +#define ICH6_REG_SD_CTL 0x00 +#define ICH6_REG_SD_STS 0x03 +#define ICH6_REG_SD_LPIB 0x04 +#define ICH6_REG_SD_CBL 0x08 +#define ICH6_REG_SD_LVI 0x0c +#define ICH6_REG_SD_FIFOW 0x0e +#define ICH6_REG_SD_FIFOSIZE 0x10 +#define ICH6_REG_SD_FORMAT 0x12 +#define ICH6_REG_SD_BDLPL 0x18 +#define ICH6_REG_SD_BDLPU 0x1c + +/* PCI space */ +#define ICH6_PCIREG_TCSEL 0x44 + +/* + * other constants + */ + +/* max number of SDs */ +#define MAX_ICH6_DEV 8 +/* max number of fragments - we may use more if allocating more pages for BDL */ +#define AZX_MAX_FRAG (PAGE_SIZE / (MAX_ICH6_DEV * 16)) +/* max buffer size - no h/w limit, you can increase as you like */ +#define AZX_MAX_BUF_SIZE (1024*1024*1024) +/* max number of PCM devics per card */ +#define AZX_MAX_PCMS 8 + +/* RIRB int mask: overrun[2], response[0] */ +#define RIRB_INT_RESPONSE 0x01 +#define RIRB_INT_OVERRUN 0x04 +#define RIRB_INT_MASK 0x05 + +/* STATESTS int mask: SD2,SD1,SD0 */ +#define STATESTS_INT_MASK 0x07 +#define AZX_MAX_CODECS 4 + +/* SD_CTL bits */ +#define SD_CTL_STREAM_RESET 0x01 /* stream reset bit */ +#define SD_CTL_DMA_START 0x02 /* stream DMA start bit */ +#define SD_CTL_STREAM_TAG_MASK (0xf << 20) +#define SD_CTL_STREAM_TAG_SHIFT 20 + +/* SD_CTL and SD_STS */ +#define SD_INT_DESC_ERR 0x10 /* descriptor error interrupt */ +#define SD_INT_FIFO_ERR 0x08 /* FIFO error interrupt */ +#define SD_INT_COMPLETE 0x04 /* completion interrupt */ +#define SD_INT_MASK (SD_INT_DESC_ERR|SD_INT_FIFO_ERR|SD_INT_COMPLETE) + +/* SD_STS */ +#define SD_STS_FIFO_READY 0x20 /* FIFO ready */ + +/* INTCTL and INTSTS */ +#define ICH6_INT_ALL_STREAM 0xff /* all stream interrupts */ +#define ICH6_INT_CTRL_EN 0x40000000 /* controller interrupt enable bit */ +#define ICH6_INT_GLOBAL_EN 0x80000000 /* global interrupt enable bit */ + +/* GCTL reset bit */ +#define ICH6_GCTL_RESET (1<<0) + +/* CORB/RIRB control, read/write pointer */ +#define ICH6_RBCTL_DMA_EN 0x02 /* enable DMA */ +#define ICH6_RBCTL_IRQ_EN 0x01 /* enable IRQ */ +#define ICH6_RBRWP_CLR 0x8000 /* read/write pointer clear */ +/* below are so far hardcoded - should read registers in future */ +#define ICH6_MAX_CORB_ENTRIES 256 +#define ICH6_MAX_RIRB_ENTRIES 256 + +/* position fix mode */ +enum { + POS_FIX_FIFO, + POS_FIX_NONE, + POS_FIX_POSBUF +}; + +/* Defines for ATI HD Audio support in SB450 south bridge */ +#define ATI_SB450_HDAUDIO_PCI_DEVICE_ID 0x437b +#define ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR 0x42 +#define ATI_SB450_HDAUDIO_ENABLE_SNOOP 0x02 + + +/* + * Use CORB/RIRB for communication from/to codecs. + * This is the way recommended by Intel (see below). + */ +#define USE_CORB_RIRB + +/* + */ + +typedef struct snd_azx azx_t; +typedef struct snd_azx_rb azx_rb_t; +typedef struct snd_azx_dev azx_dev_t; + +struct snd_azx_dev { + u32 *bdl; /* virtual address of the BDL */ + dma_addr_t bdl_addr; /* physical address of the BDL */ + volatile u32 *posbuf; /* position buffer pointer */ + + unsigned int bufsize; /* size of the play buffer in bytes */ + unsigned int fragsize; /* size of each period in bytes */ + unsigned int frags; /* number for period in the play buffer */ + unsigned int fifo_size; /* FIFO size */ + + void __iomem *sd_addr; /* stream descriptor pointer */ + + u32 sd_int_sta_mask; /* stream int status mask */ + + /* pcm support */ + snd_pcm_substream_t *substream; /* assigned substream, set in PCM open */ + unsigned int format_val; /* format value to be set in the controller and the codec */ + unsigned char stream_tag; /* assigned stream */ + unsigned char index; /* stream index */ + + unsigned int opened: 1; + unsigned int running: 1; +}; + +/* CORB/RIRB */ +struct snd_azx_rb { + u32 *buf; /* CORB/RIRB buffer + * Each CORB entry is 4byte, RIRB is 8byte + */ + dma_addr_t addr; /* physical address of CORB/RIRB buffer */ + /* for RIRB */ + unsigned short rp, wp; /* read/write pointers */ + int cmds; /* number of pending requests */ + u32 res; /* last read value */ +}; + +struct snd_azx { + snd_card_t *card; + struct pci_dev *pci; + + /* pci resources */ + unsigned long addr; + void __iomem *remap_addr; + int irq; + + /* locks */ + spinlock_t reg_lock; + struct semaphore open_mutex; + + /* streams */ + azx_dev_t azx_dev[MAX_ICH6_DEV]; + + /* PCM */ + unsigned int pcm_devs; + snd_pcm_t *pcm[AZX_MAX_PCMS]; + + /* HD codec */ + unsigned short codec_mask; + struct hda_bus *bus; + + /* CORB/RIRB */ + azx_rb_t corb; + azx_rb_t rirb; + + /* BDL, CORB/RIRB and position buffers */ + struct snd_dma_buffer bdl; + struct snd_dma_buffer rb; + struct snd_dma_buffer posbuf; + + /* flags */ + int position_fix; + unsigned int initialized: 1; +}; + +/* + * macros for easy use + */ +#define azx_writel(chip,reg,value) \ + writel(value, (chip)->remap_addr + ICH6_REG_##reg) +#define azx_readl(chip,reg) \ + readl((chip)->remap_addr + ICH6_REG_##reg) +#define azx_writew(chip,reg,value) \ + writew(value, (chip)->remap_addr + ICH6_REG_##reg) +#define azx_readw(chip,reg) \ + readw((chip)->remap_addr + ICH6_REG_##reg) +#define azx_writeb(chip,reg,value) \ + writeb(value, (chip)->remap_addr + ICH6_REG_##reg) +#define azx_readb(chip,reg) \ + readb((chip)->remap_addr + ICH6_REG_##reg) + +#define azx_sd_writel(dev,reg,value) \ + writel(value, (dev)->sd_addr + ICH6_REG_##reg) +#define azx_sd_readl(dev,reg) \ + readl((dev)->sd_addr + ICH6_REG_##reg) +#define azx_sd_writew(dev,reg,value) \ + writew(value, (dev)->sd_addr + ICH6_REG_##reg) +#define azx_sd_readw(dev,reg) \ + readw((dev)->sd_addr + ICH6_REG_##reg) +#define azx_sd_writeb(dev,reg,value) \ + writeb(value, (dev)->sd_addr + ICH6_REG_##reg) +#define azx_sd_readb(dev,reg) \ + readb((dev)->sd_addr + ICH6_REG_##reg) + +/* for pcm support */ +#define get_azx_dev(substream) (azx_dev_t*)(substream->runtime->private_data) + +/* Get the upper 32bit of the given dma_addr_t + * Compiler should optimize and eliminate the code if dma_addr_t is 32bit + */ +#define upper_32bit(addr) (sizeof(addr) > 4 ? (u32)((addr) >> 32) : (u32)0) + + +/* + * Interface for HD codec + */ + +#ifdef USE_CORB_RIRB +/* + * CORB / RIRB interface + */ +static int azx_alloc_cmd_io(azx_t *chip) +{ + int err; + + /* single page (at least 4096 bytes) must suffice for both ringbuffes */ + err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci), + PAGE_SIZE, &chip->rb); + if (err < 0) { + snd_printk(KERN_ERR SFX "cannot allocate CORB/RIRB\n"); + return err; + } + return 0; +} + +static void azx_init_cmd_io(azx_t *chip) +{ + /* CORB set up */ + chip->corb.addr = chip->rb.addr; + chip->corb.buf = (u32 *)chip->rb.area; + azx_writel(chip, CORBLBASE, (u32)chip->corb.addr); + azx_writel(chip, CORBUBASE, upper_32bit(chip->corb.addr)); + + /* set the corb write pointer to 0 */ + azx_writew(chip, CORBWP, 0); + /* reset the corb hw read pointer */ + azx_writew(chip, CORBRP, ICH6_RBRWP_CLR); + /* enable corb dma */ + azx_writeb(chip, CORBCTL, ICH6_RBCTL_DMA_EN); + + /* RIRB set up */ + chip->rirb.addr = chip->rb.addr + 2048; + chip->rirb.buf = (u32 *)(chip->rb.area + 2048); + azx_writel(chip, RIRBLBASE, (u32)chip->rirb.addr); + azx_writel(chip, RIRBUBASE, upper_32bit(chip->rirb.addr)); + + /* reset the rirb hw write pointer */ + azx_writew(chip, RIRBWP, ICH6_RBRWP_CLR); + /* set N=1, get RIRB response interrupt for new entry */ + azx_writew(chip, RINTCNT, 1); + /* enable rirb dma and response irq */ +#ifdef USE_CORB_RIRB + azx_writeb(chip, RIRBCTL, ICH6_RBCTL_DMA_EN | ICH6_RBCTL_IRQ_EN); +#else + azx_writeb(chip, RIRBCTL, ICH6_RBCTL_DMA_EN); +#endif + chip->rirb.rp = chip->rirb.cmds = 0; +} + +static void azx_free_cmd_io(azx_t *chip) +{ + /* disable ringbuffer DMAs */ + azx_writeb(chip, RIRBCTL, 0); + azx_writeb(chip, CORBCTL, 0); +} + +/* send a command */ +static int azx_send_cmd(struct hda_codec *codec, hda_nid_t nid, int direct, + unsigned int verb, unsigned int para) +{ + azx_t *chip = codec->bus->private_data; + unsigned int wp; + u32 val; + + val = (u32)(codec->addr & 0x0f) << 28; + val |= (u32)direct << 27; + val |= (u32)nid << 20; + val |= verb << 8; + val |= para; + + /* add command to corb */ + wp = azx_readb(chip, CORBWP); + wp++; + wp %= ICH6_MAX_CORB_ENTRIES; + + spin_lock_irq(&chip->reg_lock); + chip->rirb.cmds++; + chip->corb.buf[wp] = cpu_to_le32(val); + azx_writel(chip, CORBWP, wp); + spin_unlock_irq(&chip->reg_lock); + + return 0; +} + +#define ICH6_RIRB_EX_UNSOL_EV (1<<4) + +/* retrieve RIRB entry - called from interrupt handler */ +static void azx_update_rirb(azx_t *chip) +{ + unsigned int rp, wp; + u32 res, res_ex; + + wp = azx_readb(chip, RIRBWP); + if (wp == chip->rirb.wp) + return; + chip->rirb.wp = wp; + + while (chip->rirb.rp != wp) { + chip->rirb.rp++; + chip->rirb.rp %= ICH6_MAX_RIRB_ENTRIES; + + rp = chip->rirb.rp << 1; /* an RIRB entry is 8-bytes */ + res_ex = le32_to_cpu(chip->rirb.buf[rp + 1]); + res = le32_to_cpu(chip->rirb.buf[rp]); + if (res_ex & ICH6_RIRB_EX_UNSOL_EV) + snd_hda_queue_unsol_event(chip->bus, res, res_ex); + else if (chip->rirb.cmds) { + chip->rirb.cmds--; + chip->rirb.res = res; + } + } +} + +/* receive a response */ +static unsigned int azx_get_response(struct hda_codec *codec) +{ + azx_t *chip = codec->bus->private_data; + int timeout = 50; + + while (chip->rirb.cmds) { + if (! --timeout) { + snd_printk(KERN_ERR "azx_get_response timeout\n"); + chip->rirb.rp = azx_readb(chip, RIRBWP); + chip->rirb.cmds = 0; + return -1; + } + msleep(1); + } + return chip->rirb.res; /* the last value */ +} + +#else +/* + * Use the single immediate command instead of CORB/RIRB for simplicity + * + * Note: according to Intel, this is not preferred use. The command was + * intended for the BIOS only, and may get confused with unsolicited + * responses. So, we shouldn't use it for normal operation from the + * driver. + * I left the codes, however, for debugging/testing purposes. + */ + +#define azx_alloc_cmd_io(chip) 0 +#define azx_init_cmd_io(chip) +#define azx_free_cmd_io(chip) + +/* send a command */ +static int azx_send_cmd(struct hda_codec *codec, hda_nid_t nid, int direct, + unsigned int verb, unsigned int para) +{ + azx_t *chip = codec->bus->private_data; + u32 val; + int timeout = 50; + + val = (u32)(codec->addr & 0x0f) << 28; + val |= (u32)direct << 27; + val |= (u32)nid << 20; + val |= verb << 8; + val |= para; + + while (timeout--) { + /* check ICB busy bit */ + if (! (azx_readw(chip, IRS) & ICH6_IRS_BUSY)) { + /* Clear IRV valid bit */ + azx_writew(chip, IRS, azx_readw(chip, IRS) | ICH6_IRS_VALID); + azx_writel(chip, IC, val); + azx_writew(chip, IRS, azx_readw(chip, IRS) | ICH6_IRS_BUSY); + return 0; + } + udelay(1); + } + snd_printd(SFX "send_cmd timeout: IRS=0x%x, val=0x%x\n", azx_readw(chip, IRS), val); + return -EIO; +} + +/* receive a response */ +static unsigned int azx_get_response(struct hda_codec *codec) +{ + azx_t *chip = codec->bus->private_data; + int timeout = 50; + + while (timeout--) { + /* check IRV busy bit */ + if (azx_readw(chip, IRS) & ICH6_IRS_VALID) + return azx_readl(chip, IR); + udelay(1); + } + snd_printd(SFX "get_response timeout: IRS=0x%x\n", azx_readw(chip, IRS)); + return (unsigned int)-1; +} + +#define azx_update_rirb(chip) + +#endif /* USE_CORB_RIRB */ + +/* reset codec link */ +static int azx_reset(azx_t *chip) +{ + int count; + + /* reset controller */ + azx_writel(chip, GCTL, azx_readl(chip, GCTL) & ~ICH6_GCTL_RESET); + + count = 50; + while (azx_readb(chip, GCTL) && --count) + msleep(1); + + /* delay for >= 100us for codec PLL to settle per spec + * Rev 0.9 section 5.5.1 + */ + msleep(1); + + /* Bring controller out of reset */ + azx_writeb(chip, GCTL, azx_readb(chip, GCTL) | ICH6_GCTL_RESET); + + count = 50; + while (! azx_readb(chip, GCTL) && --count) + msleep(1); + + /* Brent Chartrand said to wait >= 540us for codecs to intialize */ + msleep(1); + + /* check to see if controller is ready */ + if (! azx_readb(chip, GCTL)) { + snd_printd("azx_reset: controller not ready!\n"); + return -EBUSY; + } + + /* detect codecs */ + if (! chip->codec_mask) { + chip->codec_mask = azx_readw(chip, STATESTS); + snd_printdd("codec_mask = 0x%x\n", chip->codec_mask); + } + + return 0; +} + + +/* + * Lowlevel interface + */ + +/* enable interrupts */ +static void azx_int_enable(azx_t *chip) +{ + /* enable controller CIE and GIE */ + azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) | + ICH6_INT_CTRL_EN | ICH6_INT_GLOBAL_EN); +} + +/* disable interrupts */ +static void azx_int_disable(azx_t *chip) +{ + int i; + + /* disable interrupts in stream descriptor */ + for (i = 0; i < MAX_ICH6_DEV; i++) { + azx_dev_t *azx_dev = &chip->azx_dev[i]; + azx_sd_writeb(azx_dev, SD_CTL, + azx_sd_readb(azx_dev, SD_CTL) & ~SD_INT_MASK); + } + + /* disable SIE for all streams */ + azx_writeb(chip, INTCTL, 0); + + /* disable controller CIE and GIE */ + azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) & + ~(ICH6_INT_CTRL_EN | ICH6_INT_GLOBAL_EN)); +} + +/* clear interrupts */ +static void azx_int_clear(azx_t *chip) +{ + int i; + + /* clear stream status */ + for (i = 0; i < MAX_ICH6_DEV; i++) { + azx_dev_t *azx_dev = &chip->azx_dev[i]; + azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK); + } + + /* clear STATESTS */ + azx_writeb(chip, STATESTS, STATESTS_INT_MASK); + + /* clear rirb status */ + azx_writeb(chip, RIRBSTS, RIRB_INT_MASK); + + /* clear int status */ + azx_writel(chip, INTSTS, ICH6_INT_CTRL_EN | ICH6_INT_ALL_STREAM); +} + +/* start a stream */ +static void azx_stream_start(azx_t *chip, azx_dev_t *azx_dev) +{ + /* enable SIE */ + azx_writeb(chip, INTCTL, + azx_readb(chip, INTCTL) | (1 << azx_dev->index)); + /* set DMA start and interrupt mask */ + azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) | + SD_CTL_DMA_START | SD_INT_MASK); +} + +/* stop a stream */ +static void azx_stream_stop(azx_t *chip, azx_dev_t *azx_dev) +{ + /* stop DMA */ + azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) & + ~(SD_CTL_DMA_START | SD_INT_MASK)); + azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ + /* disable SIE */ + azx_writeb(chip, INTCTL, + azx_readb(chip, INTCTL) & ~(1 << azx_dev->index)); +} + + +/* + * initialize the chip + */ +static void azx_init_chip(azx_t *chip) +{ + unsigned char tcsel_reg, ati_misc_cntl2; + + /* Clear bits 0-2 of PCI register TCSEL (at offset 0x44) + * TCSEL == Traffic Class Select Register, which sets PCI express QOS + * Ensuring these bits are 0 clears playback static on some HD Audio codecs + */ + pci_read_config_byte (chip->pci, ICH6_PCIREG_TCSEL, &tcsel_reg); + pci_write_config_byte(chip->pci, ICH6_PCIREG_TCSEL, tcsel_reg & 0xf8); + + /* reset controller */ + azx_reset(chip); + + /* initialize interrupts */ + azx_int_clear(chip); + azx_int_enable(chip); + + /* initialize the codec command I/O */ + azx_init_cmd_io(chip); + + if (chip->position_fix == POS_FIX_POSBUF) { + /* program the position buffer */ + azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr); + azx_writel(chip, DPUBASE, upper_32bit(chip->posbuf.addr)); + } + + /* For ATI SB450 azalia HD audio, we need to enable snoop */ + if (chip->pci->vendor == PCI_VENDOR_ID_ATI && + chip->pci->device == ATI_SB450_HDAUDIO_PCI_DEVICE_ID) { + pci_read_config_byte(chip->pci, ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR, + &ati_misc_cntl2); + pci_write_config_byte(chip->pci, ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR, + (ati_misc_cntl2 & 0xf8) | ATI_SB450_HDAUDIO_ENABLE_SNOOP); + } +} + + +/* + * interrupt handler + */ +static irqreturn_t azx_interrupt(int irq, void* dev_id, struct pt_regs *regs) +{ + azx_t *chip = dev_id; + azx_dev_t *azx_dev; + u32 status; + int i; + + spin_lock(&chip->reg_lock); + + status = azx_readl(chip, INTSTS); + if (status == 0) { + spin_unlock(&chip->reg_lock); + return IRQ_NONE; + } + + for (i = 0; i < MAX_ICH6_DEV; i++) { + azx_dev = &chip->azx_dev[i]; + if (status & azx_dev->sd_int_sta_mask) { + azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK); + if (azx_dev->substream && azx_dev->running) { + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(azx_dev->substream); + spin_lock(&chip->reg_lock); + } + } + } + + /* clear rirb int */ + status = azx_readb(chip, RIRBSTS); + if (status & RIRB_INT_MASK) { + if (status & RIRB_INT_RESPONSE) + azx_update_rirb(chip); + azx_writeb(chip, RIRBSTS, RIRB_INT_MASK); + } + +#if 0 + /* clear state status int */ + if (azx_readb(chip, STATESTS) & 0x04) + azx_writeb(chip, STATESTS, 0x04); +#endif + spin_unlock(&chip->reg_lock); + + return IRQ_HANDLED; +} + + +/* + * set up BDL entries + */ +static void azx_setup_periods(azx_dev_t *azx_dev) +{ + u32 *bdl = azx_dev->bdl; + dma_addr_t dma_addr = azx_dev->substream->runtime->dma_addr; + int idx; + + /* reset BDL address */ + azx_sd_writel(azx_dev, SD_BDLPL, 0); + azx_sd_writel(azx_dev, SD_BDLPU, 0); + + /* program the initial BDL entries */ + for (idx = 0; idx < azx_dev->frags; idx++) { + unsigned int off = idx << 2; /* 4 dword step */ + dma_addr_t addr = dma_addr + idx * azx_dev->fragsize; + /* program the address field of the BDL entry */ + bdl[off] = cpu_to_le32((u32)addr); + bdl[off+1] = cpu_to_le32(upper_32bit(addr)); + + /* program the size field of the BDL entry */ + bdl[off+2] = cpu_to_le32(azx_dev->fragsize); + + /* program the IOC to enable interrupt when buffer completes */ + bdl[off+3] = cpu_to_le32(0x01); + } +} + +/* + * set up the SD for streaming + */ +static int azx_setup_controller(azx_t *chip, azx_dev_t *azx_dev) +{ + unsigned char val; + int timeout; + + /* make sure the run bit is zero for SD */ + azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) & ~SD_CTL_DMA_START); + /* reset stream */ + azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) | SD_CTL_STREAM_RESET); + udelay(3); + timeout = 300; + while (!((val = azx_sd_readb(azx_dev, SD_CTL)) & SD_CTL_STREAM_RESET) && + --timeout) + ; + val &= ~SD_CTL_STREAM_RESET; + azx_sd_writeb(azx_dev, SD_CTL, val); + udelay(3); + + timeout = 300; + /* waiting for hardware to report that the stream is out of reset */ + while (((val = azx_sd_readb(azx_dev, SD_CTL)) & SD_CTL_STREAM_RESET) && + --timeout) + ; + + /* program the stream_tag */ + azx_sd_writel(azx_dev, SD_CTL, + (azx_sd_readl(azx_dev, SD_CTL) & ~SD_CTL_STREAM_TAG_MASK) | + (azx_dev->stream_tag << SD_CTL_STREAM_TAG_SHIFT)); + + /* program the length of samples in cyclic buffer */ + azx_sd_writel(azx_dev, SD_CBL, azx_dev->bufsize); + + /* program the stream format */ + /* this value needs to be the same as the one programmed */ + azx_sd_writew(azx_dev, SD_FORMAT, azx_dev->format_val); + + /* program the stream LVI (last valid index) of the BDL */ + azx_sd_writew(azx_dev, SD_LVI, azx_dev->frags - 1); + + /* program the BDL address */ + /* lower BDL address */ + azx_sd_writel(azx_dev, SD_BDLPL, (u32)azx_dev->bdl_addr); + /* upper BDL address */ + azx_sd_writel(azx_dev, SD_BDLPU, upper_32bit(azx_dev->bdl_addr)); + + if (chip->position_fix == POS_FIX_POSBUF) { + /* enable the position buffer */ + if (! (azx_readl(chip, DPLBASE) & ICH6_DPLBASE_ENABLE)) + azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr | ICH6_DPLBASE_ENABLE); + } + + /* set the interrupt enable bits in the descriptor control register */ + azx_sd_writel(azx_dev, SD_CTL, azx_sd_readl(azx_dev, SD_CTL) | SD_INT_MASK); + + return 0; +} + + +/* + * Codec initialization + */ + +static int __devinit azx_codec_create(azx_t *chip, const char *model) +{ + struct hda_bus_template bus_temp; + int c, codecs, err; + + memset(&bus_temp, 0, sizeof(bus_temp)); + bus_temp.private_data = chip; + bus_temp.modelname = model; + bus_temp.pci = chip->pci; + bus_temp.ops.command = azx_send_cmd; + bus_temp.ops.get_response = azx_get_response; + + if ((err = snd_hda_bus_new(chip->card, &bus_temp, &chip->bus)) < 0) + return err; + + codecs = 0; + for (c = 0; c < AZX_MAX_CODECS; c++) { + if (chip->codec_mask & (1 << c)) { + err = snd_hda_codec_new(chip->bus, c, NULL); + if (err < 0) + continue; + codecs++; + } + } + if (! codecs) { + snd_printk(KERN_ERR SFX "no codecs initialized\n"); + return -ENXIO; + } + + return 0; +} + + +/* + * PCM support + */ + +/* assign a stream for the PCM */ +static inline azx_dev_t *azx_assign_device(azx_t *chip, int stream) +{ + int dev, i; + dev = stream == SNDRV_PCM_STREAM_PLAYBACK ? 4 : 0; + for (i = 0; i < 4; i++, dev++) + if (! chip->azx_dev[dev].opened) { + chip->azx_dev[dev].opened = 1; + return &chip->azx_dev[dev]; + } + return NULL; +} + +/* release the assigned stream */ +static inline void azx_release_device(azx_dev_t *azx_dev) +{ + azx_dev->opened = 0; +} + +static snd_pcm_hardware_t azx_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = AZX_MAX_BUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = AZX_MAX_BUF_SIZE / 2, + .periods_min = 2, + .periods_max = AZX_MAX_FRAG, + .fifo_size = 0, +}; + +struct azx_pcm { + azx_t *chip; + struct hda_codec *codec; + struct hda_pcm_stream *hinfo[2]; +}; + +static int azx_pcm_open(snd_pcm_substream_t *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; + azx_t *chip = apcm->chip; + azx_dev_t *azx_dev; + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned long flags; + int err; + + down(&chip->open_mutex); + azx_dev = azx_assign_device(chip, substream->stream); + if (azx_dev == NULL) { + up(&chip->open_mutex); + return -EBUSY; + } + runtime->hw = azx_pcm_hw; + runtime->hw.channels_min = hinfo->channels_min; + runtime->hw.channels_max = hinfo->channels_max; + runtime->hw.formats = hinfo->formats; + runtime->hw.rates = hinfo->rates; + snd_pcm_limit_hw_rates(runtime); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if ((err = hinfo->ops.open(hinfo, apcm->codec, substream)) < 0) { + azx_release_device(azx_dev); + up(&chip->open_mutex); + return err; + } + spin_lock_irqsave(&chip->reg_lock, flags); + azx_dev->substream = substream; + azx_dev->running = 0; + spin_unlock_irqrestore(&chip->reg_lock, flags); + + runtime->private_data = azx_dev; + up(&chip->open_mutex); + return 0; +} + +static int azx_pcm_close(snd_pcm_substream_t *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; + azx_t *chip = apcm->chip; + azx_dev_t *azx_dev = get_azx_dev(substream); + unsigned long flags; + + down(&chip->open_mutex); + spin_lock_irqsave(&chip->reg_lock, flags); + azx_dev->substream = NULL; + azx_dev->running = 0; + spin_unlock_irqrestore(&chip->reg_lock, flags); + azx_release_device(azx_dev); + hinfo->ops.close(hinfo, apcm->codec, substream); + up(&chip->open_mutex); + return 0; +} + +static int azx_pcm_hw_params(snd_pcm_substream_t *substream, snd_pcm_hw_params_t *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int azx_pcm_hw_free(snd_pcm_substream_t *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + azx_dev_t *azx_dev = get_azx_dev(substream); + struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; + + /* reset BDL address */ + azx_sd_writel(azx_dev, SD_BDLPL, 0); + azx_sd_writel(azx_dev, SD_BDLPU, 0); + azx_sd_writel(azx_dev, SD_CTL, 0); + + hinfo->ops.cleanup(hinfo, apcm->codec, substream); + + return snd_pcm_lib_free_pages(substream); +} + +static int azx_pcm_prepare(snd_pcm_substream_t *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + azx_t *chip = apcm->chip; + azx_dev_t *azx_dev = get_azx_dev(substream); + struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; + snd_pcm_runtime_t *runtime = substream->runtime; + + azx_dev->bufsize = snd_pcm_lib_buffer_bytes(substream); + azx_dev->fragsize = snd_pcm_lib_period_bytes(substream); + azx_dev->frags = azx_dev->bufsize / azx_dev->fragsize; + azx_dev->format_val = snd_hda_calc_stream_format(runtime->rate, + runtime->channels, + runtime->format, + hinfo->maxbps); + if (! azx_dev->format_val) { + snd_printk(KERN_ERR SFX "invalid format_val, rate=%d, ch=%d, format=%d\n", + runtime->rate, runtime->channels, runtime->format); + return -EINVAL; + } + + snd_printdd("azx_pcm_prepare: bufsize=0x%x, fragsize=0x%x, format=0x%x\n", + azx_dev->bufsize, azx_dev->fragsize, azx_dev->format_val); + azx_setup_periods(azx_dev); + azx_setup_controller(chip, azx_dev); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + azx_dev->fifo_size = azx_sd_readw(azx_dev, SD_FIFOSIZE) + 1; + else + azx_dev->fifo_size = 0; + + return hinfo->ops.prepare(hinfo, apcm->codec, azx_dev->stream_tag, + azx_dev->format_val, substream); +} + +static int azx_pcm_trigger(snd_pcm_substream_t *substream, int cmd) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + azx_dev_t *azx_dev = get_azx_dev(substream); + azx_t *chip = apcm->chip; + int err = 0; + + spin_lock(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_START: + azx_stream_start(chip, azx_dev); + azx_dev->running = 1; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + azx_stream_stop(chip, azx_dev); + azx_dev->running = 0; + break; + default: + err = -EINVAL; + } + spin_unlock(&chip->reg_lock); + if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH || + cmd == SNDRV_PCM_TRIGGER_STOP) { + int timeout = 5000; + while (azx_sd_readb(azx_dev, SD_CTL) & SD_CTL_DMA_START && --timeout) + ; + } + return err; +} + +static snd_pcm_uframes_t azx_pcm_pointer(snd_pcm_substream_t *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + azx_t *chip = apcm->chip; + azx_dev_t *azx_dev = get_azx_dev(substream); + unsigned int pos; + + if (chip->position_fix == POS_FIX_POSBUF) { + /* use the position buffer */ + pos = *azx_dev->posbuf; + } else { + /* read LPIB */ + pos = azx_sd_readl(azx_dev, SD_LPIB); + if (chip->position_fix == POS_FIX_FIFO) + pos += azx_dev->fifo_size; + } + if (pos >= azx_dev->bufsize) + pos = 0; + return bytes_to_frames(substream->runtime, pos); +} + +static snd_pcm_ops_t azx_pcm_ops = { + .open = azx_pcm_open, + .close = azx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = azx_pcm_hw_params, + .hw_free = azx_pcm_hw_free, + .prepare = azx_pcm_prepare, + .trigger = azx_pcm_trigger, + .pointer = azx_pcm_pointer, +}; + +static void azx_pcm_free(snd_pcm_t *pcm) +{ + kfree(pcm->private_data); +} + +static int __devinit create_codec_pcm(azx_t *chip, struct hda_codec *codec, + struct hda_pcm *cpcm, int pcm_dev) +{ + int err; + snd_pcm_t *pcm; + struct azx_pcm *apcm; + + snd_assert(cpcm->stream[0].substreams || cpcm->stream[1].substreams, return -EINVAL); + snd_assert(cpcm->name, return -EINVAL); + + err = snd_pcm_new(chip->card, cpcm->name, pcm_dev, + cpcm->stream[0].substreams, cpcm->stream[1].substreams, + &pcm); + if (err < 0) + return err; + strcpy(pcm->name, cpcm->name); + apcm = kmalloc(sizeof(*apcm), GFP_KERNEL); + if (apcm == NULL) + return -ENOMEM; + apcm->chip = chip; + apcm->codec = codec; + apcm->hinfo[0] = &cpcm->stream[0]; + apcm->hinfo[1] = &cpcm->stream[1]; + pcm->private_data = apcm; + pcm->private_free = azx_pcm_free; + if (cpcm->stream[0].substreams) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &azx_pcm_ops); + if (cpcm->stream[1].substreams) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &azx_pcm_ops); + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + 1024 * 64, 1024 * 128); + chip->pcm[pcm_dev] = pcm; + + return 0; +} + +static int __devinit azx_pcm_create(azx_t *chip) +{ + struct list_head *p; + struct hda_codec *codec; + int c, err; + int pcm_dev; + + if ((err = snd_hda_build_pcms(chip->bus)) < 0) + return err; + + pcm_dev = 0; + list_for_each(p, &chip->bus->codec_list) { + codec = list_entry(p, struct hda_codec, list); + for (c = 0; c < codec->num_pcms; c++) { + if (pcm_dev >= AZX_MAX_PCMS) { + snd_printk(KERN_ERR SFX "Too many PCMs\n"); + return -EINVAL; + } + err = create_codec_pcm(chip, codec, &codec->pcm_info[c], pcm_dev); + if (err < 0) + return err; + pcm_dev++; + } + } + return 0; +} + +/* + * mixer creation - all stuff is implemented in hda module + */ +static int __devinit azx_mixer_create(azx_t *chip) +{ + return snd_hda_build_controls(chip->bus); +} + + +/* + * initialize SD streams + */ +static int __devinit azx_init_stream(azx_t *chip) +{ + int i; + + /* initialize each stream (aka device) + * assign the starting bdl address to each stream (device) and initialize + */ + for (i = 0; i < MAX_ICH6_DEV; i++) { + unsigned int off = sizeof(u32) * (i * AZX_MAX_FRAG * 4); + azx_dev_t *azx_dev = &chip->azx_dev[i]; + azx_dev->bdl = (u32 *)(chip->bdl.area + off); + azx_dev->bdl_addr = chip->bdl.addr + off; + if (chip->position_fix == POS_FIX_POSBUF) + azx_dev->posbuf = (volatile u32 *)(chip->posbuf.area + i * 8); + /* offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */ + azx_dev->sd_addr = chip->remap_addr + (0x20 * i + 0x80); + /* int mask: SDI0=0x01, SDI1=0x02, ... SDO3=0x80 */ + azx_dev->sd_int_sta_mask = 1 << i; + /* stream tag: must be non-zero and unique */ + azx_dev->index = i; + azx_dev->stream_tag = i + 1; + } + + return 0; +} + + +#ifdef CONFIG_PM +/* + * power management + */ +static int azx_suspend(snd_card_t *card, unsigned int state) +{ + azx_t *chip = card->pm_private_data; + int i; + + for (i = 0; i < chip->pcm_devs; i++) + if (chip->pcm[i]) + snd_pcm_suspend_all(chip->pcm[i]); + snd_hda_suspend(chip->bus, state); + azx_free_cmd_io(chip); + pci_disable_device(chip->pci); + return 0; +} + +static int azx_resume(snd_card_t *card, unsigned int state) +{ + azx_t *chip = card->pm_private_data; + + pci_enable_device(chip->pci); + pci_set_master(chip->pci); + azx_init_chip(chip); + snd_hda_resume(chip->bus); + return 0; +} +#endif /* CONFIG_PM */ + + +/* + * destructor + */ +static int azx_free(azx_t *chip) +{ + if (chip->initialized) { + int i; + + for (i = 0; i < MAX_ICH6_DEV; i++) + azx_stream_stop(chip, &chip->azx_dev[i]); + + /* disable interrupts */ + azx_int_disable(chip); + azx_int_clear(chip); + + /* disable CORB/RIRB */ + azx_free_cmd_io(chip); + + /* disable position buffer */ + azx_writel(chip, DPLBASE, 0); + azx_writel(chip, DPUBASE, 0); + + /* wait a little for interrupts to finish */ + msleep(1); + + iounmap(chip->remap_addr); + } + + if (chip->irq >= 0) + free_irq(chip->irq, (void*)chip); + + if (chip->bdl.area) + snd_dma_free_pages(&chip->bdl); + if (chip->rb.area) + snd_dma_free_pages(&chip->rb); + if (chip->posbuf.area) + snd_dma_free_pages(&chip->posbuf); + pci_release_regions(chip->pci); + pci_disable_device(chip->pci); + kfree(chip); + + return 0; +} + +static int azx_dev_free(snd_device_t *device) +{ + return azx_free(device->device_data); +} + +/* + * constructor + */ +static int __devinit azx_create(snd_card_t *card, struct pci_dev *pci, + int posfix, azx_t **rchip) +{ + azx_t *chip; + int err = 0; + static snd_device_ops_t ops = { + .dev_free = azx_dev_free, + }; + + *rchip = NULL; + + if ((err = pci_enable_device(pci)) < 0) + return err; + + chip = kcalloc(1, sizeof(*chip), GFP_KERNEL); + + if (NULL == chip) { + snd_printk(KERN_ERR SFX "cannot allocate chip\n"); + pci_disable_device(pci); + return -ENOMEM; + } + + spin_lock_init(&chip->reg_lock); + init_MUTEX(&chip->open_mutex); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + chip->position_fix = posfix; + + if ((err = pci_request_regions(pci, "ICH HD audio")) < 0) { + kfree(chip); + pci_disable_device(pci); + return err; + } + + chip->addr = pci_resource_start(pci,0); + chip->remap_addr = ioremap_nocache(chip->addr, pci_resource_len(pci,0)); + if (chip->remap_addr == NULL) { + snd_printk(KERN_ERR SFX "ioremap error\n"); + err = -ENXIO; + goto errout; + } + + if (request_irq(pci->irq, azx_interrupt, SA_INTERRUPT|SA_SHIRQ, + "HDA Intel", (void*)chip)) { + snd_printk(KERN_ERR SFX "unable to grab IRQ %d\n", pci->irq); + err = -EBUSY; + goto errout; + } + chip->irq = pci->irq; + + pci_set_master(pci); + synchronize_irq(chip->irq); + + /* allocate memory for the BDL for each stream */ + if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci), + PAGE_SIZE, &chip->bdl)) < 0) { + snd_printk(KERN_ERR SFX "cannot allocate BDL\n"); + goto errout; + } + if (chip->position_fix == POS_FIX_POSBUF) { + /* allocate memory for the position buffer */ + if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci), + MAX_ICH6_DEV * 8, &chip->posbuf)) < 0) { + snd_printk(KERN_ERR SFX "cannot allocate posbuf\n"); + goto errout; + } + } + /* allocate CORB/RIRB */ + if ((err = azx_alloc_cmd_io(chip)) < 0) + goto errout; + + /* initialize streams */ + azx_init_stream(chip); + + /* initialize chip */ + azx_init_chip(chip); + + chip->initialized = 1; + + /* codec detection */ + if (! chip->codec_mask) { + snd_printk(KERN_ERR SFX "no codecs found!\n"); + err = -ENODEV; + goto errout; + } + + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) <0) { + snd_printk(KERN_ERR SFX "Error creating device [card]!\n"); + goto errout; + } + + *rchip = chip; + return 0; + + errout: + azx_free(chip); + return err; +} + +static int __devinit azx_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + static int dev; + snd_card_t *card; + azx_t *chip; + int err = 0; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (! enable[dev]) { + dev++; + return -ENOENT; + } + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (NULL == card) { + snd_printk(KERN_ERR SFX "Error creating card!\n"); + return -ENOMEM; + } + + if ((err = azx_create(card, pci, position_fix[dev], &chip)) < 0) { + snd_card_free(card); + return err; + } + + strcpy(card->driver, "HDA-Intel"); + strcpy(card->shortname, "HDA Intel"); + sprintf(card->longname, "%s at 0x%lx irq %i", card->shortname, chip->addr, chip->irq); + + /* create codec instances */ + if ((err = azx_codec_create(chip, model[dev])) < 0) { + snd_card_free(card); + return err; + } + + /* create PCM streams */ + if ((err = azx_pcm_create(chip)) < 0) { + snd_card_free(card); + return err; + } + + /* create mixer controls */ + if ((err = azx_mixer_create(chip)) < 0) { + snd_card_free(card); + return err; + } + + snd_card_set_pm_callback(card, azx_suspend, azx_resume, chip); + snd_card_set_dev(card, &pci->dev); + + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + + pci_set_drvdata(pci, card); + dev++; + + return err; +} + +static void __devexit azx_remove(struct pci_dev *pci) +{ + snd_card_free(pci_get_drvdata(pci)); + pci_set_drvdata(pci, NULL); +} + +/* PCI IDs */ +static struct pci_device_id azx_ids[] = { + { 0x8086, 0x2668, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* ICH6 */ + { 0x8086, 0x27d8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* ICH7 */ + { 0x8086, 0x269a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* ESB2 */ + { 0x1002, 0x437b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* ATI SB450 */ + { 0x1106, 0x3288, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* VIA VT8251/VT8237A */ + { 0x10b9, 0x5461, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* ALI 5461? */ + { 0, } +}; +MODULE_DEVICE_TABLE(pci, azx_ids); + +/* pci_driver definition */ +static struct pci_driver driver = { + .name = "HDA Intel", + .id_table = azx_ids, + .probe = azx_probe, + .remove = __devexit_p(azx_remove), + SND_PCI_PM_CALLBACKS +}; + +static int __init alsa_card_azx_init(void) +{ + return pci_register_driver(&driver); +} + +static void __exit alsa_card_azx_exit(void) +{ + pci_unregister_driver(&driver); +} + +module_init(alsa_card_azx_init) +module_exit(alsa_card_azx_exit) --- linux-2.6.9/sound/pci/azx/hda_codec.h.orig 2005-07-17 10:31:03.644159366 -0400 +++ linux-2.6.9/sound/pci/azx/hda_codec.h 2005-07-17 10:25:03.518296891 -0400 @@ -75,6 +75,9 @@ enum { #define AC_VERB_GET_DIGI_CONVERT 0x0f0d #define AC_VERB_GET_VOLUME_KNOB_CONTROL 0x0f0f /* f10-f1a: GPIO */ +#define AC_VERB_GET_GPIO_DATA 0x0f15 +#define AC_VERB_GET_GPIO_MASK 0x0f16 +#define AC_VERB_GET_GPIO_DIRECTION 0x0f17 #define AC_VERB_GET_CONFIG_DEFAULT 0x0f1c /* @@ -97,6 +100,9 @@ enum { #define AC_VERB_SET_DIGI_CONVERT_1 0x70d #define AC_VERB_SET_DIGI_CONVERT_2 0x70e #define AC_VERB_SET_VOLUME_KNOB_CONTROL 0x70f +#define AC_VERB_SET_GPIO_DATA 0x715 +#define AC_VERB_SET_GPIO_MASK 0x716 +#define AC_VERB_SET_GPIO_DIRECTION 0x717 #define AC_VERB_SET_CONFIG_DEFAULT_BYTES_0 0x71c #define AC_VERB_SET_CONFIG_DEFAULT_BYTES_1 0x71d #define AC_VERB_SET_CONFIG_DEFAULT_BYTES_2 0x71e @@ -176,16 +182,15 @@ enum { #define AC_PINCAP_OUT (1<<4) /* output capable */ #define AC_PINCAP_IN (1<<5) /* input capable */ #define AC_PINCAP_BALANCE (1<<6) /* balanced I/O capable */ -#define AC_PINCAP_VREF (7<<8) +#define AC_PINCAP_VREF (0x37<<8) #define AC_PINCAP_VREF_SHIFT 8 #define AC_PINCAP_EAPD (1<<16) /* EAPD capable */ -/* Vref status (used in pin cap and pin ctl) */ -#define AC_PIN_VREF_HIZ (1<<0) /* Hi-Z */ -#define AC_PIN_VREF_50 (1<<1) /* 50% */ -#define AC_PIN_VREF_GRD (1<<2) /* ground */ -#define AC_PIN_VREF_80 (1<<4) /* 80% */ -#define AC_PIN_VREF_100 (1<<5) /* 100% */ - +/* Vref status (used in pin cap) */ +#define AC_PINCAP_VREF_HIZ (1<<0) /* Hi-Z */ +#define AC_PINCAP_VREF_50 (1<<1) /* 50% */ +#define AC_PINCAP_VREF_GRD (1<<2) /* ground */ +#define AC_PINCAP_VREF_80 (1<<4) /* 80% */ +#define AC_PINCAP_VREF_100 (1<<5) /* 100% */ /* Amplifier capabilities */ #define AC_AMPCAP_OFFSET (0x7f<<0) /* 0dB offset */ @@ -248,6 +253,11 @@ enum { /* Pin widget control - 8bit */ #define AC_PINCTL_VREFEN (0x7<<0) +#define AC_PINCTL_VREF_HIZ 0 /* Hi-Z */ +#define AC_PINCTL_VREF_50 1 /* 50% */ +#define AC_PINCTL_VREF_GRD 2 /* ground */ +#define AC_PINCTL_VREF_80 4 /* 80% */ +#define AC_PINCTL_VREF_100 5 /* 100% */ #define AC_PINCTL_IN_EN (1<<5) #define AC_PINCTL_OUT_EN (1<<6) #define AC_PINCTL_HP_EN (1<<7) @@ -255,7 +265,9 @@ enum { /* configuration default - 32bit */ #define AC_DEFCFG_SEQUENCE (0xf<<0) #define AC_DEFCFG_DEF_ASSOC (0xf<<4) +#define AC_DEFCFG_ASSOC_SHIFT 4 #define AC_DEFCFG_MISC (0xf<<8) +#define AC_DEFCFG_MISC_SHIFT 8 #define AC_DEFCFG_COLOR (0xf<<12) #define AC_DEFCFG_COLOR_SHIFT 12 #define AC_DEFCFG_CONN_TYPE (0xf<<16) @@ -361,6 +373,9 @@ enum { /* max. connections to a widget */ #define HDA_MAX_CONNECTIONS 16 +/* max. codec address */ +#define HDA_MAX_CODEC_ADDRESS 0x0f + /* * Structures */ @@ -369,6 +384,7 @@ struct hda_bus; struct hda_codec; struct hda_pcm; struct hda_pcm_stream; +struct hda_bus_unsolicited; /* NID type */ typedef u16 hda_nid_t; @@ -409,9 +425,13 @@ struct hda_bus { /* codec linked list */ struct list_head codec_list; + struct hda_codec *caddr_tbl[HDA_MAX_CODEC_ADDRESS + 1]; /* caddr -> codec */ struct semaphore cmd_mutex; + /* unsolicited event queue */ + struct hda_bus_unsolicited *unsol; + snd_info_entry_t *proc; }; @@ -437,8 +457,10 @@ struct hda_codec_ops { int (*build_pcms)(struct hda_codec *codec); int (*init)(struct hda_codec *codec); void (*free)(struct hda_codec *codec); + void (*unsol_event)(struct hda_codec *codec, unsigned int res); #ifdef CONFIG_PM - int (*resume)(struct hda_codec *codec, unsigned int state); + int (*suspend)(struct hda_codec *codec, unsigned int state); + int (*resume)(struct hda_codec *codec); #endif }; @@ -519,7 +541,9 @@ struct hda_codec { struct hda_amp_info amp_info[128]; /* big enough? */ struct semaphore spdif_mutex; - unsigned int spdif_status; + unsigned int spdif_status; /* IEC958 status bits */ + unsigned short spdif_ctls; /* SPDIF control bits */ + unsigned int spdif_in_enable; /* SPDIF input enable? */ }; /* direction */ @@ -555,6 +579,9 @@ struct hda_verb { void snd_hda_sequence_write(struct hda_codec *codec, const struct hda_verb *seq); +/* unsolicited event */ +int snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex); + /* * Mixer */ @@ -583,7 +610,7 @@ void snd_hda_get_codec_name(struct hda_c */ #ifdef CONFIG_PM int snd_hda_suspend(struct hda_bus *bus, unsigned int state); -int snd_hda_resume(struct hda_bus *bus, unsigned int state); +int snd_hda_resume(struct hda_bus *bus); #endif #endif /* __SOUND_HDA_CODEC_H */ --- linux-2.6.9/sound/pci/azx/hda_local.h.orig 2005-07-17 10:33:35.854819640 -0400 +++ linux-2.6.9/sound/pci/azx/hda_local.h 2005-07-17 10:25:03.492300367 -0400 @@ -36,7 +36,7 @@ #define HDA_CODEC_VOLUME_IDX(xname, xcidx, nid, xindex, direction) \ HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, 3, xindex, direction) #define HDA_CODEC_VOLUME_MONO(xname, nid, channel, xindex, direction) \ - HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, 3, xindex, direction) + HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, channel, xindex, direction) #define HDA_CODEC_VOLUME(xname, nid, xindex, direction) \ HDA_CODEC_VOLUME_MONO(xname, nid, 3, xindex, direction) #define HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \ @@ -48,7 +48,7 @@ #define HDA_CODEC_MUTE_IDX(xname, xcidx, nid, xindex, direction) \ HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, 3, xindex, direction) #define HDA_CODEC_MUTE_MONO(xname, nid, channel, xindex, direction) \ - HDA_CODEC_MUTE_MONO_IDX(xname, 0, nid, 3, xindex, direction) + HDA_CODEC_MUTE_MONO_IDX(xname, 0, nid, channel, xindex, direction) #define HDA_CODEC_MUTE(xname, nid, xindex, direction) \ HDA_CODEC_MUTE_MONO(xname, nid, 3, xindex, direction) @@ -60,6 +60,7 @@ int snd_hda_mixer_amp_switch_get(snd_kco int snd_hda_mixer_amp_switch_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol); int snd_hda_create_spdif_out_ctls(struct hda_codec *codec, hda_nid_t nid); +int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid); /* * input MUX helper @@ -125,11 +126,11 @@ static inline int snd_hda_codec_proc_new struct hda_board_config { const char *modelname; int config; - unsigned short pci_vendor; - unsigned short pci_device; + unsigned short pci_subvendor; + unsigned short pci_subdevice; }; -int snd_hda_check_board_config(struct hda_codec *codec, struct hda_board_config *tbl); +int snd_hda_check_board_config(struct hda_codec *codec, const struct hda_board_config *tbl); int snd_hda_add_new_ctls(struct hda_codec *codec, snd_kcontrol_new_t *knew); /* @@ -138,6 +139,54 @@ int snd_hda_add_new_ctls(struct hda_code #ifdef CONFIG_PM int snd_hda_resume_ctls(struct hda_codec *codec, snd_kcontrol_new_t *knew); int snd_hda_resume_spdif_out(struct hda_codec *codec); +int snd_hda_resume_spdif_in(struct hda_codec *codec); #endif +/* + * unsolicited event handler + */ + +#define HDA_UNSOL_QUEUE_SIZE 64 + +struct hda_bus_unsolicited { + /* ring buffer */ + u32 queue[HDA_UNSOL_QUEUE_SIZE * 2]; + unsigned int rp, wp; + + /* workqueue */ + struct workqueue_struct *workq; + struct work_struct work; +}; + +/* + * Helper for automatic ping configuration + */ + +enum { + AUTO_PIN_MIC, + AUTO_PIN_FRONT_MIC, + AUTO_PIN_LINE, + AUTO_PIN_FRONT_LINE, + AUTO_PIN_CD, + AUTO_PIN_AUX, + AUTO_PIN_LAST +}; + +struct auto_pin_cfg { + int line_outs; + hda_nid_t line_out_pins[4]; /* sorted in the order of Front/Surr/CLFE/Side */ + hda_nid_t hp_pin; + hda_nid_t input_pins[AUTO_PIN_LAST]; + hda_nid_t dig_out_pin; + hda_nid_t dig_in_pin; +}; + +#define get_defcfg_connect(cfg) ((cfg & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT) +#define get_defcfg_association(cfg) ((cfg & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT) +#define get_defcfg_location(cfg) ((cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT) +#define get_defcfg_sequence(cfg) (cfg & AC_DEFCFG_SEQUENCE) +#define get_defcfg_device(cfg) ((cfg & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT) + +int snd_hda_parse_pin_def_config(struct hda_codec *codec, struct auto_pin_cfg *cfg); + #endif /* __SOUND_HDA_LOCAL_H */ --- linux-2.6.9/sound/pci/azx/hda_patch.h.orig 2005-07-17 10:31:03.665156560 -0400 +++ linux-2.6.9/sound/pci/azx/hda_patch.h 2005-07-17 10:25:03.519296757 -0400 @@ -6,9 +6,15 @@ extern struct hda_codec_preset snd_hda_preset_realtek[]; /* C-Media codecs */ extern struct hda_codec_preset snd_hda_preset_cmedia[]; +/* Analog Devices codecs */ +extern struct hda_codec_preset snd_hda_preset_analog[]; +/* SigmaTel codecs */ +extern struct hda_codec_preset snd_hda_preset_sigmatel[]; static const struct hda_codec_preset *hda_preset_tables[] = { snd_hda_preset_realtek, snd_hda_preset_cmedia, + snd_hda_preset_analog, + snd_hda_preset_sigmatel, NULL }; --- linux-2.6.9/sound/pci/azx/Makefile.orig 2005-07-17 10:31:03.658157495 -0400 +++ linux-2.6.9/sound/pci/azx/Makefile 2005-07-17 10:25:03.499299431 -0400 @@ -1,5 +1,5 @@ -snd-azx-objs := azx.o -snd-hda-codec-objs := hda_codec.o hda_generic.o patch_realtek.o patch_cmedia.o +snd-azx-objs := hda_intel.o +snd-hda-codec-objs := hda_codec.o hda_generic.o patch_realtek.o patch_cmedia.o patch_analog.o patch_sigmatel.o ifdef CONFIG_PROC_FS snd-hda-codec-objs += hda_proc.o endif --- linux-2.6.9/sound/pci/azx/patch_analog.c.orig 2005-07-17 10:33:59.618644419 -0400 +++ linux-2.6.9/sound/pci/azx/patch_analog.c 2005-07-17 10:25:03.495299965 -0400 @@ -0,0 +1,798 @@ +/* + * HD audio interface patch for AD1981HD, AD1983, AD1986A + * + * Copyright (c) 2005 Takashi Iwai + * + * This driver 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 driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include "hda_codec.h" +#include "hda_local.h" + +struct ad198x_spec { + struct semaphore amp_mutex; /* PCM volume/mute control mutex */ + struct hda_multi_out multiout; /* playback */ + hda_nid_t adc_nid; + const struct hda_input_mux *input_mux; + unsigned int cur_mux; /* capture source */ + unsigned int spdif_route; + snd_kcontrol_new_t *mixers; + const struct hda_verb *init_verbs; + struct hda_pcm pcm_rec[2]; /* PCM information */ +}; + +/* + * input MUX handling (common part) + */ +static int ad198x_mux_enum_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + + return snd_hda_input_mux_info(spec->input_mux, uinfo); +} + +static int ad198x_mux_enum_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->cur_mux; + return 0; +} + +static int ad198x_mux_enum_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + + return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol, + spec->adc_nid, &spec->cur_mux); +} + +/* + * initialization (common callbacks) + */ +static int ad198x_init(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + snd_hda_sequence_write(codec, spec->init_verbs); + return 0; +} + +static int ad198x_build_controls(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + int err; + + err = snd_hda_add_new_ctls(codec, spec->mixers); + if (err < 0) + return err; + if (spec->multiout.dig_out_nid) + err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid); + if (err < 0) + return err; + return 0; +} + +/* + * Analog playback callbacks + */ +static int ad198x_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + snd_pcm_substream_t *substream) +{ + struct ad198x_spec *spec = codec->spec; + return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream); +} + +static int ad198x_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + snd_pcm_substream_t *substream) +{ + struct ad198x_spec *spec = codec->spec; + return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag, + format, substream); +} + +static int ad198x_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + snd_pcm_substream_t *substream) +{ + struct ad198x_spec *spec = codec->spec; + return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout); +} + +/* + * Digital out + */ +static int ad198x_dig_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + snd_pcm_substream_t *substream) +{ + struct ad198x_spec *spec = codec->spec; + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} + +static int ad198x_dig_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + snd_pcm_substream_t *substream) +{ + struct ad198x_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +/* + * Analog capture + */ +static int ad198x_capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + snd_pcm_substream_t *substream) +{ + struct ad198x_spec *spec = codec->spec; + snd_hda_codec_setup_stream(codec, spec->adc_nid, stream_tag, 0, format); + return 0; +} + +static int ad198x_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + snd_pcm_substream_t *substream) +{ + struct ad198x_spec *spec = codec->spec; + snd_hda_codec_setup_stream(codec, spec->adc_nid, 0, 0, 0); + return 0; +} + + +/* + */ +static struct hda_pcm_stream ad198x_pcm_analog_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 6, + .nid = 0, /* fill later */ + .ops = { + .open = ad198x_playback_pcm_open, + .prepare = ad198x_playback_pcm_prepare, + .cleanup = ad198x_playback_pcm_cleanup + }, +}; + +static struct hda_pcm_stream ad198x_pcm_analog_capture = { + .substreams = 2, + .channels_min = 2, + .channels_max = 2, + .nid = 0, /* fill later */ + .ops = { + .prepare = ad198x_capture_pcm_prepare, + .cleanup = ad198x_capture_pcm_cleanup + }, +}; + +static struct hda_pcm_stream ad198x_pcm_digital_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .nid = 0, /* fill later */ + .ops = { + .open = ad198x_dig_playback_pcm_open, + .close = ad198x_dig_playback_pcm_close + }, +}; + +static int ad198x_build_pcms(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + struct hda_pcm *info = spec->pcm_rec; + + codec->num_pcms = 1; + codec->pcm_info = info; + + info->name = "AD198x Analog"; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ad198x_pcm_analog_playback; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = spec->multiout.max_channels; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dac_nids[0]; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = ad198x_pcm_analog_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nid; + + if (spec->multiout.dig_out_nid) { + info++; + codec->num_pcms++; + info->name = "AD198x Digital"; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ad198x_pcm_digital_playback; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dig_out_nid; + } + + return 0; +} + +static void ad198x_free(struct hda_codec *codec) +{ + kfree(codec->spec); +} + +#ifdef CONFIG_PM +static int ad198x_resume(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + + ad198x_init(codec); + snd_hda_resume_ctls(codec, spec->mixers); + snd_hda_resume_spdif_out(codec); + return 0; +} +#endif + +static struct hda_codec_ops ad198x_patch_ops = { + .build_controls = ad198x_build_controls, + .build_pcms = ad198x_build_pcms, + .init = ad198x_init, + .free = ad198x_free, +#ifdef CONFIG_PM + .resume = ad198x_resume, +#endif +}; + + +/* + * AD1986A specific + */ + +#define AD1986A_SPDIF_OUT 0x02 +#define AD1986A_FRONT_DAC 0x03 +#define AD1986A_SURR_DAC 0x04 +#define AD1986A_CLFE_DAC 0x05 +#define AD1986A_ADC 0x06 + +static hda_nid_t ad1986a_dac_nids[3] = { + AD1986A_FRONT_DAC, AD1986A_SURR_DAC, AD1986A_CLFE_DAC +}; + +static struct hda_input_mux ad1986a_capture_source = { + .num_items = 7, + .items = { + { "Mic", 0x0 }, + { "CD", 0x1 }, + { "Aux", 0x3 }, + { "Line", 0x4 }, + { "Mix", 0x5 }, + { "Mono", 0x6 }, + { "Phone", 0x7 }, + }, +}; + +/* + * PCM control + * + * bind volumes/mutes of 3 DACs as a single PCM control for simplicity + */ + +#define ad1986a_pcm_amp_vol_info snd_hda_mixer_amp_volume_info + +static int ad1986a_pcm_amp_vol_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *ad = codec->spec; + + down(&ad->amp_mutex); + snd_hda_mixer_amp_volume_get(kcontrol, ucontrol); + up(&ad->amp_mutex); + return 0; +} + +static int ad1986a_pcm_amp_vol_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *ad = codec->spec; + int i, change = 0; + + down(&ad->amp_mutex); + for (i = 0; i < ARRAY_SIZE(ad1986a_dac_nids); i++) { + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(ad1986a_dac_nids[i], 3, 0, HDA_OUTPUT); + change |= snd_hda_mixer_amp_volume_put(kcontrol, ucontrol); + } + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(AD1986A_FRONT_DAC, 3, 0, HDA_OUTPUT); + up(&ad->amp_mutex); + return change; +} + +#define ad1986a_pcm_amp_sw_info snd_hda_mixer_amp_switch_info + +static int ad1986a_pcm_amp_sw_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *ad = codec->spec; + + down(&ad->amp_mutex); + snd_hda_mixer_amp_switch_get(kcontrol, ucontrol); + up(&ad->amp_mutex); + return 0; +} + +static int ad1986a_pcm_amp_sw_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *ad = codec->spec; + int i, change = 0; + + down(&ad->amp_mutex); + for (i = 0; i < ARRAY_SIZE(ad1986a_dac_nids); i++) { + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(ad1986a_dac_nids[i], 3, 0, HDA_OUTPUT); + change |= snd_hda_mixer_amp_switch_put(kcontrol, ucontrol); + } + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(AD1986A_FRONT_DAC, 3, 0, HDA_OUTPUT); + up(&ad->amp_mutex); + return change; +} + +/* + * mixers + */ +static snd_kcontrol_new_t ad1986a_mixers[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .info = ad1986a_pcm_amp_vol_info, + .get = ad1986a_pcm_amp_vol_get, + .put = ad1986a_pcm_amp_vol_put, + .private_value = HDA_COMPOSE_AMP_VAL(AD1986A_FRONT_DAC, 3, 0, HDA_OUTPUT) + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .info = ad1986a_pcm_amp_sw_info, + .get = ad1986a_pcm_amp_sw_get, + .put = ad1986a_pcm_amp_sw_put, + .private_value = HDA_COMPOSE_AMP_VAL(AD1986A_FRONT_DAC, 3, 0, HDA_OUTPUT) + }, + HDA_CODEC_VOLUME("Front Playback Volume", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x1c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x1c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x1d, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x1d, 2, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x1d, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x1d, 2, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x1a, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1a, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x17, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x17, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Aux Playback Volume", 0x16, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Aux Playback Switch", 0x16, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x18, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x18, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mono Playback Volume", 0x1e, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Mono Playback Switch", 0x1e, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x12, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + HDA_CODEC_MUTE("Stereo Downmix Switch", 0x09, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +/* + * initialization verbs + */ +static struct hda_verb ad1986a_init_verbs[] = { + /* Front, Surround, CLFE DAC; mute as default */ + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* Downmix - off */ + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* HP, Line-Out, Surround, CLFE selectors */ + {0x0a, AC_VERB_SET_CONNECT_SEL, 0x0}, + {0x0b, AC_VERB_SET_CONNECT_SEL, 0x0}, + {0x0c, AC_VERB_SET_CONNECT_SEL, 0x0}, + {0x0d, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Mono selector */ + {0x0e, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Mic selector: Mic 1/2 pin */ + {0x0f, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Line-in selector: Line-in */ + {0x10, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Mic 1/2 swap */ + {0x11, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Record selector: mic */ + {0x12, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Mic, Phone, CD, Aux, Line-In amp; mute as default */ + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* PC beep */ + {0x18, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* HP, Line-Out, Surround, CLFE, Mono pins; mute as default */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* HP Pin */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + /* Front, Surround, CLFE Pins */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* Mono Pin */ + {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* Mic Pin */ + {0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* Line, Aux, CD, Beep-In Pin */ + {0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + {0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + {0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + {0x24, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + { } /* end */ +}; + + +static int patch_ad1986a(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + + spec = kcalloc(1, sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + init_MUTEX(&spec->amp_mutex); + codec->spec = spec; + + spec->multiout.max_channels = 6; + spec->multiout.num_dacs = ARRAY_SIZE(ad1986a_dac_nids); + spec->multiout.dac_nids = ad1986a_dac_nids; + spec->multiout.dig_out_nid = AD1986A_SPDIF_OUT; + spec->adc_nid = AD1986A_ADC; + spec->input_mux = &ad1986a_capture_source; + spec->mixers = ad1986a_mixers; + spec->init_verbs = ad1986a_init_verbs; + + codec->patch_ops = ad198x_patch_ops; + + return 0; +} + +/* + * AD1983 specific + */ + +#define AD1983_SPDIF_OUT 0x02 +#define AD1983_DAC 0x03 +#define AD1983_ADC 0x04 + +static hda_nid_t ad1983_dac_nids[1] = { AD1983_DAC }; + +static struct hda_input_mux ad1983_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Line", 0x1 }, + { "Mix", 0x2 }, + { "Mix Mono", 0x3 }, + }, +}; + +/* + * SPDIF playback route + */ +static int ad1983_spdif_route_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + static char *texts[] = { "PCM", "ADC" }; + + 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; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int ad1983_spdif_route_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->spdif_route; + return 0; +} + +static int ad1983_spdif_route_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + + if (spec->spdif_route != ucontrol->value.enumerated.item[0]) { + spec->spdif_route = ucontrol->value.enumerated.item[0]; + snd_hda_codec_write(codec, spec->multiout.dig_out_nid, 0, + AC_VERB_SET_CONNECT_SEL, spec->spdif_route); + return 1; + } + return 0; +} + +static snd_kcontrol_new_t ad1983_mixers[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x05, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x05, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x06, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x06, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x07, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x07, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("PCM Playback Volume", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("PC Speaker Playback Volume", 0x10, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("PC Speaker Playback Switch", 0x10, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x15, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Playback Route", + .info = ad1983_spdif_route_info, + .get = ad1983_spdif_route_get, + .put = ad1983_spdif_route_put, + }, + { } /* end */ +}; + +static struct hda_verb ad1983_init_verbs[] = { + /* Front, HP, Mono; mute as default */ + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x06, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* Beep, PCM, Mic, Line-In: mute */ + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* Front, HP selectors; from Mix */ + {0x05, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x06, AC_VERB_SET_CONNECT_SEL, 0x01}, + /* Mono selector; from Mix */ + {0x0b, AC_VERB_SET_CONNECT_SEL, 0x03}, + /* Mic selector; Mic */ + {0x0c, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Line-in selector: Line-in */ + {0x0d, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Mic boost: 0dB */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + /* Record selector: mic */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x0}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* SPDIF route: PCM */ + {0x02, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Front Pin */ + {0x05, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* HP Pin */ + {0x06, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + /* Mono Pin */ + {0x07, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* Mic Pin */ + {0x08, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* Line Pin */ + {0x09, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + { } /* end */ +}; + +static int patch_ad1983(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + + spec = kcalloc(1, sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + init_MUTEX(&spec->amp_mutex); + codec->spec = spec; + + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = ARRAY_SIZE(ad1983_dac_nids); + spec->multiout.dac_nids = ad1983_dac_nids; + spec->multiout.dig_out_nid = AD1983_SPDIF_OUT; + spec->adc_nid = AD1983_ADC; + spec->input_mux = &ad1983_capture_source; + spec->mixers = ad1983_mixers; + spec->init_verbs = ad1983_init_verbs; + spec->spdif_route = 0; + + codec->patch_ops = ad198x_patch_ops; + + return 0; +} + + +/* + * AD1981 HD specific + */ + +#define AD1981_SPDIF_OUT 0x02 +#define AD1981_DAC 0x03 +#define AD1981_ADC 0x04 + +static hda_nid_t ad1981_dac_nids[1] = { AD1981_DAC }; + +/* 0x0c, 0x09, 0x0e, 0x0f, 0x19, 0x05, 0x18, 0x17 */ +static struct hda_input_mux ad1981_capture_source = { + .num_items = 7, + .items = { + { "Front Mic", 0x0 }, + { "Line", 0x1 }, + { "Mix", 0x2 }, + { "Mix Mono", 0x3 }, + { "CD", 0x4 }, + { "Mic", 0x6 }, + { "Aux", 0x7 }, + }, +}; + +static snd_kcontrol_new_t ad1981_mixers[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x05, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x05, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x06, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x06, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x07, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x07, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("PCM Playback Volume", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Aux Playback Volume", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Aux Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x1c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x1c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x1d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x1d, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("PC Speaker Playback Volume", 0x0d, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("PC Speaker Playback Switch", 0x0d, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x15, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + /* identical with AD1983 */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Playback Route", + .info = ad1983_spdif_route_info, + .get = ad1983_spdif_route_get, + .put = ad1983_spdif_route_put, + }, + { } /* end */ +}; + +static struct hda_verb ad1981_init_verbs[] = { + /* Front, HP, Mono; mute as default */ + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x06, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* Beep, PCM, Front Mic, Line, Rear Mic, Aux, CD-In: mute */ + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* Front, HP selectors; from Mix */ + {0x05, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x06, AC_VERB_SET_CONNECT_SEL, 0x01}, + /* Mono selector; from Mix */ + {0x0b, AC_VERB_SET_CONNECT_SEL, 0x03}, + /* Mic Mixer; select Front Mic */ + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + {0x1f, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* Mic boost: 0dB */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + /* Record selector: Front mic */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x0}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* SPDIF route: PCM */ + {0x02, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Front Pin */ + {0x05, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* HP Pin */ + {0x06, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + /* Mono Pin */ + {0x07, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* Front & Rear Mic Pins */ + {0x08, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* Line Pin */ + {0x09, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + /* Digital Beep */ + {0x0d, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* Line-Out as Input: disabled */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + { } /* end */ +}; + +static int patch_ad1981(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + + spec = kcalloc(1, sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + init_MUTEX(&spec->amp_mutex); + codec->spec = spec; + + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = ARRAY_SIZE(ad1981_dac_nids); + spec->multiout.dac_nids = ad1981_dac_nids; + spec->multiout.dig_out_nid = AD1981_SPDIF_OUT; + spec->adc_nid = AD1981_ADC; + spec->input_mux = &ad1981_capture_source; + spec->mixers = ad1981_mixers; + spec->init_verbs = ad1981_init_verbs; + spec->spdif_route = 0; + + codec->patch_ops = ad198x_patch_ops; + + return 0; +} + + +/* + * patch entries + */ +struct hda_codec_preset snd_hda_preset_analog[] = { + { .id = 0x11d41981, .name = "AD1981", .patch = patch_ad1981 }, + { .id = 0x11d41983, .name = "AD1983", .patch = patch_ad1983 }, + { .id = 0x11d41986, .name = "AD1986A", .patch = patch_ad1986a }, + {} /* terminator */ +}; --- linux-2.6.9/sound/pci/azx/patch_cmedia.c.orig 2005-07-17 10:33:35.832822579 -0400 +++ linux-2.6.9/sound/pci/azx/patch_cmedia.c 2005-07-17 10:25:03.514297426 -0400 @@ -1,4 +1,3 @@ -#define __NO_VERSION__ /* * Universal Interface for Intel High Definition Audio Codec * @@ -30,6 +29,7 @@ #include #include "hda_codec.h" #include "hda_local.h" +#define NUM_PINS 11 /* board config type */ @@ -39,6 +39,7 @@ enum { CMI_FULL, /* back 6-jack + front-panel 2-jack */ CMI_FULL_DIG, /* back 6-jack + front-panel 2-jack + digital I/O */ CMI_ALLOUT, /* back 5-jack + front-panel 2-jack + digital out */ + CMI_AUTO, /* let driver guess it */ }; struct cmi_spec { @@ -49,6 +50,8 @@ struct cmi_spec { /* playback */ struct hda_multi_out multiout; + hda_nid_t dac_nids[4]; /* NID for each DAC */ + int num_dacs; /* capture */ hda_nid_t *adc_nids; @@ -64,7 +67,29 @@ struct cmi_spec { const struct cmi_channel_mode *channel_modes; struct hda_pcm pcm_rec[2]; /* PCM information */ -}; + + /* pin deafault configuration */ + hda_nid_t pin_nid[NUM_PINS]; + unsigned int def_conf[NUM_PINS]; + unsigned int pin_def_confs; + + /* multichannel pins */ + hda_nid_t multich_pin[4]; /* max 8-channel */ + struct hda_verb multi_init[9]; /* 2 verbs for each pin + terminator */ +}; + +/* amp values */ +#define AMP_IN_MUTE(idx) (0x7080 | ((idx)<<8)) +#define AMP_IN_UNMUTE(idx) (0x7000 | ((idx)<<8)) +#define AMP_OUT_MUTE 0xb080 +#define AMP_OUT_UNMUTE 0xb000 +#define AMP_OUT_ZERO 0xb000 +/* pinctl values */ +#define PIN_IN 0x20 +#define PIN_VREF80 0x24 +#define PIN_VREF50 0x21 +#define PIN_OUT 0x40 +#define PIN_HP 0xc0 /* * input MUX @@ -103,9 +128,9 @@ static int cmi_mux_enum_put(snd_kcontrol /* 3-stack / 2 channel */ static struct hda_verb cmi9880_ch2_init[] = { /* set line-in PIN for input */ - { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, /* set mic PIN for input, also enable vref */ - { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* route front PCM (DAC1) to HP */ { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 }, {} @@ -113,10 +138,10 @@ static struct hda_verb cmi9880_ch2_init[ /* 3-stack / 6 channel */ static struct hda_verb cmi9880_ch6_init[] = { - /* set line-in PIN for input */ - { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, - /* set mic PIN for input, also enable vref */ - { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* set line-in PIN for output */ + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + /* set mic PIN for output */ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, /* route front PCM (DAC1) to HP */ { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 }, {} @@ -124,10 +149,10 @@ static struct hda_verb cmi9880_ch6_init[ /* 3-stack+front / 8 channel */ static struct hda_verb cmi9880_ch8_init[] = { - /* set line-in PIN for input */ - { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, - /* set mic PIN for input, also enable vref */ - { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* set line-in PIN for output */ + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + /* set mic PIN for output */ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, /* route rear-surround PCM (DAC4) to HP */ { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x03 }, {} @@ -270,25 +295,27 @@ static hda_nid_t cmi9880_adc_nids[2] = { */ static struct hda_verb cmi9880_basic_init[] = { /* port-D for line out (rear panel) */ - { 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + { 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, /* port-E for HP out (front panel) */ - { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, /* route front PCM to HP */ { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 }, /* port-A for surround (rear panel) */ - { 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + { 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, /* port-G for CLFE (rear panel) */ - { 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + { 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + { 0x1f, AC_VERB_SET_CONNECT_SEL, 0x02 }, /* port-H for side (rear panel) */ - { 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + { 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + { 0x20, AC_VERB_SET_CONNECT_SEL, 0x01 }, /* port-C for line-in (rear panel) */ - { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, /* port-B for mic-in (rear panel) with vref */ - { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* port-F for mic-in (front panel) with vref */ - { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* CD-in */ - { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, /* route front mic to ADC1/2 */ { 0x08, AC_VERB_SET_CONNECT_SEL, 0x05 }, { 0x09, AC_VERB_SET_CONNECT_SEL, 0x05 }, @@ -297,23 +324,27 @@ static struct hda_verb cmi9880_basic_ini static struct hda_verb cmi9880_allout_init[] = { /* port-D for line out (rear panel) */ - { 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + { 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, /* port-E for HP out (front panel) */ - { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, /* route front PCM to HP */ { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 }, /* port-A for side (rear panel) */ - { 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + { 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, /* port-G for CLFE (rear panel) */ - { 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + { 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + { 0x1f, AC_VERB_SET_CONNECT_SEL, 0x02 }, + /* port-H for side (rear panel) */ + { 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + { 0x20, AC_VERB_SET_CONNECT_SEL, 0x01 }, /* port-C for surround (rear panel) */ - { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, /* port-B for mic-in (rear panel) with vref */ - { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* port-F for mic-in (front panel) with vref */ - { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* CD-in */ - { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, /* route front mic to ADC1/2 */ { 0x08, AC_VERB_SET_CONNECT_SEL, 0x05 }, { 0x09, AC_VERB_SET_CONNECT_SEL, 0x05 }, @@ -336,11 +367,90 @@ static int cmi9880_build_controls(struct return err; } if (spec->multiout.dig_out_nid) { - err = snd_hda_create_spdif_out_ctls(codec, CMI_DIG_OUT_NID); + err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid); if (err < 0) return err; } - /* TODO: digital-in */ + if (spec->dig_in_nid) { + err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid); + if (err < 0) + return err; + } + return 0; +} + +/* fill in the multi_dac_nids table, which will decide + which audio widget to use for each channel */ +static int cmi9880_fill_multi_dac_nids(struct hda_codec *codec, const struct auto_pin_cfg *cfg) +{ + struct cmi_spec *spec = codec->spec; + hda_nid_t nid; + int assigned[4]; + int i, j; + + /* clear the table, only one c-media dac assumed here */ + memset(spec->dac_nids, 0, sizeof(spec->dac_nids)); + memset(assigned, 0, sizeof(assigned)); + /* check the pins we found */ + for (i = 0; i < cfg->line_outs; i++) { + nid = cfg->line_out_pins[i]; + /* nid 0x0b~0x0e is hardwired to audio widget 0x3~0x6 */ + if (nid >= 0x0b && nid <= 0x0e) { + spec->dac_nids[i] = (nid - 0x0b) + 0x03; + assigned[nid - 0x0b] = 1; + } + } + /* left pin can be connect to any audio widget */ + for (i = 0; i < cfg->line_outs; i++) { + nid = cfg->line_out_pins[i]; + if (nid <= 0x0e) + continue; + /* search for an empty channel */ + for (j = 0; j < cfg->line_outs; j++) { + if (! assigned[j]) { + spec->dac_nids[i] = i + 0x03; + assigned[j] = 1; + break; + } + } + } + spec->num_dacs = cfg->line_outs; + return 0; +} + +/* create multi_init table, which is used for multichannel initialization */ +static int cmi9880_fill_multi_init(struct hda_codec *codec, const struct auto_pin_cfg *cfg) +{ + struct cmi_spec *spec = codec->spec; + hda_nid_t nid; + int i, j, k, len; + + /* clear the table, only one c-media dac assumed here */ + memset(spec->multi_init, 0, sizeof(spec->multi_init)); + for (j = 0, i = 0; i < cfg->line_outs; i++) { + hda_nid_t conn[4]; + nid = cfg->line_out_pins[i]; + /* set as output */ + spec->multi_init[j].nid = nid; + spec->multi_init[j].verb = AC_VERB_SET_PIN_WIDGET_CONTROL; + spec->multi_init[j].param = PIN_OUT; + j++; + if (nid > 0x0e) { + /* set connection */ + spec->multi_init[j].nid = nid; + spec->multi_init[j].verb = AC_VERB_SET_CONNECT_SEL; + spec->multi_init[j].param = 0; + /* find the index in connect list */ + len = snd_hda_get_connections(codec, nid, conn, 4); + for (k = 0; k < len; k++) + if (conn[k] == spec->dac_nids[i]) { + spec->multi_init[j].param = j; + break; + } + j++; + break; + } + } return 0; } @@ -351,6 +461,8 @@ static int cmi9880_init(struct hda_codec snd_hda_sequence_write(codec, cmi9880_allout_init); else snd_hda_sequence_write(codec, cmi9880_basic_init); + if (spec->board_config == CMI_AUTO) + snd_hda_sequence_write(codec, spec->multi_init); return 0; } @@ -358,7 +470,7 @@ static int cmi9880_init(struct hda_codec /* * resume */ -static int cmi9880_resume(struct hda_codec *codec, unsigned int state) +static int cmi9880_resume(struct hda_codec *codec) { struct cmi_spec *spec = codec->spec; @@ -368,6 +480,8 @@ static int cmi9880_resume(struct hda_cod snd_hda_resume_ctls(codec, cmi9880_ch_mode_mixer); if (spec->multiout.dig_out_nid) snd_hda_resume_spdif_out(codec); + if (spec->dig_in_nid) + snd_hda_resume_spdif_in(codec); return 0; } @@ -535,6 +649,7 @@ static struct hda_board_config cmi9880_c { .modelname = "full", .config = CMI_FULL }, { .modelname = "full_dig", .config = CMI_FULL_DIG }, { .modelname = "allout", .config = CMI_ALLOUT }, + { .modelname = "auto", .config = CMI_AUTO }, {} /* terminator */ }; @@ -559,10 +674,14 @@ static int patch_cmi9880(struct hda_code codec->spec = spec; spec->board_config = snd_hda_check_board_config(codec, cmi9880_cfg_tbl); if (spec->board_config < 0) { - snd_printd(KERN_INFO "hda_codec: Unknown model for CMI9880\n"); - spec->board_config = CMI_MINIMAL; + snd_printdd(KERN_INFO "hda_codec: Unknown model for CMI9880\n"); + spec->board_config = CMI_AUTO; /* try everything */ } + /* copy default DAC NIDs */ + memcpy(spec->dac_nids, cmi9880_dac_nids, sizeof(spec->dac_nids)); + spec->num_dacs = 4; + switch (spec->board_config) { case CMI_MINIMAL: case CMI_MIN_FP: @@ -594,10 +713,58 @@ static int patch_cmi9880(struct hda_code spec->input_mux = &cmi9880_no_line_mux; spec->multiout.dig_out_nid = CMI_DIG_OUT_NID; break; + case CMI_AUTO: + { + unsigned int port_e, port_f, port_g, port_h; + unsigned int port_spdifi, port_spdifo; + struct auto_pin_cfg cfg; + + /* collect pin default configuration */ + port_e = snd_hda_codec_read(codec, 0x0f, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); + port_f = snd_hda_codec_read(codec, 0x10, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); + spec->front_panel = 1; + if (get_defcfg_connect(port_e) == AC_JACK_PORT_NONE || + get_defcfg_connect(port_f) == AC_JACK_PORT_NONE) { + port_g = snd_hda_codec_read(codec, 0x1f, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); + port_h = snd_hda_codec_read(codec, 0x20, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); + spec->surr_switch = 1; + /* no front panel */ + if (get_defcfg_connect(port_g) == AC_JACK_PORT_NONE || + get_defcfg_connect(port_h) == AC_JACK_PORT_NONE) { + /* no optional rear panel */ + spec->board_config = CMI_MINIMAL; + spec->front_panel = 0; + spec->num_ch_modes = 2; + } else { + spec->board_config = CMI_MIN_FP; + spec->num_ch_modes = 3; + } + spec->channel_modes = cmi9880_channel_modes; + spec->input_mux = &cmi9880_basic_mux; + spec->multiout.max_channels = cmi9880_channel_modes[0].channels; + } else { + spec->input_mux = &cmi9880_basic_mux; + port_spdifi = snd_hda_codec_read(codec, 0x13, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); + port_spdifo = snd_hda_codec_read(codec, 0x12, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); + if (get_defcfg_connect(port_spdifo) != AC_JACK_PORT_NONE) + spec->multiout.dig_out_nid = CMI_DIG_OUT_NID; + if (get_defcfg_connect(port_spdifi) != AC_JACK_PORT_NONE) + spec->dig_in_nid = CMI_DIG_IN_NID; + spec->multiout.max_channels = 8; + } + snd_hda_parse_pin_def_config(codec, &cfg); + if (cfg.line_outs) { + spec->multiout.max_channels = cfg.line_outs * 2; + cmi9880_fill_multi_dac_nids(codec, &cfg); + cmi9880_fill_multi_init(codec, &cfg); + } else + snd_printd("patch_cmedia: cannot detect association in defcfg\n"); + break; + } } - spec->multiout.num_dacs = 4; - spec->multiout.dac_nids = cmi9880_dac_nids; + spec->multiout.num_dacs = spec->num_dacs; + spec->multiout.dac_nids = spec->dac_nids; spec->adc_nids = cmi9880_adc_nids; @@ -610,6 +777,7 @@ static int patch_cmi9880(struct hda_code * patch entries */ struct hda_codec_preset snd_hda_preset_cmedia[] = { + { .id = 0x13f69880, .name = "CMI9880", .patch = patch_cmi9880 }, { .id = 0x434d4980, .name = "CMI9880", .patch = patch_cmi9880 }, {} /* terminator */ }; --- linux-2.6.9/sound/pci/azx/patch_sigmatel.c.orig 2005-07-17 10:31:03.637160301 -0400 +++ linux-2.6.9/sound/pci/azx/patch_sigmatel.c 2005-07-17 10:25:03.498299564 -0400 @@ -0,0 +1,666 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for SigmaTel STAC92xx + * + * Copyright (c) 2005 Embedded Alley Solutions, Inc. + * + * + * Based on patch_cmedia.c and patch_realtek.c + * Copyright (c) 2004 Takashi Iwai + * + * This driver 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 driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include "hda_codec.h" +#include "hda_local.h" + +#undef STAC_TEST + +struct sigmatel_spec { + /* playback */ + struct hda_multi_out multiout; + hda_nid_t playback_nid; + + /* capture */ + hda_nid_t *adc_nids; + unsigned int num_adcs; + hda_nid_t *mux_nids; + unsigned int num_muxes; + hda_nid_t capture_nid; + hda_nid_t dig_in_nid; + + /* power management*/ + hda_nid_t *pstate_nids; + unsigned int num_pstates; + + /* pin widgets */ + hda_nid_t *pin_nids; + unsigned int num_pins; +#ifdef STAC_TEST + unsigned int *pin_configs; +#endif + + /* codec specific stuff */ + struct hda_verb *init; + snd_kcontrol_new_t *mixer; + + /* capture source */ + struct hda_input_mux input_mux; + char input_labels[HDA_MAX_NUM_INPUTS][16]; + unsigned int cur_mux[2]; + + /* channel mode */ + unsigned int num_ch_modes; + unsigned int cur_ch_mode; + const struct sigmatel_channel_mode *channel_modes; + + struct hda_pcm pcm_rec[1]; /* PCM information */ +}; + +static hda_nid_t stac9200_adc_nids[1] = { + 0x03, +}; + +static hda_nid_t stac9200_mux_nids[1] = { + 0x0c, +}; + +static hda_nid_t stac9200_dac_nids[1] = { + 0x02, +}; + +static hda_nid_t stac9200_pstate_nids[3] = { + 0x01, 0x02, 0x03, +}; + +static hda_nid_t stac9200_pin_nids[8] = { + 0x08, 0x09, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, +}; + +static hda_nid_t stac922x_adc_nids[2] = { + 0x06, 0x07, +}; + +static hda_nid_t stac922x_mux_nids[2] = { + 0x12, 0x13, +}; + +static hda_nid_t stac922x_dac_nids[4] = { + 0x02, 0x03, 0x04, 0x05, +}; + +static hda_nid_t stac922x_pstate_nids[8] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x11, +}; + +static hda_nid_t stac922x_pin_nids[10] = { + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x15, 0x1b, +}; + +static int stac92xx_mux_enum_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + return snd_hda_input_mux_info(&spec->input_mux, uinfo); +} + +static int stac92xx_mux_enum_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + + ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx]; + return 0; +} + +static int stac92xx_mux_enum_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + s