//#define MULTICORE_DEBUG 1 #include #include #include #include #include #include #include #include #include #include #include #include extern u32 mips_hpt_frequency; int mc_soc_platform_register(struct device *dev); void mc_soc_platform_unregister(struct device *dev); static inline u32 _R(struct mc_i2s *iisc, unsigned idx) { return __raw_readl(iisc->mfbsp_regs + idx); } static inline void _W(struct mc_i2s *iisc, unsigned idx, u32 val) { __raw_writel(val, iisc->mfbsp_regs + idx); } static void mc_mfbsp_start(struct mc_i2s *iisc, struct snd_pcm_substream *substream, int cmd) { PDEBUG("Enter %s\n", __func__); PDEBUG("substream->ops @ %p, trigger @ %p\n", substream->ops, substream->ops->trigger); // Включаем MFBSP на передачу или приём if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) _W(iisc, RG_TSTART, 1); else _W(iisc, RG_RSTART, 1); PDEBUG("Exit %s\n", __func__); } static void mc_mfbsp_stop(struct mc_i2s *iisc, struct snd_pcm_substream *substream, int cmd) { PDEBUG("Enter %s\n", __func__); // Останавливаем MFBSP if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) _W(iisc, RG_TSTART, 0); else _W(iisc, RG_RSTART, 0); PDEBUG("Exit %s\n", __func__); } static int mc_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { struct mc_i2s *iisc = snd_soc_dai_get_drvdata(cpu_dai); PDEBUG("Enter %s\n", __func__); iisc->hw_fmt = fmt; PDEBUG("Exit %s\n", __func__); return 0; } static int mc_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct mc_i2s *iisc = snd_soc_dai_get_drvdata(dai); snd_pcm_format_t data_fmt = params_format(params); int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); unsigned dir = F_TD_DIR; unsigned rctr = 0; unsigned tctr = F_TEN; // Включаем передатчик unsigned rctr_rate = 0; unsigned tctr_rate = 0; PDEBUG("Enter %s\n", __func__); if (params_channels(params) != 2) { printk(KERN_WARNING "mc-i2s: unsupported number of channels\n"); return -EINVAL; } /* Set clock masters */ switch (iisc->hw_fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: /* CPU is master for CLK and for TWS */ dir |= F_TCS_DIR | F_RCS_DIR | F_TCLK_DIR | F_RCLK_DIR; break; case SND_SOC_DAIFMT_CBS_CFM: /* CPU is master only for CLK */ dir |= F_TCLK_DIR | F_RCLK_DIR; break; case SND_SOC_DAIFMT_CBM_CFS: /* CPU is master only for TWS */ dir |= F_TCS_DIR | F_RCS_DIR; break; case SND_SOC_DAIFMT_CBM_CFM: /* CPU is slave for CLK and for TWS */ break; default: printk(KERN_ERR "%s:bad master\n", __func__); return -EINVAL; } //if (playback) { switch (iisc->hw_fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: tctr |= F_TNEG | F_TDEL | F_TCSNEG | F_TMBF; break; /* case SND_SOC_DAIFMT_RIGHT_J: break; case SND_SOC_DAIFMT_LEFT_J: break; case SND_SOC_DAIFMT_DSP_A: break; case SND_SOC_DAIFMT_DSP_B: break; */ default: printk(KERN_ERR "%s:unsupported format\n", __func__); return -EINVAL; } switch (iisc->hw_fmt & SND_SOC_DAIFMT_CLOCK_MASK) { case SND_SOC_DAIFMT_CONT: tctr |= F_TCLK_CONT | F_TCS_CONT; break; case SND_SOC_DAIFMT_GATED: break; default: printk(KERN_ERR "%s:bad clock\n", __func__); return -EINVAL; } switch (iisc->hw_fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: tctr |= F_TNEG | F_TCSNEG; break; case SND_SOC_DAIFMT_IB_IF: break; case SND_SOC_DAIFMT_NB_IF: tctr |= F_TNEG; break; case SND_SOC_DAIFMT_IB_NF: tctr |= F_TCSNEG; break; default: printk(KERN_ERR "%s:bad inv_mask\n", __func__); return -EINVAL; } switch (data_fmt) { case SNDRV_PCM_FORMAT_S16: case SNDRV_PCM_FORMAT_U16: tctr |= F_TPACK | F_TSWAP | F_TWORDCNT(0) | F_TWORDLEN(15); tctr_rate = F_TCLK_RATE(mips_hpt_frequency * params->rate_den / (2 * 32 * params->rate_num)-1) | F_TCS_RATE(15); break; case SNDRV_PCM_FORMAT_S32: case SNDRV_PCM_FORMAT_U32: case SNDRV_PCM_FORMAT_FLOAT: tctr |= F_TWORDCNT(1) | F_TWORDLEN(31); tctr_rate = F_TCLK_RATE(mips_hpt_frequency * params->rate_den / (2 * 64 * params->rate_num)-1) | F_TCS_RATE(31); break; case SNDRV_PCM_FORMAT_FLOAT64: tctr |= F_TWORDCNT(3) | F_TWORDLEN(31); tctr_rate = F_TCLK_RATE(mips_hpt_frequency * params->rate_den / (2 * 128 * params->rate_num)-1) | F_TCS_RATE(63); break; default: printk(KERN_ERR "mc-i2s: unsupported PCM format\n"); return -EINVAL; } //} else { // !playback if (!playback) { rctr = F_REN | F_RCLK_CP | F_RCS_CP; switch (iisc->hw_fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: rctr |= F_RNEG | F_RDEL | F_RCSNEG | F_RMBF; break; /* case SND_SOC_DAIFMT_RIGHT_J: break; case SND_SOC_DAIFMT_LEFT_J: break; case SND_SOC_DAIFMT_DSP_A: break; case SND_SOC_DAIFMT_DSP_B: break; */ default: printk(KERN_ERR "%s:unsupported format\n", __func__); return -EINVAL; } switch (iisc->hw_fmt & SND_SOC_DAIFMT_CLOCK_MASK) { case SND_SOC_DAIFMT_CONT: rctr |= F_RCLK_CONT | F_RCS_CONT; break; case SND_SOC_DAIFMT_GATED: break; default: printk(KERN_ERR "%s:bad clock\n", __func__); return -EINVAL; } switch (iisc->hw_fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: rctr |= F_RNEG | F_RCSNEG; break; case SND_SOC_DAIFMT_IB_IF: break; case SND_SOC_DAIFMT_NB_IF: rctr |= F_RNEG; break; case SND_SOC_DAIFMT_IB_NF: rctr |= F_RCSNEG; break; default: printk(KERN_ERR "%s:bad inv_mask\n", __func__); return -EINVAL; } switch (data_fmt) { case SNDRV_PCM_FORMAT_S16: case SNDRV_PCM_FORMAT_U16: rctr |= F_RPACK | F_RSWAP | F_RWORDCNT(0) | F_RWORDLEN(15); rctr_rate = F_RCLK_RATE(mips_hpt_frequency * params->rate_den / (2 * 32 * params->rate_num)-1) | F_RCS_RATE(15); break; case SNDRV_PCM_FORMAT_S32: case SNDRV_PCM_FORMAT_U32: case SNDRV_PCM_FORMAT_FLOAT: rctr |= F_RWORDCNT(1) | F_RWORDLEN(31); rctr_rate = F_RCLK_RATE(mips_hpt_frequency * params->rate_den / (2 * 64 * params->rate_num)-1) | F_RCS_RATE(31); break; case SNDRV_PCM_FORMAT_FLOAT64: rctr |= F_RWORDCNT(3) | F_RWORDLEN(31); rctr_rate = F_RCLK_RATE(mips_hpt_frequency * params->rate_den / (2 * 128 * params->rate_num)-1) | F_RCS_RATE(63); break; default: printk(KERN_ERR "mc-i2s: unsupported PCM format\n"); return -EINVAL; } } // if (playback) PDEBUG("TCTR = %08X, TCTR_RATE = %08X, DIR = %08X\n", tctr, tctr_rate, dir); PDEBUG("RCTR = %08X, RCTR_RATE = %08X\n", rctr, rctr_rate); _W(iisc, RG_DIR, dir); _W(iisc, RG_RCTR, rctr); _W(iisc, RG_TCTR, tctr); _W(iisc, RG_RCTR_RATE, rctr_rate); _W(iisc, RG_TCTR_RATE, tctr_rate); PDEBUG("Exit %s\n", __func__); return 0; } static int mc_i2s_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { //struct mc_i2s *iisc = snd_soc_dai_get_drvdata(dai); PDEBUG("Enter %s\n", __func__); //mc_mfbsp_stop(iisc, substream, SNDRV_PCM_TRIGGER_STOP); PDEBUG("Exit %s\n", __func__); return 0; } static int mc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { struct mc_i2s *iisc = snd_soc_dai_get_drvdata(dai); int ret = 0; PDEBUG("Enter %s\n", __func__); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: mc_mfbsp_start(iisc, substream, cmd); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: mc_mfbsp_stop(iisc, substream, cmd); break; default: ret = -EINVAL; } PDEBUG("Exit %s\n", __func__); return ret; } static int mc_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct mc_i2s *iisc = snd_soc_dai_get_drvdata(dai); PDEBUG("Enter %s\n", __func__); MC_CLKEN |= MC_CLKEN_MFBSP; _W(iisc, RG_CSR, F_SPI_I2S_EN); PDEBUG("Exit %s\n", __func__); return 0; } static void mc_i2s_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct mc_i2s *iisc = snd_soc_dai_get_drvdata(dai); PDEBUG("Enter %s\n", __func__); mc_mfbsp_stop(iisc, substream, SNDRV_PCM_TRIGGER_STOP); PDEBUG("Exit %s\n", __func__); } static const struct snd_soc_dai_ops mc_i2s_dai_ops = { .startup = mc_i2s_startup, .shutdown = mc_i2s_shutdown, .prepare = mc_i2s_prepare, .trigger = mc_i2s_trigger, .hw_params = mc_i2s_hw_params, .set_fmt = mc_i2s_set_dai_fmt, }; static struct snd_soc_dai_driver mc_i2s_dai = { .playback = { .channels_min = 2, .channels_max = 2, .rates = MC_PCM_RATES, .formats = MC_PCM_FMTBITS, }, .capture = { .channels_min = 2, .channels_max = 2, .rates = MC_PCM_RATES, .formats = MC_PCM_RATES, }, .ops = &mc_i2s_dai_ops, }; static __devinit int mc_i2s_probe(struct platform_device *pdev) { struct mc_i2s *iisc; struct resource *r; int ret; PDEBUG("Enter %s\n", __func__); iisc = devm_kzalloc(&pdev->dev, sizeof(struct mc_i2s), GFP_KERNEL); if (!iisc) return -ENOMEM; r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); if (r == NULL) { ret = -ENODEV; goto err_get_res; } iisc->mfbsp_regs = devm_request_and_ioremap(&pdev->dev, r); if (!iisc->mfbsp_regs) { ret = -ENOMEM; goto err_get_res; } r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "txdma"); if (r == NULL) { ret = -ENODEV; goto err_get_res; } iisc->dma_tx_regs = devm_request_and_ioremap(&pdev->dev, r); if (!iisc->dma_tx_regs) { ret = -ENOMEM; goto err_get_res; } r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rxdma"); if (r == NULL) { ret = -ENODEV; goto err_get_res; } iisc->dma_rx_regs = devm_request_and_ioremap(&pdev->dev, r); if (!iisc->dma_rx_regs) { ret = -ENOMEM; goto err_get_res; } iisc->dev = &pdev->dev; iisc->port = pdev->id; dev_set_drvdata(&pdev->dev, iisc); ret = snd_soc_register_dai(&pdev->dev, &mc_i2s_dai); if (ret) goto err_get_res; PDEBUG("Exit %s with success\n", __func__); return 0; err_get_res: PDEBUG("Exit %s with code: %d\n", __func__, ret); return ret; } static __devexit int mc_i2s_remove(struct platform_device *pdev) { snd_soc_unregister_dai(&pdev->dev); return 0; } static struct platform_driver mc_i2s_driver = { .driver = { .name = "mc_i2s", .owner = THIS_MODULE, }, .probe = mc_i2s_probe, .remove = __devexit_p(mc_i2s_remove), }; module_platform_driver(mc_i2s_driver); MODULE_AUTHOR("Dmitry Podkhvatilin"); MODULE_DESCRIPTION("Multicore I2S driver"); MODULE_LICENSE("GPL");