/* SPDX-License-Identifier: BSD-3-Clause */

#include <stdint.h>
#include <delay.h>
#include <console/console.h>
#include <soc/clock.h>
#include <soc/lcc-reg.h>
#include <device/mmio.h>

typedef struct {
	void *gcc_apcs_regs;
	void *lcc_pll0_regs;
	void *lcc_ahbix_regs;
	void *lcc_mi2s_regs;
	void *lcc_pll_regs;
} Ipq806xLccClocks;

typedef struct __packed {
	uint32_t apcs;
} Ipq806xLccGccRegs;

typedef struct __packed {
	uint32_t mode;
	uint32_t l_val;
	uint32_t m_val;
	uint32_t n_val;
	uint32_t UNUSED;
	uint32_t config;
	uint32_t status;
} Ipq806xLccPll0Regs;

typedef struct __packed {
	uint32_t ns;
	uint32_t md;
	uint32_t UNUSED;
	uint32_t status;
} Ipq806xLccAhbixRegs;

typedef struct __packed {
	uint32_t ns;
	uint32_t md;
	uint32_t status;
} Ipq806xLccMi2sRegs;

typedef struct __packed {
	uint32_t pri;
	uint32_t sec;
} Ipq806xLccPllRegs;

struct lcc_freq_tbl {
	unsigned int freq;
	unsigned int pd;
	unsigned int m;
	unsigned int n;
	unsigned int d;
};

static const struct lcc_freq_tbl lcc_mi2s_freq_tbl[] = {
	{  1024000, 4,  1,  96, 8 },
	{  1411200, 4,  2, 139, 8 },
	{  1536000, 4,  1,  64, 8 },
	{  2048000, 4,  1,  48, 8 },
	{  2116800, 4,  2,  93, 8 },
	{  2304000, 4,  2,  85, 8 },
	{  2822400, 4,  6, 209, 8 },
	{  3072000, 4,  1,  32, 8 },
	{  3175200, 4,  1,  31, 8 },
	{  4096000, 4,  1,  24, 8 },
	{  4233600, 4,  9, 209, 8 },
	{  4608000, 4,  3,  64, 8 },
	{  5644800, 4, 12, 209, 8 },
	{  6144000, 4,  1,  16, 8 },
	{  6350400, 4,  2,  31, 8 },
	{  8192000, 4,  1,  12, 8 },
	{  8467200, 4, 18, 209, 8 },
	{  9216000, 4,  3,  32, 8 },
	{ 11289600, 4, 24, 209, 8 },
	{ 12288000, 4,  1,   8, 8 },
	{ 12700800, 4, 27, 209, 8 },
	{ 13824000, 4,  9,  64, 8 },
	{ 16384000, 4,  1,   6, 8 },
	{ 16934400, 4, 41, 238, 8 },
	{ 18432000, 4,  3,  16, 8 },
	{ 22579200, 2, 24, 209, 8 },
	{ 24576000, 4,  1,   4, 8 },
	{ 27648000, 4,  9,  32, 8 },
	{ 33868800, 4, 41, 119, 8 },
	{ 36864000, 4,  3,   8, 8 },
	{ 45158400, 1, 24, 209, 8 },
	{ 49152000, 4,  1,   2, 8 },
	{ 50803200, 1, 27, 209, 8 },
	{ }
};

static int lcc_init_enable_pll0(Ipq806xLccClocks *bus)
{
	Ipq806xLccGccRegs *gcc_regs = bus->gcc_apcs_regs;
	Ipq806xLccPll0Regs *pll0_regs = bus->lcc_pll0_regs;
	Ipq806xLccPllRegs *pll_regs = bus->lcc_pll_regs;
	uint32_t regval;

	regval = 0;
	regval = 15 << LCC_PLL0_L_SHIFT & LCC_PLL0_L_MASK;
	write32(&pll0_regs->l_val, regval);

	regval = 0;
	regval = 145 << LCC_PLL0_M_SHIFT & LCC_PLL0_M_MASK;
	write32(&pll0_regs->m_val, regval);

	regval = 0;
	regval = 199 << LCC_PLL0_N_SHIFT & LCC_PLL0_N_MASK;
	write32(&pll0_regs->n_val, regval);

	regval = 0;
	regval |= LCC_PLL0_CFG_LV_MAIN_ENABLE;
	regval |= LCC_PLL0_CFG_FRAC_ENABLE;
	write32(&pll0_regs->config, regval);

	regval = 0;
	regval |= LCC_PLL_PCLK_SRC_PRI;
	write32(&pll_regs->pri, regval);

	regval = 0;
	regval |= 1 << LCC_PLL0_MODE_BIAS_CNT_SHIFT &
			LCC_PLL0_MODE_BIAS_CNT_MASK;
	regval |= 8 << LCC_PLL0_MODE_LOCK_CNT_SHIFT &
			LCC_PLL0_MODE_LOCK_CNT_MASK;
	write32(&pll0_regs->mode, regval);

	regval = read32(&gcc_regs->apcs);
	regval |= GCC_PLL_APCS_PLL4_ENABLE;
	write32(&gcc_regs->apcs, regval);

	regval = read32(&pll0_regs->mode);
	regval |= LCC_PLL0_MODE_FSM_VOTE_ENABLE;
	write32(&pll0_regs->mode, regval);

	mdelay(1);

	regval = read32(&pll0_regs->status);
	if (regval & LCC_PLL0_STAT_ACTIVE_MASK)
		return 0;

	printk(BIOS_ERR, "%s: error enabling PLL4 clock\n", __func__);
	return 1;
}

static int lcc_init_enable_ahbix(Ipq806xLccClocks *bus)
{
	Ipq806xLccAhbixRegs *ahbix_regs = bus->lcc_ahbix_regs;
	uint32_t regval;

	regval = 0;
	regval |= 1 << LCC_AHBIX_MD_M_VAL_SHIFT & LCC_AHBIX_MD_M_VAL_MASK;
	regval |= 252 << LCC_AHBIX_MD_NOT_2D_VAL_SHIFT &
			LCC_AHBIX_MD_NOT_2D_VAL_MASK;
	write32(&ahbix_regs->md, regval);

	regval = 0;
	regval |= 253 << LCC_AHBIX_NS_N_VAL_SHIFT & LCC_AHBIX_NS_N_VAL_MASK;
	regval |= LCC_AHBIX_NS_CRC_ENABLE;
	regval |= LCC_AHBIX_NS_GFM_SEL_MNC;
	regval |= LCC_AHBIX_NS_MNC_CLK_ENABLE;
	regval |= LCC_AHBIX_NS_MNC_ENABLE;
	regval |= LCC_AHBIX_NS_MNC_MODE_DUAL;
	regval |= LCC_AHBIX_NS_PREDIV_BYPASS;
	regval |= LCC_AHBIX_NS_MN_SRC_LPA;
	write32(&ahbix_regs->ns, regval);

	mdelay(1);

	regval = read32(&ahbix_regs->status);
	if (regval & LCC_AHBIX_STAT_AIF_CLK_MASK)
		return 0;

	printk(BIOS_ERR, "%s: error enabling AHBIX clock\n", __func__);
	return 1;
}

static int lcc_init_mi2s(Ipq806xLccClocks *bus, unsigned int freq)
{
	Ipq806xLccMi2sRegs *mi2s_regs = bus->lcc_mi2s_regs;
	uint32_t regval;
	uint8_t pd, m, n, d;
	unsigned int i;

	i = 0;
	while (lcc_mi2s_freq_tbl[i].freq != 0) {
		if (lcc_mi2s_freq_tbl[i].freq == freq)
			break;
		++i;
	}
	if (lcc_mi2s_freq_tbl[i].freq == 0) {
		printk(BIOS_ERR, "%s: invalid frequency given: %u\n",
		       __func__, freq);
		return 1;
	}

	switch (lcc_mi2s_freq_tbl[i].pd) {
	case 1:
		pd = LCC_MI2S_NS_PREDIV_BYPASS;
		break;
	case 2:
		pd = LCC_MI2S_NS_PREDIV_DIV2;
		break;
	case 4:
		pd = LCC_MI2S_NS_PREDIV_DIV4;
		break;
	default:
		printk(BIOS_ERR, "%s: invalid prediv found: %u\n", __func__,
				lcc_mi2s_freq_tbl[i].pd);
		return 1;
	}

	m = lcc_mi2s_freq_tbl[i].m;
	n = ~(lcc_mi2s_freq_tbl[i].n - m);
	d = ~(lcc_mi2s_freq_tbl[i].d * 2);

	regval = 0;
	regval |= m << LCC_MI2S_MD_M_VAL_SHIFT & LCC_MI2S_MD_M_VAL_MASK;
	regval |= d << LCC_MI2S_MD_NOT_2D_VAL_SHIFT &
			LCC_MI2S_MD_NOT_2D_VAL_MASK;
	write32(&mi2s_regs->md, regval);

	regval = 0;
	regval |= n << LCC_MI2S_NS_N_VAL_SHIFT & LCC_MI2S_NS_N_VAL_MASK;
	regval |= LCC_MI2S_NS_BIT_DIV_DIV4;
	regval |= LCC_MI2S_NS_MNC_CLK_ENABLE;
	regval |= LCC_MI2S_NS_MNC_ENABLE;
	regval |= LCC_MI2S_NS_MNC_MODE_DUAL;
	regval |= pd;
	regval |= LCC_MI2S_NS_MN_SRC_LPA;
	write32(&mi2s_regs->ns, regval);

	return 0;
}

static int lcc_enable_mi2s(Ipq806xLccClocks *bus)
{
	Ipq806xLccMi2sRegs *mi2s_regs = bus->lcc_mi2s_regs;
	uint32_t regval;

	regval = read32(&mi2s_regs->ns);
	regval |= LCC_MI2S_NS_OSR_CXC_ENABLE;
	regval |= LCC_MI2S_NS_BIT_CXC_ENABLE;
	write32(&mi2s_regs->ns, regval);

	udelay(10);

	regval = read32(&mi2s_regs->status);
	if (regval & LCC_MI2S_STAT_OSR_CLK_MASK)
		if (regval & LCC_MI2S_STAT_BIT_CLK_MASK)
			return 0;

	printk(BIOS_ERR, "%s: error enabling MI2S clocks: %u\n",
	       __func__, regval);
	return 1;
}

int audio_clock_config(unsigned int frequency)
{
	Ipq806xLccClocks bus = {
		.gcc_apcs_regs = (void *)(MSM_GCC_BASE + GCC_PLL_APCS_REG),
		.lcc_pll0_regs = (void *)(MSM_LPASS_LCC_BASE + LCC_PLL0_MODE_REG),
		.lcc_ahbix_regs = (void *)(MSM_LPASS_LCC_BASE + LCC_AHBIX_NS_REG),
		.lcc_mi2s_regs = (void *)(MSM_LPASS_LCC_BASE + LCC_MI2S_NS_REG),
		.lcc_pll_regs = (void *)(MSM_LPASS_LCC_BASE + LCC_PLL_PCLK_REG),
	};

	if (lcc_init_enable_pll0(&bus))
		return 1;
	if (lcc_init_enable_ahbix(&bus))
		return 1;
	if (lcc_init_mi2s(&bus, frequency))
		return 1;
	if (lcc_enable_mi2s(&bus))
		return 1;

	return 0;
}