Files

409 lines
11 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018 BayLibre, SAS.
* Author: Jerome Brunet <[email protected]>
*/
#include <linux/module.h>
#include <linux/amlogic/arm-smccc.h>
#include <linux/clk.h>
#include "clk-regmap.h"
static int clk_regmap_check_is_satisfied(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_gate_data *gate = clk_get_regmap_gate_data(clk);
unsigned int val;
int cnt = 3;
if (gate->check_offset) {
do {
if (!cnt) {
pr_err("check %s failed!\n", clk_hw_get_name(hw));
return -ETIMEDOUT;
}
/*
* FIXME: due to hardware reasons, the check will be delayed per 1ms,
* and the spinlock used during the enable period will be delayed
* for at least 1ms.
*/
udelay(1200);
cnt--;
regmap_read(clk->map, gate->check_offset, &val);
} while (!(val & BIT(gate->check_bit)));
}
return 0;
}
static int clk_regmap_gate_endisable(struct clk_hw *hw, int enable)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_gate_data *gate = clk_get_regmap_gate_data(clk);
int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0;
set ^= enable;
return regmap_update_bits(clk->map, gate->offset, BIT(gate->bit_idx),
set ? BIT(gate->bit_idx) : 0);
}
static int clk_regmap_gate_enable(struct clk_hw *hw)
{
int ret;
ret = clk_regmap_gate_endisable(hw, 1);
if (ret)
return ret;
ret = clk_regmap_check_is_satisfied(hw);
if (ret)
clk_regmap_gate_endisable(hw, 0);
return ret;
}
static void clk_regmap_gate_disable(struct clk_hw *hw)
{
if (bypass_clk_disable)
return;
clk_regmap_gate_endisable(hw, 0);
}
static int clk_regmap_gate_is_enabled(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_gate_data *gate = clk_get_regmap_gate_data(clk);
unsigned int val;
regmap_read(clk->map, gate->offset, &val);
if (gate->flags & CLK_GATE_SET_TO_DISABLE)
val ^= BIT(gate->bit_idx);
val &= BIT(gate->bit_idx);
return val ? 1 : 0;
}
static int clk_regmap_gate_save_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_gate_data *gate = clk_get_regmap_gate_data(clk);
gate->saved_is_enabled = clk_regmap_gate_is_enabled(hw);
return 0;
}
static void clk_regmap_gate_restore_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_gate_data *gate = clk_get_regmap_gate_data(clk);
clk_regmap_gate_endisable(hw, gate->saved_is_enabled);
}
const struct clk_ops clk_regmap_gate_ops = {
.enable = clk_regmap_gate_enable,
.disable = clk_regmap_gate_disable,
.is_enabled = clk_regmap_gate_is_enabled,
.save_context = clk_regmap_gate_save_context,
.restore_context = clk_regmap_gate_restore_context,
};
EXPORT_SYMBOL_GPL(clk_regmap_gate_ops);
const struct clk_ops clk_regmap_gate_ro_ops = {
.is_enabled = clk_regmap_gate_is_enabled,
};
EXPORT_SYMBOL_GPL(clk_regmap_gate_ro_ops);
static unsigned long clk_regmap_div_recalc_rate(struct clk_hw *hw,
unsigned long prate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk);
unsigned int val;
int ret;
ret = regmap_read(clk->map, div->offset, &val);
if (ret)
/* Gives a hint that something is wrong */
return 0;
val >>= div->shift;
val &= clk_div_mask(div->width);
return divider_recalc_rate(hw, prate, val, div->table, div->flags,
div->width);
}
static long clk_regmap_div_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk);
unsigned int val;
int ret;
/* if read only, just return current value */
if (div->flags & CLK_DIVIDER_READ_ONLY) {
ret = regmap_read(clk->map, div->offset, &val);
if (ret)
/* Gives a hint that something is wrong */
return 0;
val >>= div->shift;
val &= clk_div_mask(div->width);
return divider_ro_round_rate(hw, rate, prate, div->table,
div->width, div->flags, val);
}
return divider_round_rate(hw, rate, prate, div->table, div->width,
div->flags);
}
static int clk_regmap_div_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk);
unsigned int val;
int ret;
ret = divider_get_val(rate, parent_rate, div->table, div->width,
div->flags);
if (ret < 0)
return ret;
val = (unsigned int)ret << div->shift;
return regmap_update_bits(clk->map, div->offset,
clk_div_mask(div->width) << div->shift, val);
};
static int clk_regmap_secure_v2_div_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk);
unsigned int val;
struct arm_smccc_res res;
int ret;
ret = divider_get_val(rate, parent_rate, div->table, div->width,
div->flags);
if (ret < 0)
return ret;
val = (unsigned int)ret << div->shift;
/* Send the divider to bl31, 0x8200009A is the general clk addr */
arm_smccc_smc(div->smc_id, div->secid,
clk_div_mask(div->width) << div->shift, val, 0, 0, 0, 0, &res);
return 0;
};
static int clk_regmap_div_save_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk);
unsigned int val;
if (!(div->flags & CLK_DIVIDER_SECURE_IGNORE_RESTORE)) {
regmap_read(clk->map, div->offset, &val);
div->saved_divider = (val >> div->shift) & clk_div_mask(div->width);
}
return 0;
}
static void clk_regmap_div_restore_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk);
regmap_update_bits(clk->map, div->offset,
clk_div_mask(div->width) << div->shift,
div->saved_divider << div->shift);
}
static void clk_regmap_div_secure_v2_restore_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk);
struct arm_smccc_res res;
if (!(div->flags & CLK_DIVIDER_SECURE_IGNORE_RESTORE)) {
/* Send the saved divider to bl31, 0x8200009A is the general clk addr */
arm_smccc_smc(div->smc_id, div->secid,
clk_div_mask(div->width) << div->shift,
div->saved_divider << div->shift, 0, 0, 0, 0, &res);
}
}
/* Would prefer clk_regmap_div_ro_ops but clashes with qcom */
const struct clk_ops clk_regmap_secure_v2_divider_ops = {
.recalc_rate = clk_regmap_div_recalc_rate,
.round_rate = clk_regmap_div_round_rate,
.set_rate = clk_regmap_secure_v2_div_set_rate,
.save_context = clk_regmap_div_save_context,
.restore_context = clk_regmap_div_secure_v2_restore_context,
};
EXPORT_SYMBOL_GPL(clk_regmap_secure_v2_divider_ops);
const struct clk_ops clk_regmap_divider_ops = {
.recalc_rate = clk_regmap_div_recalc_rate,
.round_rate = clk_regmap_div_round_rate,
.set_rate = clk_regmap_div_set_rate,
.save_context = clk_regmap_div_save_context,
.restore_context = clk_regmap_div_restore_context,
};
EXPORT_SYMBOL_GPL(clk_regmap_divider_ops);
const struct clk_ops clk_regmap_divider_ro_ops = {
.recalc_rate = clk_regmap_div_recalc_rate,
.round_rate = clk_regmap_div_round_rate,
};
EXPORT_SYMBOL_GPL(clk_regmap_divider_ro_ops);
static u8 clk_regmap_mux_get_parent(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_mux_data *mux = clk_get_regmap_mux_data(clk);
unsigned int val;
int ret;
struct arm_smccc_res res;
if (mux->smc_id) {
arm_smccc_smc(mux->smc_id, mux->secid_rd,
0, 0, 0, 0, 0, 0, &res);
val = res.a0;
} else {
ret = regmap_read(clk->map, mux->offset, &val);
if (ret)
return ret;
}
val >>= mux->shift;
val &= mux->mask;
return clk_mux_val_to_index(hw, mux->table, mux->flags, val);
}
static int clk_regmap_mux_set_parent(struct clk_hw *hw, u8 index)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_mux_data *mux = clk_get_regmap_mux_data(clk);
unsigned int val = clk_mux_index_to_val(mux->table, mux->flags, index);
struct arm_smccc_res res;
if (mux->smc_id)
arm_smccc_smc(mux->smc_id, mux->secid,
mux->mask << mux->shift, val << mux->shift,
0, 0, 0, 0, &res);
else
regmap_update_bits(clk->map, mux->offset,
mux->mask << mux->shift,
val << mux->shift);
return 0;
}
static int clk_regmap_mux_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_mux_data *mux = clk_get_regmap_mux_data(clk);
return clk_mux_determine_rate_flags(hw, req, mux->flags);
}
static int clk_regmap_mux_save_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_mux_data *mux = clk_get_regmap_mux_data(clk);
if (!mux->smc_id)
mux->saved_parent = clk_regmap_mux_get_parent(hw);
return 0;
}
static void clk_regmap_mux_restore_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_mux_data *mux = clk_get_regmap_mux_data(clk);
u8 idx = mux->saved_parent;
struct clk_hw *parent, *old_parent;
if (mux->smc_id)
return;
/**
* some a/b channel clks are configured early, and need to make sure
* they aren't disabled when clk driver buildin loads, which is why we
* can't add CLK_OPS_PARENT_ENABLE flag.
* during suspend, we need to save and restore their respective states,
* so we added CLK_MUX_OPS_PARENT_ENABLE flag to handle that.
*/
if (mux->flags & CLK_MUX_OPS_PARENT_ENABLE) {
parent = clk_hw_get_parent_by_index(hw, idx);
old_parent = clk_hw_get_parent_by_index(hw, clk_regmap_mux_get_parent(hw));
if (!parent || !old_parent)
return;
clk_prepare_enable(parent->clk);
clk_prepare_enable(old_parent->clk);
clk_regmap_mux_set_parent(hw, idx);
clk_disable_unprepare(old_parent->clk);
clk_disable_unprepare(parent->clk);
} else if (clk_hw_get_parent_by_index(hw, idx) &&
(clk_hw_get_flags(hw) & CLK_OPS_PARENT_ENABLE)) {
clk_hw_set_parent(hw, clk_hw_get_parent_by_index(hw, !idx));
clk_hw_set_parent(hw, clk_hw_get_parent_by_index(hw, idx));
} else {
clk_regmap_mux_set_parent(hw, idx);
}
}
const struct clk_ops clk_regmap_mux_ops = {
.get_parent = clk_regmap_mux_get_parent,
.set_parent = clk_regmap_mux_set_parent,
.determine_rate = clk_regmap_mux_determine_rate,
.save_context = clk_regmap_mux_save_context,
.restore_context = clk_regmap_mux_restore_context,
};
EXPORT_SYMBOL_GPL(clk_regmap_mux_ops);
const struct clk_ops clk_regmap_mux_ro_ops = {
.get_parent = clk_regmap_mux_get_parent,
};
EXPORT_SYMBOL_GPL(clk_regmap_mux_ro_ops);
static struct regmap_config clkc_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
struct regmap *meson_clk_regmap_resource(struct platform_device *pdev, struct device *dev,
unsigned int index)
{
void __iomem *base;
struct device_node *node = dev->of_node;
base = devm_platform_ioremap_resource(pdev, index);
if (IS_ERR(base))
return ERR_CAST(base);
clkc_regmap_config.name = devm_kasprintf(dev, GFP_KERNEL,
"%s-%d", node->name, index);
return devm_regmap_init_mmio(dev, base, &clkc_regmap_config);
}
EXPORT_SYMBOL_GPL(meson_clk_regmap_resource);
MODULE_DESCRIPTION("Amlogic regmap backed clock driver");
MODULE_AUTHOR("Jerome Brunet <[email protected]>");
MODULE_LICENSE("GPL v2");