| // SPDX-License-Identifier: GPL-2.0 | 
| /* | 
|  * Xilinx 'Clocking Wizard' driver | 
|  * | 
|  *  Copyright (C) 2013 - 2014 Xilinx | 
|  * | 
|  *  Sören Brinkmann <soren.brinkmann@xilinx.com> | 
|  */ | 
|   | 
| #include <linux/platform_device.h> | 
| #include <linux/clk.h> | 
| #include <linux/clk-provider.h> | 
| #include <linux/slab.h> | 
| #include <linux/io.h> | 
| #include <linux/of.h> | 
| #include <linux/module.h> | 
| #include <linux/err.h> | 
|   | 
| #define WZRD_NUM_OUTPUTS    7 | 
| #define WZRD_ACLK_MAX_FREQ    250000000UL | 
|   | 
| #define WZRD_CLK_CFG_REG(n)    (0x200 + 4 * (n)) | 
|   | 
| #define WZRD_CLKOUT0_FRAC_EN    BIT(18) | 
| #define WZRD_CLKFBOUT_FRAC_EN    BIT(26) | 
|   | 
| #define WZRD_CLKFBOUT_MULT_SHIFT    8 | 
| #define WZRD_CLKFBOUT_MULT_MASK        (0xff << WZRD_CLKFBOUT_MULT_SHIFT) | 
| #define WZRD_DIVCLK_DIVIDE_SHIFT    0 | 
| #define WZRD_DIVCLK_DIVIDE_MASK        (0xff << WZRD_DIVCLK_DIVIDE_SHIFT) | 
| #define WZRD_CLKOUT_DIVIDE_SHIFT    0 | 
| #define WZRD_CLKOUT_DIVIDE_MASK        (0xff << WZRD_DIVCLK_DIVIDE_SHIFT) | 
|   | 
| enum clk_wzrd_int_clks { | 
|     wzrd_clk_mul, | 
|     wzrd_clk_mul_div, | 
|     wzrd_clk_int_max | 
| }; | 
|   | 
| /** | 
|  * struct clk_wzrd: | 
|  * @clk_data:        Clock data | 
|  * @nb:            Notifier block | 
|  * @base:        Memory base | 
|  * @clk_in1:        Handle to input clock 'clk_in1' | 
|  * @axi_clk:        Handle to input clock 's_axi_aclk' | 
|  * @clks_internal:    Internal clocks | 
|  * @clkout:        Output clocks | 
|  * @speed_grade:    Speed grade of the device | 
|  * @suspended:        Flag indicating power state of the device | 
|  */ | 
| struct clk_wzrd { | 
|     struct clk_onecell_data clk_data; | 
|     struct notifier_block nb; | 
|     void __iomem *base; | 
|     struct clk *clk_in1; | 
|     struct clk *axi_clk; | 
|     struct clk *clks_internal[wzrd_clk_int_max]; | 
|     struct clk *clkout[WZRD_NUM_OUTPUTS]; | 
|     unsigned int speed_grade; | 
|     bool suspended; | 
| }; | 
|   | 
| #define to_clk_wzrd(_nb) container_of(_nb, struct clk_wzrd, nb) | 
|   | 
| /* maximum frequencies for input/output clocks per speed grade */ | 
| static const unsigned long clk_wzrd_max_freq[] = { | 
|     800000000UL, | 
|     933000000UL, | 
|     1066000000UL | 
| }; | 
|   | 
| static int clk_wzrd_clk_notifier(struct notifier_block *nb, unsigned long event, | 
|                  void *data) | 
| { | 
|     unsigned long max; | 
|     struct clk_notifier_data *ndata = data; | 
|     struct clk_wzrd *clk_wzrd = to_clk_wzrd(nb); | 
|   | 
|     if (clk_wzrd->suspended) | 
|         return NOTIFY_OK; | 
|   | 
|     if (ndata->clk == clk_wzrd->clk_in1) | 
|         max = clk_wzrd_max_freq[clk_wzrd->speed_grade - 1]; | 
|     else if (ndata->clk == clk_wzrd->axi_clk) | 
|         max = WZRD_ACLK_MAX_FREQ; | 
|     else | 
|         return NOTIFY_DONE;    /* should never happen */ | 
|   | 
|     switch (event) { | 
|     case PRE_RATE_CHANGE: | 
|         if (ndata->new_rate > max) | 
|             return NOTIFY_BAD; | 
|         return NOTIFY_OK; | 
|     case POST_RATE_CHANGE: | 
|     case ABORT_RATE_CHANGE: | 
|     default: | 
|         return NOTIFY_DONE; | 
|     } | 
| } | 
|   | 
| static int __maybe_unused clk_wzrd_suspend(struct device *dev) | 
| { | 
|     struct clk_wzrd *clk_wzrd = dev_get_drvdata(dev); | 
|   | 
|     clk_disable_unprepare(clk_wzrd->axi_clk); | 
|     clk_wzrd->suspended = true; | 
|   | 
|     return 0; | 
| } | 
|   | 
| static int __maybe_unused clk_wzrd_resume(struct device *dev) | 
| { | 
|     int ret; | 
|     struct clk_wzrd *clk_wzrd = dev_get_drvdata(dev); | 
|   | 
|     ret = clk_prepare_enable(clk_wzrd->axi_clk); | 
|     if (ret) { | 
|         dev_err(dev, "unable to enable s_axi_aclk\n"); | 
|         return ret; | 
|     } | 
|   | 
|     clk_wzrd->suspended = false; | 
|   | 
|     return 0; | 
| } | 
|   | 
| static SIMPLE_DEV_PM_OPS(clk_wzrd_dev_pm_ops, clk_wzrd_suspend, | 
|              clk_wzrd_resume); | 
|   | 
| static int clk_wzrd_probe(struct platform_device *pdev) | 
| { | 
|     int i, ret; | 
|     u32 reg; | 
|     unsigned long rate; | 
|     const char *clk_name; | 
|     struct clk_wzrd *clk_wzrd; | 
|     struct device_node *np = pdev->dev.of_node; | 
|   | 
|     clk_wzrd = devm_kzalloc(&pdev->dev, sizeof(*clk_wzrd), GFP_KERNEL); | 
|     if (!clk_wzrd) | 
|         return -ENOMEM; | 
|     platform_set_drvdata(pdev, clk_wzrd); | 
|   | 
|     clk_wzrd->base = devm_platform_ioremap_resource(pdev, 0); | 
|     if (IS_ERR(clk_wzrd->base)) | 
|         return PTR_ERR(clk_wzrd->base); | 
|   | 
|     ret = of_property_read_u32(np, "speed-grade", &clk_wzrd->speed_grade); | 
|     if (!ret) { | 
|         if (clk_wzrd->speed_grade < 1 || clk_wzrd->speed_grade > 3) { | 
|             dev_warn(&pdev->dev, "invalid speed grade '%d'\n", | 
|                  clk_wzrd->speed_grade); | 
|             clk_wzrd->speed_grade = 0; | 
|         } | 
|     } | 
|   | 
|     clk_wzrd->clk_in1 = devm_clk_get(&pdev->dev, "clk_in1"); | 
|     if (IS_ERR(clk_wzrd->clk_in1)) { | 
|         if (clk_wzrd->clk_in1 != ERR_PTR(-EPROBE_DEFER)) | 
|             dev_err(&pdev->dev, "clk_in1 not found\n"); | 
|         return PTR_ERR(clk_wzrd->clk_in1); | 
|     } | 
|   | 
|     clk_wzrd->axi_clk = devm_clk_get(&pdev->dev, "s_axi_aclk"); | 
|     if (IS_ERR(clk_wzrd->axi_clk)) { | 
|         if (clk_wzrd->axi_clk != ERR_PTR(-EPROBE_DEFER)) | 
|             dev_err(&pdev->dev, "s_axi_aclk not found\n"); | 
|         return PTR_ERR(clk_wzrd->axi_clk); | 
|     } | 
|     ret = clk_prepare_enable(clk_wzrd->axi_clk); | 
|     if (ret) { | 
|         dev_err(&pdev->dev, "enabling s_axi_aclk failed\n"); | 
|         return ret; | 
|     } | 
|     rate = clk_get_rate(clk_wzrd->axi_clk); | 
|     if (rate > WZRD_ACLK_MAX_FREQ) { | 
|         dev_err(&pdev->dev, "s_axi_aclk frequency (%lu) too high\n", | 
|             rate); | 
|         ret = -EINVAL; | 
|         goto err_disable_clk; | 
|     } | 
|   | 
|     /* we don't support fractional div/mul yet */ | 
|     reg = readl(clk_wzrd->base + WZRD_CLK_CFG_REG(0)) & | 
|             WZRD_CLKFBOUT_FRAC_EN; | 
|     reg |= readl(clk_wzrd->base + WZRD_CLK_CFG_REG(2)) & | 
|              WZRD_CLKOUT0_FRAC_EN; | 
|     if (reg) | 
|         dev_warn(&pdev->dev, "fractional div/mul not supported\n"); | 
|   | 
|     /* register multiplier */ | 
|     reg = (readl(clk_wzrd->base + WZRD_CLK_CFG_REG(0)) & | 
|              WZRD_CLKFBOUT_MULT_MASK) >> WZRD_CLKFBOUT_MULT_SHIFT; | 
|     clk_name = kasprintf(GFP_KERNEL, "%s_mul", dev_name(&pdev->dev)); | 
|     if (!clk_name) { | 
|         ret = -ENOMEM; | 
|         goto err_disable_clk; | 
|     } | 
|     clk_wzrd->clks_internal[wzrd_clk_mul] = clk_register_fixed_factor | 
|             (&pdev->dev, clk_name, | 
|              __clk_get_name(clk_wzrd->clk_in1), | 
|              0, reg, 1); | 
|     kfree(clk_name); | 
|     if (IS_ERR(clk_wzrd->clks_internal[wzrd_clk_mul])) { | 
|         dev_err(&pdev->dev, "unable to register fixed-factor clock\n"); | 
|         ret = PTR_ERR(clk_wzrd->clks_internal[wzrd_clk_mul]); | 
|         goto err_disable_clk; | 
|     } | 
|   | 
|     /* register div */ | 
|     reg = (readl(clk_wzrd->base + WZRD_CLK_CFG_REG(0)) & | 
|             WZRD_DIVCLK_DIVIDE_MASK) >> WZRD_DIVCLK_DIVIDE_SHIFT; | 
|     clk_name = kasprintf(GFP_KERNEL, "%s_mul_div", dev_name(&pdev->dev)); | 
|     if (!clk_name) { | 
|         ret = -ENOMEM; | 
|         goto err_rm_int_clk; | 
|     } | 
|   | 
|     clk_wzrd->clks_internal[wzrd_clk_mul_div] = clk_register_fixed_factor | 
|             (&pdev->dev, clk_name, | 
|              __clk_get_name(clk_wzrd->clks_internal[wzrd_clk_mul]), | 
|              0, 1, reg); | 
|     if (IS_ERR(clk_wzrd->clks_internal[wzrd_clk_mul_div])) { | 
|         dev_err(&pdev->dev, "unable to register divider clock\n"); | 
|         ret = PTR_ERR(clk_wzrd->clks_internal[wzrd_clk_mul_div]); | 
|         goto err_rm_int_clk; | 
|     } | 
|   | 
|     /* register div per output */ | 
|     for (i = WZRD_NUM_OUTPUTS - 1; i >= 0 ; i--) { | 
|         const char *clkout_name; | 
|   | 
|         if (of_property_read_string_index(np, "clock-output-names", i, | 
|                           &clkout_name)) { | 
|             dev_err(&pdev->dev, | 
|                 "clock output name not specified\n"); | 
|             ret = -EINVAL; | 
|             goto err_rm_int_clks; | 
|         } | 
|         reg = readl(clk_wzrd->base + WZRD_CLK_CFG_REG(2) + i * 12); | 
|         reg &= WZRD_CLKOUT_DIVIDE_MASK; | 
|         reg >>= WZRD_CLKOUT_DIVIDE_SHIFT; | 
|         clk_wzrd->clkout[i] = clk_register_fixed_factor | 
|             (&pdev->dev, clkout_name, clk_name, 0, 1, reg); | 
|         if (IS_ERR(clk_wzrd->clkout[i])) { | 
|             int j; | 
|   | 
|             for (j = i + 1; j < WZRD_NUM_OUTPUTS; j++) | 
|                 clk_unregister(clk_wzrd->clkout[j]); | 
|             dev_err(&pdev->dev, | 
|                 "unable to register divider clock\n"); | 
|             ret = PTR_ERR(clk_wzrd->clkout[i]); | 
|             goto err_rm_int_clks; | 
|         } | 
|     } | 
|   | 
|     kfree(clk_name); | 
|   | 
|     clk_wzrd->clk_data.clks = clk_wzrd->clkout; | 
|     clk_wzrd->clk_data.clk_num = ARRAY_SIZE(clk_wzrd->clkout); | 
|     of_clk_add_provider(np, of_clk_src_onecell_get, &clk_wzrd->clk_data); | 
|   | 
|     if (clk_wzrd->speed_grade) { | 
|         clk_wzrd->nb.notifier_call = clk_wzrd_clk_notifier; | 
|   | 
|         ret = clk_notifier_register(clk_wzrd->clk_in1, | 
|                         &clk_wzrd->nb); | 
|         if (ret) | 
|             dev_warn(&pdev->dev, | 
|                  "unable to register clock notifier\n"); | 
|   | 
|         ret = clk_notifier_register(clk_wzrd->axi_clk, &clk_wzrd->nb); | 
|         if (ret) | 
|             dev_warn(&pdev->dev, | 
|                  "unable to register clock notifier\n"); | 
|     } | 
|   | 
|     return 0; | 
|   | 
| err_rm_int_clks: | 
|     clk_unregister(clk_wzrd->clks_internal[1]); | 
| err_rm_int_clk: | 
|     kfree(clk_name); | 
|     clk_unregister(clk_wzrd->clks_internal[0]); | 
| err_disable_clk: | 
|     clk_disable_unprepare(clk_wzrd->axi_clk); | 
|   | 
|     return ret; | 
| } | 
|   | 
| static int clk_wzrd_remove(struct platform_device *pdev) | 
| { | 
|     int i; | 
|     struct clk_wzrd *clk_wzrd = platform_get_drvdata(pdev); | 
|   | 
|     of_clk_del_provider(pdev->dev.of_node); | 
|   | 
|     for (i = 0; i < WZRD_NUM_OUTPUTS; i++) | 
|         clk_unregister(clk_wzrd->clkout[i]); | 
|     for (i = 0; i < wzrd_clk_int_max; i++) | 
|         clk_unregister(clk_wzrd->clks_internal[i]); | 
|   | 
|     if (clk_wzrd->speed_grade) { | 
|         clk_notifier_unregister(clk_wzrd->axi_clk, &clk_wzrd->nb); | 
|         clk_notifier_unregister(clk_wzrd->clk_in1, &clk_wzrd->nb); | 
|     } | 
|   | 
|     clk_disable_unprepare(clk_wzrd->axi_clk); | 
|   | 
|     return 0; | 
| } | 
|   | 
| static const struct of_device_id clk_wzrd_ids[] = { | 
|     { .compatible = "xlnx,clocking-wizard" }, | 
|     { }, | 
| }; | 
| MODULE_DEVICE_TABLE(of, clk_wzrd_ids); | 
|   | 
| static struct platform_driver clk_wzrd_driver = { | 
|     .driver = { | 
|         .name = "clk-wizard", | 
|         .of_match_table = clk_wzrd_ids, | 
|         .pm = &clk_wzrd_dev_pm_ops, | 
|     }, | 
|     .probe = clk_wzrd_probe, | 
|     .remove = clk_wzrd_remove, | 
| }; | 
| module_platform_driver(clk_wzrd_driver); | 
|   | 
| MODULE_LICENSE("GPL"); | 
| MODULE_AUTHOR("Soeren Brinkmann <soren.brinkmann@xilinx.com"); | 
| MODULE_DESCRIPTION("Driver for the Xilinx Clocking Wizard IP core"); |