Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl>
0004  */
0005 
0006 #include <linux/clk-provider.h>
0007 #include <linux/err.h>
0008 #include <linux/io.h>
0009 #include <linux/mfd/syscon.h>
0010 #include <linux/of.h>
0011 #include <linux/of_address.h>
0012 #include <linux/regmap.h>
0013 #include <linux/slab.h>
0014 
0015 #define PMU_XTAL_FREQ_RATIO         0x66c
0016 #define  XTAL_ALP_PER_4ILP          0x00001fff
0017 #define  XTAL_CTL_EN                0x80000000
0018 #define PMU_SLOW_CLK_PERIOD         0x6dc
0019 
0020 struct bcm53573_ilp {
0021     struct clk_hw hw;
0022     struct regmap *regmap;
0023 };
0024 
0025 static int bcm53573_ilp_enable(struct clk_hw *hw)
0026 {
0027     struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw);
0028 
0029     regmap_write(ilp->regmap, PMU_SLOW_CLK_PERIOD, 0x10199);
0030     regmap_write(ilp->regmap, 0x674, 0x10000);
0031 
0032     return 0;
0033 }
0034 
0035 static void bcm53573_ilp_disable(struct clk_hw *hw)
0036 {
0037     struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw);
0038 
0039     regmap_write(ilp->regmap, PMU_SLOW_CLK_PERIOD, 0);
0040     regmap_write(ilp->regmap, 0x674, 0);
0041 }
0042 
0043 static unsigned long bcm53573_ilp_recalc_rate(struct clk_hw *hw,
0044                           unsigned long parent_rate)
0045 {
0046     struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw);
0047     struct regmap *regmap = ilp->regmap;
0048     u32 last_val, cur_val;
0049     int sum = 0, num = 0, loop_num = 0;
0050     int avg;
0051 
0052     /* Enable measurement */
0053     regmap_write(regmap, PMU_XTAL_FREQ_RATIO, XTAL_CTL_EN);
0054 
0055     /* Read initial value */
0056     regmap_read(regmap, PMU_XTAL_FREQ_RATIO, &last_val);
0057     last_val &= XTAL_ALP_PER_4ILP;
0058 
0059     /*
0060      * At minimum we should loop for a bit to let hardware do the
0061      * measurement. This isn't very accurate however, so for a better
0062      * precision lets try getting 20 different values for and use average.
0063      */
0064     while (num < 20) {
0065         regmap_read(regmap, PMU_XTAL_FREQ_RATIO, &cur_val);
0066         cur_val &= XTAL_ALP_PER_4ILP;
0067 
0068         if (cur_val != last_val) {
0069             /* Got different value, use it */
0070             sum += cur_val;
0071             num++;
0072             loop_num = 0;
0073             last_val = cur_val;
0074         } else if (++loop_num > 5000) {
0075             /* Same value over and over, give up */
0076             sum += cur_val;
0077             num++;
0078             break;
0079         }
0080 
0081         cpu_relax();
0082     }
0083 
0084     /* Disable measurement to save power */
0085     regmap_write(regmap, PMU_XTAL_FREQ_RATIO, 0x0);
0086 
0087     avg = sum / num;
0088 
0089     return parent_rate * 4 / avg;
0090 }
0091 
0092 static const struct clk_ops bcm53573_ilp_clk_ops = {
0093     .enable = bcm53573_ilp_enable,
0094     .disable = bcm53573_ilp_disable,
0095     .recalc_rate = bcm53573_ilp_recalc_rate,
0096 };
0097 
0098 static void bcm53573_ilp_init(struct device_node *np)
0099 {
0100     struct bcm53573_ilp *ilp;
0101     struct clk_init_data init = { };
0102     const char *parent_name;
0103     int err;
0104 
0105     ilp = kzalloc(sizeof(*ilp), GFP_KERNEL);
0106     if (!ilp)
0107         return;
0108 
0109     parent_name = of_clk_get_parent_name(np, 0);
0110     if (!parent_name) {
0111         err = -ENOENT;
0112         goto err_free_ilp;
0113     }
0114 
0115     ilp->regmap = syscon_node_to_regmap(of_get_parent(np));
0116     if (IS_ERR(ilp->regmap)) {
0117         err = PTR_ERR(ilp->regmap);
0118         goto err_free_ilp;
0119     }
0120 
0121     init.name = np->name;
0122     init.ops = &bcm53573_ilp_clk_ops;
0123     init.parent_names = &parent_name;
0124     init.num_parents = 1;
0125 
0126     ilp->hw.init = &init;
0127     err = clk_hw_register(NULL, &ilp->hw);
0128     if (err)
0129         goto err_free_ilp;
0130 
0131     err = of_clk_add_hw_provider(np, of_clk_hw_simple_get, &ilp->hw);
0132     if (err)
0133         goto err_clk_hw_unregister;
0134 
0135     return;
0136 
0137 err_clk_hw_unregister:
0138     clk_hw_unregister(&ilp->hw);
0139 err_free_ilp:
0140     kfree(ilp);
0141     pr_err("Failed to init ILP clock: %d\n", err);
0142 }
0143 
0144 /* We need it very early for arch code, before device model gets ready */
0145 CLK_OF_DECLARE(bcm53573_ilp_clk, "brcm,bcm53573-ilp", bcm53573_ilp_init);