1*2874c5fdSThomas 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