12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
29c568101SMaxime Ripard /*
39c568101SMaxime Ripard  * Copyright (C) 2016 Free Electrons
49c568101SMaxime Ripard  * Copyright (C) 2016 NextThing Co
59c568101SMaxime Ripard  *
69c568101SMaxime Ripard  * Maxime Ripard <maxime.ripard@free-electrons.com>
79c568101SMaxime Ripard  */
89c568101SMaxime Ripard 
99c568101SMaxime Ripard #include <linux/clk-provider.h>
1062e59c4eSStephen Boyd #include <linux/io.h>
119c568101SMaxime Ripard 
129c568101SMaxime Ripard #include "sun4i_hdmi.h"
139c568101SMaxime Ripard 
149c568101SMaxime Ripard struct sun4i_tmds {
159c568101SMaxime Ripard 	struct clk_hw		hw;
169c568101SMaxime Ripard 	struct sun4i_hdmi	*hdmi;
17939d749aSChen-Yu Tsai 
18939d749aSChen-Yu Tsai 	u8			div_offset;
199c568101SMaxime Ripard };
209c568101SMaxime Ripard 
hw_to_tmds(struct clk_hw * hw)219c568101SMaxime Ripard static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
229c568101SMaxime Ripard {
239c568101SMaxime Ripard 	return container_of(hw, struct sun4i_tmds, hw);
249c568101SMaxime Ripard }
259c568101SMaxime Ripard 
269c568101SMaxime Ripard 
sun4i_tmds_calc_divider(unsigned long rate,unsigned long parent_rate,u8 div_offset,u8 * div,bool * half)279c568101SMaxime Ripard static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
289c568101SMaxime Ripard 					     unsigned long parent_rate,
29939d749aSChen-Yu Tsai 					     u8 div_offset,
309c568101SMaxime Ripard 					     u8 *div,
319c568101SMaxime Ripard 					     bool *half)
329c568101SMaxime Ripard {
339c568101SMaxime Ripard 	unsigned long best_rate = 0;
349c568101SMaxime Ripard 	u8 best_m = 0, m;
35a25b77a1SMaxime Ripard 	bool is_double = false;
369c568101SMaxime Ripard 
37939d749aSChen-Yu Tsai 	for (m = div_offset ?: 1; m < (16 + div_offset); m++) {
389c568101SMaxime Ripard 		u8 d;
399c568101SMaxime Ripard 
409c568101SMaxime Ripard 		for (d = 1; d < 3; d++) {
419c568101SMaxime Ripard 			unsigned long tmp_rate;
429c568101SMaxime Ripard 
439c568101SMaxime Ripard 			tmp_rate = parent_rate / m / d;
449c568101SMaxime Ripard 
459c568101SMaxime Ripard 			if (tmp_rate > rate)
469c568101SMaxime Ripard 				continue;
479c568101SMaxime Ripard 
489c568101SMaxime Ripard 			if (!best_rate ||
499c568101SMaxime Ripard 			    (rate - tmp_rate) < (rate - best_rate)) {
509c568101SMaxime Ripard 				best_rate = tmp_rate;
519c568101SMaxime Ripard 				best_m = m;
521e0ff648SMaxime Ripard 				is_double = (d == 2) ? true : false;
539c568101SMaxime Ripard 			}
549c568101SMaxime Ripard 		}
559c568101SMaxime Ripard 	}
569c568101SMaxime Ripard 
579c568101SMaxime Ripard 	if (div && half) {
589c568101SMaxime Ripard 		*div = best_m;
599c568101SMaxime Ripard 		*half = is_double;
609c568101SMaxime Ripard 	}
619c568101SMaxime Ripard 
629c568101SMaxime Ripard 	return best_rate;
639c568101SMaxime Ripard }
649c568101SMaxime Ripard 
659c568101SMaxime Ripard 
sun4i_tmds_determine_rate(struct clk_hw * hw,struct clk_rate_request * req)669c568101SMaxime Ripard static int sun4i_tmds_determine_rate(struct clk_hw *hw,
679c568101SMaxime Ripard 				     struct clk_rate_request *req)
689c568101SMaxime Ripard {
69939d749aSChen-Yu Tsai 	struct sun4i_tmds *tmds = hw_to_tmds(hw);
70cc67ae90SChen-Yu Tsai 	struct clk_hw *parent = NULL;
719c568101SMaxime Ripard 	unsigned long best_parent = 0;
729c568101SMaxime Ripard 	unsigned long rate = req->rate;
739c568101SMaxime Ripard 	int best_div = 1, best_half = 1;
74cc67ae90SChen-Yu Tsai 	int i, j, p;
759c568101SMaxime Ripard 
769c568101SMaxime Ripard 	/*
779c568101SMaxime Ripard 	 * We only consider PLL3, since the TCON is very likely to be
789c568101SMaxime Ripard 	 * clocked from it, and to have the same rate than our HDMI
799c568101SMaxime Ripard 	 * clock, so we should not need to do anything.
809c568101SMaxime Ripard 	 */
819c568101SMaxime Ripard 
82cc67ae90SChen-Yu Tsai 	for (p = 0; p < clk_hw_get_num_parents(hw); p++) {
83cc67ae90SChen-Yu Tsai 		parent = clk_hw_get_parent_by_index(hw, p);
849c568101SMaxime Ripard 		if (!parent)
85cc67ae90SChen-Yu Tsai 			continue;
869c568101SMaxime Ripard 
879c568101SMaxime Ripard 		for (i = 1; i < 3; i++) {
88939d749aSChen-Yu Tsai 			for (j = tmds->div_offset ?: 1;
89939d749aSChen-Yu Tsai 			     j < (16 + tmds->div_offset); j++) {
909c568101SMaxime Ripard 				unsigned long ideal = rate * i * j;
919c568101SMaxime Ripard 				unsigned long rounded;
929c568101SMaxime Ripard 
939c568101SMaxime Ripard 				rounded = clk_hw_round_rate(parent, ideal);
949c568101SMaxime Ripard 
959c568101SMaxime Ripard 				if (rounded == ideal) {
969c568101SMaxime Ripard 					best_parent = rounded;
979c568101SMaxime Ripard 					best_half = i;
989c568101SMaxime Ripard 					best_div = j;
999c568101SMaxime Ripard 					goto out;
1009c568101SMaxime Ripard 				}
1019c568101SMaxime Ripard 
1023b9c57ceSJonathan Liu 				if (!best_parent ||
1033b9c57ceSJonathan Liu 				    abs(rate - rounded / i / j) <
1043b9c57ceSJonathan Liu 				    abs(rate - best_parent / best_half /
1053b9c57ceSJonathan Liu 					best_div)) {
1069c568101SMaxime Ripard 					best_parent = rounded;
1073b9c57ceSJonathan Liu 					best_half = i;
10858faae28SJonathan Liu 					best_div = j;
1099c568101SMaxime Ripard 				}
1109c568101SMaxime Ripard 			}
1119c568101SMaxime Ripard 		}
112cc67ae90SChen-Yu Tsai 	}
113cc67ae90SChen-Yu Tsai 
114cc67ae90SChen-Yu Tsai 	if (!parent)
115cc67ae90SChen-Yu Tsai 		return -EINVAL;
1169c568101SMaxime Ripard 
1179c568101SMaxime Ripard out:
1189c568101SMaxime Ripard 	req->rate = best_parent / best_half / best_div;
1199c568101SMaxime Ripard 	req->best_parent_rate = best_parent;
1209c568101SMaxime Ripard 	req->best_parent_hw = parent;
1219c568101SMaxime Ripard 
1229c568101SMaxime Ripard 	return 0;
1239c568101SMaxime Ripard }
1249c568101SMaxime Ripard 
sun4i_tmds_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)1259c568101SMaxime Ripard static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
1269c568101SMaxime Ripard 					    unsigned long parent_rate)
1279c568101SMaxime Ripard {
1289c568101SMaxime Ripard 	struct sun4i_tmds *tmds = hw_to_tmds(hw);
1299c568101SMaxime Ripard 	u32 reg;
1309c568101SMaxime Ripard 
1319c568101SMaxime Ripard 	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
1329c568101SMaxime Ripard 	if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
1339c568101SMaxime Ripard 		parent_rate /= 2;
1349c568101SMaxime Ripard 
1359c568101SMaxime Ripard 	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
136939d749aSChen-Yu Tsai 	reg = ((reg >> 4) & 0xf) + tmds->div_offset;
1379c568101SMaxime Ripard 	if (!reg)
1389c568101SMaxime Ripard 		reg = 1;
1399c568101SMaxime Ripard 
1409c568101SMaxime Ripard 	return parent_rate / reg;
1419c568101SMaxime Ripard }
1429c568101SMaxime Ripard 
sun4i_tmds_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)1439c568101SMaxime Ripard static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
1449c568101SMaxime Ripard 			       unsigned long parent_rate)
1459c568101SMaxime Ripard {
1469c568101SMaxime Ripard 	struct sun4i_tmds *tmds = hw_to_tmds(hw);
1479c568101SMaxime Ripard 	bool half;
1489c568101SMaxime Ripard 	u32 reg;
1499c568101SMaxime Ripard 	u8 div;
1509c568101SMaxime Ripard 
151939d749aSChen-Yu Tsai 	sun4i_tmds_calc_divider(rate, parent_rate, tmds->div_offset,
152939d749aSChen-Yu Tsai 				&div, &half);
1539c568101SMaxime Ripard 
1549c568101SMaxime Ripard 	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
1559c568101SMaxime Ripard 	reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
1569c568101SMaxime Ripard 	if (half)
1579c568101SMaxime Ripard 		reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
1589c568101SMaxime Ripard 	writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
1599c568101SMaxime Ripard 
1609c568101SMaxime Ripard 	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
1619c568101SMaxime Ripard 	reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
162939d749aSChen-Yu Tsai 	writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div - tmds->div_offset),
1639c568101SMaxime Ripard 	       tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
1649c568101SMaxime Ripard 
1659c568101SMaxime Ripard 	return 0;
1669c568101SMaxime Ripard }
1679c568101SMaxime Ripard 
sun4i_tmds_get_parent(struct clk_hw * hw)1689c568101SMaxime Ripard static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
1699c568101SMaxime Ripard {
1709c568101SMaxime Ripard 	struct sun4i_tmds *tmds = hw_to_tmds(hw);
1719c568101SMaxime Ripard 	u32 reg;
1729c568101SMaxime Ripard 
1739c568101SMaxime Ripard 	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
1749c568101SMaxime Ripard 	return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
1759c568101SMaxime Ripard 		SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
1769c568101SMaxime Ripard }
1779c568101SMaxime Ripard 
sun4i_tmds_set_parent(struct clk_hw * hw,u8 index)1789c568101SMaxime Ripard static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
1799c568101SMaxime Ripard {
1809c568101SMaxime Ripard 	struct sun4i_tmds *tmds = hw_to_tmds(hw);
1819c568101SMaxime Ripard 	u32 reg;
1829c568101SMaxime Ripard 
1839c568101SMaxime Ripard 	if (index > 1)
1849c568101SMaxime Ripard 		return -EINVAL;
1859c568101SMaxime Ripard 
1869c568101SMaxime Ripard 	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
1879c568101SMaxime Ripard 	reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
1889c568101SMaxime Ripard 	writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
1899c568101SMaxime Ripard 	       tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
1909c568101SMaxime Ripard 
1919c568101SMaxime Ripard 	return 0;
1929c568101SMaxime Ripard }
1939c568101SMaxime Ripard 
1949c568101SMaxime Ripard static const struct clk_ops sun4i_tmds_ops = {
1959c568101SMaxime Ripard 	.determine_rate	= sun4i_tmds_determine_rate,
1969c568101SMaxime Ripard 	.recalc_rate	= sun4i_tmds_recalc_rate,
1979c568101SMaxime Ripard 	.set_rate	= sun4i_tmds_set_rate,
1989c568101SMaxime Ripard 
1999c568101SMaxime Ripard 	.get_parent	= sun4i_tmds_get_parent,
2009c568101SMaxime Ripard 	.set_parent	= sun4i_tmds_set_parent,
2019c568101SMaxime Ripard };
2029c568101SMaxime Ripard 
sun4i_tmds_create(struct sun4i_hdmi * hdmi)2039c568101SMaxime Ripard int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
2049c568101SMaxime Ripard {
2059c568101SMaxime Ripard 	struct clk_init_data init;
2069c568101SMaxime Ripard 	struct sun4i_tmds *tmds;
2079c568101SMaxime Ripard 	const char *parents[2];
2089c568101SMaxime Ripard 
2099c568101SMaxime Ripard 	parents[0] = __clk_get_name(hdmi->pll0_clk);
2109c568101SMaxime Ripard 	if (!parents[0])
2119c568101SMaxime Ripard 		return -ENODEV;
2129c568101SMaxime Ripard 
2139c568101SMaxime Ripard 	parents[1] = __clk_get_name(hdmi->pll1_clk);
2149c568101SMaxime Ripard 	if (!parents[1])
2159c568101SMaxime Ripard 		return -ENODEV;
2169c568101SMaxime Ripard 
2179c568101SMaxime Ripard 	tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
2189c568101SMaxime Ripard 	if (!tmds)
2199c568101SMaxime Ripard 		return -ENOMEM;
2209c568101SMaxime Ripard 
2219c568101SMaxime Ripard 	init.name = "hdmi-tmds";
2229c568101SMaxime Ripard 	init.ops = &sun4i_tmds_ops;
2239c568101SMaxime Ripard 	init.parent_names = parents;
2249c568101SMaxime Ripard 	init.num_parents = 2;
2259c568101SMaxime Ripard 	init.flags = CLK_SET_RATE_PARENT;
2269c568101SMaxime Ripard 
2279c568101SMaxime Ripard 	tmds->hdmi = hdmi;
2289c568101SMaxime Ripard 	tmds->hw.init = &init;
229939d749aSChen-Yu Tsai 	tmds->div_offset = hdmi->variant->tmds_clk_div_offset;
2309c568101SMaxime Ripard 
2319c568101SMaxime Ripard 	hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
2329c568101SMaxime Ripard 	if (IS_ERR(hdmi->tmds_clk))
2339c568101SMaxime Ripard 		return PTR_ERR(hdmi->tmds_clk);
2349c568101SMaxime Ripard 
2359c568101SMaxime Ripard 	return 0;
2369c568101SMaxime Ripard }
237