linux/drivers/clk/bcm/clk-bcm53573-ilp.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl>
   4 */
   5
   6#include <linux/clk-provider.h>
   7#include <linux/err.h>
   8#include <linux/io.h>
   9#include <linux/mfd/syscon.h>
  10#include <linux/of.h>
  11#include <linux/of_address.h>
  12#include <linux/regmap.h>
  13#include <linux/slab.h>
  14
  15#define PMU_XTAL_FREQ_RATIO                     0x66c
  16#define  XTAL_ALP_PER_4ILP                      0x00001fff
  17#define  XTAL_CTL_EN                            0x80000000
  18#define PMU_SLOW_CLK_PERIOD                     0x6dc
  19
  20struct bcm53573_ilp {
  21        struct clk_hw hw;
  22        struct regmap *regmap;
  23};
  24
  25static int bcm53573_ilp_enable(struct clk_hw *hw)
  26{
  27        struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw);
  28
  29        regmap_write(ilp->regmap, PMU_SLOW_CLK_PERIOD, 0x10199);
  30        regmap_write(ilp->regmap, 0x674, 0x10000);
  31
  32        return 0;
  33}
  34
  35static void bcm53573_ilp_disable(struct clk_hw *hw)
  36{
  37        struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw);
  38
  39        regmap_write(ilp->regmap, PMU_SLOW_CLK_PERIOD, 0);
  40        regmap_write(ilp->regmap, 0x674, 0);
  41}
  42
  43static unsigned long bcm53573_ilp_recalc_rate(struct clk_hw *hw,
  44                                              unsigned long parent_rate)
  45{
  46        struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw);
  47        struct regmap *regmap = ilp->regmap;
  48        u32 last_val, cur_val;
  49        int sum = 0, num = 0, loop_num = 0;
  50        int avg;
  51
  52        /* Enable measurement */
  53        regmap_write(regmap, PMU_XTAL_FREQ_RATIO, XTAL_CTL_EN);
  54
  55        /* Read initial value */
  56        regmap_read(regmap, PMU_XTAL_FREQ_RATIO, &last_val);
  57        last_val &= XTAL_ALP_PER_4ILP;
  58
  59        /*
  60         * At minimum we should loop for a bit to let hardware do the
  61         * measurement. This isn't very accurate however, so for a better
  62         * precision lets try getting 20 different values for and use average.
  63         */
  64        while (num < 20) {
  65                regmap_read(regmap, PMU_XTAL_FREQ_RATIO, &cur_val);
  66                cur_val &= XTAL_ALP_PER_4ILP;
  67
  68                if (cur_val != last_val) {
  69                        /* Got different value, use it */
  70                        sum += cur_val;
  71                        num++;
  72                        loop_num = 0;
  73                        last_val = cur_val;
  74                } else if (++loop_num > 5000) {
  75                        /* Same value over and over, give up */
  76                        sum += cur_val;
  77                        num++;
  78                        break;
  79                }
  80
  81                cpu_relax();
  82        }
  83
  84        /* Disable measurement to save power */
  85        regmap_write(regmap, PMU_XTAL_FREQ_RATIO, 0x0);
  86
  87        avg = sum / num;
  88
  89        return parent_rate * 4 / avg;
  90}
  91
  92static const struct clk_ops bcm53573_ilp_clk_ops = {
  93        .enable = bcm53573_ilp_enable,
  94        .disable = bcm53573_ilp_disable,
  95        .recalc_rate = bcm53573_ilp_recalc_rate,
  96};
  97
  98static void bcm53573_ilp_init(struct device_node *np)
  99{
 100        struct bcm53573_ilp *ilp;
 101        struct clk_init_data init = { };
 102        const char *parent_name;
 103        int err;
 104
 105        ilp = kzalloc(sizeof(*ilp), GFP_KERNEL);
 106        if (!ilp)
 107                return;
 108
 109        parent_name = of_clk_get_parent_name(np, 0);
 110        if (!parent_name) {
 111                err = -ENOENT;
 112                goto err_free_ilp;
 113        }
 114
 115        ilp->regmap = syscon_node_to_regmap(of_get_parent(np));
 116        if (IS_ERR(ilp->regmap)) {
 117                err = PTR_ERR(ilp->regmap);
 118                goto err_free_ilp;
 119        }
 120
 121        init.name = np->name;
 122        init.ops = &bcm53573_ilp_clk_ops;
 123        init.parent_names = &parent_name;
 124        init.num_parents = 1;
 125
 126        ilp->hw.init = &init;
 127        err = clk_hw_register(NULL, &ilp->hw);
 128        if (err)
 129                goto err_free_ilp;
 130
 131        err = of_clk_add_hw_provider(np, of_clk_hw_simple_get, &ilp->hw);
 132        if (err)
 133                goto err_clk_hw_unregister;
 134
 135        return;
 136
 137err_clk_hw_unregister:
 138        clk_hw_unregister(&ilp->hw);
 139err_free_ilp:
 140        kfree(ilp);
 141        pr_err("Failed to init ILP clock: %d\n", err);
 142}
 143
 144/* We need it very early for arch code, before device model gets ready */
 145CLK_OF_DECLARE(bcm53573_ilp_clk, "brcm,bcm53573-ilp", bcm53573_ilp_init);
 146