[alsa-devel] [RFC] ASoC: Add support for BCM2835

Lars-Peter Clausen lars at metafoo.de
Mon Nov 11 13:37:30 EST 2013


On 11/08/2013 09:56 PM, Florian Meier wrote:

Is the I2S clock part of a generic clocking module? If yes it might be
better to implement this as a clock chip driver.

[...]
> +static void bcm2835_i2s_stop(struct bcm2835_i2s_dev *dev,
> +		struct snd_pcm_substream *substream)
> +{
> +	unsigned int still_running;
> +
> +	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
> +		regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
> +				BCM2835_I2S_RXON, 0);
> +	} else {
> +		regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
> +				BCM2835_I2S_TXON, 0);
> +	}
> +
> +	regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &still_running);
> +	still_running &= BCM2835_I2S_TXON | BCM2835_I2S_RXON;
> +

still_running can be replaced with dai->active, I think

> +	/* Stop also the clock when not SND_SOC_DAIFMT_CONT */
> +	if (!still_running && !(dev->fmt & SND_SOC_DAIFMT_CONT))
> +		bcm2835_i2s_stop_clock(dev);
> +
> +}
> +
> +static int bcm2835_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
> +			       struct snd_soc_dai *dai)
> +{
> +	struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
> +
> +	switch (cmd) {
> +	case SNDRV_PCM_TRIGGER_START:
> +	case SNDRV_PCM_TRIGGER_RESUME:
> +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> +		bcm2835_i2s_start_clock(dev);
> +
> +		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
> +			regmap_update_bits(dev->i2s_regmap,
> +					BCM2835_I2S_CS_A_REG,
> +					BCM2835_I2S_RXON, BCM2835_I2S_RXON);
> +		} else {
> +			regmap_update_bits(dev->i2s_regmap,
> +					BCM2835_I2S_CS_A_REG,
> +					BCM2835_I2S_TXON, BCM2835_I2S_TXON);
> +		}

Doing something like

		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
			mask = BCM2835_I2S_RXON;
		else
			mask = BCM2835_I2S_TXON;

		regmap_update_bits(dev->i2s_regmap,
			BCM2835_I2S_CS_A_REG, mask, mask);

makes the code a bit shorter. Same for bcm2835_i2s_stop().
> +		break;
> +
> +	case SNDRV_PCM_TRIGGER_STOP:
> +	case SNDRV_PCM_TRIGGER_SUSPEND:
> +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
> +		bcm2835_i2s_stop(dev, substream);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int bcm2835_i2s_startup(struct snd_pcm_substream *substream,
> +			       struct snd_soc_dai *dai)
> +{
> +	struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
> +
> +	if (!dev->first_stream) {
> +		dev->first_stream = substream;
> +

Since you use first_stream and second_stream for anything else I think you
can just use dai->active here instead.

> +		/* should this still be running stop it */
> +		bcm2835_i2s_stop_clock(dev);
> +
> +		/* enable PCM block */
> +		regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
> +				BCM2835_I2S_EN, -1);
> +
> +		/*
> +		 * Disable STBY
> +		 * Requires at least 4 PCM clock cycles to take effect
> +		 */
> +		regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
> +				BCM2835_I2S_STBY, -1);
> +
> +	} else {
> +		dev->second_stream = substream;
> +	}
> +
> +	return 0;
> +}
> +
[...]
> +static int bcm2835_i2s_dai_probe(struct snd_soc_dai *dai)
> +{
> +	struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
> +
> +	/* Set the appropriate DMA parameters */
> +	dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr =
> +		(dma_addr_t)BCM2835_I2S_FIFO_PHYSICAL_ADDR;
> +	dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr =
> +		(dma_addr_t)BCM2835_I2S_FIFO_PHYSICAL_ADDR;

The dma address for the FIFO should not be hardcoded but rather use an
offset to the resource that get's pass to the driver.

	dma_data->addr = mem->start + BCM2835_I2S_FIFO_A_REG;

I think it should be fine to initialize dma_data already in the driver
probe() function.

> +
> +	dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].slave_id =
> +		BCM2835_DMA_DREQ_PCM_TX;
> +	dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].slave_id =
> +		BCM2835_DMA_DREQ_PCM_RX;
> +
> +	dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr_width =
> +		DMA_SLAVE_BUSWIDTH_4_BYTES;
> +	dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr_width =
> +		DMA_SLAVE_BUSWIDTH_4_BYTES;
> +
> +	/* TODO other burst parameters possible? */
> +	dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].maxburst = 2;
> +	dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].maxburst = 2;
> +
> +	dai->playback_dma_data = &dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK];
> +	dai->capture_dma_data = &dev->dma_data[SNDRV_PCM_STREAM_CAPTURE];
> +
> +	return 0;
> +}
> +
[...]
> +
> +static bool bcm2835_i2s_wr_rd_reg(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case BCM2835_I2S_CS_A_REG:
> +	case BCM2835_I2S_FIFO_A_REG:
> +	case BCM2835_I2S_MODE_A_REG:
> +	case BCM2835_I2S_RXC_A_REG:
> +	case BCM2835_I2S_TXC_A_REG:
> +	case BCM2835_I2S_DREQ_A_REG:
> +	case BCM2835_I2S_INTEN_A_REG:
> +	case BCM2835_I2S_INTSTC_A_REG:
> +	case BCM2835_I2S_GRAY_REG:
> +		return true;
> +	default:
> +		return false;
> +	};
> +}

I think you use the default implementation of the here, which is basically
(reg >= 0 && reg <= config->max_register). Just leave the writeable_reg and
readable_reg fields of your config NULL.

[...]
> +
> +static const struct regmap_config bcm2835_i2s_regmap_config = {
> +};

This one seems to be unused.




More information about the linux-arm-kernel mailing list