| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * RZ/G2L Clock Pulse Generator |
| * |
| * Copyright (C) 2021-2023 Renesas Electronics Corp. |
| * |
| * Based on renesas-cpg-mssr.c |
| * |
| * Copyright (C) 2015 Glider bvba |
| * Copyright (C) 2013 Ideas On Board SPRL |
| * Copyright (C) 2015 Renesas Electronics Corp. |
| */ |
| |
| #include <asm/io.h> |
| #include <clk-uclass.h> |
| #include <dm.h> |
| #include <dm/device-internal.h> |
| #include <dm/device_compat.h> |
| #include <dm/devres.h> |
| #include <dm/lists.h> |
| #include <dt-bindings/clock/renesas-cpg-mssr.h> |
| #include <linux/clk-provider.h> |
| #include <linux/iopoll.h> |
| #include <reset-uclass.h> |
| #include <reset.h> |
| |
| #include "rzg2l-cpg.h" |
| |
| #define CLK_MON_R(reg) (0x180 + (reg)) |
| |
| static ulong rzg2l_cpg_clk_get_rate_by_id(struct udevice *dev, unsigned int id); |
| static ulong rzg2l_cpg_clk_get_rate_by_name(struct udevice *dev, const char *name); |
| |
| struct rzg2l_cpg_data { |
| void __iomem *base; |
| struct rzg2l_cpg_info *info; |
| }; |
| |
| /* |
| * The top 16 bits of the clock ID are used to identify if it is a core clock or |
| * a module clock. |
| */ |
| #define CPG_CLK_TYPE_SHIFT 16 |
| #define CPG_CLK_ID_MASK 0xffff |
| #define CPG_CLK_ID(x) ((x) & CPG_CLK_ID_MASK) |
| #define CPG_CLK_PACK(type, id) (((type) << CPG_CLK_TYPE_SHIFT) | CPG_CLK_ID(id)) |
| |
| static inline bool is_mod_clk(unsigned int id) |
| { |
| return (id >> CPG_CLK_TYPE_SHIFT) == CPG_MOD; |
| } |
| |
| static int rzg2l_cpg_clk_set(struct clk *clk, bool enable) |
| { |
| struct rzg2l_cpg_data *data = |
| (struct rzg2l_cpg_data *)dev_get_driver_data(clk->dev); |
| const unsigned int cpg_clk_id = CPG_CLK_ID(clk->id); |
| const struct rzg2l_mod_clk *mod_clk = NULL; |
| u32 value; |
| unsigned int i; |
| |
| dev_dbg(clk->dev, "%s %s clock %u\n", enable ? "enable" : "disable", |
| is_mod_clk(clk->id) ? "module" : "core", cpg_clk_id); |
| if (!is_mod_clk(clk->id)) { |
| dev_err(clk->dev, "ID %lu is not a module clock\n", clk->id); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < data->info->num_mod_clks; i++) { |
| if (data->info->mod_clks[i].id == cpg_clk_id) { |
| mod_clk = &data->info->mod_clks[i]; |
| break; |
| } |
| } |
| |
| if (!mod_clk) { |
| dev_err(clk->dev, "Module clock %u not found\n", cpg_clk_id); |
| return -ENODEV; |
| } |
| |
| value = BIT(mod_clk->bit) << 16; |
| if (enable) |
| value |= BIT(mod_clk->bit); |
| writel(value, data->base + mod_clk->off); |
| |
| if (enable && readl_poll_timeout(data->base + CLK_MON_R(mod_clk->off), |
| value, (value & BIT(mod_clk->bit)), |
| 10)) { |
| dev_err(clk->dev, "Timeout\n"); |
| return -ETIMEDOUT; |
| } |
| |
| return 0; |
| } |
| |
| static int rzg2l_cpg_clk_enable(struct clk *clk) |
| { |
| return rzg2l_cpg_clk_set(clk, true); |
| } |
| |
| static int rzg2l_cpg_clk_disable(struct clk *clk) |
| { |
| return rzg2l_cpg_clk_set(clk, false); |
| } |
| |
| static int rzg2l_cpg_clk_of_xlate(struct clk *clk, |
| struct ofnode_phandle_args *args) |
| { |
| struct rzg2l_cpg_data *data = |
| (struct rzg2l_cpg_data *)dev_get_driver_data(clk->dev); |
| u32 cpg_clk_type, cpg_clk_id; |
| bool found = false; |
| unsigned int i; |
| |
| if (args->args_count != 2) { |
| dev_dbg(clk->dev, "Invalid args_count: %d\n", args->args_count); |
| return -EINVAL; |
| } |
| |
| cpg_clk_type = args->args[0]; |
| cpg_clk_id = args->args[1]; |
| |
| switch (cpg_clk_type) { |
| case CPG_CORE: |
| for (i = 0; i < data->info->num_core_clks; i++) { |
| if (data->info->core_clks[i].id == cpg_clk_id) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| dev_dbg(clk->dev, |
| "Invalid second argument %u: Must be a valid core clock ID\n", |
| cpg_clk_id); |
| return -EINVAL; |
| } |
| break; |
| case CPG_MOD: |
| for (i = 0; i < data->info->num_mod_clks; i++) { |
| if (data->info->mod_clks[i].id == cpg_clk_id) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| dev_dbg(clk->dev, |
| "Invalid second argument %u: Must be a valid module clock ID\n", |
| cpg_clk_id); |
| return -EINVAL; |
| } |
| break; |
| default: |
| dev_dbg(clk->dev, |
| "Invalid first argument %u: Must be CPG_CORE or CPG_MOD\n", |
| cpg_clk_type); |
| return -EINVAL; |
| } |
| |
| clk->id = CPG_CLK_PACK(cpg_clk_type, cpg_clk_id); |
| |
| return 0; |
| } |
| |
| static ulong rzg2l_sdhi_clk_get_rate(struct udevice *dev, const struct cpg_core_clk *cc) |
| { |
| struct rzg2l_cpg_data *data = |
| (struct rzg2l_cpg_data *)dev_get_driver_data(dev); |
| const ulong offset = CPG_CONF_OFFSET(cc->conf); |
| const int shift = CPG_CONF_BITPOS(cc->conf); |
| const u32 mask = CPG_CONF_BITMASK(cc->conf); |
| unsigned int sel; |
| |
| sel = (readl(data->base + offset) >> shift) & mask; |
| |
| if (!sel || sel > cc->num_parents) { |
| dev_err(dev, "Invalid SEL_SDHI%d_SET value %u\n", shift / 4, sel); |
| return -EIO; |
| } |
| return rzg2l_cpg_clk_get_rate_by_name(dev, cc->parent_names[sel - 1]); |
| } |
| |
| static ulong rzg2l_div_clk_get_rate(struct udevice *dev, const struct cpg_core_clk *cc) |
| { |
| struct rzg2l_cpg_data *data = |
| (struct rzg2l_cpg_data *)dev_get_driver_data(dev); |
| const ulong offset = CPG_CONF_OFFSET(cc->conf); |
| const int shift = CPG_CONF_BITPOS(cc->conf); |
| const u32 mask = CPG_CONF_BITMASK(cc->conf); |
| unsigned int sel, i; |
| |
| sel = (readl(data->base + offset) >> shift) & mask; |
| |
| for (i = 0; cc->dtable[i].div; i++) { |
| if (cc->dtable[i].val == sel) |
| return rzg2l_cpg_clk_get_rate_by_id(dev, cc->parent) / cc->dtable[i].div; |
| } |
| dev_err(dev, "Invalid selector value %u for clock %s\n", sel, cc->name); |
| return -EINVAL; |
| } |
| |
| static ulong rzg2l_core_clk_get_rate(struct udevice *dev, const struct cpg_core_clk *cc) |
| { |
| switch (cc->type) { |
| case CLK_TYPE_FF: |
| const ulong parent_rate = rzg2l_cpg_clk_get_rate_by_id(dev, cc->parent); |
| return parent_rate * cc->mult / cc->div; |
| case CLK_TYPE_IN: |
| struct clk clk_in; |
| clk_get_by_name(dev, cc->name, &clk_in); |
| return clk_get_rate(&clk_in); |
| case CLK_TYPE_SD_MUX: |
| return rzg2l_sdhi_clk_get_rate(dev, cc); |
| case CLK_TYPE_DIV: |
| return rzg2l_div_clk_get_rate(dev, cc); |
| default: |
| dev_err(dev, "get_rate needed for clock %u, type %d\n", cc->id, cc->type); |
| return -ENOSYS; |
| } |
| } |
| |
| static ulong rzg2l_cpg_clk_get_rate_by_id(struct udevice *dev, unsigned int id) |
| { |
| struct rzg2l_cpg_data *data = |
| (struct rzg2l_cpg_data *)dev_get_driver_data(dev); |
| const unsigned int cpg_clk_id = CPG_CLK_ID(id); |
| unsigned int i; |
| |
| if (is_mod_clk(id)) { |
| for (i = 0; i < data->info->num_mod_clks; i++) { |
| if (data->info->mod_clks[i].id == cpg_clk_id) |
| return rzg2l_cpg_clk_get_rate_by_id(dev, |
| data->info->mod_clks[i].parent); |
| } |
| |
| dev_err(dev, "Module clock ID %u not found\n", cpg_clk_id); |
| return -ENODEV; |
| } |
| |
| for (i = 0; i < data->info->num_core_clks; i++) { |
| if (data->info->core_clks[i].id == cpg_clk_id) |
| return rzg2l_core_clk_get_rate(dev, &data->info->core_clks[i]); |
| } |
| |
| dev_err(dev, "Core clock ID %u not found\n", cpg_clk_id); |
| return -ENODEV; |
| } |
| |
| static ulong rzg2l_cpg_clk_get_rate_by_name(struct udevice *dev, const char *name) |
| { |
| struct rzg2l_cpg_data *data = |
| (struct rzg2l_cpg_data *)dev_get_driver_data(dev); |
| unsigned int i; |
| |
| for (i = 0; i < data->info->num_mod_clks; i++) { |
| if (!strcmp(name, data->info->mod_clks[i].name)) |
| return rzg2l_cpg_clk_get_rate_by_id(dev, data->info->mod_clks[i].parent); |
| } |
| for (i = 0; i < data->info->num_core_clks; i++) { |
| if (!strcmp(name, data->info->core_clks[i].name)) |
| return rzg2l_core_clk_get_rate(dev, &data->info->core_clks[i]); |
| } |
| |
| dev_err(dev, "Clock name %s not found\n", name); |
| return -EINVAL; |
| } |
| |
| static ulong rzg2l_cpg_clk_get_rate(struct clk *clk) |
| { |
| return rzg2l_cpg_clk_get_rate_by_id(clk->dev, clk->id); |
| } |
| |
| static ulong rzg2l_sdhi_clk_set_rate(struct udevice *dev, const struct cpg_core_clk *cc, ulong rate) |
| { |
| struct rzg2l_cpg_data *data = |
| (struct rzg2l_cpg_data *)dev_get_driver_data(dev); |
| const ulong offset = CPG_CONF_OFFSET(cc->conf); |
| const int shift = CPG_CONF_BITPOS(cc->conf); |
| int channel, new_sel, prev_sel; |
| ulong target_rate; |
| unsigned int i; |
| u32 value; |
| |
| prev_sel = (readl(data->base + offset) >> shift) & 0x3; |
| channel = shift / 4; |
| |
| /* |
| * Round the requested rate down, unless it is below the minimum |
| * supported rate. Assume that the parent clock names are listed in |
| * order of descending rate. |
| */ |
| for (i = 0; i < cc->num_parents; i++) { |
| target_rate = rzg2l_cpg_clk_get_rate_by_name(dev, cc->parent_names[i]); |
| if (rate >= target_rate) { |
| new_sel = i + 1; |
| break; |
| } |
| } |
| if (!new_sel) |
| new_sel = cc->num_parents - 1; |
| |
| if (new_sel == prev_sel) |
| return target_rate; |
| dev_dbg(dev, "sdhi set_rate rate=%lu target_rate=%lu sel=%d\n", |
| rate, target_rate, new_sel); |
| |
| /* |
| * As per the HW manual, we should not directly switch from 533 MHz to |
| * 400 MHz and vice versa. To change the setting from 2’b01 (533 MHz) |
| * to 2’b10 (400 MHz) or vice versa, Switch to 2’b11 (266 MHz) first, |
| * and then switch to the target setting (2’b01 (533 MHz) or 2’b10 |
| * (400 MHz)). |
| */ |
| if (new_sel != SEL_SDHI_266MHz && prev_sel != SEL_SDHI_266MHz) { |
| u32 waitbit; |
| int ret; |
| |
| dev_dbg(dev, "sdhi set_rate via 266MHz\n"); |
| value = (SEL_SDHI_WRITE_ENABLE | SEL_SDHI_266MHz) << shift; |
| writel(value, data->base + offset); |
| |
| /* Wait for the switch to complete. */ |
| waitbit = channel ? CPG_CLKSTATUS_SELSDHI1_STS : CPG_CLKSTATUS_SELSDHI0_STS; |
| ret = readl_poll_timeout(data->base + CPG_CLKSTATUS, value, |
| !(value & waitbit), |
| CPG_SDHI_CLK_SWITCH_STATUS_TIMEOUT_US); |
| if (ret) { |
| dev_err(dev, "Failed to switch SDHI%d clock source\n", channel); |
| return -EIO; |
| } |
| } |
| |
| value = (SEL_SDHI_WRITE_ENABLE | new_sel) << shift; |
| writel(value, data->base + offset); |
| |
| return target_rate; |
| } |
| |
| static ulong rzg2l_core_clk_set_rate(struct udevice *dev, const struct cpg_core_clk *cc, ulong rate) |
| { |
| if (cc->type == CLK_TYPE_SD_MUX) |
| return rzg2l_sdhi_clk_set_rate(dev, cc, rate); |
| |
| /* |
| * The sdhi driver calls clk_set_rate for SD0_DIV4 and SD1_DIV4, even |
| * though they're in a fixed relationship with SD0 and SD1 respectively. |
| * To allow the driver to proceed, simply return the current rates |
| * without making any change. |
| */ |
| if (cc->id == CLK_SD0_DIV4 || cc->id == CLK_SD1_DIV4) |
| return rzg2l_core_clk_get_rate(dev, cc); |
| |
| dev_err(dev, "set_rate needed for clock %u, type %d\n", cc->id, cc->type); |
| return -ENOSYS; |
| } |
| |
| static ulong rzg2l_cpg_clk_set_rate_by_id(struct udevice *dev, unsigned int id, ulong rate) |
| { |
| struct rzg2l_cpg_data *data = |
| (struct rzg2l_cpg_data *)dev_get_driver_data(dev); |
| const unsigned int cpg_clk_id = CPG_CLK_ID(id); |
| unsigned int i; |
| |
| if (is_mod_clk(id)) { |
| for (i = 0; i < data->info->num_mod_clks; i++) { |
| if (data->info->mod_clks[i].id == cpg_clk_id) |
| return rzg2l_cpg_clk_set_rate_by_id(dev, |
| data->info->mod_clks[i].parent, |
| rate); |
| } |
| |
| dev_err(dev, "Module clock ID %u not found\n", cpg_clk_id); |
| return -ENODEV; |
| } |
| |
| for (i = 0; i < data->info->num_core_clks; i++) { |
| if (data->info->core_clks[i].id == cpg_clk_id) |
| return rzg2l_core_clk_set_rate(dev, &data->info->core_clks[i], rate); |
| } |
| |
| dev_err(dev, "Core clock ID %u not found\n", cpg_clk_id); |
| return -ENODEV; |
| } |
| |
| static ulong rzg2l_cpg_clk_set_rate(struct clk *clk, ulong rate) |
| { |
| return rzg2l_cpg_clk_set_rate_by_id(clk->dev, clk->id, rate); |
| } |
| |
| static const struct clk_ops rzg2l_cpg_clk_ops = { |
| .enable = rzg2l_cpg_clk_enable, |
| .disable = rzg2l_cpg_clk_disable, |
| .of_xlate = rzg2l_cpg_clk_of_xlate, |
| .get_rate = rzg2l_cpg_clk_get_rate, |
| .set_rate = rzg2l_cpg_clk_set_rate, |
| }; |
| |
| U_BOOT_DRIVER(rzg2l_cpg_clk) = { |
| .name = "rzg2l-cpg-clk", |
| .id = UCLASS_CLK, |
| .ops = &rzg2l_cpg_clk_ops, |
| .flags = DM_FLAG_VITAL, |
| }; |
| |
| static int rzg2l_cpg_rst_set(struct reset_ctl *reset_ctl, bool asserted) |
| { |
| struct rzg2l_cpg_data *data = |
| (struct rzg2l_cpg_data *)dev_get_driver_data(reset_ctl->dev); |
| const struct rzg2l_reset *rst; |
| u32 value; |
| |
| dev_dbg(reset_ctl->dev, "%s %lu\n", asserted ? "assert" : "deassert", reset_ctl->id); |
| if (reset_ctl->id >= data->info->num_resets) { |
| dev_err(reset_ctl->dev, "Invalid reset id %lu\n", reset_ctl->id); |
| return -EINVAL; |
| } |
| rst = &data->info->resets[reset_ctl->id]; |
| |
| value = BIT(rst->bit) << 16; |
| if (!asserted) |
| value |= BIT(rst->bit); |
| writel(value, data->base + rst->off); |
| |
| return 0; |
| } |
| |
| static int rzg2l_cpg_rst_assert(struct reset_ctl *reset_ctl) |
| { |
| return rzg2l_cpg_rst_set(reset_ctl, true); |
| } |
| |
| static int rzg2l_cpg_rst_deassert(struct reset_ctl *reset_ctl) |
| { |
| return rzg2l_cpg_rst_set(reset_ctl, false); |
| } |
| |
| static int rzg2l_cpg_rst_of_xlate(struct reset_ctl *reset_ctl, |
| struct ofnode_phandle_args *args) |
| { |
| struct rzg2l_cpg_data *data = |
| (struct rzg2l_cpg_data *)dev_get_driver_data(reset_ctl->dev); |
| |
| if (args->args[0] >= data->info->num_resets) |
| return -EINVAL; |
| |
| reset_ctl->id = args->args[0]; |
| return 0; |
| } |
| |
| static const struct reset_ops rzg2l_cpg_rst_ops = { |
| .rst_assert = rzg2l_cpg_rst_assert, |
| .rst_deassert = rzg2l_cpg_rst_deassert, |
| .of_xlate = rzg2l_cpg_rst_of_xlate, |
| }; |
| |
| U_BOOT_DRIVER(rzg2l_cpg_rst) = { |
| .name = "rzg2l-cpg-rst", |
| .id = UCLASS_RESET, |
| .ops = &rzg2l_cpg_rst_ops, |
| .flags = DM_FLAG_VITAL, |
| }; |
| |
| int rzg2l_cpg_bind(struct udevice *parent) |
| { |
| struct udevice *cdev, *rdev; |
| struct rzg2l_cpg_data *data; |
| struct driver *drv; |
| int ret; |
| |
| data = devm_kmalloc(parent, sizeof(*data), 0); |
| if (!data) |
| return -ENOMEM; |
| |
| data->base = dev_read_addr_ptr(parent); |
| if (!data->base) |
| return -EINVAL; |
| |
| data->info = (struct rzg2l_cpg_info *)dev_get_driver_data(parent); |
| if (!data->info) |
| return -EINVAL; |
| |
| drv = lists_driver_lookup_name("rzg2l-cpg-clk"); |
| if (!drv) |
| return -ENOENT; |
| |
| ret = device_bind_with_driver_data(parent, drv, parent->name, |
| (ulong)data, dev_ofnode(parent), |
| &cdev); |
| if (ret) |
| return ret; |
| |
| drv = lists_driver_lookup_name("rzg2l-cpg-rst"); |
| if (!drv) { |
| device_unbind(cdev); |
| return -ENOENT; |
| } |
| |
| ret = device_bind_with_driver_data(parent, drv, parent->name, |
| (ulong)data, dev_ofnode(parent), |
| &rdev); |
| if (ret) |
| device_unbind(cdev); |
| |
| return ret; |
| } |