| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * TI multiplexer clock support |
| * |
| * Copyright (C) 2020 Dario Binacchi <dariobin@libero.it> |
| * |
| * Based on Linux kernel drivers/clk/ti/mux.c |
| */ |
| |
| #include <common.h> |
| #include <dm.h> |
| #include <dm/device_compat.h> |
| #include <clk-uclass.h> |
| #include <asm/io.h> |
| #include <linux/clk-provider.h> |
| #include "clk.h" |
| |
| struct clk_ti_mux_priv { |
| struct clk_bulk parents; |
| fdt_addr_t reg; |
| u32 flags; |
| u32 mux_flags; |
| u32 mask; |
| u32 shift; |
| s32 latch; |
| }; |
| |
| static struct clk *clk_ti_mux_get_parent_by_index(struct clk_bulk *parents, |
| int index) |
| { |
| if (index < 0 || !parents) |
| return ERR_PTR(-EINVAL); |
| |
| if (index >= parents->count) |
| return ERR_PTR(-ENODEV); |
| |
| return &parents->clks[index]; |
| } |
| |
| static int clk_ti_mux_get_parent_index(struct clk_bulk *parents, |
| struct clk *parent) |
| { |
| int i; |
| |
| if (!parents || !parent) |
| return -EINVAL; |
| |
| for (i = 0; i < parents->count; i++) { |
| if (parents->clks[i].dev == parent->dev) |
| return i; |
| } |
| |
| return -ENODEV; |
| } |
| |
| static int clk_ti_mux_get_index(struct clk *clk) |
| { |
| struct clk_ti_mux_priv *priv = dev_get_priv(clk->dev); |
| u32 val; |
| |
| val = readl(priv->reg); |
| val >>= priv->shift; |
| val &= priv->mask; |
| |
| if (val && (priv->flags & CLK_MUX_INDEX_BIT)) |
| val = ffs(val) - 1; |
| |
| if (val && (priv->flags & CLK_MUX_INDEX_ONE)) |
| val--; |
| |
| if (val >= priv->parents.count) |
| return -EINVAL; |
| |
| return val; |
| } |
| |
| static int clk_ti_mux_set_parent(struct clk *clk, struct clk *parent) |
| { |
| struct clk_ti_mux_priv *priv = dev_get_priv(clk->dev); |
| int index; |
| u32 val; |
| |
| index = clk_ti_mux_get_parent_index(&priv->parents, parent); |
| if (index < 0) { |
| dev_err(clk->dev, "failed to get parent clock\n"); |
| return index; |
| } |
| |
| index = clk_mux_index_to_val(NULL, priv->flags, index); |
| |
| if (priv->flags & CLK_MUX_HIWORD_MASK) { |
| val = priv->mask << (priv->shift + 16); |
| } else { |
| val = readl(priv->reg); |
| val &= ~(priv->mask << priv->shift); |
| } |
| |
| val |= index << priv->shift; |
| writel(val, priv->reg); |
| clk_ti_latch(priv->reg, priv->latch); |
| return 0; |
| } |
| |
| static ulong clk_ti_mux_set_rate(struct clk *clk, ulong rate) |
| { |
| struct clk_ti_mux_priv *priv = dev_get_priv(clk->dev); |
| struct clk *parent; |
| int index; |
| |
| if ((clk->flags & CLK_SET_RATE_PARENT) == 0) |
| return -ENOSYS; |
| |
| index = clk_ti_mux_get_index(clk); |
| parent = clk_ti_mux_get_parent_by_index(&priv->parents, index); |
| if (IS_ERR(parent)) |
| return PTR_ERR(parent); |
| |
| rate = clk_set_rate(parent, rate); |
| dev_dbg(clk->dev, "rate=%ld\n", rate); |
| return rate; |
| } |
| |
| static ulong clk_ti_mux_get_rate(struct clk *clk) |
| { |
| struct clk_ti_mux_priv *priv = dev_get_priv(clk->dev); |
| int index; |
| struct clk *parent; |
| ulong rate; |
| |
| index = clk_ti_mux_get_index(clk); |
| parent = clk_ti_mux_get_parent_by_index(&priv->parents, index); |
| if (IS_ERR(parent)) |
| return PTR_ERR(parent); |
| |
| rate = clk_get_rate(parent); |
| dev_dbg(clk->dev, "rate=%ld\n", rate); |
| return rate; |
| } |
| |
| static ulong clk_ti_mux_round_rate(struct clk *clk, ulong rate) |
| { |
| struct clk_ti_mux_priv *priv = dev_get_priv(clk->dev); |
| struct clk *parent; |
| int index; |
| |
| if ((clk->flags & CLK_SET_RATE_PARENT) == 0) |
| return -ENOSYS; |
| |
| index = clk_ti_mux_get_index(clk); |
| parent = clk_ti_mux_get_parent_by_index(&priv->parents, index); |
| if (IS_ERR(parent)) |
| return PTR_ERR(parent); |
| |
| rate = clk_round_rate(parent, rate); |
| dev_dbg(clk->dev, "rate=%ld\n", rate); |
| return rate; |
| } |
| |
| static int clk_ti_mux_request(struct clk *clk) |
| { |
| struct clk_ti_mux_priv *priv = dev_get_priv(clk->dev); |
| struct clk *parent; |
| int index; |
| |
| clk->flags = priv->flags; |
| |
| index = clk_ti_mux_get_index(clk); |
| parent = clk_ti_mux_get_parent_by_index(&priv->parents, index); |
| if (IS_ERR(parent)) |
| return PTR_ERR(parent); |
| |
| return clk_ti_mux_set_parent(clk, parent); |
| } |
| |
| static struct clk_ops clk_ti_mux_ops = { |
| .request = clk_ti_mux_request, |
| .round_rate = clk_ti_mux_round_rate, |
| .get_rate = clk_ti_mux_get_rate, |
| .set_rate = clk_ti_mux_set_rate, |
| .set_parent = clk_ti_mux_set_parent, |
| }; |
| |
| static int clk_ti_mux_remove(struct udevice *dev) |
| { |
| struct clk_ti_mux_priv *priv = dev_get_priv(dev); |
| int err; |
| |
| err = clk_release_all(priv->parents.clks, priv->parents.count); |
| if (err) |
| dev_dbg(dev, "could not release all parents' clocks\n"); |
| |
| return err; |
| } |
| |
| static int clk_ti_mux_probe(struct udevice *dev) |
| { |
| struct clk_ti_mux_priv *priv = dev_get_priv(dev); |
| int err; |
| |
| err = clk_get_bulk(dev, &priv->parents); |
| if (err || priv->parents.count < 2) { |
| dev_err(dev, "mux-clock must have parents\n"); |
| return err ? err : -EFAULT; |
| } |
| |
| /* Generate bit-mask based on parents info */ |
| priv->mask = priv->parents.count; |
| if (!(priv->mux_flags & CLK_MUX_INDEX_ONE)) |
| priv->mask--; |
| |
| priv->mask = (1 << fls(priv->mask)) - 1; |
| return 0; |
| } |
| |
| static int clk_ti_mux_of_to_plat(struct udevice *dev) |
| { |
| struct clk_ti_mux_priv *priv = dev_get_priv(dev); |
| |
| priv->reg = dev_read_addr(dev); |
| if (priv->reg == FDT_ADDR_T_NONE) { |
| dev_err(dev, "failed to get register\n"); |
| return -EINVAL; |
| } |
| |
| dev_dbg(dev, "reg=0x%08lx\n", priv->reg); |
| priv->shift = dev_read_u32_default(dev, "ti,bit-shift", 0); |
| priv->latch = dev_read_s32_default(dev, "ti,latch-bit", -EINVAL); |
| |
| priv->flags = CLK_SET_RATE_NO_REPARENT; |
| if (dev_read_bool(dev, "ti,set-rate-parent")) |
| priv->flags |= CLK_SET_RATE_PARENT; |
| |
| if (dev_read_bool(dev, "ti,index-starts-at-one")) |
| priv->mux_flags |= CLK_MUX_INDEX_ONE; |
| |
| return 0; |
| } |
| |
| static const struct udevice_id clk_ti_mux_of_match[] = { |
| {.compatible = "ti,mux-clock"}, |
| {}, |
| }; |
| |
| U_BOOT_DRIVER(clk_ti_mux) = { |
| .name = "ti_mux_clock", |
| .id = UCLASS_CLK, |
| .of_match = clk_ti_mux_of_match, |
| .of_to_plat = clk_ti_mux_of_to_plat, |
| .probe = clk_ti_mux_probe, |
| .remove = clk_ti_mux_remove, |
| .priv_auto = sizeof(struct clk_ti_mux_priv), |
| .ops = &clk_ti_mux_ops, |
| }; |