/* * Copyright (C) 2012 BLX Corporation * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gsc3280-i2s.h" #include "gsc3280-pcm.h" struct gsc3280_i2s { struct resource *mem; void __iomem *base; dma_addr_t phys_base; struct clk *clk_aic; struct clk *clk_i2s; struct gsc3280_pcm_config pcm_config_playback; struct gsc3280_pcm_config pcm_config_capture; }; static inline uint32_t gsc3280_i2s_read(const struct gsc3280_i2s *i2s, unsigned int reg) { return readl(i2s->base + reg); } static inline void gsc3280_i2s_write(const struct gsc3280_i2s *i2s, uint32_t value, unsigned int reg) { writel(value, i2s->base + reg); } static int gsc3280_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct gsc3280_i2s *i2s = snd_soc_dai_get_drvdata(dai); if (dai->active) return 0; /* reset_tx_fifos */ gsc3280_i2s_write(i2s, 0x1,TXFFR); /* reset_rx_fifos */ gsc3280_i2s_write(i2s, 0x1,RXFFR); /* flush_channel_rx_fifo */ gsc3280_i2s_write(i2s, 0x1,RFF0); /* flush_channel_tx_fifo */ gsc3280_i2s_write(i2s, 0x1,TFF0); /* i2s_channel_rx_disable */ gsc3280_i2s_write(i2s, 0, RER0); /* i2s_channel_tx_disable */ gsc3280_i2s_write(i2s, 0, TER0); /* i2s_channel_tx_enable */ gsc3280_i2s_write(i2s, 1, TER0); /* i2s_channel_rx_enable */ gsc3280_i2s_write(i2s, 1, RER0); /* enable_trans_block */ gsc3280_i2s_write(i2s, 1, ITER); /* enable_i2s_clock */ /*slave or master */ gsc3280_i2s_write(i2s, 1, CER); gsc3280_i2s_write(i2s, 0, IMR0); gsc3280_i2s_write(i2s, 2|1, DCTRL); gsc3280_i2s_write(i2s, 1, 0x1cc); //enable dma gsc3280_i2s_write(i2s, 1, 0x1c4); //enable dma gsc3280_i2s_write(i2s, 4, TFCR0); gsc3280_i2s_write(i2s, 4, RFCR0); #if 0 //gsc3280_i2s_write(i2s, 1, ITER); //enable_trans_block //gsc3280_i2s_write(i2s, 1, TER0); //i2s_channel_tx_enable #endif return 0; } static void gsc3280_i2s_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct gsc3280_i2s *i2s = snd_soc_dai_get_drvdata(dai); /* disable_trans_block */ gsc3280_i2s_write(i2s, 0, ITER); /* disable_recev_block */ gsc3280_i2s_write(i2s, 0, IRER); /* i2s_channel_rx_disable */ gsc3280_i2s_write(i2s, 0, RER0); /* i2s_channel_tx_disable */ gsc3280_i2s_write(i2s, 0, TER0); /* disable_i2s_clock */ /*slave or master */ gsc3280_i2s_write(i2s, 0, CER); /* disable_i2s */ gsc3280_i2s_write(i2s, 0, IER); } static int gsc3280_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { struct gsc3280_i2s *i2s = snd_soc_dai_get_drvdata(dai); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK){ gsc3280_i2s_write(i2s, 1, IER); gsc3280_i2s_write(i2s, 1, ITER); /* enable_trans_block */ gsc3280_i2s_write(i2s, 1, TER0); #if 0 gsc3280_i2s_write(i2s, 2, 0x200); gsc3280_i2s_write(i2s, 1, 0x1cc); #endif }else{ gsc3280_i2s_write(i2s, 1, IER); /*enable_recev_block */ gsc3280_i2s_write(i2s, 1, IRER); gsc3280_i2s_write(i2s, 1, RER0); #if 0 gsc3280_i2s_write(i2s, 1|2, 0x200); gsc3280_i2s_write(i2s, 1, 0x1c4); #endif } break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK){ gsc3280_i2s_write(i2s, 0, IER); /* disable_trans_block */ gsc3280_i2s_write(i2s, 0, ITER); /* i2s_channel_tx_disable */ gsc3280_i2s_write(i2s, 0, TER0); }else{ gsc3280_i2s_write(i2s, 0, IER); /* disable_recev_block */ gsc3280_i2s_write(i2s, 0, IRER); /* i2s_channel_rx_disable */ gsc3280_i2s_write(i2s, 0, RER0); } break; default: return -EINVAL; } return 0; } static int i2s_set_sample_cycle(struct gsc3280_i2s *i2s, int size) { int value; switch(size){ case 16: value = gsc3280_i2s_read(i2s, CCR); value = (value & ~0x18) | CCR_CLK_16; gsc3280_i2s_write(i2s, value, CCR); break; case 24: value = gsc3280_i2s_read(i2s, CCR); value = (value & ~0x18) | CCR_CLK_24; gsc3280_i2s_write(i2s, value, CCR); break; case 32: value = gsc3280_i2s_read(i2s, CCR); value = (value & ~0x18) | CCR_CLK_32; gsc3280_i2s_write(i2s, value, CCR); break; default: printk(KERN_ERR"i2s can't support %d size data cycle!", size); return -1; } return 0; } static int i2s_set_rx_format(struct gsc3280_i2s *i2s, int format) { int regvalue; switch(format){ case 0: regvalue = WLEN_0; break; case 12: regvalue = WLEN_12; break; case 16: regvalue = WLEN_16; break; case 20: regvalue = WLEN_20; break; case 24: regvalue = WLEN_24; break; case 32: regvalue = WLEN_32; break; default: printk(KERN_ERR"i2s can't support this format %d\n", format); return -1; } gsc3280_i2s_write(i2s, regvalue, RCR0); return 0; } static int i2s_set_tx_format(struct gsc3280_i2s *i2s, int format) { int regvalue; switch(format){ case 0: regvalue = WLEN_0; break; case 12: regvalue = WLEN_12; break; case 16: regvalue = WLEN_16; break; case 20: regvalue = WLEN_20; break; case 24: regvalue = WLEN_24; break; case 32: regvalue = WLEN_32; break; default: printk(KERN_ERR"i2s can't support this format %d\n", format); return -1; } gsc3280_i2s_write(i2s, regvalue, TCR0); return 0; } static int gsc3280_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct gsc3280_i2s *i2s = snd_soc_dai_get_drvdata(dai); i2s_set_sample_cycle(i2s, DATA_CYCLE_16); i2s_set_tx_format(i2s, DATA_FORMAT_16); i2s_set_rx_format(i2s, DATA_FORMAT_16); return 0; } static int gsc3280_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct gsc3280_i2s *i2s = snd_soc_dai_get_drvdata(dai); //enum gsc3280_dma_width dma_width; int dma_width; struct gsc3280_pcm_config *pcm_config; unsigned int sample_size; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8: case SNDRV_PCM_FORMAT_U8: sample_size = 0; //dma_width = DMA_WIDTH_8BIT; dma_width = 8; break; case SNDRV_PCM_FORMAT_S16: case SNDRV_PCM_FORMAT_U16: sample_size = 1; //dma_width = DMA_WIDTH_16BIT; dma_width = 16; break; default: return -EINVAL; } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { pcm_config = &i2s->pcm_config_playback; #if 0 pcm_config->dma_config.dst_width = dma_width; #endif } else { pcm_config = &i2s->pcm_config_capture; #if 0 pcm_config->dma_config.src_width = dma_width; #endif } snd_soc_dai_set_dma_data(dai, substream, pcm_config); return 0; } static int gsc3280_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { int ret = 0; return ret; } static int gsc3280_i2s_suspend(struct snd_soc_dai *dai) { struct gsc3280_i2s *i2s = snd_soc_dai_get_drvdata(dai); if (dai->active) { /* disable_trans_bloca */ gsc3280_i2s_write(i2s, 0, ITER); /* disable_recev_block */ gsc3280_i2s_write(i2s, 0, IRER); /* i2s_channel_rx_disable */ gsc3280_i2s_write(i2s, 0, RER0); /* i2s_channel_tx_disable */ gsc3280_i2s_write(i2s, 0, TER0); /* disable_i2s_clock */ /*slave or master */ gsc3280_i2s_write(i2s, 0, CER); /* disable_i2s */ gsc3280_i2s_write(i2s, 0, IER); } return 0; } static int gsc3280_i2s_resume(struct snd_soc_dai *dai) { struct gsc3280_i2s *i2s = snd_soc_dai_get_drvdata(dai); if (dai->active) { /* enable_i2s */ gsc3280_i2s_write(i2s, 1, IER); /* enable_trans_block */ gsc3280_i2s_write(i2s, 1, ITER); /* enable_recev_block */ gsc3280_i2s_write(i2s, 1, IRER); /* enable_i2s_clock */ /*slave or master */ gsc3280_i2s_write(i2s, 1, CER); /* i2s_channel_rx_enable(0); */ gsc3280_i2s_write(i2s, 1, RER0); /* i2s_channel_tx_enable(0); */ gsc3280_i2s_write(i2s, 1, TER0); } return 0; } static void gsc3280_i2s_init_pcm_config(struct gsc3280_i2s *i2s) { i2s->pcm_config_playback.fifo_addr = i2s->phys_base + TXDMA; i2s->pcm_config_capture.fifo_addr = i2s->phys_base + RXDMA; } static int gsc3280_i2s_dai_probe(struct snd_soc_dai *dai) { /* uint32_t conf; */ struct gsc3280_i2s *i2s = snd_soc_dai_get_drvdata(dai); //writel(1,0xbc10aff0); //48K gsc3280_i2s_init_pcm_config(i2s); /* reset_tx_fifos */ gsc3280_i2s_write(i2s, 0x1,TXFFR); /* reset_rx_fifos */ gsc3280_i2s_write(i2s, 0x1,RXFFR); /* flush_channel_rx_fifo */ gsc3280_i2s_write(i2s, 0x1,RFF0); /* flush_channel_tx_fifo */ gsc3280_i2s_write(i2s, 0x1,TFF0); gsc3280_i2s_write(i2s, 6, RFCR0); /* set_channel_tx_fifo_depth */ gsc3280_i2s_write(i2s, 2, TFCR0); /* i2s_channel_tx_disable(0); */ gsc3280_i2s_write(i2s, 0, TER0); /* i2s_channel_rx_disable(0); */ gsc3280_i2s_write(i2s, 0, RER0); return 0; } static int gsc3280_i2s_dai_remove(struct snd_soc_dai *dai) { #if 0 struct gsc3280_i2s *i2s = snd_soc_dai_get_drvdata(dai); clk_disable(i2s->clk_aic); #endif return 0; } static struct snd_soc_dai_ops gsc3280_i2s_dai_ops = { .startup = gsc3280_i2s_startup, .shutdown = gsc3280_i2s_shutdown, .trigger = gsc3280_i2s_trigger, .hw_params = gsc3280_i2s_hw_params, .set_fmt = gsc3280_i2s_set_fmt, .set_sysclk = gsc3280_i2s_set_sysclk, }; #define JZ4740_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \ SNDRV_PCM_FMTBIT_S16_LE) static struct snd_soc_dai_driver gsc3280_i2s_dai = { .probe = gsc3280_i2s_dai_probe, .remove = gsc3280_i2s_dai_remove, .playback = { .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = JZ4740_I2S_FMTS, }, .capture = { .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = JZ4740_I2S_FMTS, }, .symmetric_rates = 1, .ops = &gsc3280_i2s_dai_ops, .suspend = gsc3280_i2s_suspend, .resume = gsc3280_i2s_resume, }; static int __devinit gsc3280_i2s_dev_probe(struct platform_device *pdev) { struct gsc3280_i2s *i2s; int ret; i2s = kzalloc(sizeof(*i2s), GFP_KERNEL); if (!i2s) return -ENOMEM; i2s->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!i2s->mem) { ret = -ENOENT; goto err_free; } i2s->mem = request_mem_region(i2s->mem->start, resource_size(i2s->mem), pdev->name); if (!i2s->mem) { ret = -EBUSY; goto err_free; } i2s->base = ioremap_nocache(i2s->mem->start, resource_size(i2s->mem)); if (!i2s->base) { ret = -EBUSY; goto err_release_mem_region; } i2s->phys_base = i2s->mem->start; i2s->clk_i2s = clk_get(&pdev->dev, "i2s"); if(!i2s->clk_i2s){ ret = -ENODEV; goto err_release_mem_region; } clk_enable(i2s->clk_i2s); platform_set_drvdata(pdev, i2s); ret = snd_soc_register_dai(&pdev->dev, &gsc3280_i2s_dai); if (ret) { dev_err(&pdev->dev, "Failed to register DAI\n"); goto err_clk_put_i2s; } return 0; err_clk_put_i2s: clk_put(i2s->clk_i2s); err_release_mem_region: release_mem_region(i2s->mem->start, resource_size(i2s->mem)); err_free: iounmap(i2s->base); kfree(i2s); return ret; } static int __devexit gsc3280_i2s_dev_remove(struct platform_device *pdev) { struct gsc3280_i2s *i2s = platform_get_drvdata(pdev); snd_soc_unregister_dai(&pdev->dev); #if 0 clk_put(i2s->clk_i2s); clk_put(i2s->clk_aic); #endif iounmap(i2s->base); release_mem_region(i2s->mem->start, resource_size(i2s->mem)); platform_set_drvdata(pdev, NULL); kfree(i2s); return 0; } static struct platform_driver gsc3280_i2s_driver = { .probe = gsc3280_i2s_dev_probe, .remove = __devexit_p(gsc3280_i2s_dev_remove), .driver = { .name = "gsc3280-i2s", .owner = THIS_MODULE, }, }; static int __init gsc3280_i2s_init(void) { return platform_driver_register(&gsc3280_i2s_driver); } module_init(gsc3280_i2s_init); static void __exit gsc3280_i2s_exit(void) { platform_driver_unregister(&gsc3280_i2s_driver); } module_exit(gsc3280_i2s_exit); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:gsc3280-i2s");