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