123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- From 883228d88d9ad6edc7ed9a4540926e80a22c560f Mon Sep 17 00:00:00 2001
- From: "Wesley W. Terpstra" <wesley@sifive.com>
- Date: Tue, 13 Feb 2018 19:39:41 -0800
- Subject: [PATCH 04/10] u54-prci: driver for core U54 clocks
- ---
- drivers/clk/sifive/u54-prci.c | 314 ++++++++++++++++++++++++++++++++++
- 1 file changed, 314 insertions(+)
- create mode 100644 drivers/clk/sifive/u54-prci.c
- diff --git a/drivers/clk/sifive/u54-prci.c b/drivers/clk/sifive/u54-prci.c
- new file mode 100644
- index 000000000000..edc4b7818e71
- --- /dev/null
- +++ b/drivers/clk/sifive/u54-prci.c
- @@ -0,0 +1,314 @@
- +/*
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License version 2 as
- + * published by the Free Software Foundation.
- + *
- + * This program is distributed in the hope that it will be useful,
- + * but WITHOUT ANY WARRANTY; without even the implied warranty of
- + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- + * GNU General Public License for more details.
- + *
- + * Copyright (C) 2018 SiFive, Inc.
- + */
- +
- +#include <linux/clkdev.h>
- +#include <linux/clk-provider.h>
- +#include <linux/clk.h>
- +#include <linux/err.h>
- +#include <linux/of.h>
- +#include <linux/platform_device.h>
- +#include <linux/slab.h>
- +#include <linux/log2.h>
- +
- +#define CORE_CLOCK 0
- +#define GEMTX_CLOCK 1
- +#define PRCI_CLOCKS 2
- +
- +#define MIN_REF 7000000UL
- +#define MAX_REF 200000000UL
- +#define MAX_PARENT 600000000UL
- +#define MAX_VCO 4800000000UL
- +#define MAX_DIV 64
- +#define MAX_R 64UL
- +
- +#define PLL_LOCK 0x80000000U
- +#define NAME_LEN 40
- +
- +struct sifive_u54_prci_driver;
- +
- +struct sifive_u54_prci_pll {
- + struct clk_hw hw;
- + struct sifive_u54_prci_driver *driver;
- + char name[NAME_LEN];
- + u32 freq;
- + u32 glcm;
- +};
- +
- +struct sifive_u54_prci_driver {
- + struct clk_onecell_data table;
- + struct clk *clks[PRCI_CLOCKS];
- + struct sifive_u54_prci_pll plls[PRCI_CLOCKS];
- + void __iomem *reg;
- +};
- +
- +#define to_sifive_u54_prci_pll(hw) container_of(hw, struct sifive_u54_prci_pll, hw)
- +
- +struct sifive_u54_pll_cfg {
- + unsigned long r, f, q, a;
- +};
- +
- +static struct sifive_u54_pll_cfg sifive_u54_pll_cfg(u32 reg)
- +{
- + struct sifive_u54_pll_cfg cfg;
- + cfg.r = (reg >> 0) & 0x3f;
- + cfg.f = (reg >> 6) & 0x1ff;
- + cfg.q = (reg >> 15) & 0x7;
- + cfg.a = (reg >> 18) & 0x7;
- + return cfg;
- +}
- +
- +static u32 sifive_u54_pll_reg(struct sifive_u54_pll_cfg cfg)
- +{
- + u32 reg = 0;
- + reg |= (cfg.r & 0x3f) << 0;
- + reg |= (cfg.f & 0x1ff) << 6;
- + reg |= (cfg.q & 0x7) << 15;
- + reg |= (cfg.a & 0x7) << 18;
- + reg |= 1<<25; // internal feedback
- + return reg;
- +}
- +
- +static unsigned long sifive_u54_pll_rate(struct sifive_u54_pll_cfg cfg, unsigned long parent)
- +{
- + return (parent*2*(cfg.f+1) / (cfg.r+1)) >> cfg.q;
- +}
- +
- +static struct sifive_u54_pll_cfg sifive_u54_pll_configure(unsigned long target, unsigned long parent)
- +{
- + struct sifive_u54_pll_cfg cfg;
- + unsigned long scale, ratio, best_delta, filter;
- + u32 max_r, best_r, best_f, r;
- +
- + /* Confirm input frequency is within bounds */
- + if (WARN_ON(parent > MAX_PARENT)) { parent = MAX_PARENT; }
- + if (WARN_ON(parent < MIN_REF)) { parent = MIN_REF; }
- +
- + /* Calculate the Q shift and target VCO */
- + scale = MAX_VCO / target;
- + if (scale <= 1) {
- + cfg.q = 1;
- + target = MAX_VCO;
- + } else if (scale > MAX_DIV) {
- + cfg.q = ilog2(MAX_DIV);
- + target = MAX_VCO/2;
- + } else {
- + cfg.q = ilog2(scale);
- + target = target << cfg.q;
- + }
- +
- + /* Precalcualte the target ratio */
- + ratio = (target << 20) / parent;
- +
- + /* Placeholder values */
- + best_r = 0;
- + best_f = 0;
- + best_delta = MAX_VCO;
- +
- + /* Consider all values for R which land within [MIN_REF, MAX_REF]; prefer smaller R */
- + max_r = min(MAX_R, parent / MIN_REF);
- + for (r = DIV_ROUND_UP(parent, MAX_REF); r <= max_r; ++r) {
- + /* What is the best F we can pick in this case? */
- + u32 f = (ratio*r + (1<<20)) >> 21;
- + unsigned long ref = parent / r;
- + unsigned long vco = ref * f * 2;
- + unsigned long delta;
- +
- + /* Ensure rounding didn't take us out of range */
- + if (vco > target) --f;
- + if (vco < MAX_VCO/2) ++f;
- + vco = ref * f * 2;
- +
- + delta = abs(target - vco);
- + if (delta < best_delta) {
- + best_delta = delta;
- + best_r = r;
- + best_f = f;
- + }
- + }
- +
- + cfg.r = best_r - 1;
- + cfg.f = best_f - 1;
- +
- + /* Pick the best PLL jitter filter */
- + filter = parent / best_r;
- + BUG_ON(filter < 7000000);
- + if (filter < 11000000) {
- + cfg.a = 1;
- + } else if (filter < 18000000) {
- + cfg.a = 2;
- + } else if (filter < 30000000) {
- + cfg.a = 3;
- + } else if (filter < 50000000) {
- + cfg.a = 4;
- + } else if (filter < 80000000) {
- + cfg.a = 5;
- + } else if (filter < 130000000) {
- + cfg.a = 6;
- + } else {
- + BUG_ON (filter > 200000000);
- + cfg.a = 7;
- + }
- +
- + return cfg;
- +}
- +
- +static unsigned long sifive_u54_prci_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
- +{
- + struct sifive_u54_prci_pll *pll = to_sifive_u54_prci_pll(hw);
- + struct sifive_u54_prci_driver *driver = pll->driver;
- +
- + u32 reg = ioread32(driver->reg + pll->freq);
- + struct sifive_u54_pll_cfg cfg = sifive_u54_pll_cfg(reg);
- +
- + return sifive_u54_pll_rate(cfg, parent_rate);
- +}
- +
- +static long sifive_u54_prci_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate)
- +{
- + struct sifive_u54_pll_cfg cfg = sifive_u54_pll_configure(rate, *parent_rate);
- + return sifive_u54_pll_rate(cfg, *parent_rate);
- +}
- +
- +static int sifive_u54_prci_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
- +{
- + struct sifive_u54_prci_pll *pll = to_sifive_u54_prci_pll(hw);
- + struct sifive_u54_prci_driver *driver = pll->driver;
- +
- + struct sifive_u54_pll_cfg cfg = sifive_u54_pll_configure(rate, parent_rate);
- + u32 reg = sifive_u54_pll_reg(cfg);
- +
- + /* Switch to reg clock and reconfigure PLL */
- + iowrite32(1, driver->reg + pll->glcm);
- + iowrite32(reg, driver->reg + pll->freq);
- +
- + /* Wait for lock and switch back to PLL */
- + while (!(ioread32(driver->reg + pll->freq) & PLL_LOCK));
- + iowrite32(0, driver->reg + pll->glcm);
- +
- + return 0;
- +}
- +
- +static const struct clk_ops sifive_u54_prci_ops_rw = {
- + .recalc_rate = sifive_u54_prci_recalc_rate,
- + .round_rate = sifive_u54_prci_round_rate,
- + .set_rate = sifive_u54_prci_set_rate,
- +};
- +
- +static const struct clk_ops sifive_u54_prci_ops_ro = {
- + .recalc_rate = sifive_u54_prci_recalc_rate,
- +};
- +
- +static ssize_t sifive_u54_pll_show(struct device *dev, struct device_attribute *attr, char *buf)
- +{
- + struct sifive_u54_prci_driver *driver = dev_get_drvdata(dev);
- + return sprintf(buf, "%ld", clk_get_rate(driver->clks[0]));
- +}
- +
- +static ssize_t sifive_u54_pll_rate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
- +{
- + struct sifive_u54_prci_driver *driver = dev_get_drvdata(dev);
- + unsigned long rate;
- + char *endp;
- +
- + rate = simple_strtoul(buf, &endp, 0);
- + if (*endp != 0 && *endp != '\n')
- + return -EINVAL;
- +
- + clk_set_rate(driver->clks[0], rate);
- + return count;
- +}
- +
- +static DEVICE_ATTR(rate, 0644, sifive_u54_pll_show, sifive_u54_pll_rate_store);
- +
- +static int sifive_u54_prci_probe(struct platform_device *pdev)
- +{
- + struct device *dev = &pdev->dev;
- + struct clk_init_data init;
- + struct sifive_u54_prci_driver *driver;
- + struct resource *res;
- + const char *parent;
- + int i;
- +
- + parent = of_clk_get_parent_name(dev->of_node, 0);
- + if (!parent) {
- + dev_err(dev, "No OF parent clocks found\n");
- + return -EINVAL;
- + }
- +
- + driver = devm_kzalloc(dev, sizeof(*driver), GFP_KERNEL);
- + if (!driver) {
- + dev_err(dev, "Out of memory\n");
- + return -ENOMEM;
- + }
- +
- + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- + driver->reg = devm_ioremap_resource(dev, res);
- + if (IS_ERR(driver->reg))
- + return PTR_ERR(driver->reg);
- +
- + /* Link the data structure */
- + driver->table.clk_num = PRCI_CLOCKS;
- + driver->table.clks = &driver->clks[0];
- + dev_set_drvdata(dev, driver);
- +
- + /* Describe the clocks */
- + snprintf(driver->plls[CORE_CLOCK].name, NAME_LEN, "%s.core", dev->of_node->name);
- + driver->plls[CORE_CLOCK].freq = 0x4;
- + driver->plls[CORE_CLOCK].glcm = 0x24;
- + snprintf(driver->plls[GEMTX_CLOCK].name, NAME_LEN, "%s.gemtx", dev->of_node->name);
- + driver->plls[GEMTX_CLOCK].freq = 0x1c;
- + driver->plls[GEMTX_CLOCK].glcm = 0; /* None; cannot be set_rate */
- +
- + /* Export the clocks */
- + for (i = 0; i < PRCI_CLOCKS; ++i) {
- + init.name = &driver->plls[i].name[0];
- + init.ops = driver->plls[i].glcm ? &sifive_u54_prci_ops_rw : &sifive_u54_prci_ops_ro;
- + init.num_parents = 1;
- + init.parent_names = &parent;
- + init.flags = 0;
- +
- + driver->plls[i].driver = driver;
- + driver->plls[i].hw.init = &init;
- +
- + driver->clks[i] = devm_clk_register(dev, &driver->plls[i].hw);
- + if (IS_ERR(driver->clks[i])) {
- + dev_err(dev, "Failed to register clock %d, %ld\n", i, PTR_ERR(driver->clks[i]));
- + return PTR_ERR(driver->clks[i]);
- + }
- + }
- +
- + of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, &driver->table);
- + device_create_file(dev, &dev_attr_rate);
- + dev_info(dev, "Registered U54 core clocks\n");
- +
- + return 0;
- +}
- +
- +static const struct of_device_id sifive_u54_prci_of_match[] = {
- + { .compatible = "sifive,aloeprci0", },
- + {}
- +};
- +
- +static struct platform_driver sifive_u54_prci_driver = {
- + .driver = {
- + .name = "sifive-u54-prci",
- + .of_match_table = sifive_u54_prci_of_match,
- + },
- + .probe = sifive_u54_prci_probe,
- +};
- +
- +static int __init sifive_u54_prci_init(void)
- +{
- + return platform_driver_register(&sifive_u54_prci_driver);
- +}
- +core_initcall(sifive_u54_prci_init);
- --
- 2.21.0
|