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

clk-dualdiv.c (4173B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 * Copyright (c) 2017 BayLibre, SAS
      4 * Author: Neil Armstrong <narmstrong@baylibre.com>
      5 * Author: Jerome Brunet <jbrunet@baylibre.com>
      6 */
      7
      8/*
      9 * The AO Domain embeds a dual/divider to generate a more precise
     10 * 32,768KHz clock for low-power suspend mode and CEC.
     11 *     ______   ______
     12 *    |      | |      |
     13 *    | Div1 |-| Cnt1 |
     14 *   /|______| |______|\
     15 * -|  ______   ______  X--> Out
     16 *   \|      | |      |/
     17 *    | Div2 |-| Cnt2 |
     18 *    |______| |______|
     19 *
     20 * The dividing can be switched to single or dual, with a counter
     21 * for each divider to set when the switching is done.
     22 */
     23
     24#include <linux/clk-provider.h>
     25#include <linux/module.h>
     26
     27#include "clk-regmap.h"
     28#include "clk-dualdiv.h"
     29
     30static inline struct meson_clk_dualdiv_data *
     31meson_clk_dualdiv_data(struct clk_regmap *clk)
     32{
     33	return (struct meson_clk_dualdiv_data *)clk->data;
     34}
     35
     36static unsigned long
     37__dualdiv_param_to_rate(unsigned long parent_rate,
     38			const struct meson_clk_dualdiv_param *p)
     39{
     40	if (!p->dual)
     41		return DIV_ROUND_CLOSEST(parent_rate, p->n1);
     42
     43	return DIV_ROUND_CLOSEST(parent_rate * (p->m1 + p->m2),
     44				 p->n1 * p->m1 + p->n2 * p->m2);
     45}
     46
     47static unsigned long meson_clk_dualdiv_recalc_rate(struct clk_hw *hw,
     48						   unsigned long parent_rate)
     49{
     50	struct clk_regmap *clk = to_clk_regmap(hw);
     51	struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
     52	struct meson_clk_dualdiv_param setting;
     53
     54	setting.dual = meson_parm_read(clk->map, &dualdiv->dual);
     55	setting.n1 = meson_parm_read(clk->map, &dualdiv->n1) + 1;
     56	setting.m1 = meson_parm_read(clk->map, &dualdiv->m1) + 1;
     57	setting.n2 = meson_parm_read(clk->map, &dualdiv->n2) + 1;
     58	setting.m2 = meson_parm_read(clk->map, &dualdiv->m2) + 1;
     59
     60	return __dualdiv_param_to_rate(parent_rate, &setting);
     61}
     62
     63static const struct meson_clk_dualdiv_param *
     64__dualdiv_get_setting(unsigned long rate, unsigned long parent_rate,
     65		      struct meson_clk_dualdiv_data *dualdiv)
     66{
     67	const struct meson_clk_dualdiv_param *table = dualdiv->table;
     68	unsigned long best = 0, now = 0;
     69	unsigned int i, best_i = 0;
     70
     71	if (!table)
     72		return NULL;
     73
     74	for (i = 0; table[i].n1; i++) {
     75		now = __dualdiv_param_to_rate(parent_rate, &table[i]);
     76
     77		/* If we get an exact match, don't bother any further */
     78		if (now == rate) {
     79			return &table[i];
     80		} else if (abs(now - rate) < abs(best - rate)) {
     81			best = now;
     82			best_i = i;
     83		}
     84	}
     85
     86	return (struct meson_clk_dualdiv_param *)&table[best_i];
     87}
     88
     89static long meson_clk_dualdiv_round_rate(struct clk_hw *hw, unsigned long rate,
     90					 unsigned long *parent_rate)
     91{
     92	struct clk_regmap *clk = to_clk_regmap(hw);
     93	struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
     94	const struct meson_clk_dualdiv_param *setting =
     95		__dualdiv_get_setting(rate, *parent_rate, dualdiv);
     96
     97	if (!setting)
     98		return meson_clk_dualdiv_recalc_rate(hw, *parent_rate);
     99
    100	return __dualdiv_param_to_rate(*parent_rate, setting);
    101}
    102
    103static int meson_clk_dualdiv_set_rate(struct clk_hw *hw, unsigned long rate,
    104				      unsigned long parent_rate)
    105{
    106	struct clk_regmap *clk = to_clk_regmap(hw);
    107	struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
    108	const struct meson_clk_dualdiv_param *setting =
    109		__dualdiv_get_setting(rate, parent_rate, dualdiv);
    110
    111	if (!setting)
    112		return -EINVAL;
    113
    114	meson_parm_write(clk->map, &dualdiv->dual, setting->dual);
    115	meson_parm_write(clk->map, &dualdiv->n1, setting->n1 - 1);
    116	meson_parm_write(clk->map, &dualdiv->m1, setting->m1 - 1);
    117	meson_parm_write(clk->map, &dualdiv->n2, setting->n2 - 1);
    118	meson_parm_write(clk->map, &dualdiv->m2, setting->m2 - 1);
    119
    120	return 0;
    121}
    122
    123const struct clk_ops meson_clk_dualdiv_ops = {
    124	.recalc_rate	= meson_clk_dualdiv_recalc_rate,
    125	.round_rate	= meson_clk_dualdiv_round_rate,
    126	.set_rate	= meson_clk_dualdiv_set_rate,
    127};
    128EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ops);
    129
    130const struct clk_ops meson_clk_dualdiv_ro_ops = {
    131	.recalc_rate	= meson_clk_dualdiv_recalc_rate,
    132};
    133EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ro_ops);
    134
    135MODULE_DESCRIPTION("Amlogic dual divider driver");
    136MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
    137MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
    138MODULE_LICENSE("GPL v2");