289 lines
6.5 KiB
C
289 lines
6.5 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
//#include <asm/arch/io.h>
|
|
#include <linux/io.h>
|
|
#include <asm/amlogic/arch/register.h>
|
|
#include <common.h>
|
|
#include <asm/amlogic/arch/cpu.h>
|
|
#include <amlogic/cpu_id.h>
|
|
#include <clk.h>
|
|
#include <amlogic/pm.h>
|
|
#include <dm.h>
|
|
#include <i2s.h>
|
|
#include <log.h>
|
|
#include <sound.h>
|
|
#include <asm/io.h>
|
|
#include <linux/bitops.h>
|
|
#include <dm/device-internal.h>
|
|
#include <dm/lists.h>
|
|
#include <dm/uclass-internal.h>
|
|
#include <linux/err.h>
|
|
|
|
struct aiu_958_channel_status {
|
|
unsigned short chstat0_l;
|
|
unsigned short chstat1_l;
|
|
unsigned short chstat0_r;
|
|
unsigned short chstat1_r;
|
|
};
|
|
|
|
struct spdif {
|
|
struct clk clk_parenet;
|
|
int sys_clk_rate;
|
|
struct dev_pm_ops *pm_ops;
|
|
};
|
|
|
|
|
|
static int audiobus_read(unsigned long reg, unsigned int *val)
|
|
{
|
|
*val = readl(reg);
|
|
return 0;
|
|
}
|
|
|
|
static void hhi_write(unsigned long reg, unsigned int val)
|
|
{
|
|
writel(val, reg);
|
|
}
|
|
|
|
static void audiobus_write(unsigned long reg, unsigned int val)
|
|
{
|
|
writel(val, reg);
|
|
}
|
|
|
|
static int audiobus_update_bits(unsigned int reg, unsigned int mask,
|
|
unsigned int value)
|
|
{
|
|
bool change;
|
|
unsigned int old, new;
|
|
|
|
audiobus_read(reg, &old);
|
|
|
|
new = (old & ~mask) | (value & mask);
|
|
change = old != new;
|
|
if (change)
|
|
audiobus_write(reg, new);
|
|
|
|
return change;
|
|
}
|
|
|
|
static void aml_set_audio_spdif_clk(void)
|
|
{
|
|
/*mpll0: 491520000 */
|
|
// TODO
|
|
if (0)
|
|
hhi_write(0, 0x543);
|
|
//hhi_write(ANACTRL_MPLL_CTRL0, 0x543);
|
|
//hhi_write(ANACTRL_MPLL_CTRL1, 0xC040046B);
|
|
//hhi_write(ANACTRL_MPLL_CTRL2, 0x40000033);
|
|
|
|
/* audio clk gate */
|
|
audiobus_write(EE_AUDIO_CLK_GATE_EN0,
|
|
1 << 17 /* spdifout */
|
|
| 1 << 9 /* frddra */
|
|
| 1 << 0 /* ddr_arb */);
|
|
|
|
/*hifipll 1179.648M for s4*/
|
|
if (get_cpu_id().family_id == MESON_CPU_MAJOR_ID_S4)
|
|
audiobus_write(EE_AUDIO_CLK_SPDIFOUT_CTRL,
|
|
1 << 31 /* enable */
|
|
| 4 << 24 /* hifipll0 */
|
|
| 0xbf << 0 /* dividor */);
|
|
else
|
|
/*hifipll 491.52M*/
|
|
audiobus_write(EE_AUDIO_CLK_SPDIFOUT_CTRL,
|
|
1 << 31 /* enable */
|
|
| 4 << 24 /* hifipll0 */
|
|
| 0x4f << 0 /* dividor */);
|
|
}
|
|
|
|
static void aml_spdif_fifo_ctrl(void)
|
|
{
|
|
/* reg mute val */
|
|
audiobus_write(EE_AUDIO_SPDIFOUT_MUTE_VAL, 0x0);
|
|
|
|
/* mask lane0 L/R, lsb first, insert data from 31bits */
|
|
audiobus_update_bits(EE_AUDIO_SPDIFOUT_CTRL0,
|
|
0x1<<22 | 0x1<<21 | 0x1 << 20 | 0x1 << 19 | 0xffff << 0,
|
|
0x1<<22 | 0x1<<21 | 0x0 << 20 | 0x0 << 19 | 0x3 << 0);
|
|
|
|
/* split 64bits ddr data to 2 sample */
|
|
/* msb position of data */
|
|
/* frddr_A */
|
|
audiobus_update_bits(EE_AUDIO_SPDIFOUT_CTRL1,
|
|
0x3 << 24 | 0x1f << 8 | 0x7 << 4,
|
|
0 << 24 | (32 - 1) << 8 | 3 << 4);
|
|
|
|
audiobus_write(EE_AUDIO_SPDIFOUT_SWAP, 0x1 << 4);
|
|
}
|
|
|
|
static void spdifout_set_pcm_chsts(struct aiu_958_channel_status *set)
|
|
{
|
|
if (!set) {
|
|
printf("%s(), error set NULL point\n", __func__);
|
|
return;
|
|
}
|
|
|
|
audiobus_write(EE_AUDIO_SPDIFOUT_CHSTS0,
|
|
set->chstat1_l << 16 | set->chstat0_l);
|
|
audiobus_write(EE_AUDIO_SPDIFOUT_CHSTS6,
|
|
set->chstat1_r << 16 | set->chstat0_r);
|
|
}
|
|
|
|
static void frddr_init_without_mngr(void)
|
|
{
|
|
unsigned int start_addr, end_addr, int_addr;
|
|
static int buf[256];
|
|
|
|
memset(buf, 0x0, sizeof(buf));
|
|
start_addr = virt_to_phys(buf);
|
|
end_addr = start_addr + sizeof(buf) - 1;
|
|
int_addr = sizeof(buf) / 64;
|
|
|
|
/*some chip not use arb, and not define*/
|
|
#ifdef EE_AUDIO_ARB_CTRL0
|
|
audiobus_write(EE_AUDIO_ARB_CTRL0, 0x800000ff);
|
|
#endif
|
|
audiobus_write(EE_AUDIO_FRDDR_A_START_ADDR, start_addr);
|
|
|
|
audiobus_write(EE_AUDIO_FRDDR_A_INIT_ADDR, start_addr);
|
|
|
|
audiobus_write(EE_AUDIO_FRDDR_A_FINISH_ADDR, end_addr);
|
|
|
|
audiobus_write(EE_AUDIO_FRDDR_A_INT_ADDR, int_addr);
|
|
|
|
audiobus_write(EE_AUDIO_FRDDR_A_CTRL1,
|
|
(0x40 - 1) << 24 | (0x20 - 1) << 16 | 2 << 8 | 0 << 0);
|
|
|
|
audiobus_write(EE_AUDIO_FRDDR_A_CTRL0,
|
|
1 << 31
|
|
| 0 << 24
|
|
| 4 << 16
|
|
);
|
|
|
|
/* src0 enable, src0 select spdifout */
|
|
audiobus_write(EE_AUDIO_FRDDR_A_CTRL2, 1 << 4 | 3 << 0);
|
|
}
|
|
|
|
static void aml_spdif_enable(void)
|
|
{
|
|
/* reset */
|
|
audiobus_update_bits(EE_AUDIO_SPDIFOUT_CTRL0, 3 << 28, 0);
|
|
audiobus_update_bits(EE_AUDIO_SPDIFOUT_CTRL0, 1 << 29, 1 << 29);
|
|
audiobus_update_bits(EE_AUDIO_SPDIFOUT_CTRL0, 1 << 28, 1 << 28);
|
|
/* enable */
|
|
audiobus_update_bits(EE_AUDIO_SPDIFOUT_CTRL0, 1 << 31, 1 << 31);
|
|
|
|
/* tohdmitx enable */
|
|
audiobus_write(EE_AUDIO_TOHDMITX_CTRL0,
|
|
1 << 3 /* spdif_clk_cap_inv */
|
|
| 0 << 2 /* spdif_clk_inv */
|
|
| 0 << 1 /* spdif_out */
|
|
| 0 << 0 /* spdif_clk */
|
|
);
|
|
audiobus_update_bits(EE_AUDIO_TOHDMITX_CTRL0,
|
|
0x1 << 30, 0x1 << 30);
|
|
|
|
audiobus_update_bits(EE_AUDIO_TOHDMITX_CTRL0,
|
|
0x1 << 31, 0x1 << 31);
|
|
}
|
|
|
|
static void aml_spdif_play(void)
|
|
{
|
|
struct aiu_958_channel_status chstat;
|
|
|
|
chstat.chstat0_l = 0x0100;
|
|
chstat.chstat0_r = 0x0100;
|
|
chstat.chstat1_l = 0X200;
|
|
chstat.chstat1_r = 0X200;
|
|
|
|
aml_spdif_fifo_ctrl();
|
|
spdifout_set_pcm_chsts(&chstat);
|
|
frddr_init_without_mngr();
|
|
aml_spdif_enable();
|
|
}
|
|
|
|
static int aml_audio_init(void)
|
|
{
|
|
printf("%s\n", __func__);
|
|
|
|
aml_set_audio_spdif_clk();
|
|
aml_spdif_play();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int aml_spdif_suspend(void *pm_ops)
|
|
{
|
|
struct dev_pm_ops *pm = (struct dev_pm_ops *)pm_ops;
|
|
struct spdif *spdif_p = pm->private_data;
|
|
|
|
printf("%s: %s\n", __func__, pm->name);
|
|
clk_disable(&spdif_p->clk_parenet);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_spdif_resume(void *pm_ops)
|
|
{
|
|
struct dev_pm_ops *pm = (struct dev_pm_ops *)pm_ops;
|
|
struct spdif *spdif_p = pm->private_data;
|
|
|
|
clk_enable(&spdif_p->clk_parenet);
|
|
|
|
printf("%s: %s\n", __func__, pm->name);
|
|
return 0;
|
|
}
|
|
|
|
static int aml_spdif_poweroff(void *pm_ops)
|
|
{
|
|
struct dev_pm_ops *pm = (struct dev_pm_ops *)pm_ops;
|
|
struct spdif *spdif_p = pm->private_data;
|
|
|
|
clk_disable(&spdif_p->clk_parenet);
|
|
return 0;
|
|
}
|
|
|
|
static int amlogic_spdif_probe(struct udevice *dev)
|
|
{
|
|
struct spdif *spdif_p = dev_get_priv(dev);
|
|
|
|
clk_enable(&spdif_p->clk_parenet);
|
|
aml_audio_init();
|
|
spdif_p->pm_ops = dev_register_pm("spdif_ops",
|
|
&aml_spdif_suspend, &aml_spdif_resume, &aml_spdif_poweroff, 0, spdif_p);
|
|
return 0;
|
|
}
|
|
|
|
static int spdif_of_to_plat(struct udevice *dev)
|
|
{
|
|
struct spdif *spdif_p = dev_get_priv(dev);
|
|
|
|
clk_get_by_name(dev, "clk_srcpll", &spdif_p->clk_parenet);
|
|
return 0;
|
|
}
|
|
|
|
static int bind_audio(struct udevice *dev)
|
|
{
|
|
device_probe(dev);
|
|
return 0;
|
|
}
|
|
|
|
static const struct udevice_id meson_audio_ids[] = {
|
|
{ .compatible = "amlogic,spdif" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(audio) = {
|
|
.name = "audio",
|
|
.id = UCLASS_SOUND,
|
|
.bind = bind_audio,
|
|
.of_match = meson_audio_ids,
|
|
.probe = amlogic_spdif_probe,
|
|
.of_to_plat = spdif_of_to_plat,
|
|
.priv_auto = sizeof(struct spdif),
|
|
};
|
|
|