linux/drivers/clk/imgtec/clk-boston.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright (C) 2016-2017 Imagination Technologies
   4 * Author: Paul Burton <paul.burton@mips.com>
   5 */
   6
   7#define pr_fmt(fmt) "clk-boston: " fmt
   8
   9#include <linux/clk-provider.h>
  10#include <linux/kernel.h>
  11#include <linux/of.h>
  12#include <linux/regmap.h>
  13#include <linux/slab.h>
  14#include <linux/mfd/syscon.h>
  15
  16#include <dt-bindings/clock/boston-clock.h>
  17
  18#define BOSTON_PLAT_MMCMDIV             0x30
  19# define BOSTON_PLAT_MMCMDIV_CLK0DIV    (0xff << 0)
  20# define BOSTON_PLAT_MMCMDIV_INPUT      (0xff << 8)
  21# define BOSTON_PLAT_MMCMDIV_MUL        (0xff << 16)
  22# define BOSTON_PLAT_MMCMDIV_CLK1DIV    (0xff << 24)
  23
  24#define BOSTON_CLK_COUNT 3
  25
  26static u32 ext_field(u32 val, u32 mask)
  27{
  28        return (val & mask) >> (ffs(mask) - 1);
  29}
  30
  31static void __init clk_boston_setup(struct device_node *np)
  32{
  33        unsigned long in_freq, cpu_freq, sys_freq;
  34        uint mmcmdiv, mul, cpu_div, sys_div;
  35        struct clk_hw_onecell_data *onecell;
  36        struct regmap *regmap;
  37        struct clk_hw *hw;
  38        int err;
  39
  40        regmap = syscon_node_to_regmap(np->parent);
  41        if (IS_ERR(regmap)) {
  42                pr_err("failed to find regmap\n");
  43                return;
  44        }
  45
  46        err = regmap_read(regmap, BOSTON_PLAT_MMCMDIV, &mmcmdiv);
  47        if (err) {
  48                pr_err("failed to read mmcm_div register: %d\n", err);
  49                return;
  50        }
  51
  52        in_freq = ext_field(mmcmdiv, BOSTON_PLAT_MMCMDIV_INPUT) * 1000000;
  53        mul = ext_field(mmcmdiv, BOSTON_PLAT_MMCMDIV_MUL);
  54
  55        sys_div = ext_field(mmcmdiv, BOSTON_PLAT_MMCMDIV_CLK0DIV);
  56        sys_freq = mult_frac(in_freq, mul, sys_div);
  57
  58        cpu_div = ext_field(mmcmdiv, BOSTON_PLAT_MMCMDIV_CLK1DIV);
  59        cpu_freq = mult_frac(in_freq, mul, cpu_div);
  60
  61        onecell = kzalloc(struct_size(onecell, hws, BOSTON_CLK_COUNT),
  62                          GFP_KERNEL);
  63        if (!onecell)
  64                return;
  65
  66        onecell->num = BOSTON_CLK_COUNT;
  67
  68        hw = clk_hw_register_fixed_rate(NULL, "input", NULL, 0, in_freq);
  69        if (IS_ERR(hw)) {
  70                pr_err("failed to register input clock: %ld\n", PTR_ERR(hw));
  71                goto fail_input;
  72        }
  73        onecell->hws[BOSTON_CLK_INPUT] = hw;
  74
  75        hw = clk_hw_register_fixed_rate(NULL, "sys", "input", 0, sys_freq);
  76        if (IS_ERR(hw)) {
  77                pr_err("failed to register sys clock: %ld\n", PTR_ERR(hw));
  78                goto fail_sys;
  79        }
  80        onecell->hws[BOSTON_CLK_SYS] = hw;
  81
  82        hw = clk_hw_register_fixed_rate(NULL, "cpu", "input", 0, cpu_freq);
  83        if (IS_ERR(hw)) {
  84                pr_err("failed to register cpu clock: %ld\n", PTR_ERR(hw));
  85                goto fail_cpu;
  86        }
  87        onecell->hws[BOSTON_CLK_CPU] = hw;
  88
  89        err = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, onecell);
  90        if (err) {
  91                pr_err("failed to add DT provider: %d\n", err);
  92                goto fail_clk_add;
  93        }
  94
  95        return;
  96
  97fail_clk_add:
  98        clk_hw_unregister_fixed_rate(onecell->hws[BOSTON_CLK_CPU]);
  99fail_cpu:
 100        clk_hw_unregister_fixed_rate(onecell->hws[BOSTON_CLK_SYS]);
 101fail_sys:
 102        clk_hw_unregister_fixed_rate(onecell->hws[BOSTON_CLK_INPUT]);
 103fail_input:
 104        kfree(onecell);
 105}
 106
 107/*
 108 * Use CLK_OF_DECLARE so that this driver is probed early enough to provide the
 109 * CPU frequency for use with the GIC or cop0 counters/timers.
 110 */
 111CLK_OF_DECLARE(clk_boston, "img,boston-clock", clk_boston_setup);
 112