]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/clk/qcom/gcc-ipq4019.c
Merge branch 'topic/hda' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie...
[karo-tx-linux.git] / drivers / clk / qcom / gcc-ipq4019.c
index 33d09138f5e5b3b3c553a5e7d4f4787feb0b5507..46cb256b4aa23919e47fcf79e60b8e3103d680c0 100644 (file)
@@ -20,6 +20,9 @@
 #include <linux/clk-provider.h>
 #include <linux/regmap.h>
 #include <linux/reset-controller.h>
+#include <linux/math64.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
 
 #include <dt-bindings/clock/qcom,gcc-ipq4019.h>
 
 #include "clk-rcg.h"
 #include "clk-branch.h"
 #include "reset.h"
+#include "clk-regmap-divider.h"
+
+#define to_clk_regmap_div(_hw) container_of(to_clk_regmap(_hw),\
+                                       struct clk_regmap_div, clkr)
+
+#define to_clk_fepll(_hw) container_of(to_clk_regmap_div(_hw),\
+                                               struct clk_fepll, cdiv)
 
 enum {
        P_XO,
@@ -40,6 +50,41 @@ enum {
        P_DDRPLLAPSS,
 };
 
+/*
+ * struct clk_fepll_vco - vco feedback divider corresponds for FEPLL clocks
+ * @fdbkdiv_shift: lowest bit for FDBKDIV
+ * @fdbkdiv_width: number of bits in FDBKDIV
+ * @refclkdiv_shift: lowest bit for REFCLKDIV
+ * @refclkdiv_width: number of bits in REFCLKDIV
+ * @reg: PLL_DIV register address
+ */
+struct clk_fepll_vco {
+       u32 fdbkdiv_shift;
+       u32 fdbkdiv_width;
+       u32 refclkdiv_shift;
+       u32 refclkdiv_width;
+       u32 reg;
+};
+
+/*
+ * struct clk_fepll - clk divider corresponds to FEPLL clocks
+ * @fixed_div: fixed divider value if divider is fixed
+ * @parent_map: map from software's parent index to hardware's src_sel field
+ * @cdiv: divider values for PLL_DIV
+ * @pll_vco: vco feedback divider
+ * @div_table: mapping for actual divider value to register divider value
+ *             in case of non fixed divider
+ * @freq_tbl: frequency table
+ */
+struct clk_fepll {
+       u32 fixed_div;
+       const u8 *parent_map;
+       struct clk_regmap_div cdiv;
+       const struct clk_fepll_vco *pll_vco;
+       const struct clk_div_table *div_table;
+       const struct freq_tbl *freq_tbl;
+};
+
 static struct parent_map gcc_xo_200_500_map[] = {
        { P_XO, 0 },
        { P_FEPLL200, 1 },
@@ -80,7 +125,7 @@ static struct parent_map gcc_xo_sdcc1_500_map[] = {
 
 static const char * const gcc_xo_sdcc1_500[] = {
        "xo",
-       "ddrpll",
+       "ddrpllsdcc",
        "fepll500",
 };
 
@@ -121,6 +166,12 @@ static struct parent_map gcc_xo_ddr_500_200_map[] = {
        {  P_DDRPLLAPSS, 1 },
 };
 
+/*
+ * Contains index for safe clock during APSS freq change.
+ * fepll500 is being used as safe clock so initialize it
+ * with its index in parents list gcc_xo_ddr_500_200.
+ */
+static const int gcc_ipq4019_cpu_safe_parent = 2;
 static const char * const gcc_xo_ddr_500_200[] = {
        "xo",
        "fepll200",
@@ -505,7 +556,7 @@ static const struct freq_tbl ftbl_gcc_sdcc1_apps_clk[] = {
        F(25000000,  P_FEPLL500,                1,  1, 20),
        F(50000000,  P_FEPLL500,                1,  1, 10),
        F(100000000, P_FEPLL500,                1,  1, 5),
-       F(193000000, P_DDRPLL,          1,  0, 0),
+       F(192000000, P_DDRPLL,                  1,  0, 0),
        { }
 };
 
@@ -524,10 +575,20 @@ static struct clk_rcg2  sdcc1_apps_clk_src = {
 };
 
 static const struct freq_tbl ftbl_gcc_apps_clk[] = {
-       F(48000000, P_XO,          1, 0, 0),
+       F(48000000,  P_XO,         1, 0, 0),
        F(200000000, P_FEPLL200,   1, 0, 0),
+       F(384000000, P_DDRPLLAPSS, 1, 0, 0),
+       F(413000000, P_DDRPLLAPSS, 1, 0, 0),
+       F(448000000, P_DDRPLLAPSS, 1, 0, 0),
+       F(488000000, P_DDRPLLAPSS, 1, 0, 0),
        F(500000000, P_FEPLL500,   1, 0, 0),
-       F(626000000, P_DDRPLLAPSS, 1, 0, 0),
+       F(512000000, P_DDRPLLAPSS, 1, 0, 0),
+       F(537000000, P_DDRPLLAPSS, 1, 0, 0),
+       F(565000000, P_DDRPLLAPSS, 1, 0, 0),
+       F(597000000, P_DDRPLLAPSS, 1, 0, 0),
+       F(632000000, P_DDRPLLAPSS, 1, 0, 0),
+       F(672000000, P_DDRPLLAPSS, 1, 0, 0),
+       F(716000000, P_DDRPLLAPSS, 1, 0, 0),
        { }
 };
 
@@ -541,6 +602,7 @@ static struct clk_rcg2 apps_clk_src = {
                .parent_names = gcc_xo_ddr_500_200,
                .num_parents = 4,
                .ops = &clk_rcg2_ops,
+               .flags = CLK_SET_RATE_PARENT,
        },
 };
 
@@ -1154,6 +1216,364 @@ static struct clk_branch gcc_wcss5g_rtc_clk = {
        },
 };
 
+/* Calculates the VCO rate for FEPLL. */
+static u64 clk_fepll_vco_calc_rate(struct clk_fepll *pll_div,
+                                  unsigned long parent_rate)
+{
+       const struct clk_fepll_vco *pll_vco = pll_div->pll_vco;
+       u32 fdbkdiv, refclkdiv, cdiv;
+       u64 vco;
+
+       regmap_read(pll_div->cdiv.clkr.regmap, pll_vco->reg, &cdiv);
+       refclkdiv = (cdiv >> pll_vco->refclkdiv_shift) &
+                   (BIT(pll_vco->refclkdiv_width) - 1);
+       fdbkdiv = (cdiv >> pll_vco->fdbkdiv_shift) &
+                 (BIT(pll_vco->fdbkdiv_width) - 1);
+
+       vco = parent_rate / refclkdiv;
+       vco *= 2;
+       vco *= fdbkdiv;
+
+       return vco;
+}
+
+static const struct clk_fepll_vco gcc_apss_ddrpll_vco = {
+       .fdbkdiv_shift = 16,
+       .fdbkdiv_width = 8,
+       .refclkdiv_shift = 24,
+       .refclkdiv_width = 5,
+       .reg = 0x2e020,
+};
+
+static const struct clk_fepll_vco gcc_fepll_vco = {
+       .fdbkdiv_shift = 16,
+       .fdbkdiv_width = 8,
+       .refclkdiv_shift = 24,
+       .refclkdiv_width = 5,
+       .reg = 0x2f020,
+};
+
+/*
+ * Round rate function for APSS CPU PLL Clock divider.
+ * It looks up the frequency table and returns the next higher frequency
+ * supported in hardware.
+ */
+static long clk_cpu_div_round_rate(struct clk_hw *hw, unsigned long rate,
+                                  unsigned long *p_rate)
+{
+       struct clk_fepll *pll = to_clk_fepll(hw);
+       struct clk_hw *p_hw;
+       const struct freq_tbl *f;
+
+       f = qcom_find_freq(pll->freq_tbl, rate);
+       if (!f)
+               return -EINVAL;
+
+       p_hw = clk_hw_get_parent_by_index(hw, f->src);
+       *p_rate = clk_hw_get_rate(p_hw);
+
+       return f->freq;
+};
+
+/*
+ * Clock set rate function for APSS CPU PLL Clock divider.
+ * It looks up the frequency table and updates the PLL divider to corresponding
+ * divider value.
+ */
+static int clk_cpu_div_set_rate(struct clk_hw *hw, unsigned long rate,
+                               unsigned long parent_rate)
+{
+       struct clk_fepll *pll = to_clk_fepll(hw);
+       const struct freq_tbl *f;
+       u32 mask;
+       int ret;
+
+       f = qcom_find_freq(pll->freq_tbl, rate);
+       if (!f)
+               return -EINVAL;
+
+       mask = (BIT(pll->cdiv.width) - 1) << pll->cdiv.shift;
+       ret = regmap_update_bits(pll->cdiv.clkr.regmap,
+                                pll->cdiv.reg, mask,
+                                f->pre_div << pll->cdiv.shift);
+       /*
+        * There is no status bit which can be checked for successful CPU
+        * divider update operation so using delay for the same.
+        */
+       udelay(1);
+
+       return 0;
+};
+
+/*
+ * Clock frequency calculation function for APSS CPU PLL Clock divider.
+ * This clock divider is nonlinear so this function calculates the actual
+ * divider and returns the output frequency by dividing VCO Frequency
+ * with this actual divider value.
+ */
+static unsigned long
+clk_cpu_div_recalc_rate(struct clk_hw *hw,
+                       unsigned long parent_rate)
+{
+       struct clk_fepll *pll = to_clk_fepll(hw);
+       u32 cdiv, pre_div;
+       u64 rate;
+
+       regmap_read(pll->cdiv.clkr.regmap, pll->cdiv.reg, &cdiv);
+       cdiv = (cdiv >> pll->cdiv.shift) & (BIT(pll->cdiv.width) - 1);
+
+       /*
+        * Some dividers have value in 0.5 fraction so multiply both VCO
+        * frequency(parent_rate) and pre_div with 2 to make integer
+        * calculation.
+        */
+       if (cdiv > 10)
+               pre_div = (cdiv + 1) * 2;
+       else
+               pre_div = cdiv + 12;
+
+       rate = clk_fepll_vco_calc_rate(pll, parent_rate) * 2;
+       do_div(rate, pre_div);
+
+       return rate;
+};
+
+static const struct clk_ops clk_regmap_cpu_div_ops = {
+       .round_rate = clk_cpu_div_round_rate,
+       .set_rate = clk_cpu_div_set_rate,
+       .recalc_rate = clk_cpu_div_recalc_rate,
+};
+
+static const struct freq_tbl ftbl_apss_ddr_pll[] = {
+       { 384000000, P_XO, 0xd, 0, 0 },
+       { 413000000, P_XO, 0xc, 0, 0 },
+       { 448000000, P_XO, 0xb, 0, 0 },
+       { 488000000, P_XO, 0xa, 0, 0 },
+       { 512000000, P_XO, 0x9, 0, 0 },
+       { 537000000, P_XO, 0x8, 0, 0 },
+       { 565000000, P_XO, 0x7, 0, 0 },
+       { 597000000, P_XO, 0x6, 0, 0 },
+       { 632000000, P_XO, 0x5, 0, 0 },
+       { 672000000, P_XO, 0x4, 0, 0 },
+       { 716000000, P_XO, 0x3, 0, 0 },
+       { 768000000, P_XO, 0x2, 0, 0 },
+       { 823000000, P_XO, 0x1, 0, 0 },
+       { 896000000, P_XO, 0x0, 0, 0 },
+       { }
+};
+
+static struct clk_fepll gcc_apss_cpu_plldiv_clk = {
+       .cdiv.reg = 0x2e020,
+       .cdiv.shift = 4,
+       .cdiv.width = 4,
+       .cdiv.clkr = {
+               .enable_reg = 0x2e000,
+               .enable_mask = BIT(0),
+               .hw.init = &(struct clk_init_data){
+                       .name = "ddrpllapss",
+                       .parent_names = (const char *[]){
+                               "xo",
+                       },
+                       .num_parents = 1,
+                       .ops = &clk_regmap_cpu_div_ops,
+               },
+       },
+       .freq_tbl = ftbl_apss_ddr_pll,
+       .pll_vco = &gcc_apss_ddrpll_vco,
+};
+
+/* Calculates the rate for PLL divider.
+ * If the divider value is not fixed then it gets the actual divider value
+ * from divider table. Then, it calculate the clock rate by dividing the
+ * parent rate with actual divider value.
+ */
+static unsigned long
+clk_regmap_clk_div_recalc_rate(struct clk_hw *hw,
+                              unsigned long parent_rate)
+{
+       struct clk_fepll *pll = to_clk_fepll(hw);
+       u32 cdiv, pre_div = 1;
+       u64 rate;
+       const struct clk_div_table *clkt;
+
+       if (pll->fixed_div) {
+               pre_div = pll->fixed_div;
+       } else {
+               regmap_read(pll->cdiv.clkr.regmap, pll->cdiv.reg, &cdiv);
+               cdiv = (cdiv >> pll->cdiv.shift) & (BIT(pll->cdiv.width) - 1);
+
+               for (clkt = pll->div_table; clkt->div; clkt++) {
+                       if (clkt->val == cdiv)
+                               pre_div = clkt->div;
+               }
+       }
+
+       rate = clk_fepll_vco_calc_rate(pll, parent_rate);
+       do_div(rate, pre_div);
+
+       return rate;
+};
+
+static const struct clk_ops clk_fepll_div_ops = {
+       .recalc_rate = clk_regmap_clk_div_recalc_rate,
+};
+
+static struct clk_fepll gcc_apss_sdcc_clk = {
+       .fixed_div = 28,
+       .cdiv.clkr = {
+               .hw.init = &(struct clk_init_data){
+                       .name = "ddrpllsdcc",
+                       .parent_names = (const char *[]){
+                               "xo",
+                       },
+                       .num_parents = 1,
+                       .ops = &clk_fepll_div_ops,
+               },
+       },
+       .pll_vco = &gcc_apss_ddrpll_vco,
+};
+
+static struct clk_fepll gcc_fepll125_clk = {
+       .fixed_div = 32,
+       .cdiv.clkr = {
+               .hw.init = &(struct clk_init_data){
+                       .name = "fepll125",
+                       .parent_names = (const char *[]){
+                               "xo",
+                       },
+                       .num_parents = 1,
+                       .ops = &clk_fepll_div_ops,
+               },
+       },
+       .pll_vco = &gcc_fepll_vco,
+};
+
+static struct clk_fepll gcc_fepll125dly_clk = {
+       .fixed_div = 32,
+       .cdiv.clkr = {
+               .hw.init = &(struct clk_init_data){
+                       .name = "fepll125dly",
+                       .parent_names = (const char *[]){
+                               "xo",
+                       },
+                       .num_parents = 1,
+                       .ops = &clk_fepll_div_ops,
+               },
+       },
+       .pll_vco = &gcc_fepll_vco,
+};
+
+static struct clk_fepll gcc_fepll200_clk = {
+       .fixed_div = 20,
+       .cdiv.clkr = {
+               .hw.init = &(struct clk_init_data){
+                       .name = "fepll200",
+                       .parent_names = (const char *[]){
+                               "xo",
+                       },
+                       .num_parents = 1,
+                       .ops = &clk_fepll_div_ops,
+               },
+       },
+       .pll_vco = &gcc_fepll_vco,
+};
+
+static struct clk_fepll gcc_fepll500_clk = {
+       .fixed_div = 8,
+       .cdiv.clkr = {
+               .hw.init = &(struct clk_init_data){
+                       .name = "fepll500",
+                       .parent_names = (const char *[]){
+                               "xo",
+                       },
+                       .num_parents = 1,
+                       .ops = &clk_fepll_div_ops,
+               },
+       },
+       .pll_vco = &gcc_fepll_vco,
+};
+
+static const struct clk_div_table fepllwcss_clk_div_table[] = {
+       { 0, 15 },
+       { 1, 16 },
+       { 2, 18 },
+       { 3, 20 },
+       { },
+};
+
+static struct clk_fepll gcc_fepllwcss2g_clk = {
+       .cdiv.reg = 0x2f020,
+       .cdiv.shift = 8,
+       .cdiv.width = 2,
+       .cdiv.clkr = {
+               .hw.init = &(struct clk_init_data){
+                       .name = "fepllwcss2g",
+                       .parent_names = (const char *[]){
+                               "xo",
+                       },
+                       .num_parents = 1,
+                       .ops = &clk_fepll_div_ops,
+               },
+       },
+       .div_table = fepllwcss_clk_div_table,
+       .pll_vco = &gcc_fepll_vco,
+};
+
+static struct clk_fepll gcc_fepllwcss5g_clk = {
+       .cdiv.reg = 0x2f020,
+       .cdiv.shift = 12,
+       .cdiv.width = 2,
+       .cdiv.clkr = {
+               .hw.init = &(struct clk_init_data){
+                       .name = "fepllwcss5g",
+                       .parent_names = (const char *[]){
+                               "xo",
+                       },
+                       .num_parents = 1,
+                       .ops = &clk_fepll_div_ops,
+               },
+       },
+       .div_table = fepllwcss_clk_div_table,
+       .pll_vco = &gcc_fepll_vco,
+};
+
+static const struct freq_tbl ftbl_gcc_pcnoc_ahb_clk[] = {
+       F(48000000,  P_XO,       1, 0, 0),
+       F(100000000, P_FEPLL200, 2, 0, 0),
+       { }
+};
+
+static struct clk_rcg2 gcc_pcnoc_ahb_clk_src = {
+       .cmd_rcgr = 0x21024,
+       .hid_width = 5,
+       .parent_map = gcc_xo_200_500_map,
+       .freq_tbl = ftbl_gcc_pcnoc_ahb_clk,
+       .clkr.hw.init = &(struct clk_init_data){
+               .name = "gcc_pcnoc_ahb_clk_src",
+               .parent_names = gcc_xo_200_500,
+               .num_parents = 3,
+               .ops = &clk_rcg2_ops,
+       },
+};
+
+static struct clk_branch pcnoc_clk_src = {
+       .halt_reg = 0x21030,
+       .clkr = {
+               .enable_reg = 0x21030,
+               .enable_mask = BIT(0),
+               .hw.init = &(struct clk_init_data){
+                       .name = "pcnoc_clk_src",
+                       .parent_names = (const char *[]){
+                               "gcc_pcnoc_ahb_clk_src",
+                       },
+                       .num_parents = 1,
+                       .ops = &clk_branch2_ops,
+                       .flags = CLK_SET_RATE_PARENT |
+                               CLK_IS_CRITICAL,
+               },
+       },
+};
+
 static struct clk_regmap *gcc_ipq4019_clocks[] = {
        [AUDIO_CLK_SRC] = &audio_clk_src.clkr,
        [BLSP1_QUP1_I2C_APPS_CLK_SRC] = &blsp1_qup1_i2c_apps_clk_src.clkr,
@@ -1214,6 +1634,16 @@ static struct clk_regmap *gcc_ipq4019_clocks[] = {
        [GCC_WCSS5G_CLK] = &gcc_wcss5g_clk.clkr,
        [GCC_WCSS5G_REF_CLK] = &gcc_wcss5g_ref_clk.clkr,
        [GCC_WCSS5G_RTC_CLK] = &gcc_wcss5g_rtc_clk.clkr,
+       [GCC_SDCC_PLLDIV_CLK] = &gcc_apss_sdcc_clk.cdiv.clkr,
+       [GCC_FEPLL125_CLK] = &gcc_fepll125_clk.cdiv.clkr,
+       [GCC_FEPLL125DLY_CLK] = &gcc_fepll125dly_clk.cdiv.clkr,
+       [GCC_FEPLL200_CLK] = &gcc_fepll200_clk.cdiv.clkr,
+       [GCC_FEPLL500_CLK] = &gcc_fepll500_clk.cdiv.clkr,
+       [GCC_FEPLL_WCSS2G_CLK] = &gcc_fepllwcss2g_clk.cdiv.clkr,
+       [GCC_FEPLL_WCSS5G_CLK] = &gcc_fepllwcss5g_clk.cdiv.clkr,
+       [GCC_APSS_CPU_PLLDIV_CLK] = &gcc_apss_cpu_plldiv_clk.cdiv.clkr,
+       [GCC_PCNOC_AHB_CLK_SRC] = &gcc_pcnoc_ahb_clk_src.clkr,
+       [GCC_PCNOC_AHB_CLK] = &pcnoc_clk_src.clkr,
 };
 
 static const struct qcom_reset_map gcc_ipq4019_resets[] = {
@@ -1294,7 +1724,7 @@ static const struct regmap_config gcc_ipq4019_regmap_config = {
        .reg_bits       = 32,
        .reg_stride     = 4,
        .val_bits       = 32,
-       .max_register   = 0x2dfff,
+       .max_register   = 0x2ffff,
        .fast_io        = true,
 };
 
@@ -1312,23 +1742,44 @@ static const struct of_device_id gcc_ipq4019_match_table[] = {
 };
 MODULE_DEVICE_TABLE(of, gcc_ipq4019_match_table);
 
+static int
+gcc_ipq4019_cpu_clk_notifier_fn(struct notifier_block *nb,
+                               unsigned long action, void *data)
+{
+       int err = 0;
+
+       if (action == PRE_RATE_CHANGE)
+               err = clk_rcg2_ops.set_parent(&apps_clk_src.clkr.hw,
+                                             gcc_ipq4019_cpu_safe_parent);
+
+       return notifier_from_errno(err);
+}
+
+static struct notifier_block gcc_ipq4019_cpu_clk_notifier = {
+       .notifier_call = gcc_ipq4019_cpu_clk_notifier_fn,
+};
+
 static int gcc_ipq4019_probe(struct platform_device *pdev)
 {
-       struct device *dev = &pdev->dev;
+       int err;
 
-       clk_register_fixed_rate(dev, "fepll125", "xo", 0, 200000000);
-       clk_register_fixed_rate(dev, "fepll125dly", "xo", 0, 200000000);
-       clk_register_fixed_rate(dev, "fepllwcss2g", "xo", 0, 200000000);
-       clk_register_fixed_rate(dev, "fepllwcss5g", "xo", 0, 200000000);
-       clk_register_fixed_rate(dev, "fepll200", "xo", 0, 200000000);
-       clk_register_fixed_rate(dev, "fepll500", "xo", 0, 200000000);
-       clk_register_fixed_rate(dev, "ddrpllapss", "xo", 0, 666000000);
+       err = qcom_cc_probe(pdev, &gcc_ipq4019_desc);
+       if (err)
+               return err;
 
-       return qcom_cc_probe(pdev, &gcc_ipq4019_desc);
+       return clk_notifier_register(apps_clk_src.clkr.hw.clk,
+                                    &gcc_ipq4019_cpu_clk_notifier);
+}
+
+static int gcc_ipq4019_remove(struct platform_device *pdev)
+{
+       return clk_notifier_unregister(apps_clk_src.clkr.hw.clk,
+                                      &gcc_ipq4019_cpu_clk_notifier);
 }
 
 static struct platform_driver gcc_ipq4019_driver = {
        .probe          = gcc_ipq4019_probe,
+       .remove         = gcc_ipq4019_remove,
        .driver         = {
                .name   = "qcom,gcc-ipq4019",
                .of_match_table = gcc_ipq4019_match_table,