linux/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright (C) 2016 Free Electrons
   4 * Copyright (C) 2016 NextThing Co
   5 *
   6 * Maxime Ripard <maxime.ripard@free-electrons.com>
   7 */
   8
   9#include <linux/clk-provider.h>
  10#include <linux/regmap.h>
  11
  12#include "sun4i_hdmi.h"
  13
  14struct sun4i_ddc {
  15        struct clk_hw           hw;
  16        struct sun4i_hdmi       *hdmi;
  17        struct regmap_field     *reg;
  18        u8                      pre_div;
  19        u8                      m_offset;
  20};
  21
  22static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
  23{
  24        return container_of(hw, struct sun4i_ddc, hw);
  25}
  26
  27static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
  28                                            unsigned long parent_rate,
  29                                            const u8 pre_div,
  30                                            const u8 m_offset,
  31                                            u8 *m, u8 *n)
  32{
  33        unsigned long best_rate = 0;
  34        u8 best_m = 0, best_n = 0, _m, _n;
  35
  36        for (_m = 0; _m < 16; _m++) {
  37                for (_n = 0; _n < 8; _n++) {
  38                        unsigned long tmp_rate;
  39
  40                        tmp_rate = (((parent_rate / pre_div) / 10) >> _n) /
  41                                (_m + m_offset);
  42
  43                        if (tmp_rate > rate)
  44                                continue;
  45
  46                        if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
  47                                best_rate = tmp_rate;
  48                                best_m = _m;
  49                                best_n = _n;
  50                        }
  51                }
  52        }
  53
  54        if (m && n) {
  55                *m = best_m;
  56                *n = best_n;
  57        }
  58
  59        return best_rate;
  60}
  61
  62static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
  63                                 unsigned long *prate)
  64{
  65        struct sun4i_ddc *ddc = hw_to_ddc(hw);
  66
  67        return sun4i_ddc_calc_divider(rate, *prate, ddc->pre_div,
  68                                      ddc->m_offset, NULL, NULL);
  69}
  70
  71static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
  72                                            unsigned long parent_rate)
  73{
  74        struct sun4i_ddc *ddc = hw_to_ddc(hw);
  75        unsigned int reg;
  76        u8 m, n;
  77
  78        regmap_field_read(ddc->reg, &reg);
  79        m = (reg >> 3) & 0xf;
  80        n = reg & 0x7;
  81
  82        return (((parent_rate / ddc->pre_div) / 10) >> n) /
  83               (m + ddc->m_offset);
  84}
  85
  86static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
  87                              unsigned long parent_rate)
  88{
  89        struct sun4i_ddc *ddc = hw_to_ddc(hw);
  90        u8 div_m, div_n;
  91
  92        sun4i_ddc_calc_divider(rate, parent_rate, ddc->pre_div,
  93                               ddc->m_offset, &div_m, &div_n);
  94
  95        regmap_field_write(ddc->reg,
  96                           SUN4I_HDMI_DDC_CLK_M(div_m) |
  97                           SUN4I_HDMI_DDC_CLK_N(div_n));
  98
  99        return 0;
 100}
 101
 102static const struct clk_ops sun4i_ddc_ops = {
 103        .recalc_rate    = sun4i_ddc_recalc_rate,
 104        .round_rate     = sun4i_ddc_round_rate,
 105        .set_rate       = sun4i_ddc_set_rate,
 106};
 107
 108int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
 109{
 110        struct clk_init_data init;
 111        struct sun4i_ddc *ddc;
 112        const char *parent_name;
 113
 114        parent_name = __clk_get_name(parent);
 115        if (!parent_name)
 116                return -ENODEV;
 117
 118        ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
 119        if (!ddc)
 120                return -ENOMEM;
 121
 122        ddc->reg = devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
 123                                           hdmi->variant->ddc_clk_reg);
 124        if (IS_ERR(ddc->reg))
 125                return PTR_ERR(ddc->reg);
 126
 127        init.name = "hdmi-ddc";
 128        init.ops = &sun4i_ddc_ops;
 129        init.parent_names = &parent_name;
 130        init.num_parents = 1;
 131
 132        ddc->hdmi = hdmi;
 133        ddc->hw.init = &init;
 134        ddc->pre_div = hdmi->variant->ddc_clk_pre_divider;
 135        ddc->m_offset = hdmi->variant->ddc_clk_m_offset;
 136
 137        hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
 138        if (IS_ERR(hdmi->ddc_clk))
 139                return PTR_ERR(hdmi->ddc_clk);
 140
 141        return 0;
 142}
 143