📄 i2sbus-pcm.c
字号:
}#endifstatic int i2sbus_pcm_trigger(struct i2sbus_dev *i2sdev, int in, int cmd){ struct codec_info_item *cii; struct pcm_info *pi; int result = 0; unsigned long flags; spin_lock_irqsave(&i2sdev->low_lock, flags); get_pcm_info(i2sdev, in, &pi, NULL); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: if (pi->dbdma_ring.running) { result = -EALREADY; goto out_unlock; } list_for_each_entry(cii, &i2sdev->sound.codec_list, list) if (cii->codec->start) cii->codec->start(cii, pi->substream); pi->dbdma_ring.running = 1; if (pi->dbdma_ring.stopping) { /* Clear the S0 bit, then see if we stopped yet */ out_le32(&pi->dbdma->control, 1 << 16); if (in_le32(&pi->dbdma->status) & ACTIVE) { /* possible race here? */ udelay(10); if (in_le32(&pi->dbdma->status) & ACTIVE) { pi->dbdma_ring.stopping = 0; goto out_unlock; /* keep running */ } } } /* make sure RUN, PAUSE and S0 bits are cleared */ out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16); /* set branch condition select register */ out_le32(&pi->dbdma->br_sel, (1 << 16) | 1); /* write dma command buffer address to the dbdma chip */ out_le32(&pi->dbdma->cmdptr, pi->dbdma_ring.bus_cmd_start); /* initialize the frame count and current period */ pi->current_period = 0; pi->frame_count = in_le32(&i2sdev->intfregs->frame_count); /* set the DMA controller running */ out_le32(&pi->dbdma->control, (RUN << 16) | RUN); /* off you go! */ break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: if (!pi->dbdma_ring.running) { result = -EALREADY; goto out_unlock; } pi->dbdma_ring.running = 0; /* Set the S0 bit to make the DMA branch to the stop cmd */ out_le32(&pi->dbdma->control, (1 << 16) | 1); pi->dbdma_ring.stopping = 1; list_for_each_entry(cii, &i2sdev->sound.codec_list, list) if (cii->codec->stop) cii->codec->stop(cii, pi->substream); break; default: result = -EINVAL; goto out_unlock; } out_unlock: spin_unlock_irqrestore(&i2sdev->low_lock, flags); return result;}static snd_pcm_uframes_t i2sbus_pcm_pointer(struct i2sbus_dev *i2sdev, int in){ struct pcm_info *pi; u32 fc; get_pcm_info(i2sdev, in, &pi, NULL); fc = in_le32(&i2sdev->intfregs->frame_count); fc = fc - pi->frame_count; if (fc >= pi->substream->runtime->buffer_size) fc %= pi->substream->runtime->buffer_size; return fc;}static inline void handle_interrupt(struct i2sbus_dev *i2sdev, int in){ struct pcm_info *pi; u32 fc, nframes; u32 status; int timeout, i; int dma_stopped = 0; struct snd_pcm_runtime *runtime; spin_lock(&i2sdev->low_lock); get_pcm_info(i2sdev, in, &pi, NULL); if (!pi->dbdma_ring.running && !pi->dbdma_ring.stopping) goto out_unlock; i = pi->current_period; runtime = pi->substream->runtime; while (pi->dbdma_ring.cmds[i].xfer_status) { if (le16_to_cpu(pi->dbdma_ring.cmds[i].xfer_status) & BT) /* * BT is the branch taken bit. If it took a branch * it is because we set the S0 bit to make it * branch to the stop command. */ dma_stopped = 1; pi->dbdma_ring.cmds[i].xfer_status = 0; if (++i >= runtime->periods) { i = 0; pi->frame_count += runtime->buffer_size; } pi->current_period = i; /* * Check the frame count. The DMA tends to get a bit * ahead of the frame counter, which confuses the core. */ fc = in_le32(&i2sdev->intfregs->frame_count); nframes = i * runtime->period_size; if (fc < pi->frame_count + nframes) pi->frame_count = fc - nframes; } if (dma_stopped) { timeout = 1000; for (;;) { status = in_le32(&pi->dbdma->status); if (!(status & ACTIVE) && (!in || (status & 0x80))) break; if (--timeout <= 0) { printk(KERN_ERR "i2sbus: timed out " "waiting for DMA to stop!\n"); break; } udelay(1); } /* Turn off DMA controller, clear S0 bit */ out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16); pi->dbdma_ring.stopping = 0; if (pi->stop_completion) complete(pi->stop_completion); } if (!pi->dbdma_ring.running) goto out_unlock; spin_unlock(&i2sdev->low_lock); /* may call _trigger again, hence needs to be unlocked */ snd_pcm_period_elapsed(pi->substream); return; out_unlock: spin_unlock(&i2sdev->low_lock);}irqreturn_t i2sbus_tx_intr(int irq, void *devid){ handle_interrupt((struct i2sbus_dev *)devid, 0); return IRQ_HANDLED;}irqreturn_t i2sbus_rx_intr(int irq, void *devid){ handle_interrupt((struct i2sbus_dev *)devid, 1); return IRQ_HANDLED;}static int i2sbus_playback_open(struct snd_pcm_substream *substream){ struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); if (!i2sdev) return -EINVAL; i2sdev->out.substream = substream; return i2sbus_pcm_open(i2sdev, 0);}static int i2sbus_playback_close(struct snd_pcm_substream *substream){ struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); int err; if (!i2sdev) return -EINVAL; if (i2sdev->out.substream != substream) return -EINVAL; err = i2sbus_pcm_close(i2sdev, 0); if (!err) i2sdev->out.substream = NULL; return err;}static int i2sbus_playback_prepare(struct snd_pcm_substream *substream){ struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); if (!i2sdev) return -EINVAL; if (i2sdev->out.substream != substream) return -EINVAL; return i2sbus_pcm_prepare(i2sdev, 0);}static int i2sbus_playback_trigger(struct snd_pcm_substream *substream, int cmd){ struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); if (!i2sdev) return -EINVAL; if (i2sdev->out.substream != substream) return -EINVAL; return i2sbus_pcm_trigger(i2sdev, 0, cmd);}static snd_pcm_uframes_t i2sbus_playback_pointer(struct snd_pcm_substream *substream){ struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); if (!i2sdev) return -EINVAL; if (i2sdev->out.substream != substream) return 0; return i2sbus_pcm_pointer(i2sdev, 0);}static struct snd_pcm_ops i2sbus_playback_ops = { .open = i2sbus_playback_open, .close = i2sbus_playback_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = i2sbus_hw_params, .hw_free = i2sbus_playback_hw_free, .prepare = i2sbus_playback_prepare, .trigger = i2sbus_playback_trigger, .pointer = i2sbus_playback_pointer,};static int i2sbus_record_open(struct snd_pcm_substream *substream){ struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); if (!i2sdev) return -EINVAL; i2sdev->in.substream = substream; return i2sbus_pcm_open(i2sdev, 1);}static int i2sbus_record_close(struct snd_pcm_substream *substream){ struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); int err; if (!i2sdev) return -EINVAL; if (i2sdev->in.substream != substream) return -EINVAL; err = i2sbus_pcm_close(i2sdev, 1); if (!err) i2sdev->in.substream = NULL; return err;}static int i2sbus_record_prepare(struct snd_pcm_substream *substream){ struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); if (!i2sdev) return -EINVAL; if (i2sdev->in.substream != substream) return -EINVAL; return i2sbus_pcm_prepare(i2sdev, 1);}static int i2sbus_record_trigger(struct snd_pcm_substream *substream, int cmd){ struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); if (!i2sdev) return -EINVAL; if (i2sdev->in.substream != substream) return -EINVAL; return i2sbus_pcm_trigger(i2sdev, 1, cmd);}static snd_pcm_uframes_t i2sbus_record_pointer(struct snd_pcm_substream *substream){ struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); if (!i2sdev) return -EINVAL; if (i2sdev->in.substream != substream) return 0; return i2sbus_pcm_pointer(i2sdev, 1);}static struct snd_pcm_ops i2sbus_record_ops = { .open = i2sbus_record_open, .close = i2sbus_record_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = i2sbus_hw_params, .hw_free = i2sbus_record_hw_free, .prepare = i2sbus_record_prepare, .trigger = i2sbus_record_trigger, .pointer = i2sbus_record_pointer,};static void i2sbus_private_free(struct snd_pcm *pcm){ struct i2sbus_dev *i2sdev = snd_pcm_chip(pcm); struct codec_info_item *p, *tmp; i2sdev->sound.pcm = NULL; i2sdev->out.created = 0; i2sdev->in.created = 0; list_for_each_entry_safe(p, tmp, &i2sdev->sound.codec_list, list) { printk(KERN_ERR "i2sbus: a codec didn't unregister!\n"); list_del(&p->list); module_put(p->codec->owner); kfree(p); } soundbus_dev_put(&i2sdev->sound); module_put(THIS_MODULE);}inti2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card, struct codec_info *ci, void *data){ int err, in = 0, out = 0; struct transfer_info *tmp; struct i2sbus_dev *i2sdev = soundbus_dev_to_i2sbus_dev(dev); struct codec_info_item *cii; if (!dev->pcmname || dev->pcmid == -1) { printk(KERN_ERR "i2sbus: pcm name and id must be set!\n"); return -EINVAL; } list_for_each_entry(cii, &dev->codec_list, list) { if (cii->codec_data == data) return -EALREADY; } if (!ci->transfers || !ci->transfers->formats || !ci->transfers->rates || !ci->usable) return -EINVAL; /* we currently code the i2s transfer on the clock, and support only * 32 and 64 */ if (ci->bus_factor != 32 && ci->bus_factor != 64) return -EINVAL; /* If you want to fix this, you need to keep track of what transport infos * are to be used, which codecs they belong to, and then fix all the * sysclock/busclock stuff above to depend on which is usable */ list_for_each_entry(cii, &dev->codec_list, list) { if (cii->codec->sysclock_factor != ci->sysclock_factor) { printk(KERN_DEBUG "cannot yet handle multiple different sysclocks!\n"); return -EINVAL; } if (cii->codec->bus_factor != ci->bus_factor) { printk(KERN_DEBUG "cannot yet handle multiple different bus clocks!\n"); return -EINVAL; } } tmp = ci->transfers; while (tmp->formats && tmp->rates) { if (tmp->transfer_in) in = 1; else out = 1; tmp++; } cii = kzalloc(sizeof(struct codec_info_item), GFP_KERNEL); if (!cii) { printk(KERN_DEBUG "i2sbus: failed to allocate cii\n"); return -ENOMEM; } /* use the private data to point to the codec info */ cii->sdev = soundbus_dev_get(dev); cii->codec = ci; cii->codec_data = data; if (!cii->sdev) { printk(KERN_DEBUG "i2sbus: failed to get soundbus dev reference\n"); err = -ENODEV; goto out_free_cii; } if (!try_module_get(THIS_MODULE)) { printk(KERN_DEBUG "i2sbus: failed to get module reference!\n"); err = -EBUSY; goto out_put_sdev; } if (!try_module_get(ci->owner)) { printk(KERN_DEBUG "i2sbus: failed to get module reference to codec owner!\n"); err = -EBUSY; goto out_put_this_module; } if (!dev->pcm) { err = snd_pcm_new(card, dev->pcmname, dev->pcmid, 0, 0, &dev->pcm); if (err) { printk(KERN_DEBUG "i2sbus: failed to create pcm\n"); goto out_put_ci_module; } dev->pcm->dev = &dev->ofdev.dev; } /* ALSA yet again sucks. * If it is ever fixed, remove this line. See below. */ out = in = 1; if (!i2sdev->out.created && out) { if (dev->pcm->card != card) { /* eh? */ printk(KERN_ERR "Can't attach same bus to different cards!\n"); err = -EINVAL; goto out_put_ci_module; } err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, 1); if (err) goto out_put_ci_module; snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, &i2sbus_playback_ops); i2sdev->out.created = 1; } if (!i2sdev->in.created && in) { if (dev->pcm->card != card) { printk(KERN_ERR "Can't attach same bus to different cards!\n"); goto out_put_ci_module; } err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, 1); if (err) goto out_put_ci_module; snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, &i2sbus_record_ops); i2sdev->in.created = 1; } /* so we have to register the pcm after adding any substream * to it because alsa doesn't create the devices for the * substreams when we add them later. * Therefore, force in and out on both busses (above) and * register the pcm now instead of just after creating it. */ err = snd_device_register(card, dev->pcm); if (err) { printk(KERN_ERR "i2sbus: error registering new pcm\n"); goto out_put_ci_module; } /* no errors any more, so let's add this to our list */ list_add(&cii->list, &dev->codec_list); dev->pcm->private_data = i2sdev; dev->pcm->private_free = i2sbus_private_free; /* well, we really should support scatter/gather DMA */ snd_pcm_lib_preallocate_pages_for_all( dev->pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(macio_get_pci_dev(i2sdev->macio)), 64 * 1024, 64 * 1024); return 0; out_put_ci_module: module_put(ci->owner); out_put_this_module: module_put(THIS_MODULE); out_put_sdev: soundbus_dev_put(dev); out_free_cii: kfree(cii); return err;}void i2sbus_detach_codec(struct soundbus_dev *dev, void *data){ struct codec_info_item *cii = NULL, *i; list_for_each_entry(i, &dev->codec_list, list) { if (i->codec_data == data) { cii = i; break; } } if (cii) { list_del(&cii->list); module_put(cii->codec->owner); kfree(cii); } /* no more codecs, but still a pcm? */ if (list_empty(&dev->codec_list) && dev->pcm) { /* the actual cleanup is done by the callback above! */ snd_device_free(dev->pcm->card, dev->pcm); }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -