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

   Copyright (C) 2017 - 2023 Konstantin Oblaukhov <oblaukhov@sl.iae.nsk.su>

   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-lmk05028.h"
#include <linux/delay.h>

struct forward_lmk05028 {
	struct forward_pll pll;

	u8 bus;
	u8 adpll;
};

static void lmk_write_reg_40(struct forward_pll *pll, u16 reg, u64 value)
{
	struct forward_lmk05028 *lmk05028 = container_of(pll, struct forward_lmk05028, pll);
	uint8_t cmd[7];
	uint8_t result[7];
	int i;

	cmd[0] = (0 << 7) | ((reg >> 8) & 0x7F);
	cmd[1] = reg & 0xFF;
	for (i = 0; i < 5; i++)
		cmd[i + 2] = (value >> ((4 - i) * 8));

	pll->dev->spi_xfer(pll->dev, lmk05028->bus, cmd, 56, result, 56);
}

static struct forward_pll_mode *lmk05028_find_mode(struct forward_pll *pll, s64 in_pix_freq,
						   s64 in_h_freq, bool in_m, s64 out_freq,
						   bool out_m)
{
	return NULL;
}

static int lmk05028_switch_mode(struct forward_pll *pll, struct forward_pll_mode *mode)
{
	return -EOPNOTSUPP;
}

static int lmk05028_select_input(struct forward_pll *pll, int input, bool master, bool autoswitch)
{
	return -EOPNOTSUPP;
}

static s64 lmk05028_tune(struct forward_pll *pll, s64 ppt)
{
	struct forward_lmk05028 *lmk05028 = container_of(pll, struct forward_lmk05028, pll);

	// F = 24000000 * (2 * (24 + div / 0x10000000000)) = 1188000000 * (1 + ppm / 1000000000000)
	u64 div = 824633720832ULL + (6643777536LL * ppt) / 244140625LL;

	lmk_write_reg_40(pll, lmk05028->adpll ? 574 : 435, div);

	return ppt;
}

static s64 lmk05028_get_tune(struct forward_pll *pll)
{
	return 0; // TODO
}

static void lmk05028_calibrate(struct forward_pll *pll)
{
}

static struct forward_pll_ops lmk05028_ops = {
	.find_mode = lmk05028_find_mode,
	.switch_mode = lmk05028_switch_mode,
	.select_input = lmk05028_select_input,
	.tune = lmk05028_tune,
	.get_tune = lmk05028_get_tune,
	.calibrate = lmk05028_calibrate,
};

int forward_lmk05028_init(struct forward_dev *dev, struct forward_pll **pll, u8 bus, u8 adpll,
			  int num_inputs)
{
	struct forward_lmk05028 *lmk05028 = kzalloc(sizeof(struct forward_lmk05028), GFP_KERNEL);

	if (!lmk05028)
		return -ENOMEM;

	lmk05028->pll.dev = dev;
	lmk05028->pll.features = FORWARD_PLL_FEATURE_FALLBACK | FORWARD_PLL_FEATURE_MASTER |
				 FORWARD_PLL_FEATURE_TUNE;
	lmk05028->pll.num_inputs = num_inputs;
	lmk05028->pll.ops = &lmk05028_ops;
	lmk05028->bus = bus;
	lmk05028->adpll = adpll;

	*pll = &lmk05028->pll;

	return 0;
}
EXPORT_SYMBOL(forward_lmk05028_init);

void forward_lmk05028_fini(struct forward_dev *dev, struct forward_pll **pll)
{
	if (pll) {
		struct forward_lmk05028 *lmk05028 =
			container_of(*pll, struct forward_lmk05028, pll);
		kfree(lmk05028);
		*pll = NULL;
	}
}
EXPORT_SYMBOL(forward_lmk05028_fini);
