Orange Pi5 kernel

Deprecated Linux kernel 5.10.110 for OrangePi 5/5B/5+ boards

3 Commits   0 Branches   0 Tags
/*
 * Copyright (c) 2017 Rockchip Electronics Co. Ltd.
 *
 * 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.
 */

#include "clk-regmap.h"

#define PLLCON_OFFSET(x)	(x * 4)

#define PLL_BYPASS(x)			HIWORD_UPDATE(x, 15, 15)
#define PLL_BYPASS_MASK			BIT(15)
#define PLL_BYPASS_SHIFT		15
#define PLL_POSTDIV1(x)			HIWORD_UPDATE(x, 14, 12)
#define PLL_POSTDIV1_MASK		GENMASK(14, 12)
#define PLL_POSTDIV1_SHIFT		12
#define PLL_FBDIV(x)			HIWORD_UPDATE(x, 11, 0)
#define PLL_FBDIV_MASK			GENMASK(11, 0)
#define PLL_FBDIV_SHIFT			0

#define PLL_POSTDIV2(x)			HIWORD_UPDATE(x, 8, 6)
#define PLL_POSTDIV2_MASK		GENMASK(8, 6)
#define PLL_POSTDIV2_SHIFT		6
#define PLL_REFDIV(x)			HIWORD_UPDATE(x, 5, 0)
#define PLL_REFDIV_MASK			GENMASK(5, 0)
#define PLL_REFDIV_SHIFT		0

#define PLL_FOUT_4PHASE_CLK_POWER_DOWN	BIT(27)
#define PLL_FOUT_VCO_CLK_POWER_DOWN	BIT(26)
#define PLL_FOUT_POST_DIV_POWER_DOWN	BIT(25)
#define PLL_DAC_POWER_DOWN		BIT(24)
#define PLL_FRAC(x)			UPDATE(x, 23, 0)
#define PLL_FRAC_MASK			GENMASK(23, 0)
#define PLL_FRAC_SHIFT			0

#define MIN_FREF_RATE		10000000UL
#define MAX_FREF_RATE		800000000UL
#define MIN_FREFDIV_RATE	1000000UL
#define MAX_FREFDIV_RATE	40000000UL
#define MIN_FVCO_RATE		400000000UL
#define MAX_FVCO_RATE		1600000000UL
#define MIN_FOUTPOSTDIV_RATE	8000000UL
#define MAX_FOUTPOSTDIV_RATE	1600000000UL

struct clk_regmap_pll {
	struct clk_hw hw;
	struct device *dev;
	struct regmap *regmap;
	unsigned int reg;
	u8 pd_shift;
	u8 dsmpd_shift;
	u8 lock_shift;
};

#define to_clk_regmap_pll(_hw)	container_of(_hw, struct clk_regmap_pll, hw)

static unsigned long
clk_regmap_pll_recalc_rate(struct clk_hw *hw, unsigned long prate)
{
	struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
	unsigned int postdiv1, fbdiv, dsmpd, postdiv2, refdiv, frac, bypass;
	unsigned int con0, con1, con2;
	u64 foutvco, foutpostdiv;

	regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(0), &con0);
	regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(1), &con1);
	regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(2), &con2);

	bypass = (con0 & PLL_BYPASS_MASK) >> PLL_BYPASS_SHIFT;
	postdiv1 = (con0 & PLL_POSTDIV1_MASK) >> PLL_POSTDIV1_SHIFT;
	fbdiv = (con0 & PLL_FBDIV_MASK) >> PLL_FBDIV_SHIFT;
	dsmpd = (con1 & BIT(pll->dsmpd_shift)) >> pll->dsmpd_shift;
	postdiv2 = (con1 & PLL_POSTDIV2_MASK) >> PLL_POSTDIV2_SHIFT;
	refdiv = (con1 & PLL_REFDIV_MASK) >> PLL_REFDIV_SHIFT;
	frac = (con2 & PLL_FRAC_MASK) >> PLL_FRAC_SHIFT;

	if (bypass)
		return prate;

	foutvco = prate * fbdiv;
	do_div(foutvco, refdiv);

	if (!dsmpd) {
		u64 frac_rate = (u64)prate * frac;

		do_div(frac_rate, refdiv);
		foutvco += frac_rate >> 24;
	}

	foutpostdiv = foutvco;
	do_div(foutpostdiv, postdiv1);
	do_div(foutpostdiv, postdiv2);

	return foutpostdiv;
}

static long clk_pll_round_rate(unsigned long fin, unsigned long fout,
			       u8 *refdiv, u16 *fbdiv,
			       u8 *postdiv1, u8 *postdiv2,
			       u32 *frac, u8 *dsmpd, u8 *bypass)
{
	u8 min_refdiv, max_refdiv, postdiv;
	u8 _dsmpd = 1, _postdiv1 = 0, _postdiv2 = 0, _refdiv = 0;
	u16 _fbdiv = 0;
	u32 _frac = 0;
	u64 foutvco, foutpostdiv;

	/*
	 * FREF : 10MHz ~ 800MHz
	 * FREFDIV : 1MHz ~ 40MHz
	 * FOUTVCO : 400MHz ~ 1.6GHz
	 * FOUTPOSTDIV : 8MHz ~ 1.6GHz
	 */
	if (fin < MIN_FREF_RATE || fin > MAX_FREF_RATE)
		return -EINVAL;

	if (fout < MIN_FOUTPOSTDIV_RATE || fout > MAX_FOUTPOSTDIV_RATE)
		return -EINVAL;

	if (fin == fout) {
		if (bypass)
			*bypass = true;
		return fin;
	}

	min_refdiv = DIV_ROUND_UP(fin, MAX_FREFDIV_RATE);
	max_refdiv = fin / MIN_FREFDIV_RATE;
	if (max_refdiv > 64)
		max_refdiv = 64;

	if (fout < MIN_FVCO_RATE) {
		postdiv = DIV_ROUND_UP_ULL(MIN_FVCO_RATE, fout);

		for (_postdiv2 = 1; _postdiv2 < 8; _postdiv2++) {
			if (postdiv % _postdiv2)
				continue;

			_postdiv1 = postdiv / _postdiv2;

			if (_postdiv1 > 0 && _postdiv1 < 8)
				break;
		}

		if (_postdiv2 > 7)
			return -EINVAL;

		fout *= _postdiv1 * _postdiv2;
	} else {
		_postdiv1 = 1;
		_postdiv2 = 1;
	}

	for (_refdiv = min_refdiv; _refdiv <= max_refdiv; _refdiv++) {
		u64 tmp, frac_rate;

		if (fin % _refdiv)
			continue;

		tmp = (u64)fout * _refdiv;
		do_div(tmp, fin);
		_fbdiv = tmp;
		if (_fbdiv < 10 || _fbdiv > 1600)
			continue;

		tmp = (u64)_fbdiv * fin;
		do_div(tmp, _refdiv);
		if (fout < MIN_FVCO_RATE || fout > MAX_FVCO_RATE)
			continue;

		frac_rate = fout - tmp;

		if (frac_rate) {
			tmp = (u64)frac_rate * _refdiv;
			tmp <<= 24;
			do_div(tmp, fin);
			_frac = tmp;
			_dsmpd = 0;
		}

		break;
	}

	/*
	 * If DSMPD = 1 (DSM is disabled, "integer mode")
	 * FOUTVCO = FREF / REFDIV * FBDIV
	 * FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2
	 *
	 * If DSMPD = 0 (DSM is enabled, "fractional mode")
	 * FOUTVCO = FREF / REFDIV * (FBDIV + FRAC / 2^24)
	 * FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2
	 */
	foutvco = fin * _fbdiv;
	do_div(foutvco, _refdiv);

	if (!_dsmpd) {
		u64 frac_rate = (u64)fin * _frac;

		do_div(frac_rate, _refdiv);
		foutvco += frac_rate >> 24;
	}

	foutpostdiv = foutvco;
	do_div(foutpostdiv, _postdiv1);
	do_div(foutpostdiv, _postdiv2);

	if (refdiv)
		*refdiv = _refdiv;
	if (fbdiv)
		*fbdiv = _fbdiv;
	if (postdiv1)
		*postdiv1 = _postdiv1;
	if (postdiv2)
		*postdiv2 = _postdiv2;
	if (frac)
		*frac = _frac;
	if (dsmpd)
		*dsmpd = _dsmpd;
	if (bypass)
		*bypass = false;

	return (unsigned long)foutpostdiv;
}

static long
clk_regmap_pll_round_rate(struct clk_hw *hw, unsigned long drate,
			  unsigned long *prate)
{
	struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
	long rate;

	rate = clk_pll_round_rate(*prate, drate, NULL, NULL, NULL, NULL, NULL,
				  NULL, NULL);

	dev_dbg(pll->dev, "%s: prate=%ld, drate=%ld, rate=%ld\n",
		clk_hw_get_name(hw), *prate, drate, rate);

	return rate;
}

static int
clk_regmap_pll_set_rate(struct clk_hw *hw, unsigned long drate,
			unsigned long prate)
{
	struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
	u8 refdiv, postdiv1, postdiv2, dsmpd, bypass;
	u16 fbdiv;
	u32 frac;
	long rate;

	rate = clk_pll_round_rate(prate, drate, &refdiv, &fbdiv, &postdiv1,
				  &postdiv2, &frac, &dsmpd, &bypass);
	if (rate < 0)
		return rate;

	dev_dbg(pll->dev, "%s: rate=%ld, bypass=%d\n",
		clk_hw_get_name(hw), drate, bypass);

	if (bypass) {
		regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(0),
			     PLL_BYPASS(1));
	} else {
		regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(0),
			     PLL_BYPASS(0) | PLL_POSTDIV1(postdiv1) |
			     PLL_FBDIV(fbdiv));
		regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1),
			     HIWORD_UPDATE(dsmpd, pll->dsmpd_shift, pll->dsmpd_shift) |
			     PLL_POSTDIV2(postdiv2) | PLL_REFDIV(refdiv));
		regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(2),
			     PLL_FRAC(frac));

		dev_dbg(pll->dev, "refdiv=%d, fbdiv=%d, frac=%d\n",
			refdiv, fbdiv, frac);
		dev_dbg(pll->dev, "postdiv1=%d, postdiv2=%d\n",
			postdiv1, postdiv2);
	}

	return 0;
}

static int clk_regmap_pll_prepare(struct clk_hw *hw)
{
	struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
	u32 v;
	int ret;

	regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1),
		     HIWORD_UPDATE(0, pll->pd_shift, pll->pd_shift));

	ret = regmap_read_poll_timeout(pll->regmap,
				       pll->reg + PLLCON_OFFSET(1),
				       v, v & BIT(pll->lock_shift), 50, 50000);
	if (ret)
		dev_err(pll->dev, "%s is not lock\n", clk_hw_get_name(hw));

	return 0;
}

static void clk_regmap_pll_unprepare(struct clk_hw *hw)
{
	struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);

	regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1),
		     HIWORD_UPDATE(1, pll->pd_shift, pll->pd_shift));
}

static int clk_regmap_pll_is_prepared(struct clk_hw *hw)
{
	struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
	unsigned int con1;

	regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(1), &con1);

	return !(con1 & BIT(pll->pd_shift));
}

static const struct clk_ops clk_regmap_pll_ops = {
	.recalc_rate = clk_regmap_pll_recalc_rate,
	.round_rate = clk_regmap_pll_round_rate,
	.set_rate = clk_regmap_pll_set_rate,
	.prepare = clk_regmap_pll_prepare,
	.unprepare = clk_regmap_pll_unprepare,
	.is_prepared = clk_regmap_pll_is_prepared,
};

struct clk *
devm_clk_regmap_register_pll(struct device *dev, const char *name,
			     const char *parent_name,
			     struct regmap *regmap, u32 reg, u8 pd_shift,
			     u8 dsmpd_shift, u8 lock_shift,
			     unsigned long flags)
{
	struct clk_regmap_pll *pll;
	struct clk_init_data init = {};

	pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
	if (!pll)
		return ERR_PTR(-ENOMEM);

	init.name = name;
	init.ops = &clk_regmap_pll_ops;
	init.flags = flags;
	init.parent_names = (parent_name ? &parent_name : NULL);
	init.num_parents = (parent_name ? 1 : 0);

	pll->dev = dev;
	pll->regmap = regmap;
	pll->reg = reg;
	pll->pd_shift = pd_shift;
	pll->dsmpd_shift = dsmpd_shift;
	pll->lock_shift = lock_shift;
	pll->hw.init = &init;

	return devm_clk_register(dev, &pll->hw);
}
EXPORT_SYMBOL_GPL(devm_clk_regmap_register_pll);