[SPARC]: Make SBUS dma code similar to EBUS
Georg Chini [Mon, 7 Nov 2005 22:08:25 +0000 (14:08 -0800)]
From: Georg Chini <georg.chini@triaton-webhosting.com>

Introduce some sbus_dma routines similar to the
ebus_dma stuff to make the code look nearly the same
for both cases.

Thanks to Christopher for testing.

Signed-off-by: David S. Miller <davem@davemloft.net>

sound/sparc/cs4231.c

index f4361c5..110d64d 100644 (file)
@@ -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",