400 lines
10 KiB
C
400 lines
10 KiB
C
/*
|
|
* (C) Copyright 2014-2019
|
|
* allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
* caiyongheng <caiyongheng@allwinnertech.com>
|
|
*
|
|
* 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 <common.h>
|
|
#include <errno.h>
|
|
#include <asm/io.h>
|
|
#include <mapmem.h>
|
|
#include <fdtdec.h>
|
|
#include <fdt_support.h>
|
|
#include <sys_config.h>
|
|
#include <asm/global_data.h>
|
|
#include <asm/gpio.h>
|
|
#include <asm/arch/dma.h>
|
|
|
|
#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;
|
|
}
|