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