/*
   forward-si532x.c - si532x PLL driver for SoftLab-NSK Forward boards

   Copyright (C) 2017 - 2024 SoftLab-NSK <forward@softlab.tv>

   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.

   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.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "forward-pll.h"
#include "forward-si532x.h"

struct forward_si532x_mode {
	int in_freq;
	bool m;
	u8 bwsel;
	u8 n1_hs;
	u32 nc1_ls;
	u8 n2_hs;
	u32 n2_ls;
	u32 n31;
	u32 n32;
};

struct forward_si532x {
	struct forward_pll pll;
	struct forward_si532x_mode current_mode;

	u8 bus;
	u8 address;
};

const struct forward_si532x_mode forward_si532x_modes[] = {
	{ 13500000 / 200, false, 5, 9, 4, 11, 7200, 1, 400 },
	{ 13500000 / 200, true, 3, 9, 4, 11, 36000, 5, 2002 },
	{ 13500000 / 240, false, 5, 9, 4, 11, 8640, 1, 480 },
	{ 13500000 / 400, false, 5, 9, 4, 11, 14400, 1, 800 },
	{ 13500000 / 400, true, 2, 9, 4, 11, 72000, 5, 4004 },
	{ 13500000 / 480, false, 4, 9, 4, 11, 17280, 1, 960 },
	{ 13500000 / 500, false, 4, 9, 4, 11, 18000, 1, 1000 },
	{ 13500000 / 500, true, 4, 9, 4, 11, 18000, 1, 1001 },
	{ 13500000 / 400, false, 5, 9, 4, 11, 14400, 1, 800 },
	{ 13500000 / 400, true, 2, 9, 4, 11, 72000, 5, 4004 },
	{ 13500000 / 480, false, 4, 9, 4, 11, 17280, 1, 960 },
	{ 13500000 / 300, false, 5, 9, 4, 11, 10800, 1, 600 },
	{ 13500000 / 300, true, 3, 9, 4, 11, 54000, 5, 3003 },
	{ 13500000 / 360, false, 5, 9, 4, 11, 12960, 1, 720 },
	{ 13500000 / 600, false, 4, 9, 4, 11, 21600, 1, 1200 },
	{ 13500000 / 600, true, 2, 9, 4, 11, 108000, 5, 6006 },
	{ 13500000 / 720, false, 4, 9, 4, 11, 25920, 1, 1440 },
	{ 13500000 / 750, false, 4, 9, 4, 11, 27000, 1, 1500 },
	{ 13500000 / 750, true, 3, 9, 4, 11, 54000, 2, 3003 },
	{ 13500000 / 864, false, 3, 9, 4, 11, 31104, 1, 1728 },
	{ 13500000 / 858, false, 3, 9, 4, 11, 30888, 1, 1716 },
	{ 13500000 / 858, false, 3, 9, 4, 11, 30888, 1, 1716 },
	{ 6750000, false, 10, 9, 4, 11, 288, 4, 16 },
	{ 74250000, false, 10, 9, 4, 11, 288, 44, 16 },
	{ 74250000, true, 8, 9, 4, 10, 1800, 250, 91 },
	{ 148500000, false, 10, 9, 4, 11, 252, 77, 14 },
	{ 148500000, true, 8, 9, 4, 10, 1800, 500, 91 },
};

const struct forward_si532x_mode si532x_default_mode = { -1, false, 10, 9, 4, 11, 288, 44, 16 };
const struct forward_si532x_mode si532x_default_mode_m = { -1, true, 8, 9, 4, 10, 1800, 250, 91 };

const int forward_si532x_modes_count =
	sizeof(forward_si532x_modes) / sizeof(struct forward_si532x_mode);

static int si532x_write_reg(struct forward_si532x *si532x, u8 reg, u8 data)
{
	return si532x->pll.dev->i2c_xfer(si532x->pll.dev, si532x->bus, false, si532x->address, reg,
					 &data);
}

//static int si532x_read_reg(struct forward_si532x *si532x, u8 reg, u8 *data)
//{
//	return si532x->pll.dev->i2c_xfer(si532x->pll.dev, si532x->bus, true, si532x->address, reg,
//					 data);
//}

static int si532x_configure_factors(struct forward_si532x *si532x, u8 bwsel, u8 n1_hs, u32 nc1_ls,
				    u8 n2_hs, u32 n2_ls, u32 n31, u32 n32)
{
	u32 reg_value;

	if (bwsel > 15)
		return -EINVAL;

	if (n1_hs < 4 || n1_hs > 11)
		return -EINVAL;

	if (((nc1_ls != 1) && (nc1_ls % 2)) || (nc1_ls < 1) || (nc1_ls > (1 << 20)))
		return -EINVAL;

	if (n2_hs < 4 || n2_hs > 11)
		return -EINVAL;

	if (((n2_ls != 1) && (n2_ls % 2)) || (n2_ls < 1) || (n2_ls > (1 << 20)))
		return -EINVAL;

	if ((n31 < 1) || (n31 > (1 << 19)))
		return -EINVAL;

	if ((n32 < 1) || (n32 > (1 << 19)))
		return -EINVAL;

	si532x_write_reg(si532x, 0x02, (bwsel << 4) | (0x2));
	si532x_write_reg(si532x, 0x19, ((n1_hs - 4) << 5));

	reg_value = nc1_ls - 1;
	si532x_write_reg(si532x, 0x1F, (reg_value >> 16) & 0x0F);
	si532x_write_reg(si532x, 0x20, (reg_value >> 8) & 0xFF);
	si532x_write_reg(si532x, 0x21, reg_value & 0xFF);

	reg_value = n2_ls - 1;
	si532x_write_reg(si532x, 0x28, ((n2_hs - 4) << 5) | ((reg_value >> 16) & 0x0F));
	si532x_write_reg(si532x, 0x29, (reg_value >> 8) & 0xFF);
	si532x_write_reg(si532x, 0x2A, reg_value & 0xFF);

	reg_value = n31 - 1;
	si532x_write_reg(si532x, 0x2B, (reg_value >> 16) & 0x07);
	si532x_write_reg(si532x, 0x2C, (reg_value >> 8) & 0xFF);
	si532x_write_reg(si532x, 0x2D, reg_value & 0xFF);

	reg_value = n32 - 1;
	si532x_write_reg(si532x, 0x2E, (reg_value >> 16) & 0x07);
	si532x_write_reg(si532x, 0x2F, (reg_value >> 8) & 0xFF);
	si532x_write_reg(si532x, 0x30, reg_value & 0xFF);

	return 0;
}

static int si532x_load_defaults(struct forward_si532x *si532x)
{
	// PLL mode, CLK always on, no bypass
	si532x_write_reg(si532x, 0x00, 0x34);
	// CLKIN1 > CLKIN2 priority
	//si532x_write_reg(si532x, 0x01, 0xE1);
	// No output disabling during calibration, CLKIN2 selected
	si532x_write_reg(si532x, 0x03, 0x55);
	// Automatic clock selection, default hold history
	//si532x_write_reg(si532x, 0x04, 0x92);
	// LVDS outputs
	si532x_write_reg(si532x, 0x06, 0x0F);
	// External crystal as reference
	si532x_write_reg(si532x, 0x07, 0x28);
	// CLKOUT1 enabled, CLKOUT2 disabled
	si532x_write_reg(si532x, 0x0A, 0x08);
	// Frequency offset and phase error detector
	si532x_write_reg(si532x, 0x13, 0x2F);
	// Enable CK2_BAD, LOL, INT pins
	si532x_write_reg(si532x, 0x14, 0x3B);
	// Disable clock selection pins
	si532x_write_reg(si532x, 0x15, 0xFC);
	// Enable fast lock
	si532x_write_reg(si532x, 0x89, 0x01);

	return 0;
}

static struct forward_pll_mode *si532x_find_mode(struct forward_pll *pll, s64 in_pix_freq,
						 s64 in_h_freq, bool in_m, s64 out_freq, bool out_m)
{
	int i;

	if (out_freq != 148500000)
		return NULL;

	if ((in_pix_freq <= 0) && (in_h_freq <= 0)) {
		if (!in_m)
			return (struct forward_pll_mode *)&si532x_default_mode;
		else
			return (struct forward_pll_mode *)&si532x_default_mode_m;
	}

	for (i = 0; i < forward_si532x_modes_count; i++) {
		const struct forward_si532x_mode *cur = &forward_si532x_modes[i];
		if (((in_h_freq <= 0) || (cur->in_freq == in_h_freq)) &&
		    ((in_pix_freq <= 0) || (cur->in_freq == in_pix_freq)) && (cur->m == in_m)) {
			return (struct forward_pll_mode *)cur;
		}
	}
	return NULL;
}

static bool si532x_need_mode_switch(struct forward_pll *pll, struct forward_pll_mode *mode)
{
	struct forward_si532x *si532x = container_of(pll, struct forward_si532x, pll);
	struct forward_si532x_mode *m1 = (struct forward_si532x_mode *)mode;
	struct forward_si532x_mode *m2 = &si532x->current_mode;

	return (m1->in_freq != m2->in_freq) || (m1->m != m2->m) || (m1->bwsel != m2->bwsel) ||
	       (m1->n1_hs != m2->n1_hs) || (m1->nc1_ls != m2->nc1_ls) || (m1->n2_hs != m2->n2_hs) ||
	       (m1->n2_ls != m2->n2_ls) || (m1->n31 != m2->n31) || (m1->n32 != m2->n32);
}

static int si532x_switch_mode(struct forward_pll *pll, struct forward_pll_mode *mode)
{
	struct forward_si532x *si532x = container_of(pll, struct forward_si532x, pll);
	struct forward_si532x_mode *m = (struct forward_si532x_mode *)mode;

	if (mode == NULL)
		return -EINVAL;

	si532x_load_defaults(si532x);
	si532x_configure_factors(si532x, m->bwsel, m->n1_hs, m->nc1_ls, m->n2_hs, m->n2_ls, m->n31,
				 m->n32);

	si532x->current_mode = *m;

	return 0;
}

static int si532x_select_input(struct forward_pll *pll, int input, bool master, bool autoswitch)
{
	struct forward_si532x *si532x = container_of(pll, struct forward_si532x, pll);

	si532x_write_reg(si532x, 0x04, autoswitch ? 0x92 : 0x12);
	si532x_write_reg(si532x, 0x03, (input == 1) ? 0x55 : 0x15);
	si532x_write_reg(si532x, 0x01, (input == 1) ? 0xE1 : 0xE4);

	return 0;
}

static s64 si532x_tune(struct forward_pll *pll, s64 ppt)
{
	return 0;
}

static s64 si532x_get_tune(struct forward_pll *pll)
{
	return 0;
}

static void si532x_calibrate(struct forward_pll *pll)
{
	struct forward_si532x *si532x = container_of(pll, struct forward_si532x, pll);
	si532x_write_reg(si532x, 0x88, 0x40);
}

static struct forward_pll_ops si532x_ops = {
	.find_mode = si532x_find_mode,
	.need_mode_switch = si532x_need_mode_switch,
	.switch_mode = si532x_switch_mode,
	.select_input = si532x_select_input,
	.tune = si532x_tune,
	.get_tune = si532x_get_tune,
	.calibrate = si532x_calibrate,
};

int forward_si532x_init(struct forward_dev *dev, struct forward_pll **pll, u8 bus, u8 address,
			int num_inputs)
{
	struct forward_si532x *si532x = kzalloc(sizeof(struct forward_si532x), GFP_KERNEL);
	if (!si532x)
		return -ENOMEM;

	si532x->pll.dev = dev;
	si532x->pll.features = FORWARD_PLL_FEATURE_FALLBACK;
	si532x->pll.num_inputs = num_inputs;
	si532x->pll.ops = &si532x_ops;
	si532x->bus = bus;
	si532x->address = address;
	si532x->current_mode = si532x_default_mode;

	*pll = &si532x->pll;

	return 0;
}
EXPORT_SYMBOL(forward_si532x_init);

void forward_si532x_fini(struct forward_dev *dev, struct forward_pll **pll)
{
	if (pll) {
		struct forward_si532x *si532x = container_of(*pll, struct forward_si532x, pll);
		kfree(si532x);
		*pll = NULL;
	}
}
EXPORT_SYMBOL(forward_si532x_fini);
