/************************************************************** * bcm63xx-pcm.c -- ALSA SoC Audio Layer - Broadcom PCM-Controller driver * * Author: Kevin Li * * Copyright (c) 2018 Broadcom Corporation * All Rights Reserved * * <:label-BRCM:2018:DUAL/GPL:standard * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, as published by * the Free Software Foundation (the "GPL"). * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * A copy of the GPL is available at http://www.broadcom.com/licenses/GPLv2.php, or by * writing to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * * :> */ #include #include #include #include #include #include #include "bcm63xx-i2s.h" #define SILICON_VERIFICATION 0 #define WHISTLER_MAX_DEVICE 2 extern struct regmap *regmap_i2s; static struct resource *r_irq; static int snd_pcm_idx=0; static int capturedeviceidx=-1,playbackdeviceidx=-1; static struct snd_pcm *whistler_snd_pcm[WHISTLER_MAX_DEVICE]; enum I2S_DIR{ STREAM_PLAYBACK, STREAM_CAPTURE }; struct i2s_dma_desc { unsigned char *dma_area; /* Buffer address */ dma_addr_t dma_addr; /* DMA address to be passed to h/w */ unsigned int dma_len; /* Length of dma transfer */ }; struct bcm63xx_runtime_data { int dma_len; dma_addr_t dma_addr; dma_addr_t dma_addr_next; }; static const struct snd_pcm_hardware bcm63xx_pcm_hardware = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, .formats = SNDRV_PCM_FMTBIT_S32_LE,/* support S32 only */ .period_bytes_max = 8192 - 32, .periods_min = 1, .periods_max = PAGE_SIZE/sizeof(struct i2s_dma_desc), .buffer_bytes_max = 128 * 1024, .fifo_size = 32, }; struct i2s_dma_desc *pdma_desc[STREAM_CAPTURE+1]={ NULL, NULL }; static int bcm63xx_pcm_hw_params(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *params) { struct snd_pcm_runtime *runtime = substream->runtime; struct device *dev = substream->pcm->card->dev; snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); runtime->dma_bytes = params_buffer_bytes(params); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if(!pdma_desc[STREAM_PLAYBACK]) { pdma_desc[STREAM_PLAYBACK] = kzalloc( sizeof(struct i2s_dma_desc), GFP_NOWAIT ); if( !pdma_desc[STREAM_PLAYBACK] ) { dev_err(dev, "Allocate new descriptor memory failed\n"); return -ENOMEM; } } } else { if(!pdma_desc[STREAM_CAPTURE]) { pdma_desc[STREAM_CAPTURE] = kzalloc( sizeof(struct i2s_dma_desc), GFP_NOWAIT ); if( !pdma_desc[STREAM_CAPTURE] ) { dev_err(dev, "Allocate new descriptor memory failed\n"); return -ENOMEM; } } } return 0; } static int bcm63xx_pcm_hw_free(struct snd_pcm_substream *substream) { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if( pdma_desc[STREAM_PLAYBACK] ) { kfree(pdma_desc[STREAM_PLAYBACK]); } pdma_desc[STREAM_PLAYBACK] = NULL; } else { if( pdma_desc[STREAM_CAPTURE] ) { kfree(pdma_desc[STREAM_CAPTURE]); } pdma_desc[STREAM_CAPTURE] = NULL; } snd_pcm_set_runtime_buffer(substream, NULL); return 0; } static int bcm63xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { int ret = 0; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { switch (cmd) { case SNDRV_PCM_TRIGGER_START: regmap_update_bits(regmap_i2s, I2S_TX_IRQ_EN, I2S_DESC_OFF_INTR_EN, I2S_DESC_OFF_INTR_EN); regmap_update_bits(regmap_i2s, I2S_TX_CFG, I2S_ENABLE, I2S_ENABLE); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: regmap_update_bits(regmap_i2s, I2S_TX_IRQ_CTL, I2S_INTR_MASK, 0 ); regmap_write(regmap_i2s, I2S_TX_IRQ_EN, 0); regmap_update_bits(regmap_i2s, I2S_TX_CFG, I2S_ENABLE, 0 ); break; default: ret = -EINVAL; } } else { switch (cmd) { case SNDRV_PCM_TRIGGER_START: regmap_update_bits(regmap_i2s, I2S_RX_IRQ_EN, I2S_RX_DESC_OFF_INTR_EN_MASK, I2S_RX_DESC_OFF_INTR_EN ); regmap_update_bits(regmap_i2s, I2S_RX_CFG, I2S_RX_ENABLE_MASK, I2S_RX_ENABLE ); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: regmap_update_bits(regmap_i2s, I2S_RX_IRQ_EN, I2S_RX_DESC_OFF_INTR_EN_MASK, 0 ); regmap_update_bits(regmap_i2s, I2S_RX_CFG, I2S_RX_ENABLE_MASK, 0 ); break; default: ret = -EINVAL; } } return ret; } static int bcm63xx_pcm_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { pdma_desc[STREAM_PLAYBACK]->dma_len = snd_pcm_lib_period_bytes(substream); pdma_desc[STREAM_PLAYBACK]->dma_addr = runtime->dma_addr; pdma_desc[STREAM_PLAYBACK]->dma_area = runtime->dma_area; regmap_write(regmap_i2s, I2S_TX_DESC_IFF_LEN, pdma_desc[STREAM_PLAYBACK]->dma_len); regmap_write(regmap_i2s, I2S_TX_DESC_IFF_ADDR, pdma_desc[STREAM_PLAYBACK]->dma_addr); } else { pdma_desc[STREAM_CAPTURE]->dma_len = snd_pcm_lib_period_bytes(substream); pdma_desc[STREAM_CAPTURE]->dma_addr = runtime->dma_addr; pdma_desc[STREAM_CAPTURE]->dma_area = runtime->dma_area; regmap_write(regmap_i2s, I2S_RX_DESC_IFF_LEN, pdma_desc[STREAM_CAPTURE]->dma_len); regmap_write(regmap_i2s, I2S_RX_DESC_IFF_ADDR, pdma_desc[STREAM_CAPTURE]->dma_addr); } return 0; } static snd_pcm_uframes_t bcm63xx_pcm_pointer( struct snd_pcm_substream *substream) { struct bcm63xx_runtime_data *prtd = substream->runtime->private_data; if((void *) prtd->dma_addr_next == NULL) prtd->dma_addr_next = substream->runtime->dma_addr; snd_pcm_uframes_t x = bytes_to_frames(substream->runtime, prtd->dma_addr_next - substream->runtime->dma_addr); return x == substream->runtime->buffer_size ? 0 : x; } static int bcm63xx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma) { struct snd_pcm_runtime *runtime = substream->runtime; return dma_mmap_writecombine(substream->pcm->card->dev, vma, runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); } static int bcm63xx_pcm_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct bcm63xx_runtime_data *prtd; int ret=0; runtime->hw = bcm63xx_pcm_hardware; ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); if (ret) { goto out; } ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); if (ret) { goto out; } ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); if (ret < 0) { goto out; } ret = -ENOMEM; prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); if (!prtd) { goto out; } runtime->private_data = prtd; return 0; out: return ret; } static int bcm63xx_pcm_close(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct bcm63xx_runtime_data *prtd = runtime->private_data; if( prtd ) { kfree( prtd ); } return 0; } static struct snd_pcm_ops bcm63xx_pcm_ops = { .open = bcm63xx_pcm_open, .close = bcm63xx_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = bcm63xx_pcm_hw_params, .hw_free = bcm63xx_pcm_hw_free, .prepare = bcm63xx_pcm_prepare, .trigger = bcm63xx_pcm_trigger, .pointer = bcm63xx_pcm_pointer, .mmap = bcm63xx_pcm_mmap, }; static irqreturn_t i2s_dma_isr(int irq, void *snd_pcm_array) { int ret; unsigned int availdepth,ifflevel,offlevel,int_status,val_1,val_2; struct snd_pcm **psnd_pcm=(struct snd_pcm **)snd_pcm_array; if( capturedeviceidx >= WHISTLER_MAX_DEVICE || playbackdeviceidx >= WHISTLER_MAX_DEVICE ) { printk(KERN_ERR "snd pcm array idx exceeds max value.\n" ); return IRQ_NONE; } //---rx--- ret = regmap_read(regmap_i2s, I2S_RX_IRQ_CTL, &int_status); if( ret ) { printk(KERN_ERR "Read IRQ RX status wrong: ret=%d\n", ret); return IRQ_NONE; } if( int_status & I2S_RX_DESC_OFF_INTR ) { if( capturedeviceidx < 0 || !psnd_pcm[capturedeviceidx]->streams ) { printk(KERN_ERR "invalid playback streams.\n"); return IRQ_NONE; } struct snd_pcm_str *streams = psnd_pcm[capturedeviceidx]->streams; struct snd_pcm_substream *substream = streams[SNDRV_PCM_STREAM_CAPTURE].substream; struct snd_pcm_runtime *runtime = substream->runtime; struct bcm63xx_runtime_data *prtd = runtime->private_data; offlevel = int_status >> I2S_RX_DESC_OFF_LEVEL_SHIFT & I2S_RX_DESC_LEVEL_MASK; while( offlevel ) { regmap_read(regmap_i2s, I2S_RX_DESC_OFF_ADDR, &val_1); regmap_read(regmap_i2s, I2S_RX_DESC_OFF_LEN, &val_2); offlevel--; } prtd->dma_addr_next = val_1 + val_2; ifflevel = int_status >> I2S_RX_DESC_IFF_LEVEL_SHIFT & I2S_RX_DESC_LEVEL_MASK; availdepth = I2S_DESC_FIFO_DEPTH - ifflevel; while( availdepth ) { pdma_desc[STREAM_CAPTURE]->dma_addr += snd_pcm_lib_period_bytes(substream); //next dma addr in descr fifo pdma_desc[STREAM_CAPTURE]->dma_area += snd_pcm_lib_period_bytes(substream); if( pdma_desc[STREAM_CAPTURE]->dma_addr - runtime->dma_addr >= runtime->dma_bytes ) { pdma_desc[STREAM_CAPTURE]->dma_addr = runtime->dma_addr; pdma_desc[STREAM_CAPTURE]->dma_area = runtime->dma_area; } prtd->dma_addr = pdma_desc[STREAM_CAPTURE]->dma_addr;//push to private regmap_write(regmap_i2s, I2S_RX_DESC_IFF_LEN, snd_pcm_lib_period_bytes(substream));//pushhw regmap_write(regmap_i2s, I2S_RX_DESC_IFF_ADDR, pdma_desc[STREAM_CAPTURE]->dma_addr); availdepth--; } snd_pcm_period_elapsed(substream); } else if( int_status & I2S_RX_DESC_OFF_OVERRUN_INTR ) { regmap_update_bits(regmap_i2s, I2S_RX_IRQ_CTL, I2S_RX_DESC_OFF_OVERRUN_INTR , 0 ); } else if( int_status & I2S_RX_DESC_IFF_INTR ) { regmap_update_bits(regmap_i2s, I2S_RX_IRQ_CTL, I2S_RX_DESC_IFF_INTR , 0 ); } else { // dev_err(psnd_pcm[capturedeviceidx]->card->dev,"%s: unknown irq // detected int_status = 0x%08x\n", __FUNCTION__, int_status); } //---tx--- ret = regmap_read(regmap_i2s, I2S_TX_IRQ_CTL, &int_status); if( ret ) { printk(KERN_ERR "Read IRQ status wrong: ret=%d\n", ret); return IRQ_NONE; } if( int_status & I2S_DESC_OFF_INTR ) { if(playbackdeviceidx < 0 || !psnd_pcm[playbackdeviceidx]->streams ) { printk(KERN_ERR "invalid playback streams.\n"); return IRQ_NONE; } struct snd_pcm_str *streams = psnd_pcm[playbackdeviceidx]->streams; struct snd_pcm_substream *substream = streams[SNDRV_PCM_STREAM_PLAYBACK].substream; struct snd_pcm_runtime *runtime = substream->runtime; struct bcm63xx_runtime_data *prtd = runtime->private_data; offlevel = int_status >> I2S_DESC_OFF_LEVEL_SHIFT & I2S_DESC_LEVEL_MASK; while( offlevel ) { regmap_read(regmap_i2s, I2S_TX_DESC_OFF_ADDR, &val_1); regmap_read(regmap_i2s, I2S_TX_DESC_OFF_LEN, &val_2); prtd->dma_addr_next = val_1 + val_2; offlevel--; } ifflevel = int_status >> I2S_DESC_IFF_LEVEL_SHIFT & I2S_DESC_LEVEL_MASK; availdepth = I2S_DESC_FIFO_DEPTH - ifflevel; while( availdepth ) { pdma_desc[STREAM_PLAYBACK]->dma_addr += snd_pcm_lib_period_bytes(substream); pdma_desc[STREAM_PLAYBACK]->dma_area += snd_pcm_lib_period_bytes(substream); if( pdma_desc[STREAM_PLAYBACK]->dma_addr - runtime->dma_addr >= runtime->dma_bytes ) { pdma_desc[STREAM_PLAYBACK]->dma_addr = runtime->dma_addr; pdma_desc[STREAM_PLAYBACK]->dma_area = runtime->dma_area; } prtd->dma_addr = pdma_desc[STREAM_PLAYBACK]->dma_addr;//push->private #if SILICON_VERIFICATION #define OUTPUT_DATA_LEFT 0xFF00550A #define OUTPUT_DATA_RIGHT 0x5500FF0A unsigned int *p=(unsigned int *)pdma_desc[STREAM_PLAYBACK]->dma_area; int i; for(i=0;ihw regmap_write(regmap_i2s, I2S_TX_DESC_IFF_ADDR, pdma_desc[STREAM_PLAYBACK]->dma_addr); availdepth--; } snd_pcm_period_elapsed(substream); } else if( int_status & I2S_DESC_OFF_OVERRUN_INTR ) { regmap_update_bits(regmap_i2s, I2S_TX_IRQ_CTL, I2S_DESC_OFF_OVERRUN_INTR , 0 ); } else if( int_status & I2S_DESC_IFF_INTR ) { regmap_update_bits(regmap_i2s, I2S_TX_IRQ_CTL, I2S_DESC_IFF_INTR , 0 ); } else { //dev_err(psnd_pcm[playbackdeviceidx]->card->dev,"%s: //unknown irq detected int_status = 0x%08x\n", __FUNCTION__, int_status); } /* Clear interrupt by writing 0 */ regmap_update_bits(regmap_i2s, I2S_TX_IRQ_CTL,I2S_INTR_MASK , 0 ); return IRQ_HANDLED; } static int bcm63xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) { struct snd_pcm_substream *substream = pcm->streams[stream].substream; struct snd_dma_buffer *buf = &substream->dma_buffer; size_t size = bcm63xx_pcm_hardware.buffer_bytes_max; buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; buf->private_data = NULL; buf->area = dma_alloc_writecombine(pcm->card->dev, size,&buf->addr, GFP_KERNEL); if (!buf->area) { return -ENOMEM; } buf->bytes = size; return 0; } static int bcm63xx_soc_pcm_new(struct snd_soc_pcm_runtime *rtd) { struct snd_card *card = rtd->card->snd_card; struct snd_pcm *pcm = rtd->pcm; int ret; static int dma_irq_registered = 0; if( snd_pcm_idx >= WHISTLER_MAX_DEVICE ) { dev_err(card->dev, "pcm_new error:sound card has too many devices(%d)\n", snd_pcm_idx); ret = -EINVAL; goto out; } whistler_snd_pcm[snd_pcm_idx] = pcm; if( !r_irq ) { ret = -EINVAL; goto out; } ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); if (ret) { goto out; } if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { ret = bcm63xx_pcm_preallocate_dma_buffer(pcm,SNDRV_PCM_STREAM_PLAYBACK); if (ret) { goto out; } playbackdeviceidx = snd_pcm_idx; } if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { ret = bcm63xx_pcm_preallocate_dma_buffer(pcm,SNDRV_PCM_STREAM_CAPTURE); if (ret) { goto out; } capturedeviceidx = snd_pcm_idx; } if( !dma_irq_registered ) // only 1 pcm needs to be newed { ret = devm_request_irq( card->dev, r_irq->start , i2s_dma_isr , r_irq->flags , "i2s_dma", (void*)(whistler_snd_pcm)); if ( ret ) { dev_err(card->dev, "i2s_init: failed to request interrupt.ret=%d\n", ret); } dma_irq_registered = 1; } snd_pcm_idx++; out: return ret; } void bcm63xx_pcm_free_dma_buffers(struct snd_pcm *pcm) { struct snd_pcm_substream *substream; struct snd_dma_buffer *buf; int stream; for (stream = 0; stream < 2; stream++) { substream = pcm->streams[stream].substream; if (!substream) continue; buf = &substream->dma_buffer; if (!buf->area) continue; dma_free_writecombine(pcm->card->dev, buf->bytes, buf->area, buf->addr); buf->area = NULL; } } static struct snd_soc_platform_driver bcm63xx_soc_platform = { .ops = &bcm63xx_pcm_ops, .pcm_new = bcm63xx_soc_pcm_new, .pcm_free = bcm63xx_pcm_free_dma_buffers, }; int bcm63xx_soc_platform_probe(struct platform_device *pdev) { r_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!r_irq) { dev_err(&pdev->dev, "Unable to get register irq resource.\n"); return -ENODEV; } return devm_snd_soc_register_platform(&pdev->dev, &bcm63xx_soc_platform); } int bcm63xx_soc_platform_remove(struct platform_device *pdev) { snd_soc_unregister_platform(&pdev->dev); return 0; }