cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

sun4i_hdmi_tmds_clk.c (5280B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 * Copyright (C) 2016 Free Electrons
      4 * Copyright (C) 2016 NextThing Co
      5 *
      6 * Maxime Ripard <maxime.ripard@free-electrons.com>
      7 */
      8
      9#include <linux/clk-provider.h>
     10#include <linux/io.h>
     11
     12#include "sun4i_hdmi.h"
     13
     14struct sun4i_tmds {
     15	struct clk_hw		hw;
     16	struct sun4i_hdmi	*hdmi;
     17
     18	u8			div_offset;
     19};
     20
     21static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
     22{
     23	return container_of(hw, struct sun4i_tmds, hw);
     24}
     25
     26
     27static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
     28					     unsigned long parent_rate,
     29					     u8 div_offset,
     30					     u8 *div,
     31					     bool *half)
     32{
     33	unsigned long best_rate = 0;
     34	u8 best_m = 0, m;
     35	bool is_double = false;
     36
     37	for (m = div_offset ?: 1; m < (16 + div_offset); m++) {
     38		u8 d;
     39
     40		for (d = 1; d < 3; d++) {
     41			unsigned long tmp_rate;
     42
     43			tmp_rate = parent_rate / m / d;
     44
     45			if (tmp_rate > rate)
     46				continue;
     47
     48			if (!best_rate ||
     49			    (rate - tmp_rate) < (rate - best_rate)) {
     50				best_rate = tmp_rate;
     51				best_m = m;
     52				is_double = (d == 2) ? true : false;
     53			}
     54		}
     55	}
     56
     57	if (div && half) {
     58		*div = best_m;
     59		*half = is_double;
     60	}
     61
     62	return best_rate;
     63}
     64
     65
     66static int sun4i_tmds_determine_rate(struct clk_hw *hw,
     67				     struct clk_rate_request *req)
     68{
     69	struct sun4i_tmds *tmds = hw_to_tmds(hw);
     70	struct clk_hw *parent = NULL;
     71	unsigned long best_parent = 0;
     72	unsigned long rate = req->rate;
     73	int best_div = 1, best_half = 1;
     74	int i, j, p;
     75
     76	/*
     77	 * We only consider PLL3, since the TCON is very likely to be
     78	 * clocked from it, and to have the same rate than our HDMI
     79	 * clock, so we should not need to do anything.
     80	 */
     81
     82	for (p = 0; p < clk_hw_get_num_parents(hw); p++) {
     83		parent = clk_hw_get_parent_by_index(hw, p);
     84		if (!parent)
     85			continue;
     86
     87		for (i = 1; i < 3; i++) {
     88			for (j = tmds->div_offset ?: 1;
     89			     j < (16 + tmds->div_offset); j++) {
     90				unsigned long ideal = rate * i * j;
     91				unsigned long rounded;
     92
     93				rounded = clk_hw_round_rate(parent, ideal);
     94
     95				if (rounded == ideal) {
     96					best_parent = rounded;
     97					best_half = i;
     98					best_div = j;
     99					goto out;
    100				}
    101
    102				if (!best_parent ||
    103				    abs(rate - rounded / i / j) <
    104				    abs(rate - best_parent / best_half /
    105					best_div)) {
    106					best_parent = rounded;
    107					best_half = i;
    108					best_div = j;
    109				}
    110			}
    111		}
    112	}
    113
    114	if (!parent)
    115		return -EINVAL;
    116
    117out:
    118	req->rate = best_parent / best_half / best_div;
    119	req->best_parent_rate = best_parent;
    120	req->best_parent_hw = parent;
    121
    122	return 0;
    123}
    124
    125static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
    126					    unsigned long parent_rate)
    127{
    128	struct sun4i_tmds *tmds = hw_to_tmds(hw);
    129	u32 reg;
    130
    131	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
    132	if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
    133		parent_rate /= 2;
    134
    135	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
    136	reg = ((reg >> 4) & 0xf) + tmds->div_offset;
    137	if (!reg)
    138		reg = 1;
    139
    140	return parent_rate / reg;
    141}
    142
    143static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
    144			       unsigned long parent_rate)
    145{
    146	struct sun4i_tmds *tmds = hw_to_tmds(hw);
    147	bool half;
    148	u32 reg;
    149	u8 div;
    150
    151	sun4i_tmds_calc_divider(rate, parent_rate, tmds->div_offset,
    152				&div, &half);
    153
    154	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
    155	reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
    156	if (half)
    157		reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
    158	writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
    159
    160	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
    161	reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
    162	writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div - tmds->div_offset),
    163	       tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
    164
    165	return 0;
    166}
    167
    168static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
    169{
    170	struct sun4i_tmds *tmds = hw_to_tmds(hw);
    171	u32 reg;
    172
    173	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
    174	return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
    175		SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
    176}
    177
    178static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
    179{
    180	struct sun4i_tmds *tmds = hw_to_tmds(hw);
    181	u32 reg;
    182
    183	if (index > 1)
    184		return -EINVAL;
    185
    186	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
    187	reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
    188	writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
    189	       tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
    190
    191	return 0;
    192}
    193
    194static const struct clk_ops sun4i_tmds_ops = {
    195	.determine_rate	= sun4i_tmds_determine_rate,
    196	.recalc_rate	= sun4i_tmds_recalc_rate,
    197	.set_rate	= sun4i_tmds_set_rate,
    198
    199	.get_parent	= sun4i_tmds_get_parent,
    200	.set_parent	= sun4i_tmds_set_parent,
    201};
    202
    203int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
    204{
    205	struct clk_init_data init;
    206	struct sun4i_tmds *tmds;
    207	const char *parents[2];
    208
    209	parents[0] = __clk_get_name(hdmi->pll0_clk);
    210	if (!parents[0])
    211		return -ENODEV;
    212
    213	parents[1] = __clk_get_name(hdmi->pll1_clk);
    214	if (!parents[1])
    215		return -ENODEV;
    216
    217	tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
    218	if (!tmds)
    219		return -ENOMEM;
    220
    221	init.name = "hdmi-tmds";
    222	init.ops = &sun4i_tmds_ops;
    223	init.parent_names = parents;
    224	init.num_parents = 2;
    225	init.flags = CLK_SET_RATE_PARENT;
    226
    227	tmds->hdmi = hdmi;
    228	tmds->hw.init = &init;
    229	tmds->div_offset = hdmi->variant->tmds_clk_div_offset;
    230
    231	hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
    232	if (IS_ERR(hdmi->tmds_clk))
    233		return PTR_ERR(hdmi->tmds_clk);
    234
    235	return 0;
    236}