Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

clk: bcm: Add driver for BCM53573 ILP clock

This clock is present on BCM53573 devices (including BCM47189) that use
Cortex-A7. ILP is a part of PMU (Power Management Unit) multi-function
device so we use syscon (and regmap) for it.

Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
Acked-by: Rob Herring <robh@kernel.org>
[sboyd@codeaurora.org: Remove 0 from clk_init_data to silence sparse]
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>

authored by

Rafał Miłecki and committed by
Stephen Boyd
bd8dd593 dc19b6f5

+185
+36
Documentation/devicetree/bindings/clock/brcm,bcm53573-ilp.txt
··· 1 + Broadcom BCM53573 ILP clock 2 + =========================== 3 + 4 + This binding uses the common clock binding: 5 + Documentation/devicetree/bindings/clock/clock-bindings.txt 6 + 7 + This binding is used for ILP clock (sometimes referred as "slow clock") 8 + on Broadcom BCM53573 devices using Cortex-A7 CPU. 9 + 10 + ILP's rate has to be calculated on runtime and it depends on ALP clock 11 + which has to be referenced. 12 + 13 + This clock is part of PMU (Power Management Unit), a Broadcom's device 14 + handing power-related aspects. Its node must be sub-node of the PMU 15 + device. 16 + 17 + Required properties: 18 + - compatible: "brcm,bcm53573-ilp" 19 + - clocks: has to reference an ALP clock 20 + - #clock-cells: should be <0> 21 + - clock-output-names: from common clock bindings, should contain clock 22 + name 23 + 24 + Example: 25 + 26 + pmu@18012000 { 27 + compatible = "simple-mfd", "syscon"; 28 + reg = <0x18012000 0x00001000>; 29 + 30 + ilp { 31 + compatible = "brcm,bcm53573-ilp"; 32 + clocks = <&alp>; 33 + #clock-cells = <0>; 34 + clock-output-names = "ilp"; 35 + }; 36 + };
+1
drivers/clk/bcm/Makefile
··· 6 6 obj-$(CONFIG_COMMON_CLK_IPROC) += clk-iproc-armpll.o clk-iproc-pll.o clk-iproc-asiu.o 7 7 obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835.o 8 8 obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835-aux.o 9 + obj-$(CONFIG_ARCH_BCM_53573) += clk-bcm53573-ilp.o 9 10 obj-$(CONFIG_CLK_BCM_CYGNUS) += clk-cygnus.o 10 11 obj-$(CONFIG_CLK_BCM_NSP) += clk-nsp.o 11 12 obj-$(CONFIG_CLK_BCM_NS2) += clk-ns2.o
+148
drivers/clk/bcm/clk-bcm53573-ilp.c
··· 1 + /* 2 + * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl> 3 + * 4 + * This program is free software; you can redistribute it and/or modify 5 + * it under the terms of the GNU General Public License version 2 as 6 + * published by the Free Software Foundation. 7 + */ 8 + 9 + #include <linux/clk-provider.h> 10 + #include <linux/err.h> 11 + #include <linux/io.h> 12 + #include <linux/mfd/syscon.h> 13 + #include <linux/of.h> 14 + #include <linux/of_address.h> 15 + #include <linux/regmap.h> 16 + #include <linux/slab.h> 17 + 18 + #define PMU_XTAL_FREQ_RATIO 0x66c 19 + #define XTAL_ALP_PER_4ILP 0x00001fff 20 + #define XTAL_CTL_EN 0x80000000 21 + #define PMU_SLOW_CLK_PERIOD 0x6dc 22 + 23 + struct bcm53573_ilp { 24 + struct clk_hw hw; 25 + struct regmap *regmap; 26 + }; 27 + 28 + static int bcm53573_ilp_enable(struct clk_hw *hw) 29 + { 30 + struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw); 31 + 32 + regmap_write(ilp->regmap, PMU_SLOW_CLK_PERIOD, 0x10199); 33 + regmap_write(ilp->regmap, 0x674, 0x10000); 34 + 35 + return 0; 36 + } 37 + 38 + static void bcm53573_ilp_disable(struct clk_hw *hw) 39 + { 40 + struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw); 41 + 42 + regmap_write(ilp->regmap, PMU_SLOW_CLK_PERIOD, 0); 43 + regmap_write(ilp->regmap, 0x674, 0); 44 + } 45 + 46 + static unsigned long bcm53573_ilp_recalc_rate(struct clk_hw *hw, 47 + unsigned long parent_rate) 48 + { 49 + struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw); 50 + struct regmap *regmap = ilp->regmap; 51 + u32 last_val, cur_val; 52 + int sum = 0, num = 0, loop_num = 0; 53 + int avg; 54 + 55 + /* Enable measurement */ 56 + regmap_write(regmap, PMU_XTAL_FREQ_RATIO, XTAL_CTL_EN); 57 + 58 + /* Read initial value */ 59 + regmap_read(regmap, PMU_XTAL_FREQ_RATIO, &last_val); 60 + last_val &= XTAL_ALP_PER_4ILP; 61 + 62 + /* 63 + * At minimum we should loop for a bit to let hardware do the 64 + * measurement. This isn't very accurate however, so for a better 65 + * precision lets try getting 20 different values for and use average. 66 + */ 67 + while (num < 20) { 68 + regmap_read(regmap, PMU_XTAL_FREQ_RATIO, &cur_val); 69 + cur_val &= XTAL_ALP_PER_4ILP; 70 + 71 + if (cur_val != last_val) { 72 + /* Got different value, use it */ 73 + sum += cur_val; 74 + num++; 75 + loop_num = 0; 76 + last_val = cur_val; 77 + } else if (++loop_num > 5000) { 78 + /* Same value over and over, give up */ 79 + sum += cur_val; 80 + num++; 81 + break; 82 + } 83 + 84 + cpu_relax(); 85 + } 86 + 87 + /* Disable measurement to save power */ 88 + regmap_write(regmap, PMU_XTAL_FREQ_RATIO, 0x0); 89 + 90 + avg = sum / num; 91 + 92 + return parent_rate * 4 / avg; 93 + } 94 + 95 + static const struct clk_ops bcm53573_ilp_clk_ops = { 96 + .enable = bcm53573_ilp_enable, 97 + .disable = bcm53573_ilp_disable, 98 + .recalc_rate = bcm53573_ilp_recalc_rate, 99 + }; 100 + 101 + static void bcm53573_ilp_init(struct device_node *np) 102 + { 103 + struct bcm53573_ilp *ilp; 104 + struct clk_init_data init = { }; 105 + const char *parent_name; 106 + int err; 107 + 108 + ilp = kzalloc(sizeof(*ilp), GFP_KERNEL); 109 + if (!ilp) 110 + return; 111 + 112 + parent_name = of_clk_get_parent_name(np, 0); 113 + if (!parent_name) { 114 + err = -ENOENT; 115 + goto err_free_ilp; 116 + } 117 + 118 + ilp->regmap = syscon_node_to_regmap(of_get_parent(np)); 119 + if (IS_ERR(ilp->regmap)) { 120 + err = PTR_ERR(ilp->regmap); 121 + goto err_free_ilp; 122 + } 123 + 124 + init.name = np->name; 125 + init.ops = &bcm53573_ilp_clk_ops; 126 + init.parent_names = &parent_name; 127 + init.num_parents = 1; 128 + 129 + ilp->hw.init = &init; 130 + err = clk_hw_register(NULL, &ilp->hw); 131 + if (err) 132 + goto err_free_ilp; 133 + 134 + err = of_clk_add_hw_provider(np, of_clk_hw_simple_get, &ilp->hw); 135 + if (err) 136 + goto err_clk_hw_unregister; 137 + 138 + return; 139 + 140 + err_clk_hw_unregister: 141 + clk_hw_unregister(&ilp->hw); 142 + err_free_ilp: 143 + kfree(ilp); 144 + pr_err("Failed to init ILP clock: %d\n", err); 145 + } 146 + 147 + /* We need it very early for arch code, before device model gets ready */ 148 + CLK_OF_DECLARE(bcm53573_ilp_clk, "brcm,bcm53573-ilp", bcm53573_ilp_init);