| /* arch/arm/mach-msm/qdsp5v2/audio_glue.c |
| * |
| * Copyright (C) 2010 Google, Inc. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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. |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/io.h> |
| #include <linux/clk.h> |
| #include <linux/err.h> |
| #include <linux/delay.h> |
| |
| /* the audio codec control consists of |
| * - mi2s transports (x3) |
| * - lpa (low power audio) frontend for mi2s tx |
| * - various related clocks |
| */ |
| struct msm_codec { |
| void *tx_base; |
| void *rx_base; |
| void *lpa_base; |
| |
| struct clk *rx_mclk; |
| struct clk *rx_sclk; |
| struct clk *tx_mclk; |
| struct clk *tx_sclk; |
| |
| struct clk *lpa_codec_clk; |
| struct clk *lpa_core_clk; |
| struct clk *lpa_pclk; |
| |
| struct clk *adsp_clk; |
| }; |
| |
| #define LPA_MAX_BUF_SIZE 0x30000 |
| |
| #define LPA_CONTROL 0x00000000 |
| #define LPA_CODEC 0x00000004 |
| #define LPA_HLB_MIN_ADDR 0x00000008 |
| #define LPA_HLB_MAX_ADDR 0x0000000C |
| #define LPA_HLB_WPTR 0x00000010 |
| #define LPA_HLB_VOLUME_CONTROL 0x00000014 |
| #define LPA_LLB_MIN_ADDR 0x00000018 |
| #define LPA_LLB_MAX_ADDR 0x0000001C |
| #define LPA_SB_MIN_ADDR 0x00000020 |
| #define LPA_SB_MAX_ADDR 0x00000024 |
| #define LPA_INTR_ENABLE 0x00000028 |
| #define LPA_INTR_STATUS 0x0000002C |
| #define LPA_WMARK_ASSIGN 0x00000030 |
| #define LPA_WMARK_0_LLB 0x00000034 |
| #define LPA_WMARK_1_LLB 0x00000038 |
| #define LPA_WMARK_2_LLB 0x0000003C |
| #define LPA_WMARK_3_LLB 0x00000040 |
| #define LPA_WMARK_HLB 0x00000044 |
| #define LPA_WMARK_SB 0x00000048 |
| #define LPA_RDPTR_LLB 0x0000004C |
| #define LPA_RDPTR_HLB 0x00000050 |
| #define LPA_WRPTR_SB 0x00000054 |
| #define LPA_UTC_CONFIG 0x00000058 |
| #define LPA_UTC_INTR_LOW 0x0000005C |
| #define LPA_UTC_INTR_HIGH 0x00000060 |
| #define LPA_UTC_LOW 0x00000064 |
| #define LPA_UTC_HIGH 0x00000068 |
| #define LPA_MISR 0x0000006C |
| #define LPA_STATUS 0x00000070 |
| #define LPA_ACK 0x00000074 |
| #define LPA_MEMORY_CONTROL 0x00000078 |
| #define LPA_MEMORY_STATUS 0x0000007C |
| #define LPA_MEMORY_TIME_CONTROL 0x00000080 |
| #define LPA_ACC_LV 0x00000084 |
| #define LPA_ACC_HV 0x0000008c |
| #define LPA_RESETS 0x00000090 |
| #define LPA_TESTBUS 0x00000094 |
| |
| #define LPA_AICTL 0x00000100 |
| |
| /* OBUF_CODEC */ |
| #define LPA_CODEC_LOAD 0x200000 |
| #define LPA_CODEC_INTF_EN 0x100000 |
| #define LPA_CODEC_CFG_MASK 0x0FC07F |
| |
| #define LPA_SAMPLE_RATE_8KHZ 0x000000 |
| #define LPA_SAMPLE_RATE_11P025KHZ 0x010000 |
| #define LPA_SAMPLE_RATE_16KHZ 0x020000 |
| #define LPA_SAMPLE_RATE_22P05KHZ 0x030000 |
| #define LPA_SAMPLE_RATE_32KHZ 0x040000 |
| #define LPA_SAMPLE_RATE_44P1KHZ 0x050000 |
| #define LPA_SAMPLE_RATE_48KHZ 0x060000 |
| #define LPA_SAMPLE_RATE_64KHZ 0x070000 |
| #define LPA_SAMPLE_RATE_96KHZ 0x080000 |
| |
| #define LPA_BITS_PER_CHAN_16BITS 0x000000 |
| #define LPA_BITS_PER_CHAN_24BITS 0x004000 |
| #define LPA_BITS_PER_CHAN_32BITS 0x008000 |
| #define LPA_BITS_PER_CHAN_RESERVED 0x00C000 |
| |
| #define LPA_INTF_SDAC 0x000010 |
| #define LPA_INTF_MI2S 0x000020 |
| #define LPA_INTF_WB_CODEC 0x000030 |
| |
| /* WB_CODEC & SDAC can only support 16bit mono/stereo. |
| * MI2S can bit format and number of channel |
| */ |
| #define LPA_NUM_CHAN_MONO 0x000000 |
| #define LPA_NUM_CHAN_STEREO 0x000001 |
| #define LPA_NUM_CHAN_5P1 0x000002 |
| #define LPA_NUM_CHAN_7P1 0x000003 |
| #define LPA_NUM_CHAN_4_CHANNEL 0x000004 |
| |
| /* OBUF_CONTROL */ |
| #define LPA_CONTROL_TEST_EN 0x100 |
| #define LPA_CONTROL_LLB_CLR_CMD 0x080 |
| #define LPA_CONTROL_SB_SAT_EN 0x040 |
| #define LPA_CONTROL_LLB_SAT_EN 0x020 |
| #define LPA_CONTROL_LLB_ACC_EN 0x008 |
| #define LPA_CONTROL_HLB_EN 0x004 |
| #define LPA_CONTROL_LLB_EN 0x002 |
| #define LPA_CONTROL_SB_EN 0x001 |
| |
| /* OBUF_RESET definition */ |
| #define LPA_RESETS_MISR 0x1 |
| #define LPA_RESETS_OVERALL 0x2 |
| |
| /* OBUF_STATUS definition */ |
| #define LPA_STATUS_RESET_DONE 0x80000 |
| #define LPA_STATUS_LLB_CLR 0x40000 |
| |
| /* OBUF_HLB_MIN_ADDR definition */ |
| #define LPA_HLB_MIN_ADDR_LOAD 0x40000 |
| #define LPA_HLB_MIN_ADDR_SEG_MASK 0x3e000 |
| |
| /* OBUF_HLB_MAX_ADDR definition */ |
| #define LPA_HLB_MAX_ADDR_SEG_MASK 0x3fff8 |
| |
| /* OBUF_LLB_MIN_ADDR definition */ |
| #define LPA_LLB_MIN_ADDR_LOAD 0x40000 |
| #define LPA_LLB_MIN_ADDR_SEG_BMSK 0x3e000 |
| |
| /* OBUF_LLB_MAX_ADDR definition */ |
| #define LPA_LLB_MAX_ADDR_SEG_MASK 0x3ff8 |
| #define LPA_LLB_MAX_ADDR_SEG_SHFT 0x3 |
| |
| /* OBUF_SB_MIN_ADDR definition */ |
| #define LPA_SB_MIN_ADDR_LOAD 0x4000 |
| #define LPA_SB_MIN_ADDR_SEG_BMSK 0x3e00 |
| |
| /* OBUF_SB_MAX_ADDR definition */ |
| #define LPA_SB_MAX_ADDR_SEG_BMSK 0x3ff8 |
| |
| /* OBUF_MEMORY_CONTROL definition */ |
| #define LPA_MEM_CTL_PWRUP 0xfff |
| |
| /* OBUF_INTR_ENABLE definition */ |
| #define LPA_INTR_EN 0x3 |
| |
| /* OBUF_WMARK_ASSIGN definition */ |
| #define LPA_WMARK_ASSIGN_BMSK 0xF |
| #define LPA_WMARK_ASSIGN_DONE 0xF |
| |
| /* OBUF_WMARK_n_LLB definition */ |
| #define LPA_WMARK_n_LLB_ADDR(n) (0x00000034 + 0x4 * (n)) |
| #define LPA_WMARK_CTRL_MASK 0x0c0000 |
| #define LPA_WMARK_CTRL_SHFT 0x12 |
| #define LPA_WMARK_MAP_MASK 0xf00000 |
| #define LPA_WMARK_MAP_SHFT 0x14 |
| |
| #define LPA_WMARK_CTL_DISABLED 0x0 |
| #define LPA_WMARK_CTL_NON_BLOCK 0x1 |
| #define LPA_WMARK_CTL_ZERO_INSERT 0x2 |
| #define LPA_WMARK_CTL_RESERVED 0x3 |
| |
| /* OBUF_UTC_CONFIG definition */ |
| #define LPA_UTC_CONFIG_MAP_MASK 0xf0 |
| #define LPA_UTC_CONFIG_MAP_SHFT 0x4 |
| #define LPA_UTC_CONFIG_EN 0x1 |
| #define LPA_UTC_CONFIG_NO_INTR 0xF |
| |
| /* OBUF_ACK definition */ |
| #define LPA_ACK_RESET_DONE 0x80000 |
| |
| |
| #define LPA_BUF_ID_HLB 0 /* HLB buffer */ |
| #define LPA_BUF_ID_LLB 1 /* LLB buffer */ |
| #define LPA_BUF_ID_SB 2 /* SB buffer */ |
| #define LPA_BUF_ID_UTC 3 |
| |
| |
| /* from board file in qct tree */ |
| |
| #define LPA_HLB_SIZE 0x2BFF8 |
| |
| #define LPA_ID_DSP 0 |
| #define LPA_ID_APP 2 |
| |
| #if 0 |
| #define CFG_LLB_MIN_ADDR 0x0000 |
| #define CFG_LLB_MAX_ADDR 0x3ff8 |
| #define CFG_SB_MIN_ADDR 0 |
| #define CFG_SB_MAX_ADDR 0 |
| #else |
| #define CFG_LLB_MIN_ADDR 0x0000 |
| #define CFG_LLB_MAX_ADDR 0x37f8 |
| #define CFG_SB_MIN_ADDR 0x3800 |
| #define CFG_SB_MAX_ADDR 0x3ff8 |
| #endif |
| |
| #define CFG_HLB_MIN_ADDR 0x00000 |
| #define CFG_HLB_MAX_ADDR 0x2BFF8 |
| |
| /* 7x30 MI2S Registers */ |
| |
| /* MI2S Registers are named from the MI2S block's point of view: |
| * - TX = transmit from SoC to external codec |
| * - RX = receive from external codec to SoC |
| */ |
| #define MI2S_RESET 0x00 |
| #define MI2S_MODE 0x04 |
| #define MI2S_TX_MODE 0x08 |
| #define MI2S_RX_MODE 0x0C |
| |
| #define MI2S_RESET_RESET 1 |
| |
| #define MI2S_MODE_MASTER 0x1000 |
| #define MI2S_MODE_16BIT 0x0100 |
| #define MI2S_MODE_24BIT 0x0200 |
| #define MI2S_MODE_32BIT 0x0300 |
| #define MI2S_MODE_EN_3 0x0080 |
| #define MI2S_MODE_EN_2 0x0040 |
| #define MI2S_MODE_EN_1 0x0020 |
| #define MI2S_MODE_EN_0 0x0010 |
| #define MI2S_MODE_TX_3 0x0008 |
| #define MI2S_MODE_TX_2 0x0004 |
| #define MI2S_MODE_TX_1 0x0002 |
| #define MI2S_MODE_TX_0 0x0001 |
| |
| #define MI2S_TX_MODE_2CH 0x0000 |
| #define MI2S_TX_MODE_4CH 0x0008 |
| #define MI2S_TX_MODE_6CH 0x0010 |
| #define MI2S_TX_MODE_8CH 0x0018 |
| #define MI2S_TX_MODE_STEREO 0x0004 |
| #define MI2S_TX_MODE_MONO_PACK 0x0002 /* 2 mono samples packed together */ |
| #define MI2S_TX_MODE_DMA_SYNC 0x0001 /* sync dma ack clocks */ |
| |
| #define MI2S_RX_MODE_2CH 0x0000 |
| #define MI2S_RX_MODE_4CH 0x0008 |
| #define MI2S_RX_MODE_6CH 0x0010 |
| #define MI2S_RX_MODE_8CH 0x0018 |
| #define MI2S_RX_MODE_STEREO 0x0004 |
| #define MI2S_RX_MODE_MONO_PACK 0x0002 /* 2 mono samples packed together */ |
| #define MI2S_RX_MODE_DMA_SYNC 0x0001 /* sync dma ack clocks */ |
| |
| static int mi2s_set_output(struct msm_codec *mc, |
| unsigned channels, unsigned bitdepth) |
| { |
| unsigned mode = 0; |
| unsigned tx_mode = 0; |
| |
| if (channels != 2 || bitdepth != 16) |
| return -EINVAL; |
| |
| /* TODO: support non stereo-16 (does the DSP even do that?) */ |
| |
| mode |= MI2S_MODE_MASTER; |
| mode |= MI2S_MODE_16BIT; |
| mode |= MI2S_MODE_EN_0; |
| mode |= MI2S_MODE_TX_0; |
| |
| tx_mode |= MI2S_TX_MODE_STEREO; |
| tx_mode |= MI2S_TX_MODE_2CH; |
| tx_mode |= MI2S_RX_MODE_DMA_SYNC; |
| |
| writel(1, mc->tx_base + MI2S_RESET); |
| writel(mode, mc->tx_base + MI2S_MODE); |
| writel(tx_mode, mc->tx_base + MI2S_TX_MODE); |
| writel(0, mc->tx_base + MI2S_RESET); |
| |
| return 0; |
| } |
| |
| static int mi2s_set_input(struct msm_codec *mc, |
| unsigned channels, unsigned bitdepth) |
| { |
| unsigned mode = 0; |
| unsigned rx_mode = 0; |
| |
| if (channels != 2 || bitdepth != 16) |
| return -EINVAL; |
| |
| /* TODO: support non stereo-16 */ |
| /* TODO: packed mono mode? */ |
| |
| mode |= MI2S_MODE_MASTER; |
| mode |= MI2S_MODE_16BIT; |
| mode |= MI2S_MODE_EN_0; |
| |
| rx_mode |= MI2S_RX_MODE_STEREO; |
| rx_mode |= MI2S_RX_MODE_2CH; |
| rx_mode |= MI2S_RX_MODE_DMA_SYNC; |
| |
| writel(1, mc->rx_base + MI2S_RESET); |
| writel(mode, mc->rx_base + MI2S_MODE); |
| writel(rx_mode, mc->rx_base + MI2S_RX_MODE); |
| writel(0, mc->rx_base + MI2S_RESET); |
| |
| return 0; |
| } |
| |
| void lpa_enable(struct msm_codec *mc) |
| { |
| unsigned val; |
| |
| /* for "hardware reasons" we must ensure the |
| * adsp clock is on during this reset sequence. |
| */ |
| clk_enable(mc->adsp_clk); |
| |
| /* disable codec */ |
| writel(LPA_CODEC_LOAD, mc->lpa_base + LPA_CODEC); |
| |
| writel(LPA_RESETS_MISR | LPA_RESETS_OVERALL, |
| mc->lpa_base + LPA_RESETS); |
| |
| while (!(readl(mc->lpa_base + LPA_STATUS) & LPA_STATUS_RESET_DONE)) |
| ; |
| |
| writel(LPA_ACK_RESET_DONE, mc->lpa_base + LPA_ACK); |
| |
| clk_disable(mc->adsp_clk); |
| |
| /* configure memory buffers */ |
| writel(CFG_LLB_MIN_ADDR | LPA_LLB_MIN_ADDR_LOAD, |
| mc->lpa_base + LPA_LLB_MIN_ADDR); |
| writel(CFG_LLB_MAX_ADDR, mc->lpa_base + LPA_LLB_MAX_ADDR); |
| |
| writel(CFG_SB_MIN_ADDR | LPA_SB_MIN_ADDR_LOAD, |
| mc->lpa_base + LPA_SB_MIN_ADDR); |
| writel(CFG_SB_MAX_ADDR, mc->lpa_base + LPA_SB_MAX_ADDR); |
| |
| writel(CFG_HLB_MIN_ADDR | LPA_HLB_MIN_ADDR_LOAD, |
| mc->lpa_base + LPA_HLB_MIN_ADDR); |
| writel(CFG_HLB_MAX_ADDR, mc->lpa_base + LPA_HLB_MAX_ADDR); |
| |
| writel(LPA_MEM_CTL_PWRUP, mc->lpa_base + LPA_MEMORY_CONTROL); |
| |
| |
| while (readl(mc->lpa_base + LPA_WMARK_ASSIGN) != LPA_WMARK_ASSIGN_DONE) |
| ; |
| |
| /* setup watermark ownership */ |
| writel(LPA_ID_DSP << LPA_WMARK_MAP_SHFT, |
| mc->lpa_base + LPA_WMARK_0_LLB); |
| writel(LPA_ID_DSP << LPA_WMARK_MAP_SHFT, |
| mc->lpa_base + LPA_WMARK_1_LLB); |
| writel(LPA_ID_APP << LPA_WMARK_MAP_SHFT, |
| mc->lpa_base + LPA_WMARK_2_LLB); |
| writel(LPA_ID_APP << LPA_WMARK_MAP_SHFT, |
| mc->lpa_base + LPA_WMARK_3_LLB); |
| writel(LPA_ID_DSP << LPA_WMARK_MAP_SHFT, |
| mc->lpa_base + LPA_WMARK_HLB); |
| writel(LPA_ID_DSP << LPA_WMARK_MAP_SHFT, |
| mc->lpa_base + LPA_WMARK_SB); |
| writel(0, mc->lpa_base + LPA_UTC_CONFIG); |
| |
| |
| val = readl(mc->lpa_base + LPA_CONTROL); |
| val |= LPA_CONTROL_LLB_EN; |
| val |= LPA_CONTROL_LLB_SAT_EN; |
| val |= LPA_CONTROL_SB_EN; |
| val |= LPA_CONTROL_SB_SAT_EN; |
| writel(val, mc->lpa_base + LPA_CONTROL); |
| |
| writel(1 << LPA_ID_DSP, mc->lpa_base + LPA_INTR_ENABLE); |
| } |
| |
| void lpa_start(struct msm_codec *mc) |
| { |
| unsigned val, codec; |
| |
| codec = LPA_CODEC_LOAD; |
| codec |= LPA_NUM_CHAN_STEREO; |
| codec |= LPA_SAMPLE_RATE_48KHZ; |
| codec |= LPA_BITS_PER_CHAN_16BITS; |
| codec |= LPA_INTF_WB_CODEC; |
| writel(codec, mc->lpa_base + LPA_CODEC); |
| |
| /* clear LLB */ |
| val = readl(mc->lpa_base + LPA_CONTROL); |
| writel(val | LPA_CONTROL_LLB_CLR_CMD, mc->lpa_base + LPA_CONTROL); |
| |
| while (!(readl(mc->lpa_base + LPA_STATUS) & LPA_STATUS_LLB_CLR)) |
| udelay(100); |
| |
| /* enable codec */ |
| codec |= LPA_CODEC_INTF_EN; |
| writel(codec, mc->lpa_base + LPA_CODEC); |
| } |
| |
| void lpa_disable(struct msm_codec *mc) |
| { |
| writel(LPA_CODEC_LOAD, mc->lpa_base + LPA_CODEC); |
| } |
| |
| int msm_codec_output_enable(struct msm_codec *mc) |
| { |
| unsigned rate, val; |
| |
| pr_info("msm_codec_output_enable()\n"); |
| |
| /* yes rx clks for tx codec -- the clocks |
| * are named from the opposite POV of the |
| * codec for some reason... |
| */ |
| |
| |
| /* bitrate * bits * channels * 8 */ |
| rate = 48000 * 16 * 2 * 8; |
| clk_set_rate(mc->rx_mclk, rate); |
| |
| printk("RATE %d\n", clk_get_rate(mc->rx_mclk)); |
| |
| clk_enable(mc->rx_mclk); |
| clk_enable(mc->rx_sclk); |
| |
| clk_enable(mc->lpa_pclk); |
| clk_enable(mc->lpa_codec_clk); |
| clk_enable(mc->lpa_core_clk); |
| /* LPA init */ |
| |
| lpa_enable(mc); |
| |
| /* interconnect reg -> LPA */ |
| val = readl(mc->lpa_base + LPA_AICTL); |
| writel(val | 4, mc->lpa_base + LPA_AICTL); |
| |
| /* fire up mi2s transport */ |
| mi2s_set_output(mc, 2, 16); |
| |
| lpa_start(mc); |
| |
| /* AFE enable */ |
| |
| /* ADIE enable */ |
| |
| /* AMP enable */ |
| |
| return 0; |
| } |
| |
| int msm_codec_output_disable(struct msm_codec *mc) |
| { |
| pr_info("msm_codec_output_disable()\n"); |
| /* AMP disable */ |
| /* ADIE disable */ |
| /* AFE disable */ |
| /* LPA disable */ |
| |
| clk_disable(mc->lpa_core_clk); |
| clk_disable(mc->lpa_codec_clk); |
| clk_disable(mc->lpa_pclk); |
| |
| clk_disable(mc->rx_sclk); |
| clk_disable(mc->rx_mclk); |
| |
| return 0; |
| } |
| |
| |
| static struct msm_codec the_msm_codec; |
| |
| int msm_codec_output(int enable) |
| { |
| struct msm_codec *mc = &the_msm_codec; |
| if (enable) |
| return msm_codec_output_enable(mc); |
| else |
| return msm_codec_output_disable(mc); |
| } |
| |
| /* 7x30 memory map */ |
| |
| #define PHYS_ADDR_LPA 0xA5000000 |
| #define PHYS_SIZE_LPA 0x00000800 |
| |
| #define PHYS_ADDR_MI2S_HDMI 0xAC900000 |
| #define PHYS_ADDR_MI2S_CODEC_RX 0xAC940040 |
| #define PHYS_ADDR_MI2S_CODEC_TX 0xAC980080 |
| #define PHYS_SIZE_MI2S 0x00000040 |
| |
| int msm_codec_init(void) |
| { |
| struct msm_codec *mc = &the_msm_codec; |
| |
| printk("msm_codec_init()\n"); |
| |
| mc->rx_mclk = clk_get(NULL, "mi2s_codec_rx_mclk"); |
| if (IS_ERR(mc->rx_mclk)) |
| return -ENODEV; |
| mc->rx_sclk = clk_get(NULL, "mi2s_codec_rx_sclk"); |
| if (IS_ERR(mc->rx_sclk)) |
| return -ENODEV; |
| mc->tx_mclk = clk_get(NULL, "mi2s_codec_tx_mclk"); |
| if (IS_ERR(mc->tx_mclk)) |
| return -ENODEV; |
| mc->tx_sclk = clk_get(NULL, "mi2s_codec_tx_sclk"); |
| if (IS_ERR(mc->tx_sclk)) |
| return -ENODEV; |
| mc->lpa_codec_clk = clk_get(NULL, "lpa_codec_clk"); |
| if (IS_ERR(mc->lpa_codec_clk)) |
| return -ENODEV; |
| mc->lpa_core_clk = clk_get(NULL, "lpa_core_clk"); |
| if (IS_ERR(mc->lpa_core_clk)) |
| return -ENODEV; |
| mc->lpa_pclk = clk_get(NULL, "lpa_pclk"); |
| if (IS_ERR(mc->lpa_pclk)) |
| return -ENODEV; |
| mc->adsp_clk = clk_get(NULL, "adsp_clk"); |
| if (IS_ERR(mc->adsp_clk)) |
| return -ENODEV; |
| |
| mc->lpa_base = ioremap(PHYS_ADDR_LPA, PHYS_SIZE_LPA); |
| if (!mc->lpa_base) |
| return -ENODEV; |
| mc->rx_base = ioremap(PHYS_ADDR_MI2S_CODEC_RX, PHYS_SIZE_MI2S); |
| if (!mc->rx_base) |
| return -ENODEV; |
| mc->tx_base = ioremap(PHYS_ADDR_MI2S_CODEC_TX, PHYS_SIZE_MI2S); |
| if (!mc->tx_base) |
| return -ENODEV; |
| |
| return 0; |
| } |