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 26 static u32 ext_field(u32 val, u32 mask) 27 { 28 return (val & mask) >> (ffs(mask) - 1); 29 } 30 31 static 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 97 fail_clk_add: 98 clk_hw_unregister_fixed_rate(onecell->hws[BOSTON_CLK_CPU]); 99 fail_cpu: 100 clk_hw_unregister_fixed_rate(onecell->hws[BOSTON_CLK_SYS]); 101 fail_sys: 102 clk_hw_unregister_fixed_rate(onecell->hws[BOSTON_CLK_INPUT]); 103 fail_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 */ 111 CLK_OF_DECLARE(clk_boston, "img,boston-clock", clk_boston_setup); 112