/* * (C) Copyright 2014-2019 * allwinner Technology Co., Ltd. * caiyongheng * * some simple description for this code * * 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 "sun8iw18-codec.h" #include "sunxi_rw_func.h" DECLARE_GLOBAL_DATA_PTR; struct sunxi_codec { void __iomem *addr_clkbase; void __iomem *addr_dbase; void __iomem *addr_abase; user_gpio_set_t spk_cfg; u32 lineout_vol; u32 spk_gpio; u32 pa_ctl_level; }; static struct sunxi_codec g_codec; #if 0 static int sunxi_codec_read(struct sunxi_codec *codec, u32 reg) { if (reg >= SUNXI_PR_CFG) { /* Analog part */ reg = reg - SUNXI_PR_CFG; return read_prcm_wvalue(reg, codec->addr_abase); } else { return codec_rdreg(codec->addr_dbase + reg); } return 0; } #endif static int sunxi_codec_write(struct sunxi_codec *codec, u32 reg, u32 val) { if (reg >= SUNXI_PR_CFG) { /* Analog part */ reg = reg - SUNXI_PR_CFG; write_prcm_wvalue(reg, val, codec->addr_abase); } else { codec_wrreg(codec->addr_dbase + reg, val); } return 0; } static int sunxi_codec_update_bits(struct sunxi_codec *codec, u32 reg, u32 mask, u32 val) { if (reg >= SUNXI_PR_CFG) { /* Analog part */ reg = reg - SUNXI_PR_CFG; codec_wrreg_prcm_bits(codec->addr_abase, reg, mask, val); } else { codec_wrreg_bits(codec->addr_dbase + reg, mask, val); } return 0; } static const struct sample_rate sample_rate_conv[] = { {44100, 0}, {48000, 0}, {8000, 5}, {32000, 1}, {22050, 2}, {24000, 2}, {16000, 3}, {11025, 4}, {12000, 4}, {192000, 6}, {96000, 7}, }; /* * sample bits * sample rate * channels * */ int sunxi_codec_hw_params(u32 sb, u32 sr, u32 ch) { int i; struct sunxi_codec *codec = &g_codec; /* * Audio codec * SUNXI_DAC_FIFO_CTL 0xF0 * SUNXI_DAC_DPC 0x00 * */ /* set playback sample resolution, only little endian*/ switch (sb) { case 16: sunxi_codec_update_bits(codec, SUNXI_DAC_FIFO_CTL, (3 << FIFO_MODE), (3 << FIFO_MODE)); sunxi_codec_update_bits(codec, SUNXI_DAC_FIFO_CTL, (1 << TX_SAMPLE_BITS), (0 << TX_SAMPLE_BITS)); break; case 24: case 32: sunxi_codec_update_bits(codec, SUNXI_DAC_FIFO_CTL, (3 << FIFO_MODE), (0 << FIFO_MODE)); sunxi_codec_update_bits(codec, SUNXI_DAC_FIFO_CTL, (1 << TX_SAMPLE_BITS), (1 << TX_SAMPLE_BITS)); break; default: return -1; } /* set playback sample rate */ for (i = 0; i < ARRAY_SIZE(sample_rate_conv); i++) { if (sample_rate_conv[i].samplerate == sr) { sunxi_codec_update_bits(codec, SUNXI_DAC_FIFO_CTL, (0x7 << DAC_FS), (sample_rate_conv[i].rate_bit << DAC_FS)); } } /* set playback channels */ if (ch == 1) { sunxi_codec_update_bits(codec, SUNXI_DAC_FIFO_CTL, (1 << DAC_MONO_EN), (1 << DAC_MONO_EN)); } else if (ch == 2) { sunxi_codec_update_bits(codec, SUNXI_DAC_FIFO_CTL, (1 << DAC_MONO_EN), (0 << DAC_MONO_EN)); } else { printf("channel:%u is not support!\n", ch); } return 0; } static int sunxi_codec_init(struct sunxi_codec *codec) { void __iomem *cfg_reg; volatile u32 tmp_val = 0; /* * CCMU register * BASE 0x0300 1000 * PLL_AUDIO_CTRL_REG 0x78 * PLL_AUDIO_PAT0_CTRL_REG 0x178 * PLL_AUDIO_PAT1_CTRL_REG 0x17C * PLL_AUDIO_BIAS_REG 0x378 * AUDIO_CODEC_1X_CLK_REG 0xA50 * AUDIO_CODEC_BGR_REG 0xA5C */ cfg_reg = codec->addr_clkbase + 0x78; tmp_val = 0xA90B1701; writel(tmp_val, cfg_reg); /*printf("reg:0x%08x, before: 0x%x, after:0x%x\n", cfg_reg, val, tmp_val);*/ cfg_reg = codec->addr_clkbase + 0x178; tmp_val = 0xC00126E9; writel(tmp_val, cfg_reg); cfg_reg = codec->addr_clkbase + 0x17C; tmp_val = 0x0; writel(tmp_val, cfg_reg); cfg_reg = codec->addr_clkbase + 0x378; tmp_val = 0x00030000; writel(tmp_val, cfg_reg); cfg_reg = codec->addr_clkbase + 0xA5C; tmp_val = 0x00010000; writel(tmp_val, cfg_reg); tmp_val = 0x00010001; writel(tmp_val, cfg_reg); cfg_reg = codec->addr_clkbase + 0xA50; tmp_val = 0x80000000; writel(tmp_val, cfg_reg); /* * Audio codec * SUNXI_DAC_DAP_CTL 0xF0 * SUNXI_DAC_DPC 0x00 * */ /* set digital volume 0x0, 0*-1.16 = 0dB */ sunxi_codec_update_bits(codec, SUNXI_DAC_DPC, (0x3f << DVOL), (0x0 << DVOL)); /* set MIC2,3 Boost AMP disable */ sunxi_codec_write(codec, SUNXI_MIC2_MIC3_CTL, 0x44); /* set LADC Mixer mute */ sunxi_codec_write(codec, SUNXI_LADCMIX_SRC, 0x0); /* set LINEOUT volume, such as 0x19, -(31-0x19)*1.5 = -9dB */ sunxi_codec_update_bits(codec, SUNXI_LINEOUT_CTL1, (0x1f << LINEOUT_VOL), (codec->lineout_vol << LINEOUT_VOL)); /* set Right Lineout source */ sunxi_codec_update_bits(codec, SUNXI_LINEOUT_CTL0, (0x1 << LINEOUTR_SRC), (0x1 << LINEOUTR_SRC)); return 0; } #if 1 fdt_addr_t new_fdtdec_get_addr_size_fixed(const void *blob, int node, const char *prop_name, int index, int na, int ns, fdt_size_t *sizep, bool translate) { const fdt32_t *prop, *prop_end; const fdt32_t *prop_addr, *prop_size, *prop_after_size; int len; fdt_addr_t addr; prop = fdt_getprop(blob, node, prop_name, &len); if (!prop) { debug("(not found)\n"); return FDT_ADDR_T_NONE; } prop_end = prop + (len / sizeof(*prop)); prop_addr = prop + (index * (na + ns)); prop_size = prop_addr + na; prop_after_size = prop_size + ns; if (prop_after_size > prop_end) { debug("(not enough data: expected >= %d cells, got %d cells)\n", (u32)(prop_after_size - prop), ((u32)(prop_end - prop))); return FDT_ADDR_T_NONE; } #if CONFIG_IS_ENABLED(OF_TRANSLATE) if (translate) addr = fdt_translate_address(blob, node, prop_addr); else #endif addr = fdtdec_get_number(prop_addr, na); if (sizep) { *sizep = fdtdec_get_number(prop_size, ns); debug("addr=%08llx, size=%llx\n", (unsigned long long)addr, (unsigned long long)*sizep); } else { debug("addr=%08llx\n", (unsigned long long)addr); } return addr; } #else #define new_fdtdec_get_addr_size_fixed fdtdec_get_addr_size_fixed #endif static int get_sunxi_codec_values(struct sunxi_codec *codec, const void *blob) { int node, ret; fdt_addr_t addr = 0; fdt_size_t size = 0; node = fdt_node_offset_by_compatible(blob, 0, "allwinner,clk-init"); if (node < 0) { printf("unable to find allwinner,clk-init node in device tree.\n"); return node; } addr = new_fdtdec_get_addr_size_fixed(blob, node, "reg", 0, 2, 2, &size, false); codec->addr_clkbase = map_sysmem(addr, size); node = fdt_node_offset_by_compatible(blob, 0, "allwinner,sunxi-internal-codec"); if (node < 0) { printf("unable to find allwinner,sunxi-internal-codec node in device tree.\n"); return node; } if (!fdtdec_get_is_enabled(blob, node)) { printf("sunxi-internal-codec disabled in device tree.\n"); return -1; } /*regs = fdtdec_get_addr();*/ addr = new_fdtdec_get_addr_size_fixed(blob, node, "reg", 0, 2, 2, &size, false); codec->addr_dbase = map_sysmem(addr, size); addr = new_fdtdec_get_addr_size_fixed(blob, node, "reg", 1, 2, 2, &size, false); codec->addr_abase = map_sysmem(addr, size); /*printf("dbase:0x%x, abase:0x%x\n", codec->addr_dbase, codec->addr_abase);*/ ret = fdt_getprop_u32(blob, node, "lineout_vol", &codec->lineout_vol); if (ret < 0) { printf("lineout_vol not set. default 0x1a\n"); codec->lineout_vol = 0x1a; } ret = fdt_getprop_u32(blob, node, "pa_ctl_level", &codec->pa_ctl_level); if (ret < 0) { printf("pa_ctl_level not set. default 1\n"); codec->pa_ctl_level = 1; } ret = fdt_get_one_gpio_by_offset(node, "gpio-spk", &codec->spk_cfg); if (ret < 0 || codec->spk_cfg.port == 0) { printf("parser gpio-spk failed, ret:%d, port:%u\n", ret, codec->spk_cfg.port); return -1; } codec->spk_gpio = (codec->spk_cfg.port - 1) * 32 + codec->spk_cfg.port_num; gpio_request(codec->spk_gpio, NULL); #if 0 printf("pa_ctl_level:%u, port:%u, num:%u, gpio:%u\n", codec->pa_ctl_level, codec->spk_cfg.port, codec->spk_cfg.port_num, codec->spk_gpio); #endif return 0; } int sunxi_codec_playback_prepare(void) { struct sunxi_codec *codec = &g_codec; /* FIFO flush, clear pending, clear sample counter */ sunxi_codec_update_bits(codec, SUNXI_DAC_FIFO_CTL, (1 << FIFO_FLUSH), (1 << FIFO_FLUSH)); sunxi_codec_write(codec, SUNXI_DAC_FIFO_STA, (1 << TXE_INT | 1 << TXU_INT | 1 << TXO_INT)); sunxi_codec_write(codec, SUNXI_DAC_CNT, 0); /* enable FIFO empty DRQ */ sunxi_codec_update_bits(codec, SUNXI_DAC_FIFO_CTL, (1 << DAC_DRQ_EN), (1 << DAC_DRQ_EN)); /* Enable DAC analog left channel */ sunxi_codec_update_bits(codec, SUNXI_MIX_DAC_CTL, (0x1 << DACALEN), (0x1 << DACALEN)); /* enable DAC digital part */ sunxi_codec_update_bits(codec, SUNXI_DAC_DPC, (0x1 << EN_DAC), (0x1 << EN_DAC)); /* enable left and right LINEOUT */ sunxi_codec_update_bits(codec, SUNXI_LINEOUT_CTL0, (0x1 << LINEOUTL_EN), (0x1 << LINEOUTL_EN)); sunxi_codec_update_bits(codec, SUNXI_LINEOUT_CTL0, (0x1 << LINEOUTR_EN), (0x1 << LINEOUTR_EN)); /* increase delay time, if Pop occured */ mdelay(10); gpio_direction_output(codec->spk_gpio, codec->pa_ctl_level); return 0; } int sunxi_codec_probe(void) { int ret; struct sunxi_codec *codec = &g_codec; const void *blob = gd->fdt_blob; if (get_sunxi_codec_values(codec, blob) < 0) { debug("FDT Codec values failed\n"); return -1; } ret = sunxi_codec_init(codec); return ret; } void sunxi_codec_fill_txfifo(u32 *data) { struct sunxi_codec *codec = &g_codec; sunxi_codec_write(codec, SUNXI_DAC_TXDATA, *data); } int sunxi_codec_playback_start(ulong handle, u32 *srcBuf, u32 cnt) { int ret = 0; struct sunxi_codec *codec = &g_codec; flush_cache((unsigned long)srcBuf, cnt); #if 0 printf("start dma: form 0x%x to 0x%x total 0x%x(%u)byte\n", srcBuf, codec->addr_dbase + SUNXI_DAC_TXDATA, cnt, cnt); #endif /*ret = sunxi_dma_start(handle, (uint)srcBuf, codec->addr_dbase + SUNXI_DAC_TXDATA);*/ ret = sunxi_dma_start(handle, (uint)srcBuf, (uint)(codec->addr_dbase + SUNXI_DAC_TXDATA), cnt); return ret; }