Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 81652
b: refs/heads/master
c: e70515d
h: refs/heads/master
v: v3
  • Loading branch information
T. H. Huth authored and Jaroslav Kysela committed Jan 31, 2008
1 parent e10e267 commit 5b6d27b
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 6 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: 599c3e76fe89b314667e699a20ad08f8d16d0454
refs/heads/master: e70515dd518bbd5b9e2e5c90a56347df0e871389
107 changes: 102 additions & 5 deletions trunk/sound/ppc/pmac.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ static int tumbler_freqs[1] = {
44100
};


/*
* we will allocate a single 'emergency' dbdma cmd block to use if the
* tx status comes up "DEAD". This happens on some PowerComputing Pmac
* clones, either owing to a bug in dbdma or some interaction between
* IDE and sound. However, this measure would deal with DEAD status if
* it appeared elsewhere.
*/
static struct pmac_dbdma emergency_dbdma;
static int emergency_in_use;


/*
* allocate DBDMA command arrays
*/
Expand Down Expand Up @@ -374,6 +386,75 @@ static snd_pcm_uframes_t snd_pmac_capture_pointer(struct snd_pcm_substream *subs
}


/*
* Handle DEAD DMA transfers:
* if the TX status comes up "DEAD" - reported on some Power Computing machines
* we need to re-start the dbdma - but from a different physical start address
* and with a different transfer length. It would get very messy to do this
* with the normal dbdma_cmd blocks - we would have to re-write the buffer start
* addresses each time. So, we will keep a single dbdma_cmd block which can be
* fiddled with.
* When DEAD status is first reported the content of the faulted dbdma block is
* copied into the emergency buffer and we note that the buffer is in use.
* we then bump the start physical address by the amount that was successfully
* output before it died.
* On any subsequent DEAD result we just do the bump-ups (we know that we are
* already using the emergency dbdma_cmd).
* CHECK: this just tries to "do it". It is possible that we should abandon
* xfers when the number of residual bytes gets below a certain value - I can
* see that this might cause a loop-forever if a too small transfer causes
* DEAD status. However this is a TODO for now - we'll see what gets reported.
* When we get a successful transfer result with the emergency buffer we just
* pretend that it completed using the original dmdma_cmd and carry on. The
* 'next_cmd' field will already point back to the original loop of blocks.
*/
static inline void snd_pmac_pcm_dead_xfer(struct pmac_stream *rec,
volatile struct dbdma_cmd __iomem *cp)
{
unsigned short req, res ;
unsigned int phy ;

/* printk(KERN_WARNING "snd-powermac: DMA died - patching it up!\n"); */

/* to clear DEAD status we must first clear RUN
set it to quiescent to be on the safe side */
(void)in_le32(&rec->dma->status);
out_le32(&rec->dma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);

if (!emergency_in_use) { /* new problem */
memcpy((void *)emergency_dbdma.cmds, (void *)cp,
sizeof(struct dbdma_cmd));
emergency_in_use = 1;
st_le16(&cp->xfer_status, 0);
st_le16(&cp->req_count, rec->period_size);
cp = emergency_dbdma.cmds;
}

/* now bump the values to reflect the amount
we haven't yet shifted */
req = ld_le16(&cp->req_count);
res = ld_le16(&cp->res_count);
phy = ld_le32(&cp->phy_addr);
phy += (req - res);
st_le16(&cp->req_count, res);
st_le16(&cp->res_count, 0);
st_le16(&cp->xfer_status, 0);
st_le32(&cp->phy_addr, phy);

st_le32(&cp->cmd_dep, rec->cmd.addr
+ sizeof(struct dbdma_cmd)*((rec->cur_period+1)%rec->nperiods));

st_le16(&cp->command, OUTPUT_MORE | BR_ALWAYS | INTR_ALWAYS);

/* point at our patched up command block */
out_le32(&rec->dma->cmdptr, emergency_dbdma.addr);

/* we must re-start the controller */
(void)in_le32(&rec->dma->status);
/* should complete clearing the DEAD status */
out_le32(&rec->dma->control, ((RUN|WAKE) << 16) + (RUN|WAKE));
}

/*
* update playback/capture pointer from interrupts
*/
Expand All @@ -385,21 +466,35 @@ static void snd_pmac_pcm_update(struct snd_pmac *chip, struct pmac_stream *rec)

spin_lock(&chip->reg_lock);
if (rec->running) {
cp = &rec->cmd.cmds[rec->cur_period];
for (c = 0; c < rec->nperiods; c++) { /* at most all fragments */

if (emergency_in_use) /* already using DEAD xfer? */
cp = emergency_dbdma.cmds;
else
cp = &rec->cmd.cmds[rec->cur_period];

stat = ld_le16(&cp->xfer_status);

if (stat & DEAD) {
snd_pmac_pcm_dead_xfer(rec, cp);
break; /* this block is still going */
}

if (emergency_in_use)
emergency_in_use = 0 ; /* done that */

if (! (stat & ACTIVE))
break;

/*printk("update frag %d\n", rec->cur_period);*/
st_le16(&cp->xfer_status, 0);
st_le16(&cp->req_count, rec->period_size);
/*st_le16(&cp->res_count, 0);*/
rec->cur_period++;
if (rec->cur_period >= rec->nperiods) {
rec->cur_period = 0;
cp = rec->cmd.cmds;
} else
cp++;
}

spin_unlock(&chip->reg_lock);
snd_pcm_period_elapsed(rec->substream);
spin_lock(&chip->reg_lock);
Expand Down Expand Up @@ -769,6 +864,7 @@ static int snd_pmac_free(struct snd_pmac *chip)
snd_pmac_dbdma_free(chip, &chip->playback.cmd);
snd_pmac_dbdma_free(chip, &chip->capture.cmd);
snd_pmac_dbdma_free(chip, &chip->extra_dma);
snd_pmac_dbdma_free(chip, &emergency_dbdma);
if (chip->macio_base)
iounmap(chip->macio_base);
if (chip->latch_base)
Expand Down Expand Up @@ -1107,7 +1203,8 @@ int __init snd_pmac_new(struct snd_card *card, struct snd_pmac **chip_return)

if (snd_pmac_dbdma_alloc(chip, &chip->playback.cmd, PMAC_MAX_FRAGS + 1) < 0 ||
snd_pmac_dbdma_alloc(chip, &chip->capture.cmd, PMAC_MAX_FRAGS + 1) < 0 ||
snd_pmac_dbdma_alloc(chip, &chip->extra_dma, 2) < 0) {
snd_pmac_dbdma_alloc(chip, &chip->extra_dma, 2) < 0 ||
snd_pmac_dbdma_alloc(chip, &emergency_dbdma, 2) < 0) {
err = -ENOMEM;
goto __error;
}
Expand Down

0 comments on commit 5b6d27b

Please sign in to comment.