diff options
author | Masahiro Yamada <yamada.masahiro@socionext.com> | 2016-02-02 21:11:32 +0900 |
---|---|---|
committer | Masahiro Yamada <yamada.masahiro@socionext.com> | 2016-02-14 16:36:13 +0900 |
commit | 48264d9beba2ccc18b9497944048eea135883529 (patch) | |
tree | 165956e2eae989ea4ca52144aad1aabf9a7bdb8d /drivers/clk/uniphier/clk-uniphier-core.c | |
parent | fec4816387a978e651da74c83bcdd5019a6d024c (diff) |
clk: uniphier: add Media I/O clock driver for UniPhier SoCs
This is the initial commit for the UniPhier clock drivers.
Currently, only the Media I/O clock is supported.
Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
Diffstat (limited to 'drivers/clk/uniphier/clk-uniphier-core.c')
-rw-r--r-- | drivers/clk/uniphier/clk-uniphier-core.c | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/drivers/clk/uniphier/clk-uniphier-core.c b/drivers/clk/uniphier/clk-uniphier-core.c new file mode 100644 index 0000000000..e79e0ff689 --- /dev/null +++ b/drivers/clk/uniphier/clk-uniphier-core.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2016 Masahiro Yamada <yamada.masahiro@socionext.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <mapmem.h> +#include <linux/bitops.h> +#include <linux/io.h> +#include <clk.h> +#include <dm/device.h> + +#include "clk-uniphier.h" + +DECLARE_GLOBAL_DATA_PTR; + +static int uniphier_clk_enable(struct udevice *dev, int index) +{ + struct uniphier_clk_priv *priv = dev_get_priv(dev); + struct uniphier_clk_gate_data *gate = priv->socdata->gate; + unsigned int nr_gate = priv->socdata->nr_gate; + void __iomem *reg; + u32 mask, data, tmp; + int i; + + for (i = 0; i < nr_gate; i++) { + if (gate[i].index != index) + continue; + + reg = priv->base + gate[i].reg; + mask = gate[i].mask; + data = gate[i].data & mask; + + tmp = readl(reg); + tmp &= ~mask; + tmp |= data & mask; + debug("%s: %p: %08x\n", __func__, reg, tmp); + writel(tmp, reg); + } + + return 0; +} + +static ulong uniphier_clk_get_rate(struct udevice *dev, int index) +{ + struct uniphier_clk_priv *priv = dev_get_priv(dev); + struct uniphier_clk_rate_data *rdata = priv->socdata->rate; + unsigned int nr_rdata = priv->socdata->nr_rate; + void __iomem *reg; + u32 mask, data; + ulong matched_rate = 0; + int i; + + for (i = 0; i < nr_rdata; i++) { + if (rdata[i].index != index) + continue; + + if (rdata[i].reg == UNIPHIER_CLK_RATE_IS_FIXED) + return rdata[i].rate; + + reg = priv->base + rdata[i].reg; + mask = rdata[i].mask; + data = rdata[i].data & mask; + if ((readl(reg) & mask) == data) { + if (matched_rate && rdata[i].rate != matched_rate) { + printf("failed to get clk rate for insane register values\n"); + return -EINVAL; + } + matched_rate = rdata[i].rate; + } + } + + debug("%s: rate = %lu\n", __func__, matched_rate); + + return matched_rate; +} + +static ulong uniphier_clk_set_rate(struct udevice *dev, int index, ulong rate) +{ + struct uniphier_clk_priv *priv = dev_get_priv(dev); + struct uniphier_clk_rate_data *rdata = priv->socdata->rate; + unsigned int nr_rdata = priv->socdata->nr_rate; + void __iomem *reg; + u32 mask, data, tmp; + ulong best_rate = 0; + int i; + + /* first, decide the best match rate */ + for (i = 0; i < nr_rdata; i++) { + if (rdata[i].index != index) + continue; + + if (rdata[i].reg == UNIPHIER_CLK_RATE_IS_FIXED) + return 0; + + if (rdata[i].rate > best_rate && rdata[i].rate <= rate) + best_rate = rdata[i].rate; + } + + if (!best_rate) + return -ENODEV; + + debug("%s: requested rate = %lu, set rate = %lu\n", __func__, + rate, best_rate); + + /* second, really set registers */ + for (i = 0; i < nr_rdata; i++) { + if (rdata[i].index != index || rdata[i].rate != best_rate) + continue; + + reg = priv->base + rdata[i].reg; + mask = rdata[i].mask; + data = rdata[i].data & mask; + + tmp = readl(reg); + tmp &= ~mask; + tmp |= data; + debug("%s: %p: %08x\n", __func__, reg, tmp); + writel(tmp, reg); + } + + return best_rate; +} + +const struct clk_ops uniphier_clk_ops = { + .enable = uniphier_clk_enable, + .get_periph_rate = uniphier_clk_get_rate, + .set_periph_rate = uniphier_clk_set_rate, +}; + +int uniphier_clk_probe(struct udevice *dev) +{ + struct uniphier_clk_priv *priv = dev_get_priv(dev); + fdt_addr_t addr; + fdt_size_t size; + + addr = fdtdec_get_addr_size(gd->fdt_blob, dev->of_offset, "reg", + &size); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + + priv->base = map_sysmem(addr, size); + if (!priv->base) + return -ENOMEM; + + priv->socdata = (void *)dev_get_driver_data(dev); + + return 0; +} + +int uniphier_clk_remove(struct udevice *dev) +{ + struct uniphier_clk_priv *priv = dev_get_priv(dev); + + unmap_sysmem(priv->base); + + return 0; +} |