Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Microchip Sparx5 SoC Clock driver.
0004  *
0005  * Copyright (c) 2019 Microchip Inc.
0006  *
0007  * Author: Lars Povlsen <lars.povlsen@microchip.com>
0008  */
0009 
0010 #include <linux/io.h>
0011 #include <linux/module.h>
0012 #include <linux/clk-provider.h>
0013 #include <linux/bitfield.h>
0014 #include <linux/of.h>
0015 #include <linux/slab.h>
0016 #include <linux/platform_device.h>
0017 #include <dt-bindings/clock/microchip,sparx5.h>
0018 
0019 #define PLL_DIV     GENMASK(7, 0)
0020 #define PLL_PRE_DIV GENMASK(10, 8)
0021 #define PLL_ROT_DIR BIT(11)
0022 #define PLL_ROT_SEL GENMASK(13, 12)
0023 #define PLL_ROT_ENA BIT(14)
0024 #define PLL_CLK_ENA BIT(15)
0025 
0026 #define MAX_SEL 4
0027 #define MAX_PRE BIT(3)
0028 
0029 static const u8 sel_rates[MAX_SEL] = { 0, 2*8, 2*4, 2*2 };
0030 
0031 static const char *clk_names[N_CLOCKS] = {
0032     "core", "ddr", "cpu2", "arm2",
0033     "aux1", "aux2", "aux3", "aux4",
0034     "synce",
0035 };
0036 
0037 struct s5_hw_clk {
0038     struct clk_hw hw;
0039     void __iomem *reg;
0040 };
0041 
0042 struct s5_clk_data {
0043     void __iomem *base;
0044     struct s5_hw_clk s5_hw[N_CLOCKS];
0045 };
0046 
0047 struct s5_pll_conf {
0048     unsigned long freq;
0049     u8 div;
0050     bool rot_ena;
0051     u8 rot_sel;
0052     u8 rot_dir;
0053     u8 pre_div;
0054 };
0055 
0056 #define to_s5_pll(hw) container_of(hw, struct s5_hw_clk, hw)
0057 
0058 static unsigned long s5_calc_freq(unsigned long parent_rate,
0059                   const struct s5_pll_conf *conf)
0060 {
0061     unsigned long rate = parent_rate / conf->div;
0062 
0063     if (conf->rot_ena) {
0064         int sign = conf->rot_dir ? -1 : 1;
0065         int divt = sel_rates[conf->rot_sel] * (1 + conf->pre_div);
0066         int divb = divt + sign;
0067 
0068         rate = mult_frac(rate, divt, divb);
0069         rate = roundup(rate, 1000);
0070     }
0071 
0072     return rate;
0073 }
0074 
0075 static void s5_search_fractional(unsigned long rate,
0076                  unsigned long parent_rate,
0077                  int div,
0078                  struct s5_pll_conf *conf)
0079 {
0080     struct s5_pll_conf best;
0081     ulong cur_offset, best_offset = rate;
0082     int d, i, j;
0083 
0084     memset(conf, 0, sizeof(*conf));
0085     conf->div = div;
0086     conf->rot_ena = 1;  /* Fractional rate */
0087 
0088     for (d = 0; best_offset > 0 && d <= 1 ; d++) {
0089         conf->rot_dir = !!d;
0090         for (i = 0; best_offset > 0 && i < MAX_PRE; i++) {
0091             conf->pre_div = i;
0092             for (j = 1; best_offset > 0 && j < MAX_SEL; j++) {
0093                 conf->rot_sel = j;
0094                 conf->freq = s5_calc_freq(parent_rate, conf);
0095                 cur_offset = abs(rate - conf->freq);
0096                 if (cur_offset < best_offset) {
0097                     best_offset = cur_offset;
0098                     best = *conf;
0099                 }
0100             }
0101         }
0102     }
0103 
0104     /* Best match */
0105     *conf = best;
0106 }
0107 
0108 static unsigned long s5_calc_params(unsigned long rate,
0109                     unsigned long parent_rate,
0110                     struct s5_pll_conf *conf)
0111 {
0112     if (parent_rate % rate) {
0113         struct s5_pll_conf alt1, alt2;
0114         int div;
0115 
0116         div = DIV_ROUND_CLOSEST_ULL(parent_rate, rate);
0117         s5_search_fractional(rate, parent_rate, div, &alt1);
0118 
0119         /* Straight match? */
0120         if (alt1.freq == rate) {
0121             *conf = alt1;
0122         } else {
0123             /* Try without rounding divider */
0124             div = parent_rate / rate;
0125             if (div != alt1.div) {
0126                 s5_search_fractional(rate, parent_rate, div,
0127                              &alt2);
0128                 /* Select the better match */
0129                 if (abs(rate - alt1.freq) <
0130                     abs(rate - alt2.freq))
0131                     *conf = alt1;
0132                 else
0133                     *conf = alt2;
0134             }
0135         }
0136     } else {
0137         /* Straight fit */
0138         memset(conf, 0, sizeof(*conf));
0139         conf->div = parent_rate / rate;
0140     }
0141 
0142     return conf->freq;
0143 }
0144 
0145 static int s5_pll_enable(struct clk_hw *hw)
0146 {
0147     struct s5_hw_clk *pll = to_s5_pll(hw);
0148     u32 val = readl(pll->reg);
0149 
0150     val |= PLL_CLK_ENA;
0151     writel(val, pll->reg);
0152 
0153     return 0;
0154 }
0155 
0156 static void s5_pll_disable(struct clk_hw *hw)
0157 {
0158     struct s5_hw_clk *pll = to_s5_pll(hw);
0159     u32 val = readl(pll->reg);
0160 
0161     val &= ~PLL_CLK_ENA;
0162     writel(val, pll->reg);
0163 }
0164 
0165 static int s5_pll_set_rate(struct clk_hw *hw,
0166                unsigned long rate,
0167                unsigned long parent_rate)
0168 {
0169     struct s5_hw_clk *pll = to_s5_pll(hw);
0170     struct s5_pll_conf conf;
0171     unsigned long eff_rate;
0172     u32 val;
0173 
0174     eff_rate = s5_calc_params(rate, parent_rate, &conf);
0175     if (eff_rate != rate)
0176         return -EOPNOTSUPP;
0177 
0178     val = readl(pll->reg) & PLL_CLK_ENA;
0179     val |= FIELD_PREP(PLL_DIV, conf.div);
0180     if (conf.rot_ena) {
0181         val |= PLL_ROT_ENA;
0182         val |= FIELD_PREP(PLL_ROT_SEL, conf.rot_sel);
0183         val |= FIELD_PREP(PLL_PRE_DIV, conf.pre_div);
0184         if (conf.rot_dir)
0185             val |= PLL_ROT_DIR;
0186     }
0187     writel(val, pll->reg);
0188 
0189     return 0;
0190 }
0191 
0192 static unsigned long s5_pll_recalc_rate(struct clk_hw *hw,
0193                     unsigned long parent_rate)
0194 {
0195     struct s5_hw_clk *pll = to_s5_pll(hw);
0196     struct s5_pll_conf conf;
0197     u32 val;
0198 
0199     val = readl(pll->reg);
0200 
0201     if (val & PLL_CLK_ENA) {
0202         conf.div     = FIELD_GET(PLL_DIV, val);
0203         conf.pre_div = FIELD_GET(PLL_PRE_DIV, val);
0204         conf.rot_ena = FIELD_GET(PLL_ROT_ENA, val);
0205         conf.rot_dir = FIELD_GET(PLL_ROT_DIR, val);
0206         conf.rot_sel = FIELD_GET(PLL_ROT_SEL, val);
0207 
0208         conf.freq = s5_calc_freq(parent_rate, &conf);
0209     } else {
0210         conf.freq = 0;
0211     }
0212 
0213     return conf.freq;
0214 }
0215 
0216 static long s5_pll_round_rate(struct clk_hw *hw, unsigned long rate,
0217                   unsigned long *parent_rate)
0218 {
0219     struct s5_pll_conf conf;
0220 
0221     return s5_calc_params(rate, *parent_rate, &conf);
0222 }
0223 
0224 static const struct clk_ops s5_pll_ops = {
0225     .enable     = s5_pll_enable,
0226     .disable    = s5_pll_disable,
0227     .set_rate   = s5_pll_set_rate,
0228     .round_rate = s5_pll_round_rate,
0229     .recalc_rate    = s5_pll_recalc_rate,
0230 };
0231 
0232 static struct clk_hw *s5_clk_hw_get(struct of_phandle_args *clkspec, void *data)
0233 {
0234     struct s5_clk_data *s5_clk = data;
0235     unsigned int idx = clkspec->args[0];
0236 
0237     if (idx >= N_CLOCKS) {
0238         pr_err("%s: invalid index %u\n", __func__, idx);
0239         return ERR_PTR(-EINVAL);
0240     }
0241 
0242     return &s5_clk->s5_hw[idx].hw;
0243 }
0244 
0245 static int s5_clk_probe(struct platform_device *pdev)
0246 {
0247     struct device *dev = &pdev->dev;
0248     int i, ret;
0249     struct s5_clk_data *s5_clk;
0250     struct clk_parent_data pdata = { .index = 0 };
0251     struct clk_init_data init = {
0252         .ops = &s5_pll_ops,
0253         .num_parents = 1,
0254         .parent_data = &pdata,
0255     };
0256 
0257     s5_clk = devm_kzalloc(dev, sizeof(*s5_clk), GFP_KERNEL);
0258     if (!s5_clk)
0259         return -ENOMEM;
0260 
0261     s5_clk->base = devm_platform_ioremap_resource(pdev, 0);
0262     if (IS_ERR(s5_clk->base))
0263         return PTR_ERR(s5_clk->base);
0264 
0265     for (i = 0; i < N_CLOCKS; i++) {
0266         struct s5_hw_clk *s5_hw = &s5_clk->s5_hw[i];
0267 
0268         init.name = clk_names[i];
0269         s5_hw->reg = s5_clk->base + (i * 4);
0270         s5_hw->hw.init = &init;
0271         ret = devm_clk_hw_register(dev, &s5_hw->hw);
0272         if (ret) {
0273             dev_err(dev, "failed to register %s clock\n",
0274                 init.name);
0275             return ret;
0276         }
0277     }
0278 
0279     return devm_of_clk_add_hw_provider(dev, s5_clk_hw_get, s5_clk);
0280 }
0281 
0282 static const struct of_device_id s5_clk_dt_ids[] = {
0283     { .compatible = "microchip,sparx5-dpll", },
0284     { }
0285 };
0286 MODULE_DEVICE_TABLE(of, s5_clk_dt_ids);
0287 
0288 static struct platform_driver s5_clk_driver = {
0289     .probe  = s5_clk_probe,
0290     .driver = {
0291         .name = "sparx5-clk",
0292         .of_match_table = s5_clk_dt_ids,
0293     },
0294 };
0295 builtin_platform_driver(s5_clk_driver);