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 void 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
  93static int meson_clk_triphase_get_phase(struct clk_hw *hw)
  94{
  95        struct clk_regmap *clk = to_clk_regmap(hw);
  96        struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk);
  97        unsigned int val;
  98
  99        /* Phase are in sync, reading phase 0 is enough */
 100        val = meson_parm_read(clk->map, &tph->ph0);
 101
 102        return meson_clk_degrees_from_val(val, tph->ph0.width);
 103}
 104
 105static int meson_clk_triphase_set_phase(struct clk_hw *hw, int degrees)
 106{
 107        struct clk_regmap *clk = to_clk_regmap(hw);
 108        struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk);
 109        unsigned int val;
 110
 111        val = meson_clk_degrees_to_val(degrees, tph->ph0.width);
 112        meson_parm_write(clk->map, &tph->ph0, val);
 113        meson_parm_write(clk->map, &tph->ph1, val);
 114        meson_parm_write(clk->map, &tph->ph2, val);
 115
 116        return 0;
 117}
 118
 119const struct clk_ops meson_clk_triphase_ops = {
 120        .init           = meson_clk_triphase_sync,
 121        .get_phase      = meson_clk_triphase_get_phase,
 122        .set_phase      = meson_clk_triphase_set_phase,
 123};
 124EXPORT_SYMBOL_GPL(meson_clk_triphase_ops);
 125
 126MODULE_DESCRIPTION("Amlogic phase driver");
 127MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
 128MODULE_LICENSE("GPL v2");
 129