diff options
Diffstat (limited to 'sound/sparc/cs4231.c')
-rw-r--r-- | sound/sparc/cs4231.c | 311 |
1 files changed, 199 insertions, 112 deletions
diff --git a/sound/sparc/cs4231.c b/sound/sparc/cs4231.c index f4361c518e46..110d64d4848d 100644 --- a/sound/sparc/cs4231.c +++ b/sound/sparc/cs4231.c @@ -61,6 +61,14 @@ MODULE_DESCRIPTION("Sun CS4231"); MODULE_LICENSE("GPL"); MODULE_SUPPORTED_DEVICE("{{Sun,CS4231}}"); +#ifdef SBUS_SUPPORT +struct sbus_dma_info { + spinlock_t lock; + int dir; + void __iomem *regs; +}; +#endif + typedef struct snd_cs4231 { spinlock_t lock; void __iomem *port; @@ -69,6 +77,11 @@ typedef struct snd_cs4231 { struct ebus_dma_info eb2p; #endif +#ifdef SBUS_SUPPORT + struct sbus_dma_info sb2c; + struct sbus_dma_info sb2p; +#endif + u32 flags; #define CS4231_FLAG_EBUS 0x00000001 #define CS4231_FLAG_PLAYBACK 0x00000002 @@ -251,6 +264,15 @@ static cs4231_t *cs4231_list; #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 */ @@ -472,6 +494,103 @@ static unsigned char snd_cs4231_in(cs4231_t *chip, unsigned char reg) } /* + * SBUS DMA routines + */ +#ifdef SBUS_SUPPORT + +int sbus_dma_request(struct sbus_dma_info *base, dma_addr_t bus_addr, size_t len) +{ + unsigned long flags; + u32 test, csr; + int err; + + if (len >= (1 << 24)) + return -EINVAL; + spin_lock_irqsave(&base->lock, flags); + csr = sbus_readl(base->regs + APCCSR); + err = -EINVAL; + test = APC_CDMA_READY; + if ( base->dir == APC_PLAY ) + test = APC_PDMA_READY; + if (!(csr & test)) + goto out; + err = -EBUSY; + csr = sbus_readl(base->regs + APCCSR); + test = APC_XINT_CNVA; + if ( base->dir == APC_PLAY ) + test = APC_XINT_PNVA; + if (!(csr & test)) + goto out; + err = 0; + sbus_writel(bus_addr, base->regs + base->dir + APCNVA); + sbus_writel(len, base->regs + base->dir + APCNC); +out: + spin_unlock_irqrestore(&base->lock, flags); + return err; +} + +void sbus_dma_prepare(struct sbus_dma_info *base) +{ + unsigned long flags; + u32 csr, test; + + spin_lock_irqsave(&base->lock, flags); + csr = sbus_readl(base->regs + APCCSR); + test = APC_GENL_INT | APC_PLAY_INT | APC_XINT_ENA | + APC_XINT_PLAY | APC_XINT_PEMP | APC_XINT_GENL | + APC_XINT_PENA; + if ( base->dir == APC_RECORD ) + test = APC_GENL_INT | APC_CAPT_INT | APC_XINT_ENA | + APC_XINT_CAPT | APC_XINT_CEMP | APC_XINT_GENL; + csr |= test; + sbus_writel(csr, base->regs + APCCSR); + spin_unlock_irqrestore(&base->lock, flags); +} + +void sbus_dma_enable(struct sbus_dma_info *base, int on) +{ + unsigned long flags; + u32 csr, shift; + + spin_lock_irqsave(&base->lock, flags); + if (!on) { + if (base->dir == APC_PLAY) { + sbus_writel(0, base->regs + base->dir + APCNVA); + sbus_writel(1, base->regs + base->dir + APCC); + } + else + { + sbus_writel(0, base->regs + base->dir + APCNC); + sbus_writel(0, base->regs + base->dir + APCVA); + } + } + udelay(500); + csr = sbus_readl(base->regs + APCCSR); + shift = 0; + if ( base->dir == APC_PLAY ) + shift = 1; + if (on) + csr &= ~(APC_CPAUSE << shift); + else + csr |= (APC_CPAUSE << shift); + sbus_writel(csr, base->regs + APCCSR); + if (on) + csr |= (APC_CDMA_READY << shift); + else + csr &= ~(APC_CDMA_READY << shift); + sbus_writel(csr, base->regs + APCCSR); + + spin_unlock_irqrestore(&base->lock, flags); +} + +unsigned int sbus_dma_addr(struct sbus_dma_info *base) +{ + return sbus_readl(base->regs + base->dir + APCVA); +} + +#endif + +/* * CS4231 detection / MCE routines */ @@ -589,29 +708,21 @@ static void snd_cs4231_ebus_advance_dma(struct ebus_dma_info *p, snd_pcm_substre #endif #ifdef SBUS_SUPPORT -static void snd_cs4231_sbus_advance_dma(snd_pcm_substream_t *substream, unsigned int *periods_sent) +static void snd_cs4231_sbus_advance_dma(struct sbus_dma_info *p, snd_pcm_substream_t *substream, unsigned int *periods_sent) { - cs4231_t *chip = snd_pcm_substream_chip(substream); snd_pcm_runtime_t *runtime = substream->runtime; - unsigned int period_size = snd_pcm_lib_period_bytes(substream); - unsigned int offset = period_size * (*periods_sent % runtime->periods); - - if (runtime->period_size > 0xffff + 1) - BUG(); - - switch (substream->stream) { - case SNDRV_PCM_STREAM_PLAYBACK: - sbus_writel(runtime->dma_addr + offset, chip->port + APCPNVA); - sbus_writel(period_size, chip->port + APCPNC); - break; - case SNDRV_PCM_STREAM_CAPTURE: - sbus_writel(runtime->dma_addr + offset, chip->port + APCCNVA); - sbus_writel(period_size, chip->port + APCCNC); - break; - } + while (1) { + unsigned int period_size = snd_pcm_lib_period_bytes(substream); + unsigned int offset = period_size * (*periods_sent); - (*periods_sent) = (*periods_sent + 1) % runtime->periods; + if (period_size > 0xffff + 1) + BUG(); + + if (sbus_dma_request(p, runtime->dma_addr + offset, period_size)) + return; + (*periods_sent) = (*periods_sent + 1) % runtime->periods; + } } #endif @@ -646,59 +757,27 @@ static void cs4231_dma_trigger(snd_pcm_substream_t *substream, unsigned int what } else { #endif #ifdef SBUS_SUPPORT - u32 csr = sbus_readl(chip->port + APCCSR); - /* I don't know why, but on sbus the period counter must - * only start counting after the first period is sent. - * Therefore this dummy thing. - */ - unsigned int dummy = 0; - - switch (what) { - case CS4231_PLAYBACK_ENABLE: + if (what & CS4231_PLAYBACK_ENABLE) { if (on) { - csr &= ~APC_XINT_PLAY; - sbus_writel(csr, chip->port + APCCSR); - - csr &= ~APC_PPAUSE; - sbus_writel(csr, chip->port + APCCSR); - - snd_cs4231_sbus_advance_dma(substream, &dummy); - - csr |= APC_GENL_INT | APC_PLAY_INT | APC_XINT_ENA | - APC_XINT_PLAY | APC_XINT_EMPT | APC_XINT_GENL | - APC_XINT_PENA | APC_PDMA_READY; - sbus_writel(csr, chip->port + APCCSR); + sbus_dma_prepare(&chip->sb2p); + sbus_dma_enable(&chip->sb2p, 1); + snd_cs4231_sbus_advance_dma(&chip->sb2p, + chip->playback_substream, + &chip->p_periods_sent); } else { - csr |= APC_PPAUSE; - sbus_writel(csr, chip->port + APCCSR); - - csr &= ~APC_PDMA_READY; - sbus_writel(csr, chip->port + APCCSR); + sbus_dma_enable(&chip->sb2p, 0); } - break; - case CS4231_RECORD_ENABLE: + } + if (what & CS4231_RECORD_ENABLE) { if (on) { - csr &= ~APC_XINT_CAPT; - sbus_writel(csr, chip->port + APCCSR); - - csr &= ~APC_CPAUSE; - sbus_writel(csr, chip->port + APCCSR); - - snd_cs4231_sbus_advance_dma(substream, &dummy); - - csr |= APC_GENL_INT | APC_CAPT_INT | APC_XINT_ENA | - APC_XINT_CAPT | APC_XINT_CEMP | APC_XINT_GENL | - APC_CDMA_READY; - - sbus_writel(csr, chip->port + APCCSR); + sbus_dma_prepare(&chip->sb2c); + sbus_dma_enable(&chip->sb2c, 1); + snd_cs4231_sbus_advance_dma(&chip->sb2c, + chip->capture_substream, + &chip->c_periods_sent); } else { - csr |= APC_CPAUSE; - sbus_writel(csr, chip->port + APCCSR); - - csr &= ~APC_CDMA_READY; - sbus_writel(csr, chip->port + APCCSR); + sbus_dma_enable(&chip->sb2c, 0); } - break; } #endif #ifdef EBUS_SUPPORT @@ -1136,10 +1215,7 @@ static int snd_cs4231_playback_prepare(snd_pcm_substream_t *substream) if (runtime->period_size > 0xffff + 1) BUG(); - snd_cs4231_out(chip, CS4231_PLY_LWR_CNT, (runtime->period_size - 1) & 0x00ff); - snd_cs4231_out(chip, CS4231_PLY_UPR_CNT, (runtime->period_size - 1) >> 8 & 0x00ff); chip->p_periods_sent = 0; - spin_unlock_irqrestore(&chip->lock, flags); return 0; @@ -1171,16 +1247,14 @@ static int snd_cs4231_capture_hw_free(snd_pcm_substream_t *substream) static int snd_cs4231_capture_prepare(snd_pcm_substream_t *substream) { cs4231_t *chip = snd_pcm_substream_chip(substream); - snd_pcm_runtime_t *runtime = substream->runtime; unsigned long flags; spin_lock_irqsave(&chip->lock, flags); chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_RECORD_ENABLE | CS4231_RECORD_PIO); - snd_cs4231_out(chip, CS4231_REC_LWR_CNT, (runtime->period_size - 1) & 0x00ff); - snd_cs4231_out(chip, CS4231_REC_LWR_CNT, (runtime->period_size - 1) >> 8 & 0x00ff); + chip->c_periods_sent = 0; spin_unlock_irqrestore(&chip->lock, flags); return 0; @@ -1199,40 +1273,20 @@ static void snd_cs4231_overrange(cs4231_t *chip) chip->capture_substream->runtime->overrange++; } -static irqreturn_t snd_cs4231_generic_interrupt(cs4231_t *chip) +#ifdef SBUS_SUPPORT +static irqreturn_t snd_cs4231_sbus_interrupt(int irq, void *dev_id, struct pt_regs *regs) { unsigned long flags; unsigned char status; + u32 csr; + cs4231_t *chip = dev_id; /*This is IRQ is not raised by the cs4231*/ if (!(__cs4231_readb(chip, CS4231P(chip, STATUS)) & CS4231_GLOBALIRQ)) return IRQ_NONE; - status = snd_cs4231_in(chip, CS4231_IRQ_STATUS); - - if (status & CS4231_TIMER_IRQ) { - if (chip->timer) - snd_timer_interrupt(chip->timer, chip->timer->sticks); - } - - if (status & CS4231_RECORD_IRQ) - snd_cs4231_overrange(chip); - - /* ACK the CS4231 interrupt. */ - spin_lock_irqsave(&chip->lock, flags); - snd_cs4231_outm(chip, CS4231_IRQ_STATUS, ~CS4231_ALL_IRQS | ~status, 0); - spin_unlock_irqrestore(&chip->lock, flags); - - return 0; -} - -#ifdef SBUS_SUPPORT -static irqreturn_t snd_cs4231_sbus_interrupt(int irq, void *dev_id, struct pt_regs *regs) -{ - cs4231_t *chip = dev_id; - /* ACK the APC interrupt. */ - u32 csr = sbus_readl(chip->port + APCCSR); + csr = sbus_readl(chip->port + APCCSR); sbus_writel(csr, chip->port + APCCSR); @@ -1240,20 +1294,36 @@ static irqreturn_t snd_cs4231_sbus_interrupt(int irq, void *dev_id, struct pt_re (csr & APC_PLAY_INT) && (csr & APC_XINT_PNVA) && !(csr & APC_XINT_EMPT)) { - snd_cs4231_sbus_advance_dma(chip->playback_substream, - &chip->p_periods_sent); snd_pcm_period_elapsed(chip->playback_substream); + snd_cs4231_sbus_advance_dma(&chip->sb2p, chip->playback_substream, + &chip->p_periods_sent); } if ((chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE) && (csr & APC_CAPT_INT) && - (csr & APC_XINT_CNVA)) { - snd_cs4231_sbus_advance_dma(chip->capture_substream, - &chip->c_periods_sent); + (csr & APC_XINT_CNVA) && + !(csr & APC_XINT_EMPT)) { snd_pcm_period_elapsed(chip->capture_substream); + snd_cs4231_sbus_advance_dma(&chip->sb2c,chip->capture_substream, + &chip->c_periods_sent); } + + status = snd_cs4231_in(chip, CS4231_IRQ_STATUS); + + if (status & CS4231_TIMER_IRQ) { + if (chip->timer) + snd_timer_interrupt(chip->timer, chip->timer->sticks); + } + + if (status & CS4231_RECORD_IRQ) + snd_cs4231_overrange(chip); - return snd_cs4231_generic_interrupt(chip); + /* ACK the CS4231 interrupt. */ + spin_lock_irqsave(&chip->lock, flags); + snd_cs4231_outm(chip, CS4231_IRQ_STATUS, ~CS4231_ALL_IRQS | ~status, 0); + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; } #endif @@ -1284,24 +1354,29 @@ static void snd_cs4231_ebus_capture_callback(struct ebus_dma_info *p, int event, static snd_pcm_uframes_t snd_cs4231_playback_pointer(snd_pcm_substream_t *substream) { cs4231_t *chip = snd_pcm_substream_chip(substream); - size_t ptr, residue, period_bytes; - + size_t ptr; +#ifdef EBUS_SUPPORT + size_t residue, period_bytes; +#endif + if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) return 0; +#ifdef EBUS_SUPPORT period_bytes = snd_pcm_lib_period_bytes(substream); ptr = period_bytes * chip->p_periods_sent; -#ifdef EBUS_SUPPORT if (chip->flags & CS4231_FLAG_EBUS) { residue = ebus_dma_residue(&chip->eb2p); + ptr += period_bytes - residue; } else { #endif #ifdef SBUS_SUPPORT - residue = sbus_readl(chip->port + APCPC); + ptr = sbus_dma_addr(&chip->sb2p); + if (ptr != 0) + ptr -= substream->runtime->dma_addr; #endif #ifdef EBUS_SUPPORT } #endif - ptr += period_bytes - residue; return bytes_to_frames(substream->runtime, ptr); } @@ -1309,24 +1384,29 @@ static snd_pcm_uframes_t snd_cs4231_playback_pointer(snd_pcm_substream_t *substr static snd_pcm_uframes_t snd_cs4231_capture_pointer(snd_pcm_substream_t * substream) { cs4231_t *chip = snd_pcm_substream_chip(substream); - size_t ptr, residue, period_bytes; + size_t ptr; +#ifdef EBUS_SUPPORT + size_t residue, period_bytes; +#endif if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE)) return 0; +#ifdef EBUS_SUPPORT period_bytes = snd_pcm_lib_period_bytes(substream); ptr = period_bytes * chip->c_periods_sent; -#ifdef EBUS_SUPPORT if (chip->flags & CS4231_FLAG_EBUS) { residue = ebus_dma_residue(&chip->eb2c); + ptr += period_bytes - residue; } else { #endif #ifdef SBUS_SUPPORT - residue = sbus_readl(chip->port + APCCC); + ptr = sbus_dma_addr(&chip->sb2c); + if (ptr != 0) + ptr -= substream->runtime->dma_addr; #endif #ifdef EBUS_SUPPORT } #endif - ptr += period_bytes - residue; return bytes_to_frames(substream->runtime, ptr); } @@ -1983,6 +2063,8 @@ static int __init snd_cs4231_sbus_create(snd_card_t *card, return -ENOMEM; spin_lock_init(&chip->lock); + spin_lock_init(&chip->sb2c.lock); + spin_lock_init(&chip->sb2p.lock); init_MUTEX(&chip->mce_mutex); init_MUTEX(&chip->open_mutex); chip->card = card; @@ -1998,6 +2080,11 @@ static int __init snd_cs4231_sbus_create(snd_card_t *card, return -EIO; } + chip->sb2c.regs = chip->port; + chip->sb2p.regs = chip->port; + chip->sb2c.dir = APC_RECORD; + chip->sb2p.dir = APC_PLAY; + if (request_irq(sdev->irqs[0], snd_cs4231_sbus_interrupt, SA_SHIRQ, "cs4231", chip)) { snd_printdd("cs4231-%d: Unable to grab SBUS IRQ %s\n", |