// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2024 Neil Armstrong */ #include #include "vclk.h" /* The VCLK gate has a supplementary reset bit to pulse after ungating */ static inline struct meson_vclk_gate_data * clk_get_meson_vclk_gate_data(struct clk_regmap *clk) { return (struct meson_vclk_gate_data *)clk->data; } static int meson_vclk_gate_enable(struct clk_hw *hw) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk); meson_parm_write(clk->map, &vclk->enable, 1); /* Do a reset pulse */ meson_parm_write(clk->map, &vclk->reset, 1); meson_parm_write(clk->map, &vclk->reset, 0); return 0; } static void meson_vclk_gate_disable(struct clk_hw *hw) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk); meson_parm_write(clk->map, &vclk->enable, 0); } static int meson_vclk_gate_is_enabled(struct clk_hw *hw) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk); return meson_parm_read(clk->map, &vclk->enable); } const struct clk_ops meson_vclk_gate_ops = { .enable = meson_vclk_gate_enable, .disable = meson_vclk_gate_disable, .is_enabled = meson_vclk_gate_is_enabled, }; EXPORT_SYMBOL_GPL(meson_vclk_gate_ops); /* The VCLK Divider has supplementary reset & enable bits */ static inline struct meson_vclk_div_data * clk_get_meson_vclk_div_data(struct clk_regmap *clk) { return (struct meson_vclk_div_data *)clk->data; } static unsigned long meson_vclk_div_recalc_rate(struct clk_hw *hw, unsigned long prate) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); return divider_recalc_rate(hw, prate, meson_parm_read(clk->map, &vclk->div), vclk->table, vclk->flags, vclk->div.width); } static int meson_vclk_div_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); return divider_determine_rate(hw, req, vclk->table, vclk->div.width, vclk->flags); } static int meson_vclk_div_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); int ret; ret = divider_get_val(rate, parent_rate, vclk->table, vclk->div.width, vclk->flags); if (ret < 0) return ret; meson_parm_write(clk->map, &vclk->div, ret); return 0; }; static int meson_vclk_div_enable(struct clk_hw *hw) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); /* Unreset the divider when ungating */ meson_parm_write(clk->map, &vclk->reset, 0); meson_parm_write(clk->map, &vclk->enable, 1); return 0; } static void meson_vclk_div_disable(struct clk_hw *hw) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); /* Reset the divider when gating */ meson_parm_write(clk->map, &vclk->enable, 0); meson_parm_write(clk->map, &vclk->reset, 1); } static int meson_vclk_div_is_enabled(struct clk_hw *hw) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); return meson_parm_read(clk->map, &vclk->enable); } const struct clk_ops meson_vclk_div_ops = { .recalc_rate = meson_vclk_div_recalc_rate, .determine_rate = meson_vclk_div_determine_rate, .set_rate = meson_vclk_div_set_rate, .enable = meson_vclk_div_enable, .disable = meson_vclk_div_disable, .is_enabled = meson_vclk_div_is_enabled, }; EXPORT_SYMBOL_GPL(meson_vclk_div_ops); MODULE_DESCRIPTION("Amlogic vclk clock driver"); MODULE_AUTHOR("Neil Armstrong "); MODULE_LICENSE("GPL");