214 lines
6.7 KiB
Diff
214 lines
6.7 KiB
Diff
From e277168cabe9fd99e647f5dad0bc846d5d6b0093 Mon Sep 17 00:00:00 2001
|
|
From: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
|
|
Date: Fri, 2 May 2025 13:03:09 +0200
|
|
Subject: [PATCH] clk: rockchip: introduce GRF gates
|
|
|
|
Some rockchip SoCs, namely the RK3576, have bits in a General Register
|
|
File (GRF) that act just like clock gates. The downstream vendor kernel
|
|
simply maps over the already mapped GRF range with a generic clock gate
|
|
driver. This solution isn't suitable for upstream, as a memory range
|
|
will be in use by multiple drivers at the same time, and it leaks
|
|
implementation details into the device tree.
|
|
|
|
Instead, implement this with a new clock branch type in the Rockchip
|
|
clock driver: GRF gates. Somewhat akin to MUXGRF, this clock branch
|
|
depends on the type of GRF, but functions like a gate instead.
|
|
|
|
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
|
|
Link: https://lore.kernel.org/r/20250502-rk3576-sai-v3-3-376cef19dd7c@collabora.com
|
|
Signed-off-by: Heiko Stuebner <heiko@sntech.de>
|
|
---
|
|
drivers/clk/rockchip/Makefile | 1 +
|
|
drivers/clk/rockchip/clk.c | 9 ++-
|
|
drivers/clk/rockchip/clk.h | 20 ++++++
|
|
drivers/clk/rockchip/gate-grf.c | 105 ++++++++++++++++++++++++++++++++
|
|
4 files changed, 134 insertions(+), 1 deletion(-)
|
|
create mode 100644 drivers/clk/rockchip/gate-grf.c
|
|
|
|
--- a/drivers/clk/rockchip/Makefile
|
|
+++ b/drivers/clk/rockchip/Makefile
|
|
@@ -14,6 +14,7 @@ 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-y += gate-grf.o
|
|
clk-rockchip-$(CONFIG_RESET_CONTROLLER) += softrst.o
|
|
|
|
obj-$(CONFIG_CLK_PX30) += clk-px30.o
|
|
--- a/drivers/clk/rockchip/clk.c
|
|
+++ b/drivers/clk/rockchip/clk.c
|
|
@@ -509,7 +509,7 @@ void rockchip_clk_register_branches(stru
|
|
clk = NULL;
|
|
|
|
/* for GRF-dependent branches, choose the right grf first */
|
|
- if (list->branch_type == branch_muxgrf &&
|
|
+ if ((list->branch_type == branch_muxgrf || list->branch_type == branch_grf_gate) &&
|
|
list->grf_type != grf_type_sys) {
|
|
hash_for_each_possible(ctx->aux_grf_table, agrf, node, list->grf_type) {
|
|
if (agrf->type == list->grf_type) {
|
|
@@ -588,6 +588,13 @@ void rockchip_clk_register_branches(stru
|
|
ctx->reg_base + list->gate_offset,
|
|
list->gate_shift, list->gate_flags, &ctx->lock);
|
|
break;
|
|
+ case branch_grf_gate:
|
|
+ flags |= CLK_SET_RATE_PARENT;
|
|
+ clk = rockchip_clk_register_gate_grf(list->name,
|
|
+ list->parent_names[0], flags, grf,
|
|
+ list->gate_offset, list->gate_shift,
|
|
+ list->gate_flags);
|
|
+ break;
|
|
case branch_composite:
|
|
clk = rockchip_clk_register_branch(list->name,
|
|
list->parent_names, list->num_parents,
|
|
--- a/drivers/clk/rockchip/clk.h
|
|
+++ b/drivers/clk/rockchip/clk.h
|
|
@@ -586,6 +586,11 @@ struct clk *rockchip_clk_register_muxgrf
|
|
int flags, struct regmap *grf, int reg,
|
|
int shift, int width, int mux_flags);
|
|
|
|
+struct clk *rockchip_clk_register_gate_grf(const char *name,
|
|
+ const char *parent_name, unsigned long flags,
|
|
+ struct regmap *regmap, unsigned int reg,
|
|
+ unsigned int shift, u8 gate_flags);
|
|
+
|
|
#define PNAME(x) static const char *const x[] __initconst
|
|
|
|
enum rockchip_clk_branch_type {
|
|
@@ -595,6 +600,7 @@ enum rockchip_clk_branch_type {
|
|
branch_divider,
|
|
branch_fraction_divider,
|
|
branch_gate,
|
|
+ branch_grf_gate,
|
|
branch_linked_gate,
|
|
branch_mmc,
|
|
branch_inverter,
|
|
@@ -924,6 +930,20 @@ struct rockchip_clk_branch {
|
|
.gate_flags = gf, \
|
|
}
|
|
|
|
+#define GATE_GRF(_id, cname, pname, f, o, b, gf, gt) \
|
|
+ { \
|
|
+ .id = _id, \
|
|
+ .branch_type = branch_grf_gate, \
|
|
+ .name = cname, \
|
|
+ .parent_names = (const char *[]){ pname }, \
|
|
+ .num_parents = 1, \
|
|
+ .flags = f, \
|
|
+ .gate_offset = o, \
|
|
+ .gate_shift = b, \
|
|
+ .gate_flags = gf, \
|
|
+ .grf_type = gt, \
|
|
+ }
|
|
+
|
|
#define GATE_LINK(_id, cname, pname, linkedclk, f, o, b, gf) \
|
|
{ \
|
|
.id = _id, \
|
|
--- /dev/null
|
|
+++ b/drivers/clk/rockchip/gate-grf.c
|
|
@@ -0,0 +1,105 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-or-later
|
|
+/*
|
|
+ * Copyright (c) 2025 Collabora Ltd.
|
|
+ * Author: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
|
|
+ *
|
|
+ * Certain clocks on Rockchip are "gated" behind an additional register bit
|
|
+ * write in a GRF register, such as the SAI MCLKs on RK3576. This code
|
|
+ * implements a clock driver for these types of gates, based on regmaps.
|
|
+ */
|
|
+
|
|
+#include <linux/clk.h>
|
|
+#include <linux/clk-provider.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/slab.h>
|
|
+#include "clk.h"
|
|
+
|
|
+struct rockchip_gate_grf {
|
|
+ struct clk_hw hw;
|
|
+ struct regmap *regmap;
|
|
+ unsigned int reg;
|
|
+ unsigned int shift;
|
|
+ u8 flags;
|
|
+};
|
|
+
|
|
+#define to_gate_grf(_hw) container_of(_hw, struct rockchip_gate_grf, hw)
|
|
+
|
|
+static int rockchip_gate_grf_enable(struct clk_hw *hw)
|
|
+{
|
|
+ struct rockchip_gate_grf *gate = to_gate_grf(hw);
|
|
+ u32 val = !(gate->flags & CLK_GATE_SET_TO_DISABLE) ? BIT(gate->shift) : 0;
|
|
+ u32 hiword = ((gate->flags & CLK_GATE_HIWORD_MASK) ? 1 : 0) << (gate->shift + 16);
|
|
+ int ret;
|
|
+
|
|
+ ret = regmap_update_bits(gate->regmap, gate->reg,
|
|
+ hiword | BIT(gate->shift), hiword | val);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void rockchip_gate_grf_disable(struct clk_hw *hw)
|
|
+{
|
|
+ struct rockchip_gate_grf *gate = to_gate_grf(hw);
|
|
+ u32 val = !(gate->flags & CLK_GATE_SET_TO_DISABLE) ? 0 : BIT(gate->shift);
|
|
+ u32 hiword = ((gate->flags & CLK_GATE_HIWORD_MASK) ? 1 : 0) << (gate->shift + 16);
|
|
+
|
|
+ regmap_update_bits(gate->regmap, gate->reg,
|
|
+ hiword | BIT(gate->shift), hiword | val);
|
|
+}
|
|
+
|
|
+static int rockchip_gate_grf_is_enabled(struct clk_hw *hw)
|
|
+{
|
|
+ struct rockchip_gate_grf *gate = to_gate_grf(hw);
|
|
+ bool invert = !!(gate->flags & CLK_GATE_SET_TO_DISABLE);
|
|
+ int ret;
|
|
+
|
|
+ ret = regmap_test_bits(gate->regmap, gate->reg, BIT(gate->shift));
|
|
+ if (ret < 0)
|
|
+ ret = 0;
|
|
+
|
|
+ return invert ? 1 - ret : ret;
|
|
+
|
|
+}
|
|
+
|
|
+static const struct clk_ops rockchip_gate_grf_ops = {
|
|
+ .enable = rockchip_gate_grf_enable,
|
|
+ .disable = rockchip_gate_grf_disable,
|
|
+ .is_enabled = rockchip_gate_grf_is_enabled,
|
|
+};
|
|
+
|
|
+struct clk *rockchip_clk_register_gate_grf(const char *name,
|
|
+ const char *parent_name, unsigned long flags,
|
|
+ struct regmap *regmap, unsigned int reg, unsigned int shift,
|
|
+ u8 gate_flags)
|
|
+{
|
|
+ struct rockchip_gate_grf *gate;
|
|
+ struct clk_init_data init;
|
|
+ struct clk *clk;
|
|
+
|
|
+ if (IS_ERR(regmap)) {
|
|
+ pr_err("%s: regmap not available\n", __func__);
|
|
+ return ERR_PTR(-EOPNOTSUPP);
|
|
+ }
|
|
+
|
|
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
|
|
+ if (!gate)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ init.name = name;
|
|
+ init.flags = flags;
|
|
+ init.num_parents = parent_name ? 1 : 0;
|
|
+ init.parent_names = parent_name ? &parent_name : NULL;
|
|
+ init.ops = &rockchip_gate_grf_ops;
|
|
+
|
|
+ gate->hw.init = &init;
|
|
+ gate->regmap = regmap;
|
|
+ gate->reg = reg;
|
|
+ gate->shift = shift;
|
|
+ gate->flags = gate_flags;
|
|
+
|
|
+ clk = clk_register(NULL, &gate->hw);
|
|
+ if (IS_ERR(clk))
|
|
+ kfree(gate);
|
|
+
|
|
+ return clk;
|
|
+}
|