]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
clk: rockchip: implement linked gate clock support
authorSebastian Reichel <sebastian.reichel@collabora.com>
Wed, 11 Dec 2024 16:58:53 +0000 (17:58 +0100)
committerHeiko Stuebner <heiko@sntech.de>
Thu, 9 Jan 2025 15:19:21 +0000 (16:19 +0100)
Recent Rockchip SoCs have a new hardware block called Native Interface
Unit (NIU), which gates clocks to devices behind them. These clock
gates will only have a running output clock when all of the following
conditions are met:

1. the parent clock is enabled
2. the enable bit is set correctly
3. the linked clock is enabled

To handle them this code registers them as a normal gate type clock,
which takes care of condition 1 + 2. The linked clock is handled by
using runtime PM clocks. Handling it via runtime PM requires setting
up a struct device for each of these clocks with a driver attached
to use the correct runtime PM operations. Thus the complete handling
of these clocks has been moved into its own driver.

Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Link: https://lore.kernel.org/r/20241211165957.94922-5-sebastian.reichel@collabora.com
Signed-off-by: Heiko Stuebner <heiko@sntech.de>
drivers/clk/rockchip/Makefile
drivers/clk/rockchip/clk-rk3588.c
drivers/clk/rockchip/clk.c
drivers/clk/rockchip/clk.h
drivers/clk/rockchip/gate-link.c [new file with mode: 0644]

index af2ade54a7efa974ce77e091b1f4cb4f9851cc43..3fe7616f0ebe11dbf5ab5f01727a2cd75d151fd7 100644 (file)
@@ -13,6 +13,7 @@ clk-rockchip-y += clk-inverter.o
 clk-rockchip-y += clk-mmc-phase.o
 clk-rockchip-y += clk-muxgrf.o
 clk-rockchip-y += clk-ddr.o
+clk-rockchip-y += gate-link.o
 clk-rockchip-$(CONFIG_RESET_CONTROLLER) += softrst.o
 
 obj-$(CONFIG_CLK_PX30)          += clk-px30.o
index 618bda0be5a48806f5f1d1dd1a72d4e22c608276..d76ab27dd1b82d9a50cd0eab8f0ac86a5d9938e8 100644 (file)
 #include <dt-bindings/clock/rockchip,rk3588-cru.h>
 #include "clk.h"
 
-/*
- * Recent Rockchip SoCs have a new hardware block called Native Interface
- * Unit (NIU), which gates clocks to devices behind them. These effectively
- * need two parent clocks.
- *
- * Downstream enables the linked clock via runtime PM whenever the gate is
- * enabled. This implementation uses separate clock nodes for each of the
- * linked gate clocks, which leaks parts of the clock tree into DT.
- *
- * The GATE_LINK macro instead takes the second parent via 'linkname', but
- * ignores the information. Once the clock framework is ready to handle it, the
- * information should be passed on here. But since these clocks are required to
- * access multiple relevant IP blocks, such as PCIe or USB, we mark all linked
- * clocks critical until a better solution is available. This will waste some
- * power, but avoids leaking implementation details into DT or hanging the
- * system.
- */
-#define GATE_LINK(_id, cname, pname, linkedclk, f, o, b, gf) \
-       GATE(_id, cname, pname, f, o, b, gf)
 #define RK3588_LINKED_CLK              CLK_IS_CRITICAL
 
 
@@ -2513,8 +2494,8 @@ static int clk_rk3588_probe(struct platform_device *pdev)
        struct device *dev = &pdev->dev;
        struct device_node *np = dev->of_node;
 
-       rockchip_clk_register_branches(ctx, rk3588_clk_branches,
-                                      ARRAY_SIZE(rk3588_clk_branches));
+       rockchip_clk_register_late_branches(dev, ctx, rk3588_clk_branches,
+                                           ARRAY_SIZE(rk3588_clk_branches));
 
        rockchip_clk_finalize(ctx);
 
index d54b58a9e9fd9c8f2d906a532475ae5bdb43c8ff..cbf93ea119a9e25c037607ded1f6f358918e8656 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/clk-provider.h>
 #include <linux/io.h>
 #include <linux/mfd/syscon.h>
+#include <linux/platform_device.h>
 #include <linux/regmap.h>
 #include <linux/reboot.h>
 
@@ -468,6 +469,29 @@ unsigned long rockchip_clk_find_max_clk_id(struct rockchip_clk_branch *list,
 }
 EXPORT_SYMBOL_GPL(rockchip_clk_find_max_clk_id);
 
+static struct platform_device *rockchip_clk_register_gate_link(
+               struct device *parent_dev,
+               struct rockchip_clk_provider *ctx,
+               struct rockchip_clk_branch *clkbr)
+{
+       struct rockchip_gate_link_platdata gate_link_pdata = {
+               .ctx = ctx,
+               .clkbr = clkbr,
+       };
+
+       struct platform_device_info pdevinfo = {
+               .parent = parent_dev,
+               .name = "rockchip-gate-link-clk",
+               .id = clkbr->id,
+               .fwnode = dev_fwnode(parent_dev),
+               .of_node_reused = true,
+               .data = &gate_link_pdata,
+               .size_data = sizeof(gate_link_pdata),
+       };
+
+       return platform_device_register_full(&pdevinfo);
+}
+
 void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
                                    struct rockchip_clk_branch *list,
                                    unsigned int nr_clk)
@@ -593,6 +617,9 @@ void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
                                list->div_width, list->div_flags,
                                ctx->reg_base, &ctx->lock);
                        break;
+               case branch_linked_gate:
+                       /* must be registered late, fall-through for error message */
+                       break;
                }
 
                /* none of the cases above matched */
@@ -613,6 +640,31 @@ void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
 }
 EXPORT_SYMBOL_GPL(rockchip_clk_register_branches);
 
+void rockchip_clk_register_late_branches(struct device *dev,
+                                        struct rockchip_clk_provider *ctx,
+                                        struct rockchip_clk_branch *list,
+                                        unsigned int nr_clk)
+{
+       unsigned int idx;
+
+       for (idx = 0; idx < nr_clk; idx++, list++) {
+               struct platform_device *pdev = NULL;
+
+               switch (list->branch_type) {
+               case branch_linked_gate:
+                       pdev = rockchip_clk_register_gate_link(dev, ctx, list);
+                       break;
+               default:
+                       dev_err(dev, "unknown clock type %d\n", list->branch_type);
+                       break;
+               }
+
+               if (!pdev)
+                       dev_err(dev, "failed to register device for clock %s\n", list->name);
+       }
+}
+EXPORT_SYMBOL_GPL(rockchip_clk_register_late_branches);
+
 void rockchip_clk_register_armclk(struct rockchip_clk_provider *ctx,
                                  unsigned int lookup_id,
                                  const char *name, const char *const *parent_names,
index d9e071ca9c5d2115cbcbba8da888db6b5e4fdee5..9b37d44b9e5d866e2f683177c6c59744cf309600 100644 (file)
@@ -570,6 +570,7 @@ enum rockchip_clk_branch_type {
        branch_divider,
        branch_fraction_divider,
        branch_gate,
+       branch_linked_gate,
        branch_mmc,
        branch_inverter,
        branch_factor,
@@ -597,6 +598,7 @@ struct rockchip_clk_branch {
        int                             gate_offset;
        u8                              gate_shift;
        u8                              gate_flags;
+       unsigned int                    linked_clk_id;
        struct rockchip_clk_branch      *child;
 };
 
@@ -895,6 +897,20 @@ struct rockchip_clk_branch {
                .gate_flags     = gf,                           \
        }
 
+#define GATE_LINK(_id, cname, pname, linkedclk, f, o, b, gf)   \
+       {                                                       \
+               .id             = _id,                          \
+               .branch_type    = branch_linked_gate,           \
+               .name           = cname,                        \
+               .parent_names   = (const char *[]){ pname },    \
+               .linked_clk_id  = linkedclk,                    \
+               .num_parents    = 1,                            \
+               .flags          = f,                            \
+               .gate_offset    = o,                            \
+               .gate_shift     = b,                            \
+               .gate_flags     = gf,                           \
+       }
+
 #define MMC(_id, cname, pname, offset, shift)                  \
        {                                                       \
                .id             = _id,                          \
@@ -1034,6 +1050,11 @@ static inline void rockchip_clk_set_lookup(struct rockchip_clk_provider *ctx,
        ctx->clk_data.clks[id] = clk;
 }
 
+struct rockchip_gate_link_platdata {
+       struct rockchip_clk_provider *ctx;
+       struct rockchip_clk_branch *clkbr;
+};
+
 struct rockchip_clk_provider *rockchip_clk_init(struct device_node *np,
                        void __iomem *base, unsigned long nr_clks);
 struct rockchip_clk_provider *rockchip_clk_init_early(struct device_node *np,
@@ -1046,6 +1067,10 @@ unsigned long rockchip_clk_find_max_clk_id(struct rockchip_clk_branch *list,
 void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
                                    struct rockchip_clk_branch *list,
                                    unsigned int nr_clk);
+void rockchip_clk_register_late_branches(struct device *dev,
+                                        struct rockchip_clk_provider *ctx,
+                                        struct rockchip_clk_branch *list,
+                                        unsigned int nr_clk);
 void rockchip_clk_register_plls(struct rockchip_clk_provider *ctx,
                                struct rockchip_pll_clock *pll_list,
                                unsigned int nr_pll, int grf_lock_offset);
diff --git a/drivers/clk/rockchip/gate-link.c b/drivers/clk/rockchip/gate-link.c
new file mode 100644 (file)
index 0000000..cd0f7a2
--- /dev/null
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2024 Collabora Ltd.
+ * Author: Sebastian Reichel <sebastian.reichel@collabora.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/pm_clock.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include "clk.h"
+
+static int rk_clk_gate_link_register(struct device *dev,
+                                    struct rockchip_clk_provider *ctx,
+                                    struct rockchip_clk_branch *clkbr)
+{
+       unsigned long flags = clkbr->flags | CLK_SET_RATE_PARENT;
+       struct clk *clk;
+
+       clk = clk_register_gate(dev, clkbr->name, clkbr->parent_names[0],
+                               flags, ctx->reg_base + clkbr->gate_offset,
+                               clkbr->gate_shift, clkbr->gate_flags,
+                               &ctx->lock);
+
+       if (IS_ERR(clk))
+               return PTR_ERR(clk);
+
+       rockchip_clk_set_lookup(ctx, clk, clkbr->id);
+       return 0;
+}
+
+static int rk_clk_gate_link_probe(struct platform_device *pdev)
+{
+       struct rockchip_gate_link_platdata *pdata;
+       struct device *dev = &pdev->dev;
+       struct clk *linked_clk;
+       int ret;
+
+       pdata = dev_get_platdata(dev);
+       if (!pdata)
+               return dev_err_probe(dev, -ENODEV, "missing platform data");
+
+       ret = devm_pm_runtime_enable(dev);
+       if (ret)
+               return ret;
+
+       ret = devm_pm_clk_create(dev);
+       if (ret)
+               return ret;
+
+       linked_clk = rockchip_clk_get_lookup(pdata->ctx, pdata->clkbr->linked_clk_id);
+       ret = pm_clk_add_clk(dev, linked_clk);
+       if (ret)
+               return ret;
+
+       ret = rk_clk_gate_link_register(dev, pdata->ctx, pdata->clkbr);
+       if (ret)
+               goto err;
+
+       return 0;
+
+err:
+       pm_clk_remove_clk(dev, linked_clk);
+       return ret;
+}
+
+static const struct dev_pm_ops rk_clk_gate_link_pm_ops = {
+       SET_RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL)
+};
+
+static struct platform_driver rk_clk_gate_link_driver = {
+       .probe          = rk_clk_gate_link_probe,
+       .driver         = {
+               .name   = "rockchip-gate-link-clk",
+               .pm = &rk_clk_gate_link_pm_ops,
+               .suppress_bind_attrs = true,
+       },
+};
+
+static int __init rk_clk_gate_link_drv_register(void)
+{
+       return platform_driver_register(&rk_clk_gate_link_driver);
+}
+core_initcall(rk_clk_gate_link_drv_register);