linux/drivers/clk/meson/clk-phase.c
<<
>>
Prefs
   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 rounded
  32         * 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_GPL(meson_clk_phase_ops);
  65
  66/*
  67 * This is a special clock for the audio controller.
  68 * The phase of mst_sclk clock output can be controlled independently
  69 * for the outside world (ph0), the tdmout (ph1) and tdmin (ph2).
  70 * Controlling these 3 phases as just one makes things simpler and
  71 * 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 block
  73 * 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_GPL(meson_clk_triphase_ops);
 127
 128/*
 129 * This is a special clock for the audio controller.
 130 * This drive a bit clock inverter for which the
 131 * opposite value of the inverter bit needs to be manually
 132 * set into another bit
 133 */
 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_GPL(meson_sclk_ws_inv_ops);
 182
 183
 184MODULE_DESCRIPTION("Amlogic phase driver");
 185MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
 186MODULE_LICENSE("GPL v2");
 187