Commit efc91ae4 authored by Zong Li's avatar Zong Li Committed by Stephen Boyd
Browse files

clk: sifive: Add a driver for the SiFive FU740 PRCI IP block

Add driver code for the SiFive FU740 PRCI IP block. This IP block
handles reset and clock control for the SiFive FU740 device and
implements SoC-level clock tree controls and dividers.

The link of unmatched as follow, and the U740-C000 manual would
be present in the same page as soon.
https://www.sifive.com/boards/hifive-unmatched



This driver contains bug fixes and contributions from
Henry Styles <hes@sifive.com>
Erik Danie <erik.danie@sifive.com>
Pragnesh Patel <pragnesh.patel@sifive.com>

Signed-off-by: default avatarZong Li <zong.li@sifive.com>
Reviewed-by: default avatarPragnesh Patel <Pragnesh.patel@sifive.com>
Acked-by: default avatarPalmer Dabbelt <palmerdabbelt@google.com>
Cc: Henry Styles <hes@sifive.com>
Cc: Erik Danie <erik.danie@sifive.com>
Cc: Pragnesh Patel <pragnesh.patel@sifive.com>
Link: https://lore.kernel.org/r/20201209094916.17383-4-zong.li@sifive.com


[sboyd@kernel.org: Include header to silence sparse]
Signed-off-by: default avatarStephen Boyd <sboyd@kernel.org>
parent 28108fc8
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@ config CLK_SIFIVE_PRCI
	select CLK_ANALOGBITS_WRPLL_CLN28HPC
	help
	  Supports the Power Reset Clock interface (PRCI) IP block found in
	  FU540 SoCs. If this kernel is meant to run on a SiFive FU540 SoC,
	  enable this driver.
	  FU540/FU740 SoCs. If this kernel is meant to run on a SiFive FU540/
	  FU740 SoCs, enable this driver.

endif
+1 −1
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_CLK_SIFIVE_PRCI)	+= sifive-prci.o fu540-prci.o
obj-$(CONFIG_CLK_SIFIVE_PRCI)	+= sifive-prci.o fu540-prci.o fu740-prci.o
+114 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2020 SiFive, Inc.
 * Copyright (C) 2020 Zong Li
 */

#include <linux/module.h>

#include <dt-bindings/clock/sifive-fu740-prci.h>

#include "fu540-prci.h"
#include "sifive-prci.h"

/* PRCI integration data for each WRPLL instance */

static struct __prci_wrpll_data __prci_corepll_data = {
	.cfg0_offs = PRCI_COREPLLCFG0_OFFSET,
	.enable_bypass = sifive_prci_coreclksel_use_hfclk,
	.disable_bypass = sifive_prci_coreclksel_use_final_corepll,
};

static struct __prci_wrpll_data __prci_ddrpll_data = {
	.cfg0_offs = PRCI_DDRPLLCFG0_OFFSET,
};

static struct __prci_wrpll_data __prci_gemgxlpll_data = {
	.cfg0_offs = PRCI_GEMGXLPLLCFG0_OFFSET,
};

static struct __prci_wrpll_data __prci_dvfscorepll_data = {
	.cfg0_offs = PRCI_DVFSCOREPLLCFG0_OFFSET,
	.enable_bypass = sifive_prci_corepllsel_use_corepll,
	.disable_bypass = sifive_prci_corepllsel_use_dvfscorepll,
};

static struct __prci_wrpll_data __prci_hfpclkpll_data = {
	.cfg0_offs = PRCI_HFPCLKPLLCFG0_OFFSET,
	.enable_bypass = sifive_prci_hfpclkpllsel_use_hfclk,
	.disable_bypass = sifive_prci_hfpclkpllsel_use_hfpclkpll,
};

static struct __prci_wrpll_data __prci_cltxpll_data = {
	.cfg0_offs = PRCI_CLTXPLLCFG0_OFFSET,
};

/* Linux clock framework integration */

static const struct clk_ops sifive_fu740_prci_wrpll_clk_ops = {
	.set_rate = sifive_prci_wrpll_set_rate,
	.round_rate = sifive_prci_wrpll_round_rate,
	.recalc_rate = sifive_prci_wrpll_recalc_rate,
};

static const struct clk_ops sifive_fu740_prci_wrpll_ro_clk_ops = {
	.recalc_rate = sifive_prci_wrpll_recalc_rate,
};

static const struct clk_ops sifive_fu740_prci_tlclksel_clk_ops = {
	.recalc_rate = sifive_prci_tlclksel_recalc_rate,
};

static const struct clk_ops sifive_fu740_prci_hfpclkplldiv_clk_ops = {
	.recalc_rate = sifive_prci_hfpclkplldiv_recalc_rate,
};

/* List of clock controls provided by the PRCI */
struct __prci_clock __prci_init_clocks_fu740[] = {
	[PRCI_CLK_COREPLL] = {
		.name = "corepll",
		.parent_name = "hfclk",
		.ops = &sifive_fu740_prci_wrpll_clk_ops,
		.pwd = &__prci_corepll_data,
	},
	[PRCI_CLK_DDRPLL] = {
		.name = "ddrpll",
		.parent_name = "hfclk",
		.ops = &sifive_fu740_prci_wrpll_ro_clk_ops,
		.pwd = &__prci_ddrpll_data,
	},
	[PRCI_CLK_GEMGXLPLL] = {
		.name = "gemgxlpll",
		.parent_name = "hfclk",
		.ops = &sifive_fu740_prci_wrpll_clk_ops,
		.pwd = &__prci_gemgxlpll_data,
	},
	[PRCI_CLK_DVFSCOREPLL] = {
		.name = "dvfscorepll",
		.parent_name = "hfclk",
		.ops = &sifive_fu740_prci_wrpll_clk_ops,
		.pwd = &__prci_dvfscorepll_data,
	},
	[PRCI_CLK_HFPCLKPLL] = {
		.name = "hfpclkpll",
		.parent_name = "hfclk",
		.ops = &sifive_fu740_prci_wrpll_clk_ops,
		.pwd = &__prci_hfpclkpll_data,
	},
	[PRCI_CLK_CLTXPLL] = {
		.name = "cltxpll",
		.parent_name = "hfclk",
		.ops = &sifive_fu740_prci_wrpll_clk_ops,
		.pwd = &__prci_cltxpll_data,
	},
	[PRCI_CLK_TLCLK] = {
		.name = "tlclk",
		.parent_name = "corepll",
		.ops = &sifive_fu740_prci_tlclksel_clk_ops,
	},
	[PRCI_CLK_PCLK] = {
		.name = "pclk",
		.parent_name = "hfpclkpll",
		.ops = &sifive_fu740_prci_hfpclkplldiv_clk_ops,
	},
};
+21 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright (C) 2020 SiFive, Inc.
 * Zong Li
 */

#ifndef __SIFIVE_CLK_FU740_PRCI_H
#define __SIFIVE_CLK_FU740_PRCI_H

#include "sifive-prci.h"

#define NUM_CLOCK_FU740	8

extern struct __prci_clock __prci_init_clocks_fu740[NUM_CLOCK_FU740];

static const struct prci_clk_desc prci_clk_fu740 = {
	.clks = __prci_init_clocks_fu740,
	.num_clks = ARRAY_SIZE(__prci_init_clocks_fu740),
};

#endif /* __SIFIVE_CLK_FU740_PRCI_H */
+120 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
#include <linux/of_device.h>
#include "sifive-prci.h"
#include "fu540-prci.h"
#include "fu740-prci.h"

/*
 * Private functions
@@ -225,6 +226,18 @@ unsigned long sifive_prci_tlclksel_recalc_rate(struct clk_hw *hw,
	return div_u64(parent_rate, div);
}

/* HFPCLK clock integration */

unsigned long sifive_prci_hfpclkplldiv_recalc_rate(struct clk_hw *hw,
						   unsigned long parent_rate)
{
	struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
	struct __prci_data *pd = pc->pd;
	u32 div = __prci_readl(pd, PRCI_HFPCLKPLLDIV_OFFSET);

	return div_u64(parent_rate, div + 2);
}

/*
 * Core clock mux control
 */
@@ -270,6 +283,112 @@ void sifive_prci_coreclksel_use_corepll(struct __prci_data *pd)
	r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET);	/* barrier */
}

/**
 * sifive_prci_coreclksel_use_final_corepll() - switch the CORECLK mux to output
 * FINAL_COREPLL
 * @pd: struct __prci_data * for the PRCI containing the CORECLK mux reg
 *
 * Switch the CORECLK mux to the final COREPLL output clock; return once
 * complete.
 *
 * Context: Any context.  Caller must prevent concurrent changes to the
 *          PRCI_CORECLKSEL_OFFSET register.
 */
void sifive_prci_coreclksel_use_final_corepll(struct __prci_data *pd)
{
	u32 r;

	r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET);
	r &= ~PRCI_CORECLKSEL_CORECLKSEL_MASK;
	__prci_writel(r, PRCI_CORECLKSEL_OFFSET, pd);

	r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET);	/* barrier */
}

/**
 * sifive_prci_corepllsel_use_dvfscorepll() - switch the COREPLL mux to
 * output DVFS_COREPLL
 * @pd: struct __prci_data * for the PRCI containing the COREPLL mux reg
 *
 * Switch the COREPLL mux to the DVFSCOREPLL output clock; return once complete.
 *
 * Context: Any context.  Caller must prevent concurrent changes to the
 *          PRCI_COREPLLSEL_OFFSET register.
 */
void sifive_prci_corepllsel_use_dvfscorepll(struct __prci_data *pd)
{
	u32 r;

	r = __prci_readl(pd, PRCI_COREPLLSEL_OFFSET);
	r |= PRCI_COREPLLSEL_COREPLLSEL_MASK;
	__prci_writel(r, PRCI_COREPLLSEL_OFFSET, pd);

	r = __prci_readl(pd, PRCI_COREPLLSEL_OFFSET);	/* barrier */
}

/**
 * sifive_prci_corepllsel_use_corepll() - switch the COREPLL mux to
 * output COREPLL
 * @pd: struct __prci_data * for the PRCI containing the COREPLL mux reg
 *
 * Switch the COREPLL mux to the COREPLL output clock; return once complete.
 *
 * Context: Any context.  Caller must prevent concurrent changes to the
 *          PRCI_COREPLLSEL_OFFSET register.
 */
void sifive_prci_corepllsel_use_corepll(struct __prci_data *pd)
{
	u32 r;

	r = __prci_readl(pd, PRCI_COREPLLSEL_OFFSET);
	r &= ~PRCI_COREPLLSEL_COREPLLSEL_MASK;
	__prci_writel(r, PRCI_COREPLLSEL_OFFSET, pd);

	r = __prci_readl(pd, PRCI_COREPLLSEL_OFFSET);	/* barrier */
}

/**
 * sifive_prci_hfpclkpllsel_use_hfclk() - switch the HFPCLKPLL mux to
 * output HFCLK
 * @pd: struct __prci_data * for the PRCI containing the HFPCLKPLL mux reg
 *
 * Switch the HFPCLKPLL mux to the HFCLK input source; return once complete.
 *
 * Context: Any context.  Caller must prevent concurrent changes to the
 *          PRCI_HFPCLKPLLSEL_OFFSET register.
 */
void sifive_prci_hfpclkpllsel_use_hfclk(struct __prci_data *pd)
{
	u32 r;

	r = __prci_readl(pd, PRCI_HFPCLKPLLSEL_OFFSET);
	r |= PRCI_HFPCLKPLLSEL_HFPCLKPLLSEL_MASK;
	__prci_writel(r, PRCI_HFPCLKPLLSEL_OFFSET, pd);

	r = __prci_readl(pd, PRCI_HFPCLKPLLSEL_OFFSET);	/* barrier */
}

/**
 * sifive_prci_hfpclkpllsel_use_hfpclkpll() - switch the HFPCLKPLL mux to
 * output HFPCLKPLL
 * @pd: struct __prci_data * for the PRCI containing the HFPCLKPLL mux reg
 *
 * Switch the HFPCLKPLL mux to the HFPCLKPLL output clock; return once complete.
 *
 * Context: Any context.  Caller must prevent concurrent changes to the
 *          PRCI_HFPCLKPLLSEL_OFFSET register.
 */
void sifive_prci_hfpclkpllsel_use_hfpclkpll(struct __prci_data *pd)
{
	u32 r;

	r = __prci_readl(pd, PRCI_HFPCLKPLLSEL_OFFSET);
	r &= ~PRCI_HFPCLKPLLSEL_HFPCLKPLLSEL_MASK;
	__prci_writel(r, PRCI_HFPCLKPLLSEL_OFFSET, pd);

	r = __prci_readl(pd, PRCI_HFPCLKPLLSEL_OFFSET);	/* barrier */
}

/**
 * __prci_register_clocks() - register clock controls in the PRCI
 * @dev: Linux struct device
@@ -377,6 +496,7 @@ static int sifive_prci_probe(struct platform_device *pdev)

static const struct of_device_id sifive_prci_of_match[] = {
	{.compatible = "sifive,fu540-c000-prci", .data = &prci_clk_fu540},
	{.compatible = "sifive,fu740-c000-prci", .data = &prci_clk_fu740},
	{}
};

Loading