Commit c62fa612 authored by Sebastian Reichel's avatar Sebastian Reichel Committed by Heiko Stuebner
Browse files

clk: rockchip: implement linked gate clock support



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: default avatarSebastian Reichel <sebastian.reichel@collabora.com>
Link: https://lore.kernel.org/r/20241211165957.94922-5-sebastian.reichel@collabora.com


Signed-off-by: default avatarHeiko Stuebner <heiko@sntech.de>
parent fe0fb667
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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
+2 −21
Original line number Diff line number Diff line
@@ -12,25 +12,6 @@
#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,7 +2494,7 @@ 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,
	rockchip_clk_register_late_branches(dev, ctx, rk3588_clk_branches,
					    ARRAY_SIZE(rk3588_clk_branches));

	rockchip_clk_finalize(ctx);
+52 −0
Original line number Diff line number Diff line
@@ -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,
+25 −0
Original line number Diff line number Diff line
@@ -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);
+85 −0
Original line number Diff line number Diff line
// 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);