/* * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include #include #include #include #include #include #include #include #include #include #include #include "ipq-adss.h" static void __iomem *adss_audio_local_base; void __iomem *adss_audio_spdifin_base; static struct reset_control *audio_blk_rst; static spinlock_t i2s_ctrl_lock; static spinlock_t tdm_ctrl_lock; static spinlock_t glb_mode_lock; static struct ipq_configs ipq4019_cfgs = { .txd_oe = { .reg = ADSS_GLB_AUDIO_MODE_REG, .mask = GLB_AUDIO_MODE_I2S0_TXD_OE, }, .rxd_oe = { .reg = ADSS_GLB_AUDIO_MODE_REG, .mask = GLB_AUDIO_MODE_I2S3_RXD_OE, }, .i2s0_fs_oe = { .reg = ADSS_GLB_AUDIO_MODE_REG, .mask = GLB_AUDIO_MODE_I2S0_FS_OE, }, .i2s3_fs_oe = { .reg = ADSS_GLB_AUDIO_MODE_REG, .mask = GLB_AUDIO_MODE_I2S3_FS_OE, }, .i2s_reset_val = { .reg = ADSS_GLB_I2S_RST_REG, .mask = GLB_I2S_RESET_VAL_4019, }, .spdif_enable = 1, }; static struct ipq_configs ipq8074_cfgs = { .txd_oe = { .reg = ADSS_GLB_CLK_I2S_CTRL_REG, .mask = GLB_CLK_I2S_CTRL_I2S0_TXD_OE, }, .rxd_oe = { .reg = ADSS_GLB_CLK_I2S_CTRL_REG, .mask = GLB_CLK_I2S_CTRL_I2S3_RXD_OE, }, .i2s0_fs_oe = { .reg = ADSS_GLB_CLK_I2S_CTRL_REG, .mask = GLB_CLK_I2S_CTRL_I2S0_FS_OE, }, .i2s3_fs_oe = { .reg = ADSS_GLB_CLK_I2S_CTRL_REG, .mask = GLB_CLK_I2S_CTRL_I2S3_FS_OE, }, .i2s_reset_val = { .reg = ADSS_GLB_I2S_RST_REG, .mask = GLB_I2S_RESET_VAL_8074, }, .spdif_enable = 0, }; static struct ipq_configs *ipq_cfgs; /* API to write ADSS registers */ void ipq_audio_adss_writel(uint32_t val, uint32_t offset) { if (!adss_audio_local_base) { pr_err("adss_audio_local_base not mapped\n"); return; } writel(val, adss_audio_local_base + offset); } EXPORT_SYMBOL(ipq_audio_adss_writel); /* API to read ADSS regitsers */ uint32_t ipq_audio_adss_readl(uint32_t offset) { if (adss_audio_local_base) return readl(adss_audio_local_base + offset); pr_err("adss_audio_local_base not mapped\n"); return 0; } EXPORT_SYMBOL(ipq_audio_adss_readl); /* Channel Number Per Frame for Transmitter/Receiver * Real value = val + 1 */ void ipq_glb_tdm_ctrl_ch_num(uint32_t val, uint32_t dir) { uint32_t cfg; unsigned long flags; spin_lock_irqsave(&tdm_ctrl_lock, flags); cfg = readl(adss_audio_local_base + ADSS_GLB_TDM_CTRL_REG); if (dir == PLAYBACK) { cfg &= ~(GLB_TDM_CTRL_TX_CHAN_NUM_MASK); cfg |= GLB_TDM_CTRL_TX_CHAN_NUM(val); } else if (dir == CAPTURE) { cfg &= ~(GLB_TDM_CTRL_RX_CHAN_NUM_MASK); cfg |= GLB_TDM_CTRL_RX_CHAN_NUM(val); } writel(cfg, adss_audio_local_base + ADSS_GLB_TDM_CTRL_REG); spin_unlock_irqrestore(&tdm_ctrl_lock, flags); } EXPORT_SYMBOL(ipq_glb_tdm_ctrl_ch_num); /* FSYNC Hi Duration for Transmitter/Receiver */ void ipq_glb_tdm_ctrl_sync_num(uint32_t val, uint32_t dir) { uint32_t cfg; unsigned long flags; spin_lock_irqsave(&tdm_ctrl_lock, flags); cfg = readl(adss_audio_local_base + ADSS_GLB_TDM_CTRL_REG); if (dir == PLAYBACK) { cfg &= ~(GLB_TDM_CTRL_TX_SYNC_NUM_MASK); cfg |= GLB_TDM_CTRL_TX_SYNC_NUM(val); } else if (dir == CAPTURE) { cfg &= ~(GLB_TDM_CTRL_RX_SYNC_NUM_MASK); cfg |= GLB_TDM_CTRL_RX_SYNC_NUM(val); } writel(cfg, adss_audio_local_base + ADSS_GLB_TDM_CTRL_REG); spin_unlock_irqrestore(&tdm_ctrl_lock, flags); } EXPORT_SYMBOL(ipq_glb_tdm_ctrl_sync_num); /* Serial Data Delay for transmitter/receiver */ void ipq_glb_tdm_ctrl_delay(uint32_t delay, uint32_t dir) { uint32_t cfg; unsigned long flags; spin_lock_irqsave(&tdm_ctrl_lock, flags); cfg = readl(adss_audio_local_base + ADSS_GLB_TDM_CTRL_REG); if (dir == PLAYBACK) { cfg &= ~(GLB_TDM_CTRL_TX_DELAY); if (delay) cfg |= GLB_TDM_CTRL_TX_DELAY; } else if (dir == CAPTURE) { cfg &= ~(GLB_TDM_CTRL_RX_DELAY); if (delay) cfg |= GLB_TDM_CTRL_RX_DELAY; } writel(cfg, adss_audio_local_base + ADSS_GLB_TDM_CTRL_REG); spin_unlock_irqrestore(&tdm_ctrl_lock, flags); } EXPORT_SYMBOL(ipq_glb_tdm_ctrl_delay); /* I2S Interface Enable */ static void ipq_glb_i2s_interface_en(int enable) { u32 cfg; unsigned long flags; spin_lock_irqsave(&i2s_ctrl_lock, flags); cfg = readl(adss_audio_local_base + ADSS_GLB_CHIP_CTRL_I2S_REG); cfg &= ~GLB_CHIP_CTRL_I2S_INTERFACE_EN; if (enable) cfg |= GLB_CHIP_CTRL_I2S_INTERFACE_EN; writel(cfg, adss_audio_local_base + ADSS_GLB_CHIP_CTRL_I2S_REG); spin_unlock_irqrestore(&i2s_ctrl_lock, flags); /* * As per the audio controller susbsytem after writing to * the register wait 5ms for the i2s settle down. */ mdelay(5); } EXPORT_SYMBOL(ipq_glb_i2s_interface_en); /* Enable Stereo0/Stereo1/Stereo2 channel */ void ipq_glb_stereo_ch_en(int enable, int stereo_ch) { uint32_t cfg; unsigned long flags; spin_lock_irqsave(&i2s_ctrl_lock, flags); cfg = readl(adss_audio_local_base + ADSS_GLB_CHIP_CTRL_I2S_REG); if (stereo_ch == STEREO0) { cfg &= ~(GLB_CHIP_CTRL_I2S_STEREO0_GLB_EN); cfg |= GLB_CHIP_CTRL_I2S_STEREO0_GLB_EN; } else if (stereo_ch == STEREO1) { cfg &= ~(GLB_CHIP_CTRL_I2S_STEREO1_GLB_EN); cfg |= GLB_CHIP_CTRL_I2S_STEREO1_GLB_EN; } else if (stereo_ch == STEREO2) { cfg &= ~(GLB_CHIP_CTRL_I2S_STEREO2_GLB_EN); cfg |= GLB_CHIP_CTRL_I2S_STEREO2_GLB_EN; } writel(cfg, adss_audio_local_base + ADSS_GLB_CHIP_CTRL_I2S_REG); spin_unlock_irqrestore(&i2s_ctrl_lock, flags); } EXPORT_SYMBOL(ipq_glb_stereo_ch_en); /* * I2S Module Reset */ static void ipq_glb_i2s_reset(void) { writel(ipq_cfgs->i2s_reset_val.mask, adss_audio_local_base + ipq_cfgs->i2s_reset_val.reg); mdelay(5); writel(0x0, adss_audio_local_base + ADSS_GLB_I2S_RST_REG); mdelay(5); } /* * MBOX Module Reset */ void ipq_glb_mbox_reset(void) { writel(GLB_I2S_RST_MBOX_RESET_MASK, adss_audio_local_base + ipq_cfgs->i2s_reset_val.reg); mdelay(5); writel(0x0, adss_audio_local_base + ADSS_GLB_I2S_RST_REG); mdelay(5); } EXPORT_SYMBOL(ipq_glb_mbox_reset); /* * Enable I2S/TDM and Playback/Capture Audio Mode */ void ipq_glb_audio_mode(int mode, int dir) { u32 cfg; unsigned long flags; spin_lock_irqsave(&glb_mode_lock, flags); cfg = readl(adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); if (mode == I2S && dir == PLAYBACK) { cfg &= ~GLB_AUDIO_MODE_XMIT_MASK; cfg |= GLB_AUDIO_MODE_XMIT_I2S; } else if (mode == I2S && dir == CAPTURE) { cfg &= ~GLB_AUDIO_MODE_RECV_MASK; cfg |= GLB_AUDIO_MODE_RECV_I2S; } else if (mode == TDM && dir == PLAYBACK) { cfg &= ~GLB_AUDIO_MODE_XMIT_MASK; cfg |= GLB_AUDIO_MODE_XMIT_TDM; } else if (mode == TDM && dir == CAPTURE) { cfg &= ~GLB_AUDIO_MODE_RECV_MASK; cfg |= GLB_AUDIO_MODE_RECV_TDM; } writel(cfg, adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); spin_unlock_irqrestore(&glb_mode_lock, flags); } EXPORT_SYMBOL(ipq_glb_audio_mode); /* * I2S0 TX Data Port Enable * * Todo : * Check if bits 6:4 configures only * I2S0 or other channels as well */ void ipq_glb_tx_data_port_en(u32 enable) { u32 cfg; unsigned long flags; uint32_t reg = ipq_cfgs->txd_oe.reg; uint32_t val = ipq_cfgs->txd_oe.mask; spin_lock_irqsave(&glb_mode_lock, flags); cfg = readl(adss_audio_local_base + reg); cfg &= ~val; if (enable) cfg |= val; writel(cfg, adss_audio_local_base + reg); spin_unlock_irqrestore(&glb_mode_lock, flags); } EXPORT_SYMBOL(ipq_glb_tx_data_port_en); /* * I2S3 RX Data Port Enable */ void ipq_glb_rx_data_port_en(u32 enable) { u32 cfg; unsigned long flags; uint32_t reg = ipq_cfgs->rxd_oe.reg; uint32_t val = ipq_cfgs->rxd_oe.mask; spin_lock_irqsave(&glb_mode_lock, flags); cfg = readl(adss_audio_local_base + reg); cfg &= ~val; if (enable) cfg |= val; writel(cfg, adss_audio_local_base + reg); spin_unlock_irqrestore(&glb_mode_lock, flags); } EXPORT_SYMBOL(ipq_glb_rx_data_port_en); /* * Cross 1K Boundary */ void ipq_glb_audio_mode_B1K(void) { u32 cfg; unsigned long flags; spin_lock_irqsave(&glb_mode_lock, flags); cfg = readl(adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); cfg &= ~GLB_AUDIO_MODE_B1K; cfg |= GLB_AUDIO_MODE_B1K; writel(cfg, adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); spin_unlock_irqrestore(&glb_mode_lock, flags); } EXPORT_SYMBOL(ipq_glb_audio_mode_B1K); /* * Frame Sync Port Enable for I2S0 TX */ void ipq_glb_tx_framesync_port_en(u32 enable) { u32 cfg; unsigned long flags; uint32_t reg = ipq_cfgs->i2s0_fs_oe.reg; uint32_t val = ipq_cfgs->i2s0_fs_oe.mask; spin_lock_irqsave(&glb_mode_lock, flags); cfg = readl(adss_audio_local_base + reg); cfg &= ~val; if (enable) cfg |= val; writel(cfg, adss_audio_local_base + reg); spin_unlock_irqrestore(&glb_mode_lock, flags); } EXPORT_SYMBOL(ipq_glb_tx_framesync_port_en); /* * Frame Sync Port Enable for I2S3 RX */ void ipq_glb_rx_framesync_port_en(u32 enable) { u32 cfg; unsigned long flags; uint32_t reg = ipq_cfgs->i2s3_fs_oe.reg; uint32_t val = ipq_cfgs->i2s3_fs_oe.mask; spin_lock_irqsave(&glb_mode_lock, flags); cfg = readl(adss_audio_local_base + reg); cfg &= ~val; if (enable) cfg |= val; writel(cfg, adss_audio_local_base + reg); spin_unlock_irqrestore(&glb_mode_lock, flags); } EXPORT_SYMBOL(ipq_glb_rx_framesync_port_en); void ipq_glb_clk_enable_oe(u32 dir) { u32 cfg; unsigned long flags; spin_lock_irqsave(&i2s_ctrl_lock, flags); cfg = readl(adss_audio_local_base + ADSS_GLB_CLK_I2S_CTRL_REG); if (dir == PLAYBACK) { cfg |= (GLB_CLK_I2S_CTRL_TX_BCLK_OE | GLB_CLK_I2S_CTRL_TX_MCLK_OE); } else { cfg |= (GLB_CLK_I2S_CTRL_RX_BCLK_OE | GLB_CLK_I2S_CTRL_RX_MCLK_OE); } writel(cfg, adss_audio_local_base + ADSS_GLB_CLK_I2S_CTRL_REG); spin_unlock_irqrestore(&i2s_ctrl_lock, flags); } EXPORT_SYMBOL(ipq_glb_clk_enable_oe); /* PCM RAW ADSS_GLB_PCM_RST_REG register */ void ipq_glb_pcm_rst(uint32_t enable) { uint32_t reg_val; if (enable) reg_val = GLB_PCM_RST_CTRL(1); else reg_val = GLB_PCM_RST_CTRL(0); writel(reg_val, adss_audio_local_base + ADSS_GLB_PCM_RST_REG); } EXPORT_SYMBOL(ipq_glb_pcm_rst); /* PCM RAW clock configuration */ void ipq_pcm_clk_cfg(uint32_t rate) { uint32_t reg_val; uint32_t div_1, div_2; if (rate == 8000) { div_1 = 3; div_2 = 1; } else if (rate == 16000) { div_1 = 3; div_2 = 0; } else { pr_err("%s invalid rate %d\n", __func__, rate); return; } /* set ADSS_AUDIO_PCM_CFG_RCGR_REG as required */ reg_val = readl(adss_audio_local_base + ADSS_AUDIO_PCM_CFG_RCGR_REG); reg_val |= AUDIO_PCM_CFG_RCGR_SRC_SEL(1) | AUDIO_PCM_CFG_RGCR_SRC_DIV(div_1); writel(reg_val, adss_audio_local_base + ADSS_AUDIO_PCM_CFG_RCGR_REG); /* set ADSS_AUDIO_PCM_MISC_REG as required */ reg_val = AUDIO_PCM_MISC_AUTO_SCALE_DIV(div_2); writel(reg_val, adss_audio_local_base + ADSS_AUDIO_PCM_MISC_REG); /* set ADSS_AUDIO_PCM_CMD_RCGR_REG as required */ reg_val = 3; writel(reg_val, adss_audio_local_base + ADSS_AUDIO_PCM_CMD_RCGR_REG); } EXPORT_SYMBOL(ipq_pcm_clk_cfg); void ipq_pcm_clk_enable(void) { /* write ADSS_AUDIO_PCM_CBCR_REG*/ writel(0x1, adss_audio_local_base + ADSS_AUDIO_PCM_CBCR_REG); writel(0x1, adss_audio_local_base + ADSS_AUDIO_ZSI_CBCR_REG); } EXPORT_SYMBOL(ipq_pcm_clk_enable); void ipq_spdifin_ctrl_spdif_en(uint32_t enable) { uint32_t reg_val; reg_val = readl(adss_audio_spdifin_base + ADSS_SPDIFIN_SPDIF_CTRL_REG); if (enable) reg_val |= SPDIF_CTRL_SPDIF_ENABLE; else reg_val &= ~SPDIF_CTRL_SPDIF_ENABLE; writel(reg_val, adss_audio_spdifin_base + ADSS_SPDIFIN_SPDIF_CTRL_REG); } EXPORT_SYMBOL(ipq_spdifin_ctrl_spdif_en); void ipq_spdifin_cfg(void) { uint32_t reg_val; reg_val = readl(adss_audio_spdifin_base + ADSS_SPDIFIN_SPDIF_CTRL_REG); reg_val &= ~(SPDIF_CTRL_CHANNEL_MODE | SPDIF_CTRL_VALIDITYCHECK | SPDIF_CTRL_PARITYCHECK); reg_val |= (SPDIF_CTRL_USE_FIFO_IF | SPDIF_CTRL_SFR_ENABLE | SPDIF_CTRL_FIFO_ENABLE); writel(reg_val, adss_audio_spdifin_base + ADSS_SPDIFIN_SPDIF_CTRL_REG); } EXPORT_SYMBOL(ipq_spdifin_cfg); void ipq_glb_spdif_out_en(uint32_t enable) { int32_t cfg; cfg = readl(adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); cfg &= ~(GLB_AUDIO_MODE_SPDIF_OUT_OE); if (enable) cfg |= GLB_AUDIO_MODE_SPDIF_OUT_OE; writel(cfg, adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); } EXPORT_SYMBOL(ipq_glb_spdif_out_en); static const struct of_device_id ipq_audio_adss_id_table[] = { { .compatible = "qca,ipq4019-audio-adss", .data = &ipq4019_cfgs}, { .compatible = "qca,ipq8074-audio-adss", .data = &ipq8074_cfgs}, {}, }; MODULE_DEVICE_TABLE(of, ipq_audio_adss_id_table); void ipq_audio_adss_init(void) { spin_lock_init(&i2s_ctrl_lock); spin_lock_init(&glb_mode_lock); spin_lock_init(&tdm_ctrl_lock); /* * Reset order is critical here. * First audio block should be out of reset, * followed by I2S block. * Since the audio block is brought out of * reset by hardware by default, it is not * required to be done in software explicitly. */ ipq_glb_i2s_reset(); ipq_glb_i2s_interface_en(ENABLE); ipq_glb_audio_mode_B1K(); } EXPORT_SYMBOL(ipq_audio_adss_init); static int ipq_audio_adss_probe(struct platform_device *pdev) { struct resource *res; const struct of_device_id *match; match = of_match_device(ipq_audio_adss_id_table, &pdev->dev); if (!match) return -ENODEV; ipq_cfgs = (struct ipq_configs *)match->data; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); adss_audio_local_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(adss_audio_local_base)) return PTR_ERR(adss_audio_local_base); if (ipq_cfgs->spdif_enable == 1) { res = platform_get_resource(pdev, IORESOURCE_MEM, 1); adss_audio_spdifin_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(adss_audio_spdifin_base)) return PTR_ERR(adss_audio_spdifin_base); } audio_blk_rst = devm_reset_control_get(&pdev->dev, "blk_rst"); if (IS_ERR(audio_blk_rst)) return PTR_ERR(audio_blk_rst); return 0; } static int ipq_audio_adss_remove(struct platform_device *pdev) { ipq_glb_i2s_interface_en(DISABLE); return 0; } static struct platform_driver ipq_audio_adss_driver = { .probe = ipq_audio_adss_probe, .remove = ipq_audio_adss_remove, .driver = { .name = "ipq-adss", .of_match_table = ipq_audio_adss_id_table, }, }; module_platform_driver(ipq_audio_adss_driver); MODULE_ALIAS("platform:ipq-adss"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("IPQ Audio subsytem driver");