Commit e277168c authored by Nicolas Frattaroli's avatar Nicolas Frattaroli Committed by Heiko Stuebner
Browse files

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: default avatarNicolas Frattaroli <nicolas.frattaroli@collabora.com>
Link: https://lore.kernel.org/r/20250502-rk3576-sai-v3-3-376cef19dd7c@collabora.com


Signed-off-by: default avatarHeiko Stuebner <heiko@sntech.de>
parent 70a114da
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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
+8 −1
Original line number Diff line number Diff line
@@ -509,7 +509,7 @@ void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
		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(struct rockchip_clk_provider *ctx,
				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,
+20 −0
Original line number Diff line number Diff line
@@ -647,6 +647,11 @@ struct clk *rockchip_clk_register_muxgrf(const char *name,
				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 {
@@ -656,6 +661,7 @@ enum rockchip_clk_branch_type {
	branch_divider,
	branch_fraction_divider,
	branch_gate,
	branch_grf_gate,
	branch_linked_gate,
	branch_mmc,
	branch_inverter,
@@ -985,6 +991,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,				\
+105 −0
Original line number Diff line number Diff line
// 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;
}