📄 cs4231.c
字号:
/* * Driver for CS4231 sound chips found on Sparcs. * Copyright (C) 2002 David S. Miller <davem@redhat.com> * * Based entirely upon drivers/sbus/audio/cs4231.c which is: * Copyright (C) 1996, 1997, 1998 Derrick J Brashear (shadow@andrew.cmu.edu) * and also sound/isa/cs423x/cs4231_lib.c which is: * Copyright (c) by Jaroslav Kysela <perex@perex.cz> */#include <linux/module.h>#include <linux/kernel.h>#include <linux/slab.h>#include <linux/delay.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/moduleparam.h>#include <linux/irq.h>#include <linux/io.h>#include <sound/driver.h>#include <sound/core.h>#include <sound/pcm.h>#include <sound/info.h>#include <sound/control.h>#include <sound/timer.h>#include <sound/initval.h>#include <sound/pcm_params.h>#ifdef CONFIG_SBUS#define SBUS_SUPPORT#include <asm/sbus.h>#endif#if defined(CONFIG_PCI) && defined(CONFIG_SPARC64)#define EBUS_SUPPORT#include <linux/pci.h>#include <asm/ebus.h>#endifstatic int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card *//* Enable this card */static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;module_param_array(index, int, NULL, 0444);MODULE_PARM_DESC(index, "Index value for Sun CS4231 soundcard.");module_param_array(id, charp, NULL, 0444);MODULE_PARM_DESC(id, "ID string for Sun CS4231 soundcard.");module_param_array(enable, bool, NULL, 0444);MODULE_PARM_DESC(enable, "Enable Sun CS4231 soundcard.");MODULE_AUTHOR("Jaroslav Kysela, Derrick J. Brashear and David S. Miller");MODULE_DESCRIPTION("Sun CS4231");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("{{Sun,CS4231}}");#ifdef SBUS_SUPPORTstruct sbus_dma_info { spinlock_t lock; /* DMA access lock */ int dir; void __iomem *regs;};#endifstruct snd_cs4231;struct cs4231_dma_control { void (*prepare)(struct cs4231_dma_control *dma_cont, int dir); void (*enable)(struct cs4231_dma_control *dma_cont, int on); int (*request)(struct cs4231_dma_control *dma_cont, dma_addr_t bus_addr, size_t len); unsigned int (*address)(struct cs4231_dma_control *dma_cont); void (*preallocate)(struct snd_cs4231 *chip, struct snd_pcm *pcm);#ifdef EBUS_SUPPORT struct ebus_dma_info ebus_info;#endif#ifdef SBUS_SUPPORT struct sbus_dma_info sbus_info;#endif};struct snd_cs4231 { spinlock_t lock; /* registers access lock */ void __iomem *port; struct cs4231_dma_control p_dma; struct cs4231_dma_control c_dma; u32 flags;#define CS4231_FLAG_EBUS 0x00000001#define CS4231_FLAG_PLAYBACK 0x00000002#define CS4231_FLAG_CAPTURE 0x00000004 struct snd_card *card; struct snd_pcm *pcm; struct snd_pcm_substream *playback_substream; unsigned int p_periods_sent; struct snd_pcm_substream *capture_substream; unsigned int c_periods_sent; struct snd_timer *timer; unsigned short mode;#define CS4231_MODE_NONE 0x0000#define CS4231_MODE_PLAY 0x0001#define CS4231_MODE_RECORD 0x0002#define CS4231_MODE_TIMER 0x0004#define CS4231_MODE_OPEN (CS4231_MODE_PLAY | CS4231_MODE_RECORD | \ CS4231_MODE_TIMER) unsigned char image[32]; /* registers image */ int mce_bit; int calibrate_mute; struct mutex mce_mutex; /* mutex for mce register */ struct mutex open_mutex; /* mutex for ALSA open/close */ union {#ifdef SBUS_SUPPORT struct sbus_dev *sdev;#endif#ifdef EBUS_SUPPORT struct pci_dev *pdev;#endif } dev_u; unsigned int irq[2]; unsigned int regs_size; struct snd_cs4231 *next;};static struct snd_cs4231 *cs4231_list;/* Eventually we can use sound/isa/cs423x/cs4231_lib.c directly, but for * now.... -DaveM *//* IO ports */#include <sound/cs4231-regs.h>/* XXX offsets are different than PC ISA chips... */#define CS4231U(chip, x) ((chip)->port + ((c_d_c_CS4231##x) << 2))/* SBUS DMA register defines. */#define APCCSR 0x10UL /* APC DMA CSR */#define APCCVA 0x20UL /* APC Capture DMA Address */#define APCCC 0x24UL /* APC Capture Count */#define APCCNVA 0x28UL /* APC Capture DMA Next Address */#define APCCNC 0x2cUL /* APC Capture Next Count */#define APCPVA 0x30UL /* APC Play DMA Address */#define APCPC 0x34UL /* APC Play Count */#define APCPNVA 0x38UL /* APC Play DMA Next Address */#define APCPNC 0x3cUL /* APC Play Next Count *//* Defines for SBUS DMA-routines */#define APCVA 0x0UL /* APC DMA Address */#define APCC 0x4UL /* APC Count */#define APCNVA 0x8UL /* APC DMA Next Address */#define APCNC 0xcUL /* APC Next Count */#define APC_PLAY 0x30UL /* Play registers start at 0x30 */#define APC_RECORD 0x20UL /* Record registers start at 0x20 *//* APCCSR bits */#define APC_INT_PENDING 0x800000 /* Interrupt Pending */#define APC_PLAY_INT 0x400000 /* Playback interrupt */#define APC_CAPT_INT 0x200000 /* Capture interrupt */#define APC_GENL_INT 0x100000 /* General interrupt */#define APC_XINT_ENA 0x80000 /* General ext int. enable */#define APC_XINT_PLAY 0x40000 /* Playback ext intr */#define APC_XINT_CAPT 0x20000 /* Capture ext intr */#define APC_XINT_GENL 0x10000 /* Error ext intr */#define APC_XINT_EMPT 0x8000 /* Pipe empty interrupt (0 write to pva) */#define APC_XINT_PEMP 0x4000 /* Play pipe empty (pva and pnva not set) */#define APC_XINT_PNVA 0x2000 /* Playback NVA dirty */#define APC_XINT_PENA 0x1000 /* play pipe empty Int enable */#define APC_XINT_COVF 0x800 /* Cap data dropped on floor */#define APC_XINT_CNVA 0x400 /* Capture NVA dirty */#define APC_XINT_CEMP 0x200 /* Capture pipe empty (cva and cnva not set) */#define APC_XINT_CENA 0x100 /* Cap. pipe empty int enable */#define APC_PPAUSE 0x80 /* Pause the play DMA */#define APC_CPAUSE 0x40 /* Pause the capture DMA */#define APC_CDC_RESET 0x20 /* CODEC RESET */#define APC_PDMA_READY 0x08 /* Play DMA Go */#define APC_CDMA_READY 0x04 /* Capture DMA Go */#define APC_CHIP_RESET 0x01 /* Reset the chip *//* EBUS DMA register offsets */#define EBDMA_CSR 0x00UL /* Control/Status */#define EBDMA_ADDR 0x04UL /* DMA Address */#define EBDMA_COUNT 0x08UL /* DMA Count *//* * Some variables */static unsigned char freq_bits[14] = { /* 5510 */ 0x00 | CS4231_XTAL2, /* 6620 */ 0x0E | CS4231_XTAL2, /* 8000 */ 0x00 | CS4231_XTAL1, /* 9600 */ 0x0E | CS4231_XTAL1, /* 11025 */ 0x02 | CS4231_XTAL2, /* 16000 */ 0x02 | CS4231_XTAL1, /* 18900 */ 0x04 | CS4231_XTAL2, /* 22050 */ 0x06 | CS4231_XTAL2, /* 27042 */ 0x04 | CS4231_XTAL1, /* 32000 */ 0x06 | CS4231_XTAL1, /* 33075 */ 0x0C | CS4231_XTAL2, /* 37800 */ 0x08 | CS4231_XTAL2, /* 44100 */ 0x0A | CS4231_XTAL2, /* 48000 */ 0x0C | CS4231_XTAL1};static unsigned int rates[14] = { 5510, 6620, 8000, 9600, 11025, 16000, 18900, 22050, 27042, 32000, 33075, 37800, 44100, 48000};static struct snd_pcm_hw_constraint_list hw_constraints_rates = { .count = ARRAY_SIZE(rates), .list = rates,};static int snd_cs4231_xrate(struct snd_pcm_runtime *runtime){ return snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);}static unsigned char snd_cs4231_original_image[32] ={ 0x00, /* 00/00 - lic */ 0x00, /* 01/01 - ric */ 0x9f, /* 02/02 - la1ic */ 0x9f, /* 03/03 - ra1ic */ 0x9f, /* 04/04 - la2ic */ 0x9f, /* 05/05 - ra2ic */ 0xbf, /* 06/06 - loc */ 0xbf, /* 07/07 - roc */ 0x20, /* 08/08 - pdfr */ CS4231_AUTOCALIB, /* 09/09 - ic */ 0x00, /* 0a/10 - pc */ 0x00, /* 0b/11 - ti */ CS4231_MODE2, /* 0c/12 - mi */ 0x00, /* 0d/13 - lbc */ 0x00, /* 0e/14 - pbru */ 0x00, /* 0f/15 - pbrl */ 0x80, /* 10/16 - afei */ 0x01, /* 11/17 - afeii */ 0x9f, /* 12/18 - llic */ 0x9f, /* 13/19 - rlic */ 0x00, /* 14/20 - tlb */ 0x00, /* 15/21 - thb */ 0x00, /* 16/22 - la3mic/reserved */ 0x00, /* 17/23 - ra3mic/reserved */ 0x00, /* 18/24 - afs */ 0x00, /* 19/25 - lamoc/version */ 0x00, /* 1a/26 - mioc */ 0x00, /* 1b/27 - ramoc/reserved */ 0x20, /* 1c/28 - cdfr */ 0x00, /* 1d/29 - res4 */ 0x00, /* 1e/30 - cbru */ 0x00, /* 1f/31 - cbrl */};static u8 __cs4231_readb(struct snd_cs4231 *cp, void __iomem *reg_addr){#ifdef EBUS_SUPPORT if (cp->flags & CS4231_FLAG_EBUS) return readb(reg_addr); else#endif#ifdef SBUS_SUPPORT return sbus_readb(reg_addr);#endif}static void __cs4231_writeb(struct snd_cs4231 *cp, u8 val, void __iomem *reg_addr){#ifdef EBUS_SUPPORT if (cp->flags & CS4231_FLAG_EBUS) return writeb(val, reg_addr); else#endif#ifdef SBUS_SUPPORT return sbus_writeb(val, reg_addr);#endif}/* * Basic I/O functions */static void snd_cs4231_ready(struct snd_cs4231 *chip){ int timeout; for (timeout = 250; timeout > 0; timeout--) { int val = __cs4231_readb(chip, CS4231U(chip, REGSEL)); if ((val & CS4231_INIT) == 0) break; udelay(100); }}static void snd_cs4231_dout(struct snd_cs4231 *chip, unsigned char reg, unsigned char value){ snd_cs4231_ready(chip);#ifdef CONFIG_SND_DEBUG if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT) snd_printdd("out: auto calibration time out - reg = 0x%x, " "value = 0x%x\n", reg, value);#endif __cs4231_writeb(chip, chip->mce_bit | reg, CS4231U(chip, REGSEL)); wmb(); __cs4231_writeb(chip, value, CS4231U(chip, REG)); mb();}static inline void snd_cs4231_outm(struct snd_cs4231 *chip, unsigned char reg, unsigned char mask, unsigned char value){ unsigned char tmp = (chip->image[reg] & mask) | value; chip->image[reg] = tmp; if (!chip->calibrate_mute) snd_cs4231_dout(chip, reg, tmp);}static void snd_cs4231_out(struct snd_cs4231 *chip, unsigned char reg, unsigned char value){ snd_cs4231_dout(chip, reg, value); chip->image[reg] = value; mb();}static unsigned char snd_cs4231_in(struct snd_cs4231 *chip, unsigned char reg){ snd_cs4231_ready(chip);#ifdef CONFIG_SND_DEBUG if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT) snd_printdd("in: auto calibration time out - reg = 0x%x\n", reg);#endif __cs4231_writeb(chip, chip->mce_bit | reg, CS4231U(chip, REGSEL)); mb(); return __cs4231_readb(chip, CS4231U(chip, REG));}/* * CS4231 detection / MCE routines */static void snd_cs4231_busy_wait(struct snd_cs4231 *chip){ int timeout; /* looks like this sequence is proper for CS4231A chip (GUS MAX) */ for (timeout = 5; timeout > 0; timeout--) __cs4231_readb(chip, CS4231U(chip, REGSEL)); /* end of cleanup sequence */ for (timeout = 500; timeout > 0; timeout--) { int val = __cs4231_readb(chip, CS4231U(chip, REGSEL)); if ((val & CS4231_INIT) == 0) break; msleep(1); }}static void snd_cs4231_mce_up(struct snd_cs4231 *chip){ unsigned long flags; int timeout; spin_lock_irqsave(&chip->lock, flags); snd_cs4231_ready(chip);#ifdef CONFIG_SND_DEBUG if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT) snd_printdd("mce_up - auto calibration time out (0)\n");#endif chip->mce_bit |= CS4231_MCE; timeout = __cs4231_readb(chip, CS4231U(chip, REGSEL)); if (timeout == 0x80) snd_printdd("mce_up [%p]: serious init problem - " "codec still busy\n", chip->port); if (!(timeout & CS4231_MCE)) __cs4231_writeb(chip, chip->mce_bit | (timeout & 0x1f), CS4231U(chip, REGSEL)); spin_unlock_irqrestore(&chip->lock, flags);}static void snd_cs4231_mce_down(struct snd_cs4231 *chip){ unsigned long flags, timeout; int reg; snd_cs4231_busy_wait(chip); spin_lock_irqsave(&chip->lock, flags);#ifdef CONFIG_SND_DEBUG if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT) snd_printdd("mce_down [%p] - auto calibration time out (0)\n", CS4231U(chip, REGSEL));#endif chip->mce_bit &= ~CS4231_MCE; reg = __cs4231_readb(chip, CS4231U(chip, REGSEL)); __cs4231_writeb(chip, chip->mce_bit | (reg & 0x1f), CS4231U(chip, REGSEL)); if (reg == 0x80) snd_printdd("mce_down [%p]: serious init problem " "- codec still busy\n", chip->port); if ((reg & CS4231_MCE) == 0) { spin_unlock_irqrestore(&chip->lock, flags); return; } /* * Wait for auto-calibration (AC) process to finish, i.e. ACI to go low. */ timeout = jiffies + msecs_to_jiffies(250); do { spin_unlock_irqrestore(&chip->lock, flags); msleep(1); spin_lock_irqsave(&chip->lock, flags); reg = snd_cs4231_in(chip, CS4231_TEST_INIT); reg &= CS4231_CALIB_IN_PROGRESS; } while (reg && time_before(jiffies, timeout)); spin_unlock_irqrestore(&chip->lock, flags); if (reg) snd_printk(KERN_ERR "mce_down - auto calibration time out (2)\n");}static void snd_cs4231_advance_dma(struct cs4231_dma_control *dma_cont, struct snd_pcm_substream *substream, unsigned int *periods_sent){ struct snd_pcm_runtime *runtime = substream->runtime; while (1) { unsigned int period_size = snd_pcm_lib_period_bytes(substream); unsigned int offset = period_size * (*periods_sent); BUG_ON(period_size >= (1 << 24)); if (dma_cont->request(dma_cont, runtime->dma_addr + offset, period_size)) return; (*periods_sent) = ((*periods_sent) + 1) % runtime->periods; }}static void cs4231_dma_trigger(struct snd_pcm_substream *substream, unsigned int what, int on){ struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); struct cs4231_dma_control *dma_cont; if (what & CS4231_PLAYBACK_ENABLE) { dma_cont = &chip->p_dma; if (on) { dma_cont->prepare(dma_cont, 0); dma_cont->enable(dma_cont, 1); snd_cs4231_advance_dma(dma_cont, chip->playback_substream, &chip->p_periods_sent); } else { dma_cont->enable(dma_cont, 0); } } if (what & CS4231_RECORD_ENABLE) { dma_cont = &chip->c_dma; if (on) { dma_cont->prepare(dma_cont, 1); dma_cont->enable(dma_cont, 1); snd_cs4231_advance_dma(dma_cont, chip->capture_substream, &chip->c_periods_sent); } else { dma_cont->enable(dma_cont, 0); } }}static int snd_cs4231_trigger(struct snd_pcm_substream *substream, int cmd){ struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); int result = 0; switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_STOP: { unsigned int what = 0; struct snd_pcm_substream *s; unsigned long flags; snd_pcm_group_for_each_entry(s, substream) { if (s == chip->playback_substream) { what |= CS4231_PLAYBACK_ENABLE; snd_pcm_trigger_done(s, substream); } else if (s == chip->capture_substream) { what |= CS4231_RECORD_ENABLE; snd_pcm_trigger_done(s, substream); } } spin_lock_irqsave(&chip->lock, flags); if (cmd == SNDRV_PCM_TRIGGER_START) { cs4231_dma_trigger(substream, what, 1); chip->image[CS4231_IFACE_CTRL] |= what; } else { cs4231_dma_trigger(substream, what, 0); chip->image[CS4231_IFACE_CTRL] &= ~what; } snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]); spin_unlock_irqrestore(&chip->lock, flags); break; } default: result = -EINVAL; break; } return result;}/* * CODEC I/O */static unsigned char snd_cs4231_get_rate(unsigned int rate){
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -