// SPDX-License-Identifier: GPL-2.0-only
#include <common.h>
#include <io.h>
#include <linux/clk.h>
#include <linux/err.h>

#include "clk.h"

struct clk_lcdif {
	struct clk_hw hw;

	struct clk *frac, *div, *gate;
	const char *parent;
};

#define to_clk_lcdif(_hw) container_of(_hw, struct clk_lcdif, hw)

static int clk_lcdif_set_rate(struct clk_hw *hw, unsigned long rate,
			    unsigned long unused)
{
	struct clk_lcdif *lcdif = to_clk_lcdif(hw);
	unsigned long frac, div, best_div = 1;
	int delta, best_delta = 0x7fffffff;
	unsigned long frate, rrate, best_frate;
	unsigned long parent_rate = clk_get_rate(clk_get_parent(lcdif->frac));

	best_frate = parent_rate;

	for (frac = 18; frac < 35; frac++) {
		frate = (parent_rate / frac) * 18;
		div = frate / rate;
		if (!div)
			div = 1;
		rrate = frate / div;
		delta = rate - rrate;
		if (abs(delta) < abs(best_delta)) {
			best_frate = frate;
			best_div = div;
			best_delta = delta;
		}
	}

	clk_set_rate(lcdif->frac, best_frate);
	best_frate = clk_get_rate(lcdif->frac);
	clk_set_rate(lcdif->div, (best_frate + best_div) / best_div);

	return 0;
}

static const struct clk_ops clk_lcdif_ops = {
	.set_rate	= clk_lcdif_set_rate,
};

struct clk *mxs_clk_lcdif(const char *name, struct clk *frac, struct clk *div,
			  struct clk *gate)
{
	struct clk_lcdif *lcdif;
	int ret;

	lcdif = xzalloc(sizeof(*lcdif));

	lcdif->parent = gate->name;
	lcdif->frac = frac;
	lcdif->div = div;
	lcdif->gate = gate;
	lcdif->hw.clk.name = name;
	lcdif->hw.clk.ops = &clk_lcdif_ops;
	lcdif->hw.clk.parent_names = &lcdif->parent;
	lcdif->hw.clk.num_parents = 1;

	ret = bclk_register(&lcdif->hw.clk);
	if (ret)
		return ERR_PTR(ret);

	return &lcdif->hw.clk;
}
