⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 i2sbus-pcm.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 2 页
字号:
}#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 + -