mirror of
https://github.com/torvalds/linux.git
synced 2026-05-30 18:13:41 +02:00
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: 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>
This commit is contained in:
parent
fe0fb6675f
commit
c62fa612cf
|
|
@ -13,6 +13,7 @@ clk-rockchip-y += clk-inverter.o
|
||||||
clk-rockchip-y += clk-mmc-phase.o
|
clk-rockchip-y += clk-mmc-phase.o
|
||||||
clk-rockchip-y += clk-muxgrf.o
|
clk-rockchip-y += clk-muxgrf.o
|
||||||
clk-rockchip-y += clk-ddr.o
|
clk-rockchip-y += clk-ddr.o
|
||||||
|
clk-rockchip-y += gate-link.o
|
||||||
clk-rockchip-$(CONFIG_RESET_CONTROLLER) += softrst.o
|
clk-rockchip-$(CONFIG_RESET_CONTROLLER) += softrst.o
|
||||||
|
|
||||||
obj-$(CONFIG_CLK_PX30) += clk-px30.o
|
obj-$(CONFIG_CLK_PX30) += clk-px30.o
|
||||||
|
|
|
||||||
|
|
@ -12,25 +12,6 @@
|
||||||
#include <dt-bindings/clock/rockchip,rk3588-cru.h>
|
#include <dt-bindings/clock/rockchip,rk3588-cru.h>
|
||||||
#include "clk.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
|
#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 *dev = &pdev->dev;
|
||||||
struct device_node *np = dev->of_node;
|
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));
|
ARRAY_SIZE(rk3588_clk_branches));
|
||||||
|
|
||||||
rockchip_clk_finalize(ctx);
|
rockchip_clk_finalize(ctx);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
#include <linux/clk-provider.h>
|
#include <linux/clk-provider.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
#include <linux/mfd/syscon.h>
|
#include <linux/mfd/syscon.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
#include <linux/reboot.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);
|
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,
|
void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
|
||||||
struct rockchip_clk_branch *list,
|
struct rockchip_clk_branch *list,
|
||||||
unsigned int nr_clk)
|
unsigned int nr_clk)
|
||||||
|
|
@ -593,6 +617,9 @@ void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
|
||||||
list->div_width, list->div_flags,
|
list->div_width, list->div_flags,
|
||||||
ctx->reg_base, &ctx->lock);
|
ctx->reg_base, &ctx->lock);
|
||||||
break;
|
break;
|
||||||
|
case branch_linked_gate:
|
||||||
|
/* must be registered late, fall-through for error message */
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* none of the cases above matched */
|
/* 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);
|
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,
|
void rockchip_clk_register_armclk(struct rockchip_clk_provider *ctx,
|
||||||
unsigned int lookup_id,
|
unsigned int lookup_id,
|
||||||
const char *name, const char *const *parent_names,
|
const char *name, const char *const *parent_names,
|
||||||
|
|
|
||||||
|
|
@ -570,6 +570,7 @@ enum rockchip_clk_branch_type {
|
||||||
branch_divider,
|
branch_divider,
|
||||||
branch_fraction_divider,
|
branch_fraction_divider,
|
||||||
branch_gate,
|
branch_gate,
|
||||||
|
branch_linked_gate,
|
||||||
branch_mmc,
|
branch_mmc,
|
||||||
branch_inverter,
|
branch_inverter,
|
||||||
branch_factor,
|
branch_factor,
|
||||||
|
|
@ -597,6 +598,7 @@ struct rockchip_clk_branch {
|
||||||
int gate_offset;
|
int gate_offset;
|
||||||
u8 gate_shift;
|
u8 gate_shift;
|
||||||
u8 gate_flags;
|
u8 gate_flags;
|
||||||
|
unsigned int linked_clk_id;
|
||||||
struct rockchip_clk_branch *child;
|
struct rockchip_clk_branch *child;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -895,6 +897,20 @@ struct rockchip_clk_branch {
|
||||||
.gate_flags = gf, \
|
.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) \
|
#define MMC(_id, cname, pname, offset, shift) \
|
||||||
{ \
|
{ \
|
||||||
.id = _id, \
|
.id = _id, \
|
||||||
|
|
@ -1034,6 +1050,11 @@ static inline void rockchip_clk_set_lookup(struct rockchip_clk_provider *ctx,
|
||||||
ctx->clk_data.clks[id] = clk;
|
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,
|
struct rockchip_clk_provider *rockchip_clk_init(struct device_node *np,
|
||||||
void __iomem *base, unsigned long nr_clks);
|
void __iomem *base, unsigned long nr_clks);
|
||||||
struct rockchip_clk_provider *rockchip_clk_init_early(struct device_node *np,
|
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,
|
void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
|
||||||
struct rockchip_clk_branch *list,
|
struct rockchip_clk_branch *list,
|
||||||
unsigned int nr_clk);
|
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,
|
void rockchip_clk_register_plls(struct rockchip_clk_provider *ctx,
|
||||||
struct rockchip_pll_clock *pll_list,
|
struct rockchip_pll_clock *pll_list,
|
||||||
unsigned int nr_pll, int grf_lock_offset);
|
unsigned int nr_pll, int grf_lock_offset);
|
||||||
|
|
|
||||||
85
drivers/clk/rockchip/gate-link.c
Normal file
85
drivers/clk/rockchip/gate-link.c
Normal file
|
|
@ -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);
|
||||||
Loading…
Reference in New Issue
Block a user