Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Anirudh Srinivasan 4453 100.00% 1 100.00%
Total 4453 1


// SPDX-License-Identifier: GPL-2.0-only
/*
 * Tenstorrent Atlantis PRCM Clock Driver
 *
 * Copyright (c) 2026 Tenstorrent
 */

#include <dt-bindings/clock/tenstorrent,atlantis-prcm-rcpu.h>
#include <linux/auxiliary_bus.h>
#include <linux/bitfield.h>
#include <linux/clk-provider.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>

/* RCPU Clock Register Offsets */
#define PLL_RCPU_CFG_REG	0x0000
#define PLL_NOCC_CFG_REG	0x0004
#define NOCC_CLK_CFG_REG	0x0008
#define RCPU_DIV_CFG_REG	0x000C
#define RCPU_BLK_CG_REG		0x0014
#define LSIO_BLK_CG_REG		0x0018
#define PLL_RCPU_EN_REG		0x011C
#define PLL_NOCC_EN_REG		0x0120
#define BUS_CG_REG		0x01FC

/* PLL Bit Definitions */
#define PLL_CFG_EN_BIT		BIT(0)
#define PLL_CFG_BYPASS_BIT	BIT(1)
#define PLL_CFG_REFDIV_MASK	GENMASK(7, 2)
#define PLL_CFG_REFDIV_SHIFT	2
#define PLL_CFG_POSTDIV1_MASK	GENMASK(10, 8)
#define PLL_CFG_POSTDIV1_SHIFT	8
#define PLL_CFG_POSTDIV2_MASK	GENMASK(13, 11)
#define PLL_CFG_POSTDIV2_SHIFT	11
#define PLL_CFG_FBDIV_MASK	GENMASK(25, 14)
#define PLL_CFG_FBDIV_SHIFT	14
#define PLL_CFG_LKDT_BIT	BIT(30)
#define PLL_CFG_LOCK_BIT	BIT(31)
#define PLL_LOCK_TIMEOUT_US	1000
#define PLL_BYPASS_WAIT_US	500

struct atlantis_clk_common {
	int clkid;
	struct regmap *regmap;
	struct clk_hw hw;
};

static inline struct atlantis_clk_common *
hw_to_atlantis_clk_common(struct clk_hw *hw)
{
	return container_of(hw, struct atlantis_clk_common, hw);
}

struct atlantis_clk_mux_config {
	u8 shift;
	u8 width;
	u32 reg_offset;
};

struct atlantis_clk_mux {
	struct atlantis_clk_common common;
	struct atlantis_clk_mux_config config;
};

struct atlantis_clk_gate_config {
	u32 reg_offset;
	u32 enable;
};

struct atlantis_clk_gate {
	struct atlantis_clk_common common;
	struct atlantis_clk_gate_config config;
};

struct atlantis_clk_divider_config {
	u8 shift;
	u8 width;
	u32 flags;
	u32 reg_offset;
};

struct atlantis_clk_divider {
	struct atlantis_clk_common common;
	struct atlantis_clk_divider_config config;
};

struct atlantis_clk_pll_config {
	u32 tbl_num;
	u32 reg_offset;
	u32 en_reg_offset;
	u32 cg_reg_offset;
	u32 cg_reg_enable;
};

/* Models a PLL with Bypass Functionality and Enable Bit + an optional Gate Clock at it's output */
struct atlantis_clk_pll {
	struct atlantis_clk_common common;
	struct atlantis_clk_pll_config config;
};

struct atlantis_clk_gate_shared_config {
	u32 reg_offset;
	u32 enable;
	unsigned int *share_count;
	spinlock_t *refcount_lock;
};

struct atlantis_clk_gate_shared {
	struct atlantis_clk_common common;
	struct atlantis_clk_gate_shared_config config;
};

struct atlantis_clk_fixed_factor_config {
	unsigned int mult;
	unsigned int div;
};

struct atlantis_clk_fixed_factor {
	struct atlantis_clk_fixed_factor_config config;
	struct atlantis_clk_common common;
};

static inline struct atlantis_clk_mux *hw_to_atlantis_clk_mux(struct clk_hw *hw)
{
	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);

	return container_of(common, struct atlantis_clk_mux, common);
}

static inline struct atlantis_clk_gate *
hw_to_atlantis_clk_gate(struct clk_hw *hw)
{
	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);

	return container_of(common, struct atlantis_clk_gate, common);
}

static inline struct atlantis_clk_divider *
hw_to_atlantis_clk_divider(struct clk_hw *hw)
{
	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);

	return container_of(common, struct atlantis_clk_divider, common);
}

static inline struct atlantis_clk_pll *hw_to_atlantis_pll(struct clk_hw *hw)
{
	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);

	return container_of(common, struct atlantis_clk_pll, common);
}

static inline struct atlantis_clk_gate_shared *
hw_to_atlantis_clk_gate_shared(struct clk_hw *hw)
{
	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);

	return container_of(common, struct atlantis_clk_gate_shared, common);
}

static inline struct atlantis_clk_fixed_factor *
hw_to_atlantis_clk_fixed_factor(struct clk_hw *hw)
{
	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);

	return container_of(common, struct atlantis_clk_fixed_factor, common);
}

static u8 atlantis_clk_mux_get_parent(struct clk_hw *hw)
{
	struct atlantis_clk_mux *mux = hw_to_atlantis_clk_mux(hw);
	u32 val;

	regmap_read(mux->common.regmap, mux->config.reg_offset, &val);
	val >>= mux->config.shift;
	val &= (BIT(mux->config.width) - 1);

	return val;
}

static int atlantis_clk_mux_set_parent(struct clk_hw *hw, u8 index)
{
	struct atlantis_clk_mux *mux = hw_to_atlantis_clk_mux(hw);
	u32 val = index;

	return regmap_update_bits(mux->common.regmap, mux->config.reg_offset,
				  (BIT(mux->config.width) - 1) << mux->config.shift,
				  val << mux->config.shift);
}

static int atlantis_clk_mux_determine_rate(struct clk_hw *hw,
					   struct clk_rate_request *req)
{
	return clk_mux_determine_rate_flags(hw, req, hw->init->flags);
}

static const struct clk_ops atlantis_clk_mux_ops = {
	.get_parent = atlantis_clk_mux_get_parent,
	.set_parent = atlantis_clk_mux_set_parent,
	.determine_rate = atlantis_clk_mux_determine_rate,
};

static int atlantis_clk_gate_endisable(struct clk_hw *hw, int enable)
{
	struct atlantis_clk_gate *gate = hw_to_atlantis_clk_gate(hw);

	if (enable)
		return regmap_set_bits(gate->common.regmap,
				       gate->config.reg_offset,
				       gate->config.enable);
	else
		return regmap_clear_bits(gate->common.regmap,
					 gate->config.reg_offset,
					 gate->config.enable);
}

static int atlantis_clk_gate_enable(struct clk_hw *hw)
{
	return atlantis_clk_gate_endisable(hw, 1);
}

static void atlantis_clk_gate_disable(struct clk_hw *hw)
{
	atlantis_clk_gate_endisable(hw, 0);
}

static int atlantis_clk_gate_is_enabled(struct clk_hw *hw)
{
	struct atlantis_clk_gate *gate = hw_to_atlantis_clk_gate(hw);

	return regmap_test_bits(gate->common.regmap, gate->config.reg_offset, gate->config.enable);
}

static const struct clk_ops atlantis_clk_gate_ops = {
	.enable = atlantis_clk_gate_enable,
	.disable = atlantis_clk_gate_disable,
	.is_enabled = atlantis_clk_gate_is_enabled,
};

static unsigned long atlantis_clk_divider_recalc_rate(struct clk_hw *hw,
						      unsigned long parent_rate)
{
	struct atlantis_clk_divider *divider = hw_to_atlantis_clk_divider(hw);
	u32 val;

	regmap_read(divider->common.regmap, divider->config.reg_offset, &val);

	val >>= divider->config.shift;
	val &= ((1 << (divider->config.width)) - 1);

	return DIV_ROUND_UP_ULL((u64)parent_rate, val + 1);
}

static const struct clk_ops atlantis_clk_divider_ops = {
	.recalc_rate = atlantis_clk_divider_recalc_rate,
};

static unsigned long
atlantis_clk_fixed_factor_recalc_rate(struct clk_hw *hw,
				      unsigned long parent_rate)
{
	struct atlantis_clk_fixed_factor *factor =
		hw_to_atlantis_clk_fixed_factor(hw);
	unsigned long long rate;

	rate = (unsigned long long)parent_rate * factor->config.mult;
	do_div(rate, factor->config.div);

	return (unsigned long)rate;
}

static const struct clk_ops atlantis_clk_fixed_factor_ops = {
	.recalc_rate = atlantis_clk_fixed_factor_recalc_rate,
};

static int atlantis_clk_pll_is_enabled(struct clk_hw *hw)
{
	struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
	u32 val, en_val, cg_val;

	regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
	regmap_read(pll->common.regmap, pll->config.en_reg_offset, &en_val);
	regmap_read(pll->common.regmap, pll->config.cg_reg_offset, &cg_val);

	/* Check if PLL is powered on, locked, not bypassed and Gate clk is enabled */
	return !!(en_val & PLL_CFG_EN_BIT) && !!(val & PLL_CFG_LOCK_BIT) &&
	       (!pll->config.cg_reg_enable || (cg_val & pll->config.cg_reg_enable)) &&
	       !(val & PLL_CFG_BYPASS_BIT);
}

static int atlantis_clk_pll_enable(struct clk_hw *hw)
{
	struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
	u32 val, en_val, cg_val;
	int ret;

	regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
	regmap_read(pll->common.regmap, pll->config.en_reg_offset, &en_val);
	regmap_read(pll->common.regmap, pll->config.cg_reg_offset, &cg_val);

	/* Check if PLL is already enabled, locked, not bypassed and Gate clk is enabled */
	if ((en_val & PLL_CFG_EN_BIT) && (val & PLL_CFG_LOCK_BIT) &&
	    (!pll->config.cg_reg_enable || (cg_val & pll->config.cg_reg_enable)) &&
	    !(val & PLL_CFG_BYPASS_BIT)) {
		return 0;
	}

	/* Step 1: Set bypass mode first */
	regmap_update_bits(pll->common.regmap, pll->config.reg_offset,
			   PLL_CFG_BYPASS_BIT, PLL_CFG_BYPASS_BIT);

	/* Step 2: Enable PLL (clear then set power bit) */
	regmap_update_bits(pll->common.regmap, pll->config.en_reg_offset,
			   PLL_CFG_EN_BIT, 0);

	regmap_update_bits(pll->common.regmap, pll->config.en_reg_offset,
			   PLL_CFG_EN_BIT, PLL_CFG_EN_BIT);

	/* Step 3: Wait for PLL lock */
	ret = regmap_read_poll_timeout(pll->common.regmap,
				       pll->config.reg_offset, val,
				       val & PLL_CFG_LOCK_BIT,
				       PLL_BYPASS_WAIT_US, PLL_LOCK_TIMEOUT_US);
	if (ret) {
		pr_err("PLL failed to lock within timeout\n");
		return ret;
	}

	/* Step 4: Switch from bypass to PLL output */
	regmap_update_bits(pll->common.regmap, pll->config.reg_offset,
			   PLL_CFG_BYPASS_BIT, 0);

	/* Enable Gate clk at PLL Output */
	return regmap_update_bits(pll->common.regmap, pll->config.cg_reg_offset,
				  pll->config.cg_reg_enable,
				  pll->config.cg_reg_enable);
}

static void atlantis_clk_pll_disable(struct clk_hw *hw)
{
	struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);

	/* Step 1: Switch to bypass mode before disabling */
	regmap_update_bits(pll->common.regmap, pll->config.reg_offset,
			   PLL_CFG_BYPASS_BIT, PLL_CFG_BYPASS_BIT);
	/* Step 2: Power down PLL */
	regmap_update_bits(pll->common.regmap, pll->config.en_reg_offset,
			   PLL_CFG_EN_BIT, 0);
}

static unsigned long atlantis_clk_pll_recalc_rate(struct clk_hw *hw,
						  unsigned long parent_rate)
{
	struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);

	u32 val, refdiv, fbdiv, postdiv1, postdiv2;
	u64 fout;

	regmap_read(pll->common.regmap, pll->config.reg_offset, &val);

	if (val & PLL_CFG_BYPASS_BIT)
		return parent_rate;

	refdiv = FIELD_GET(PLL_CFG_REFDIV_MASK, val);
	fbdiv = FIELD_GET(PLL_CFG_FBDIV_MASK, val);
	postdiv1 = FIELD_GET(PLL_CFG_POSTDIV1_MASK, val);
	postdiv2 = FIELD_GET(PLL_CFG_POSTDIV2_MASK, val);

	if (!refdiv)
		refdiv = 1;
	if (!postdiv1)
		postdiv1 = 1;
	if (!postdiv2)
		postdiv2 = 1;
	if (!fbdiv)
		return 0;

	fout = div64_u64((u64)parent_rate * fbdiv,
			 refdiv * postdiv1 * postdiv2);

	return fout;
}

static const struct clk_ops atlantis_clk_pll_ops = {
	.enable = atlantis_clk_pll_enable,
	.disable = atlantis_clk_pll_disable,
	.recalc_rate = atlantis_clk_pll_recalc_rate,
	.is_enabled = atlantis_clk_pll_is_enabled,
};

static int atlantis_clk_gate_shared_enable(struct clk_hw *hw)
{
	struct atlantis_clk_gate_shared *gate =
		hw_to_atlantis_clk_gate_shared(hw);
	bool need_enable;

	scoped_guard(spinlock_irqsave, gate->config.refcount_lock)
	{
		need_enable = (*gate->config.share_count)++ == 0;
		if (need_enable) {
			regmap_set_bits(gate->common.regmap,
					gate->config.reg_offset,
					gate->config.enable);
		}
	}

	if (need_enable) {
		if (!regmap_test_bits(gate->common.regmap,
				      gate->config.reg_offset,
				      gate->config.enable)) {
			pr_warn("%s: gate enable %d failed to enable\n",
				clk_hw_get_name(hw), gate->config.enable);
			return -EIO;
		}
	}

	return 0;
}

static void atlantis_clk_gate_shared_disable(struct clk_hw *hw)
{
	struct atlantis_clk_gate_shared *gate =
		hw_to_atlantis_clk_gate_shared(hw);

	scoped_guard(spinlock_irqsave, gate->config.refcount_lock)
	{
		if (WARN_ON(*gate->config.share_count == 0))
			return;
		if (--(*gate->config.share_count) > 0)
			return;

		regmap_clear_bits(gate->common.regmap,
				  gate->config.reg_offset,
				  gate->config.enable);
	}
}

static int atlantis_clk_gate_shared_is_enabled(struct clk_hw *hw)
{
	struct atlantis_clk_gate_shared *gate =
		hw_to_atlantis_clk_gate_shared(hw);

	return regmap_test_bits(gate->common.regmap, gate->config.reg_offset, gate->config.enable);
}

static void atlantis_clk_gate_shared_disable_unused(struct clk_hw *hw)
{
	struct atlantis_clk_gate_shared *gate =
		hw_to_atlantis_clk_gate_shared(hw);

	scoped_guard(spinlock_irqsave, gate->config.refcount_lock)
	{
		if (*gate->config.share_count == 0)
			regmap_clear_bits(gate->common.regmap,
					  gate->config.reg_offset,
					  gate->config.enable);
	}
}

static const struct clk_ops atlantis_clk_gate_shared_ops = {
	.enable = atlantis_clk_gate_shared_enable,
	.disable = atlantis_clk_gate_shared_disable,
	.disable_unused = atlantis_clk_gate_shared_disable_unused,
	.is_enabled = atlantis_clk_gate_shared_is_enabled,
};

#define ATLANTIS_PLL_CONFIG(_reg_offset, _en_reg_offset, _cg_reg_offset, \
			    _cg_reg_enable)                              \
	{                                                                \
		.reg_offset = (_reg_offset),                             \
		.en_reg_offset = (_en_reg_offset),                       \
		.cg_reg_offset = (_cg_reg_offset),                       \
		.cg_reg_enable = (_cg_reg_enable),                       \
	}

#define ATLANTIS_PLL_DEFINE(_clkid, _name, _parent, _reg_offset,               \
			    _en_reg_offset, _cg_reg_offset, _cg_reg_enable,    \
			    _flags)                                            \
	static struct atlantis_clk_pll _name = {                               \
		.config = ATLANTIS_PLL_CONFIG(_reg_offset, _en_reg_offset,     \
					      _cg_reg_offset, _cg_reg_enable), \
		.common = { .clkid = _clkid,                                   \
			    .hw.init = CLK_HW_INIT_PARENTS_DATA(               \
				    #_name, _parent, &atlantis_clk_pll_ops,    \
				    _flags) },                                 \
	}
#define ATLANTIS_MUX_CONFIG(_shift, _width, _reg_offset)                    \
	{                                                                   \
		.shift = _shift, .width = _width, .reg_offset = _reg_offset \
	}

#define ATLANTIS_MUX_DEFINE(_clkid, _name, _parents, _reg_offset, _shift,    \
			    _width, _flags)                                  \
	static struct atlantis_clk_mux _name = {                             \
		.config = ATLANTIS_MUX_CONFIG(_shift, _width, _reg_offset),  \
		.common = { .clkid = _clkid,                                 \
			    .hw.init = CLK_HW_INIT_PARENTS_DATA(             \
				    #_name, _parents, &atlantis_clk_mux_ops, \
				    _flags) }                                \
	}

#define ATLANTIS_DIVIDER_CONFIG(_shift, _width, _flags, _reg_offset) \
	{                                                            \
		.shift = _shift, .width = _width, .flags = _flags,   \
		.reg_offset = _reg_offset                            \
	}

#define ATLANTIS_DIVIDER_DEFINE(_clkid, _name, _parent, _reg_offset, _shift, \
				_width, _divflags, _flags)                   \
	static struct atlantis_clk_divider _name = {                         \
		.config = ATLANTIS_DIVIDER_CONFIG(_shift, _width, _divflags, \
						  _reg_offset),              \
		.common = { .clkid = _clkid,                                 \
			    .hw.init = CLK_HW_INIT_HW(                       \
				    #_name, &_parent.common.hw,              \
				    &atlantis_clk_divider_ops, _flags) }     \
	}
#define ATLANTIS_GATE_CONFIG(_enable, _reg_offset)           \
	{                                                    \
		.enable = _enable, .reg_offset = _reg_offset \
	}

#define ATLANTIS_GATE_DEFINE(_clkid, _name, _parent, _reg_offset, _enable, \
			     _flags)                                       \
	static struct atlantis_clk_gate _name = {                          \
		.config = ATLANTIS_GATE_CONFIG(_enable, _reg_offset),      \
		.common = { .clkid = _clkid,                               \
			    .hw.init = CLK_HW_INIT_HW(                     \
				    #_name, &_parent.common.hw,            \
				    &atlantis_clk_gate_ops, _flags) }      \
	}
#define ATLANTIS_GATE_SHARED_CONFIG(_reg_offset, _enable, _share_count)      \
	{                                                                    \
		.reg_offset = _reg_offset, .enable = _enable,                \
		.share_count = _share_count, .refcount_lock = &refcount_lock \
	}
#define ATLANTIS_GATE_SHARED_DEFINE(_clkid, _name, _parent, _reg_offset,     \
				    _enable, _share_count, _flags)           \
	static struct atlantis_clk_gate_shared _name = {                     \
		.config = ATLANTIS_GATE_SHARED_CONFIG(_reg_offset, _enable,  \
						      _share_count),         \
		.common = { .clkid = _clkid,                                 \
			    .hw.init = CLK_HW_INIT_HW(                       \
				    #_name, &_parent.common.hw,              \
				    &atlantis_clk_gate_shared_ops, _flags) } \
	}
#define ATLANTIS_FIXED_FACTOR_DEFINE(_clkid, _name, _parent, _mult, _div,     \
				     _flags)                                  \
	static struct atlantis_clk_fixed_factor _name = {                     \
		.config = { .mult = _mult, .div = _div },                     \
		.common = { .clkid = _clkid,                                  \
			    .hw.init = CLK_HW_INIT_HW(                        \
				    #_name, &_parent.common.hw,               \
				    &atlantis_clk_fixed_factor_ops, _flags) } \
	}

static DEFINE_SPINLOCK(refcount_lock); /* Lock for refcount value accesses */

static const struct regmap_config atlantis_prcm_regmap_config = {
	.reg_bits = 32,
	.reg_stride = 4,
	.val_bits = 32,
	.max_register = 0xFFFC,
	.cache_type = REGCACHE_NONE,
};

struct atlantis_prcm_data {
	struct clk_hw **hws;
	size_t num;
	const char *reset_name;
};

static const struct clk_parent_data osc_24m_clk[] = {
	{ .index = 0 },
};

ATLANTIS_PLL_DEFINE(CLK_RCPU_PLL, rcpu_pll_clk, osc_24m_clk, PLL_RCPU_CFG_REG,
		    PLL_RCPU_EN_REG, BUS_CG_REG, 0, /* No Gate Clk at Output */
		    CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL);

static const struct clk_parent_data rcpu_root_parents[] = {
	{ .index = 0 },
	{ .hw = &rcpu_pll_clk.common.hw },
};

ATLANTIS_MUX_DEFINE(CLK_RCPU_ROOT, rcpu_root_clk, rcpu_root_parents,
		    RCPU_DIV_CFG_REG, 0, 1, CLK_SET_RATE_NO_REPARENT);

ATLANTIS_DIVIDER_DEFINE(CLK_RCPU_DIV2, rcpu_div2_clk, rcpu_root_clk,
			RCPU_DIV_CFG_REG, 2, 4, 0, 0);
ATLANTIS_DIVIDER_DEFINE(CLK_RCPU_DIV4, rcpu_div4_clk, rcpu_root_clk,
			RCPU_DIV_CFG_REG, 7, 4, 0, 0);
ATLANTIS_DIVIDER_DEFINE(CLK_RCPU_RTC, rcpu_rtc_clk, rcpu_div4_clk,
			RCPU_DIV_CFG_REG, 12, 6, 0, 0);

ATLANTIS_GATE_DEFINE(CLK_SMNDMA0_ACLK, rcpu_dma0_clk, rcpu_div2_clk,
		     RCPU_BLK_CG_REG, BIT(0), 0);
ATLANTIS_GATE_DEFINE(CLK_SMNDMA1_ACLK, rcpu_dma1_clk, rcpu_div2_clk,
		     RCPU_BLK_CG_REG, BIT(1), 0);
ATLANTIS_GATE_DEFINE(CLK_WDT0_PCLK, sl_wdt0_pclk, rcpu_div4_clk,
		     RCPU_BLK_CG_REG, BIT(2), 0);
ATLANTIS_GATE_DEFINE(CLK_WDT1_PCLK, sl_wdt1_pclk, rcpu_div4_clk,
		     RCPU_BLK_CG_REG, BIT(3), 0);
ATLANTIS_GATE_DEFINE(CLK_TIMER_PCLK, sl_timer_pclk, rcpu_div4_clk,
		     RCPU_BLK_CG_REG, BIT(4), 0);
ATLANTIS_GATE_DEFINE(CLK_PVTC_PCLK, sl_pvtc_pclk, rcpu_div4_clk,
		     RCPU_BLK_CG_REG, BIT(12), 0);
ATLANTIS_GATE_DEFINE(CLK_PMU_PCLK, sl_pmu_pclk, rcpu_div4_clk, RCPU_BLK_CG_REG,
		     BIT(13), 0);
ATLANTIS_GATE_DEFINE(CLK_MAILBOX_HCLK, rcpu_ipc_clk, rcpu_div2_clk,
		     RCPU_BLK_CG_REG, BIT(14), 0);
ATLANTIS_GATE_DEFINE(CLK_SEC_SPACC_HCLK, sec_spacc_hclk, rcpu_div2_clk,
		     RCPU_BLK_CG_REG, BIT(26), 0);
ATLANTIS_GATE_DEFINE(CLK_SEC_OTP_HCLK, sec_otp_hclk, rcpu_div2_clk,
		     RCPU_BLK_CG_REG, BIT(28), 0);
ATLANTIS_GATE_DEFINE(CLK_TRNG_PCLK, sec_trng_pclk, rcpu_div4_clk,
		     RCPU_BLK_CG_REG, BIT(29), 0);
ATLANTIS_GATE_DEFINE(CLK_SEC_CRC_HCLK, sec_crc_hclk, rcpu_div2_clk,
		     RCPU_BLK_CG_REG, BIT(30), 0);

ATLANTIS_FIXED_FACTOR_DEFINE(CLK_SMN_HCLK, rcpu_smn_hclk, rcpu_div2_clk, 1, 1,
			     0);
ATLANTIS_FIXED_FACTOR_DEFINE(CLK_AHB0_HCLK, rcpu_ahb0_hclk, rcpu_div2_clk, 1, 1,
			     0);

ATLANTIS_FIXED_FACTOR_DEFINE(CLK_SMN_PCLK, rcpu_smn_pclk, rcpu_div4_clk, 1, 1,
			     0);

ATLANTIS_FIXED_FACTOR_DEFINE(CLK_SMN_CLK, rcpu_smn_clk, rcpu_root_clk, 1, 1, 0);
ATLANTIS_FIXED_FACTOR_DEFINE(CLK_SCRATCHPAD_CLK, rcpu_scratchpad_aclk,
			     rcpu_root_clk, 1, 1, 0);
ATLANTIS_FIXED_FACTOR_DEFINE(CLK_RCPU_CORE_CLK, rcpu_core_clk, rcpu_root_clk, 1,
			     1, 0);
ATLANTIS_FIXED_FACTOR_DEFINE(CLK_RCPU_ROM_CLK, rcpu_rom_aclk, rcpu_root_clk, 1,
			     1, 0);

static struct atlantis_clk_fixed_factor
	otp_load_clk = { .config = { .mult = 1, .div = 1 },
			 .common = {
				 .clkid = CLK_OTP_LOAD_CLK,
				 .hw.init = CLK_HW_INIT_PARENTS_DATA(
					 "otp_load_clk", osc_24m_clk,
					 &atlantis_clk_fixed_factor_ops,
					 CLK_SET_RATE_NO_REPARENT),
			 } };

ATLANTIS_PLL_DEFINE(CLK_NOC_PLL, nocc_pll_clk, osc_24m_clk, PLL_NOCC_CFG_REG,
		    PLL_NOCC_EN_REG, BUS_CG_REG, BIT(0),
		    CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL);

static const struct clk_parent_data nocc_mux_parents[] = {
	{ .index = 0 },
	{ .hw = &nocc_pll_clk.common.hw },
};

ATLANTIS_MUX_DEFINE(CLK_NOCC_CLK, nocc_clk, nocc_mux_parents, NOCC_CLK_CFG_REG,
		    0, 1, CLK_SET_RATE_NO_REPARENT);

ATLANTIS_DIVIDER_DEFINE(CLK_NOCC_DIV2, nocc_div2_clk, nocc_clk,
			NOCC_CLK_CFG_REG, 1, 4, 0, 0);
ATLANTIS_DIVIDER_DEFINE(CLK_NOCC_DIV4, nocc_div4_clk, nocc_clk,
			NOCC_CLK_CFG_REG, 5, 4, 0, 0);
ATLANTIS_DIVIDER_DEFINE(CLK_NOCC_RTC, nocc_rtc_clk, nocc_div4_clk,
			NOCC_CLK_CFG_REG, 9, 6, 0, 0);
ATLANTIS_DIVIDER_DEFINE(CLK_NOCC_CAN, nocc_can_clk, nocc_clk, NOCC_CLK_CFG_REG,
			15, 4, 0, 0);

static unsigned int refcnt_qspi;
ATLANTIS_GATE_SHARED_DEFINE(CLK_QSPI_SCLK, lsio_qspi_sclk, nocc_clk,
			    LSIO_BLK_CG_REG, BIT(0), &refcnt_qspi, 0);
ATLANTIS_GATE_SHARED_DEFINE(CLK_QSPI_HCLK, lsio_qspi_hclk, nocc_div2_clk,
			    LSIO_BLK_CG_REG, BIT(0), &refcnt_qspi, 0);
ATLANTIS_GATE_DEFINE(CLK_I2C0_PCLK, lsio_i2c0_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(1), 0);
ATLANTIS_GATE_DEFINE(CLK_I2C1_PCLK, lsio_i2c1_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(2), 0);
ATLANTIS_GATE_DEFINE(CLK_I2C2_PCLK, lsio_i2c2_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(3), 0);
ATLANTIS_GATE_DEFINE(CLK_I2C3_PCLK, lsio_i2c3_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(4), 0);
ATLANTIS_GATE_DEFINE(CLK_I2C4_PCLK, lsio_i2c4_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(5), 0);

ATLANTIS_GATE_DEFINE(CLK_UART0_PCLK, lsio_uart0_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(6), 0);
ATLANTIS_GATE_DEFINE(CLK_UART1_PCLK, lsio_uart1_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(7), 0);
ATLANTIS_GATE_DEFINE(CLK_UART2_PCLK, lsio_uart2_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(8), 0);
ATLANTIS_GATE_DEFINE(CLK_UART3_PCLK, lsio_uart3_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(9), 0);
ATLANTIS_GATE_DEFINE(CLK_UART4_PCLK, lsio_uart4_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(10), 0);
ATLANTIS_GATE_DEFINE(CLK_SPI0_PCLK, lsio_spi0_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(11), 0);
ATLANTIS_GATE_DEFINE(CLK_SPI1_PCLK, lsio_spi1_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(12), 0);
ATLANTIS_GATE_DEFINE(CLK_SPI2_PCLK, lsio_spi2_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(13), 0);
ATLANTIS_GATE_DEFINE(CLK_SPI3_PCLK, lsio_spi3_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(14), 0);
ATLANTIS_GATE_DEFINE(CLK_GPIO_PCLK, lsio_gpio_pclk, nocc_div4_clk,
		     LSIO_BLK_CG_REG, BIT(15), 0);

static unsigned int refcnt_can0;
ATLANTIS_GATE_SHARED_DEFINE(CLK_CAN0_HCLK, lsio_can0_hclk, nocc_div2_clk,
			    LSIO_BLK_CG_REG, BIT(17), &refcnt_can0, 0);
ATLANTIS_GATE_SHARED_DEFINE(CLK_CAN0_CLK, lsio_can0_clk, nocc_can_clk,
			    LSIO_BLK_CG_REG, BIT(17), &refcnt_can0, 0);

static unsigned int refcnt_can1;
ATLANTIS_GATE_SHARED_DEFINE(CLK_CAN1_HCLK, lsio_can1_hclk, nocc_div2_clk,
			    LSIO_BLK_CG_REG, BIT(18), &refcnt_can1, 0);
ATLANTIS_GATE_SHARED_DEFINE(CLK_CAN1_CLK, lsio_can1_clk, nocc_can_clk,
			    LSIO_BLK_CG_REG, BIT(18), &refcnt_can1, 0);

ATLANTIS_FIXED_FACTOR_DEFINE(CLK_CAN0_TIMER_CLK, lsio_can0_timer_clk,
				 nocc_rtc_clk, 1, 1, 0);
ATLANTIS_FIXED_FACTOR_DEFINE(CLK_CAN1_TIMER_CLK, lsio_can1_timer_clk,
				 nocc_rtc_clk, 1, 1, 0);

static struct clk_hw *atlantis_rcpu_clks[] = {
	[CLK_RCPU_PLL]		= &rcpu_pll_clk.common.hw,
	[CLK_RCPU_ROOT]		= &rcpu_root_clk.common.hw,
	[CLK_RCPU_DIV2]		= &rcpu_div2_clk.common.hw,
	[CLK_RCPU_DIV4]		= &rcpu_div4_clk.common.hw,
	[CLK_RCPU_RTC]		= &rcpu_rtc_clk.common.hw,
	[CLK_SMNDMA0_ACLK]	= &rcpu_dma0_clk.common.hw,
	[CLK_SMNDMA1_ACLK]	= &rcpu_dma1_clk.common.hw,
	[CLK_WDT0_PCLK]		= &sl_wdt0_pclk.common.hw,
	[CLK_WDT1_PCLK]		= &sl_wdt1_pclk.common.hw,
	[CLK_TIMER_PCLK]	= &sl_timer_pclk.common.hw,
	[CLK_PVTC_PCLK]		= &sl_pvtc_pclk.common.hw,
	[CLK_PMU_PCLK]		= &sl_pmu_pclk.common.hw,
	[CLK_MAILBOX_HCLK]	= &rcpu_ipc_clk.common.hw,
	[CLK_SEC_SPACC_HCLK]	= &sec_spacc_hclk.common.hw,
	[CLK_SEC_OTP_HCLK]	= &sec_otp_hclk.common.hw,
	[CLK_TRNG_PCLK]		= &sec_trng_pclk.common.hw,
	[CLK_SEC_CRC_HCLK]	= &sec_crc_hclk.common.hw,
	[CLK_SMN_HCLK]		= &rcpu_smn_hclk.common.hw,
	[CLK_AHB0_HCLK]		= &rcpu_ahb0_hclk.common.hw,
	[CLK_SMN_PCLK]		= &rcpu_smn_pclk.common.hw,
	[CLK_SMN_CLK]		= &rcpu_smn_clk.common.hw,
	[CLK_SCRATCHPAD_CLK]	= &rcpu_scratchpad_aclk.common.hw,
	[CLK_RCPU_CORE_CLK]	= &rcpu_core_clk.common.hw,
	[CLK_RCPU_ROM_CLK]	= &rcpu_rom_aclk.common.hw,
	[CLK_OTP_LOAD_CLK]	= &otp_load_clk.common.hw,
	[CLK_NOC_PLL]		= &nocc_pll_clk.common.hw,
	[CLK_NOCC_CLK]		= &nocc_clk.common.hw,
	[CLK_NOCC_DIV2]		= &nocc_div2_clk.common.hw,
	[CLK_NOCC_DIV4]		= &nocc_div4_clk.common.hw,
	[CLK_NOCC_RTC]		= &nocc_rtc_clk.common.hw,
	[CLK_NOCC_CAN]		= &nocc_can_clk.common.hw,
	[CLK_QSPI_SCLK]		= &lsio_qspi_sclk.common.hw,
	[CLK_QSPI_HCLK]		= &lsio_qspi_hclk.common.hw,
	[CLK_I2C0_PCLK]		= &lsio_i2c0_pclk.common.hw,
	[CLK_I2C1_PCLK]		= &lsio_i2c1_pclk.common.hw,
	[CLK_I2C2_PCLK]		= &lsio_i2c2_pclk.common.hw,
	[CLK_I2C3_PCLK]		= &lsio_i2c3_pclk.common.hw,
	[CLK_I2C4_PCLK]		= &lsio_i2c4_pclk.common.hw,
	[CLK_UART0_PCLK]	= &lsio_uart0_pclk.common.hw,
	[CLK_UART1_PCLK]	= &lsio_uart1_pclk.common.hw,
	[CLK_UART2_PCLK]	= &lsio_uart2_pclk.common.hw,
	[CLK_UART3_PCLK]	= &lsio_uart3_pclk.common.hw,
	[CLK_UART4_PCLK]	= &lsio_uart4_pclk.common.hw,
	[CLK_SPI0_PCLK]		= &lsio_spi0_pclk.common.hw,
	[CLK_SPI1_PCLK]		= &lsio_spi1_pclk.common.hw,
	[CLK_SPI2_PCLK]		= &lsio_spi2_pclk.common.hw,
	[CLK_SPI3_PCLK]		= &lsio_spi3_pclk.common.hw,
	[CLK_GPIO_PCLK]		= &lsio_gpio_pclk.common.hw,
	[CLK_CAN0_HCLK]		= &lsio_can0_hclk.common.hw,
	[CLK_CAN0_CLK]		= &lsio_can0_clk.common.hw,
	[CLK_CAN1_HCLK]		= &lsio_can1_hclk.common.hw,
	[CLK_CAN1_CLK]		= &lsio_can1_clk.common.hw,
	[CLK_CAN0_TIMER_CLK]	= &lsio_can0_timer_clk.common.hw,
	[CLK_CAN1_TIMER_CLK]	= &lsio_can1_timer_clk.common.hw,
};

static const struct atlantis_prcm_data atlantis_prcm_rcpu_data = {
	.hws = atlantis_rcpu_clks,
	.num = ARRAY_SIZE(atlantis_rcpu_clks),
	.reset_name = "rcpu-reset"
};

static int atlantis_prcm_clocks_register(struct device *dev,
					 struct regmap *regmap,
					 const struct atlantis_prcm_data *data)
{
	struct clk_hw_onecell_data *clk_data;
	int i, ret;
	size_t num_clks = data->num;

	clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, data->num),
				GFP_KERNEL);
	if (!clk_data)
		return -ENOMEM;

	for (i = 0; i < data->num; i++) {
		struct clk_hw *hw = data->hws[i];
		struct atlantis_clk_common *common =
			hw_to_atlantis_clk_common(hw);
		common->regmap = regmap;

		ret = devm_clk_hw_register(dev, hw);
		if (ret)
			return ret;

		clk_data->hws[common->clkid] = hw;
	}

	clk_data->num = num_clks;

	return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
}

static int atlantis_prcm_probe(struct platform_device *pdev)
{
	const struct atlantis_prcm_data *data;
	struct auxiliary_device *reset_adev;
	struct regmap *regmap;
	void __iomem *base;
	struct device *dev = &pdev->dev;
	int ret;

	base = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(base))
		return dev_err_probe(dev, PTR_ERR(base),
				     "Failed to map registers\n");

	regmap = devm_regmap_init_mmio(dev, base, &atlantis_prcm_regmap_config);
	if (IS_ERR(regmap))
		return dev_err_probe(dev, PTR_ERR(regmap),
				     "Failed to init regmap\n");

	data = of_device_get_match_data(dev);

	ret = atlantis_prcm_clocks_register(dev, regmap, data);
	if (ret)
		return dev_err_probe(dev, ret, "failed to register clocks\n");

	reset_adev = devm_auxiliary_device_create(dev, data->reset_name, NULL);
	if (!reset_adev)
		return dev_err_probe(dev, -ENODEV, "failed to register resets\n");

	return 0;
}

static const struct of_device_id atlantis_prcm_of_match[] = {
	{
		.compatible = "tenstorrent,atlantis-prcm-rcpu",
		.data = &atlantis_prcm_rcpu_data,
	},
	{}

};
MODULE_DEVICE_TABLE(of, atlantis_prcm_of_match);

static struct platform_driver atlantis_prcm_driver = {
	.probe = atlantis_prcm_probe,
	.driver = {
		.name = "atlantis-prcm",
		.of_match_table = atlantis_prcm_of_match,
	},
};
module_platform_driver(atlantis_prcm_driver);

MODULE_DESCRIPTION("Tenstorrent Atlantis PRCM Clock Controller Driver");
MODULE_AUTHOR("Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>");
MODULE_LICENSE("GPL");