Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | // SPDX-License-Identifier: (GPL-2.0 OR MIT) /* * Copyright (c) 2018 BayLibre, SAS. * Author: Jerome Brunet <jbrunet@baylibre.com> */ #include <linux/clk-provider.h> #include <linux/module.h> #include "clk-regmap.h" #include "clk-phase.h" #define phase_step(_width) (360 / (1 << (_width))) static inline struct meson_clk_phase_data * meson_clk_phase_data(struct clk_regmap *clk) { return (struct meson_clk_phase_data *)clk->data; } static int meson_clk_degrees_from_val(unsigned int val, unsigned int width) { return phase_step(width) * val; } static unsigned int meson_clk_degrees_to_val(int degrees, unsigned int width) { unsigned int val = DIV_ROUND_CLOSEST(degrees, phase_step(width)); /* * This last calculation is here for cases when degrees is rounded * to 360, in which case val == (1 << width). */ return val % (1 << width); } static int meson_clk_phase_get_phase(struct clk_hw *hw) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_clk_phase_data *phase = meson_clk_phase_data(clk); unsigned int val; val = meson_parm_read(clk->map, &phase->ph); return meson_clk_degrees_from_val(val, phase->ph.width); } static int meson_clk_phase_set_phase(struct clk_hw *hw, int degrees) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_clk_phase_data *phase = meson_clk_phase_data(clk); unsigned int val; val = meson_clk_degrees_to_val(degrees, phase->ph.width); meson_parm_write(clk->map, &phase->ph, val); return 0; } const struct clk_ops meson_clk_phase_ops = { .get_phase = meson_clk_phase_get_phase, .set_phase = meson_clk_phase_set_phase, }; EXPORT_SYMBOL_GPL(meson_clk_phase_ops); /* * This is a special clock for the audio controller. * The phase of mst_sclk clock output can be controlled independently * for the outside world (ph0), the tdmout (ph1) and tdmin (ph2). * Controlling these 3 phases as just one makes things simpler and * give the same clock view to all the element on the i2s bus. * If necessary, we can still control the phase in the tdm block * which makes these independent control redundant. */ static inline struct meson_clk_triphase_data * meson_clk_triphase_data(struct clk_regmap *clk) { return (struct meson_clk_triphase_data *)clk->data; } static int meson_clk_triphase_sync(struct clk_hw *hw) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk); unsigned int val; /* Get phase 0 and sync it to phase 1 and 2 */ val = meson_parm_read(clk->map, &tph->ph0); meson_parm_write(clk->map, &tph->ph1, val); meson_parm_write(clk->map, &tph->ph2, val); return 0; } static int meson_clk_triphase_get_phase(struct clk_hw *hw) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk); unsigned int val; /* Phase are in sync, reading phase 0 is enough */ val = meson_parm_read(clk->map, &tph->ph0); return meson_clk_degrees_from_val(val, tph->ph0.width); } static int meson_clk_triphase_set_phase(struct clk_hw *hw, int degrees) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk); unsigned int val; val = meson_clk_degrees_to_val(degrees, tph->ph0.width); meson_parm_write(clk->map, &tph->ph0, val); meson_parm_write(clk->map, &tph->ph1, val); meson_parm_write(clk->map, &tph->ph2, val); return 0; } const struct clk_ops meson_clk_triphase_ops = { .init = meson_clk_triphase_sync, .get_phase = meson_clk_triphase_get_phase, .set_phase = meson_clk_triphase_set_phase, }; EXPORT_SYMBOL_GPL(meson_clk_triphase_ops); /* * This is a special clock for the audio controller. * This drive a bit clock inverter for which the * opposite value of the inverter bit needs to be manually * set into another bit */ static inline struct meson_sclk_ws_inv_data * meson_sclk_ws_inv_data(struct clk_regmap *clk) { return (struct meson_sclk_ws_inv_data *)clk->data; } static int meson_sclk_ws_inv_sync(struct clk_hw *hw) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_sclk_ws_inv_data *tph = meson_sclk_ws_inv_data(clk); unsigned int val; /* Get phase and sync the inverted value to ws */ val = meson_parm_read(clk->map, &tph->ph); meson_parm_write(clk->map, &tph->ws, val ? 0 : 1); return 0; } static int meson_sclk_ws_inv_get_phase(struct clk_hw *hw) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_sclk_ws_inv_data *tph = meson_sclk_ws_inv_data(clk); unsigned int val; val = meson_parm_read(clk->map, &tph->ph); return meson_clk_degrees_from_val(val, tph->ph.width); } static int meson_sclk_ws_inv_set_phase(struct clk_hw *hw, int degrees) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_sclk_ws_inv_data *tph = meson_sclk_ws_inv_data(clk); unsigned int val; val = meson_clk_degrees_to_val(degrees, tph->ph.width); meson_parm_write(clk->map, &tph->ph, val); meson_parm_write(clk->map, &tph->ws, val ? 0 : 1); return 0; } const struct clk_ops meson_sclk_ws_inv_ops = { .init = meson_sclk_ws_inv_sync, .get_phase = meson_sclk_ws_inv_get_phase, .set_phase = meson_sclk_ws_inv_set_phase, }; EXPORT_SYMBOL_GPL(meson_sclk_ws_inv_ops); MODULE_DESCRIPTION("Amlogic phase driver"); MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); MODULE_LICENSE("GPL v2"); |