Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

clk: meson: add vclk driver

The VCLK and VCLK_DIV clocks have supplementary bits.

The VCLK gate has a "SOFT RESET" bit to toggle after the whole
VCLK sub-tree rate has been set, this is implemented in
the gate enable callback.

The VCLK_DIV clocks as enable and reset bits used to disable
and reset the divider, associated with CLK_SET_RATE_GATE it ensures
the rate is set while the divider is disabled and in reset mode.

The VCLK_DIV enable bit isn't implemented as a gate since it's part
of the divider logic and vendor does this exact sequence to ensure
the divider is correctly set.

Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Link: https://lore.kernel.org/r/20240403-amlogic-v6-4-upstream-dsi-ccf-vim3-v12-2-99ecdfdc87fc@linaro.org
Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>

authored by

Neil Armstrong and committed by
Jerome Brunet
bb5aa085 16182ac3

+197
+4
drivers/clk/meson/Kconfig
··· 30 30 tristate 31 31 select COMMON_CLK_MESON_REGMAP 32 32 33 + config COMMON_CLK_MESON_VCLK 34 + tristate 35 + select COMMON_CLK_MESON_REGMAP 36 + 33 37 config COMMON_CLK_MESON_CLKC_UTILS 34 38 tristate 35 39
+1
drivers/clk/meson/Makefile
··· 12 12 obj-$(CONFIG_COMMON_CLK_MESON_REGMAP) += clk-regmap.o 13 13 obj-$(CONFIG_COMMON_CLK_MESON_SCLK_DIV) += sclk-div.o 14 14 obj-$(CONFIG_COMMON_CLK_MESON_VID_PLL_DIV) += vid-pll-div.o 15 + obj-$(CONFIG_COMMON_CLK_MESON_VCLK) += vclk.o 15 16 16 17 # Amlogic Clock controllers 17 18
+141
drivers/clk/meson/vclk.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright (c) 2024 Neil Armstrong <neil.armstrong@linaro.org> 4 + */ 5 + 6 + #include <linux/module.h> 7 + #include "vclk.h" 8 + 9 + /* The VCLK gate has a supplementary reset bit to pulse after ungating */ 10 + 11 + static inline struct meson_vclk_gate_data * 12 + clk_get_meson_vclk_gate_data(struct clk_regmap *clk) 13 + { 14 + return (struct meson_vclk_gate_data *)clk->data; 15 + } 16 + 17 + static int meson_vclk_gate_enable(struct clk_hw *hw) 18 + { 19 + struct clk_regmap *clk = to_clk_regmap(hw); 20 + struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk); 21 + 22 + meson_parm_write(clk->map, &vclk->enable, 1); 23 + 24 + /* Do a reset pulse */ 25 + meson_parm_write(clk->map, &vclk->reset, 1); 26 + meson_parm_write(clk->map, &vclk->reset, 0); 27 + 28 + return 0; 29 + } 30 + 31 + static void meson_vclk_gate_disable(struct clk_hw *hw) 32 + { 33 + struct clk_regmap *clk = to_clk_regmap(hw); 34 + struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk); 35 + 36 + meson_parm_write(clk->map, &vclk->enable, 0); 37 + } 38 + 39 + static int meson_vclk_gate_is_enabled(struct clk_hw *hw) 40 + { 41 + struct clk_regmap *clk = to_clk_regmap(hw); 42 + struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk); 43 + 44 + return meson_parm_read(clk->map, &vclk->enable); 45 + } 46 + 47 + const struct clk_ops meson_vclk_gate_ops = { 48 + .enable = meson_vclk_gate_enable, 49 + .disable = meson_vclk_gate_disable, 50 + .is_enabled = meson_vclk_gate_is_enabled, 51 + }; 52 + EXPORT_SYMBOL_GPL(meson_vclk_gate_ops); 53 + 54 + /* The VCLK Divider has supplementary reset & enable bits */ 55 + 56 + static inline struct meson_vclk_div_data * 57 + clk_get_meson_vclk_div_data(struct clk_regmap *clk) 58 + { 59 + return (struct meson_vclk_div_data *)clk->data; 60 + } 61 + 62 + static unsigned long meson_vclk_div_recalc_rate(struct clk_hw *hw, 63 + unsigned long prate) 64 + { 65 + struct clk_regmap *clk = to_clk_regmap(hw); 66 + struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); 67 + 68 + return divider_recalc_rate(hw, prate, meson_parm_read(clk->map, &vclk->div), 69 + vclk->table, vclk->flags, vclk->div.width); 70 + } 71 + 72 + static int meson_vclk_div_determine_rate(struct clk_hw *hw, 73 + struct clk_rate_request *req) 74 + { 75 + struct clk_regmap *clk = to_clk_regmap(hw); 76 + struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); 77 + 78 + return divider_determine_rate(hw, req, vclk->table, vclk->div.width, 79 + vclk->flags); 80 + } 81 + 82 + static int meson_vclk_div_set_rate(struct clk_hw *hw, unsigned long rate, 83 + unsigned long parent_rate) 84 + { 85 + struct clk_regmap *clk = to_clk_regmap(hw); 86 + struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); 87 + int ret; 88 + 89 + ret = divider_get_val(rate, parent_rate, vclk->table, vclk->div.width, 90 + vclk->flags); 91 + if (ret < 0) 92 + return ret; 93 + 94 + meson_parm_write(clk->map, &vclk->div, ret); 95 + 96 + return 0; 97 + }; 98 + 99 + static int meson_vclk_div_enable(struct clk_hw *hw) 100 + { 101 + struct clk_regmap *clk = to_clk_regmap(hw); 102 + struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); 103 + 104 + /* Unreset the divider when ungating */ 105 + meson_parm_write(clk->map, &vclk->reset, 0); 106 + meson_parm_write(clk->map, &vclk->enable, 1); 107 + 108 + return 0; 109 + } 110 + 111 + static void meson_vclk_div_disable(struct clk_hw *hw) 112 + { 113 + struct clk_regmap *clk = to_clk_regmap(hw); 114 + struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); 115 + 116 + /* Reset the divider when gating */ 117 + meson_parm_write(clk->map, &vclk->enable, 0); 118 + meson_parm_write(clk->map, &vclk->reset, 1); 119 + } 120 + 121 + static int meson_vclk_div_is_enabled(struct clk_hw *hw) 122 + { 123 + struct clk_regmap *clk = to_clk_regmap(hw); 124 + struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); 125 + 126 + return meson_parm_read(clk->map, &vclk->enable); 127 + } 128 + 129 + const struct clk_ops meson_vclk_div_ops = { 130 + .recalc_rate = meson_vclk_div_recalc_rate, 131 + .determine_rate = meson_vclk_div_determine_rate, 132 + .set_rate = meson_vclk_div_set_rate, 133 + .enable = meson_vclk_div_enable, 134 + .disable = meson_vclk_div_disable, 135 + .is_enabled = meson_vclk_div_is_enabled, 136 + }; 137 + EXPORT_SYMBOL_GPL(meson_vclk_div_ops); 138 + 139 + MODULE_DESCRIPTION("Amlogic vclk clock driver"); 140 + MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>"); 141 + MODULE_LICENSE("GPL v2");
+51
drivers/clk/meson/vclk.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * Copyright (c) 2024 Neil Armstrong <neil.armstrong@linaro.org> 4 + */ 5 + 6 + #ifndef __VCLK_H 7 + #define __VCLK_H 8 + 9 + #include "clk-regmap.h" 10 + #include "parm.h" 11 + 12 + /** 13 + * struct meson_vclk_gate_data - vclk_gate regmap backed specific data 14 + * 15 + * @enable: vclk enable field 16 + * @reset: vclk reset field 17 + * @flags: hardware-specific flags 18 + * 19 + * Flags: 20 + * Same as clk_gate except CLK_GATE_HIWORD_MASK which is ignored 21 + */ 22 + struct meson_vclk_gate_data { 23 + struct parm enable; 24 + struct parm reset; 25 + u8 flags; 26 + }; 27 + 28 + extern const struct clk_ops meson_vclk_gate_ops; 29 + 30 + /** 31 + * struct meson_vclk_div_data - vclk_div regmap back specific data 32 + * 33 + * @div: divider field 34 + * @enable: vclk divider enable field 35 + * @reset: vclk divider reset field 36 + * @table: array of value/divider pairs, last entry should have div = 0 37 + * 38 + * Flags: 39 + * Same as clk_divider except CLK_DIVIDER_HIWORD_MASK which is ignored 40 + */ 41 + struct meson_vclk_div_data { 42 + struct parm div; 43 + struct parm enable; 44 + struct parm reset; 45 + const struct clk_div_table *table; 46 + u8 flags; 47 + }; 48 + 49 + extern const struct clk_ops meson_vclk_div_ops; 50 + 51 + #endif /* __VCLK_H */