brintos

brintos / linux-shallow public Read only

0
0
Text · 16.2 KiB · caba040 Raw
620 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (C) 2020 SiFive, Inc.4 * Copyright (C) 2020 Zong Li5 */6 7#include <linux/delay.h>8#include <linux/io.h>9#include <linux/module.h>10#include <linux/of.h>11#include "sifive-prci.h"12#include "fu540-prci.h"13#include "fu740-prci.h"14 15/*16 * Private functions17 */18 19/**20 * __prci_readl() - read from a PRCI register21 * @pd: PRCI context22 * @offs: register offset to read from (in bytes, from PRCI base address)23 *24 * Read the register located at offset @offs from the base virtual25 * address of the PRCI register target described by @pd, and return26 * the value to the caller.27 *28 * Context: Any context.29 *30 * Return: the contents of the register described by @pd and @offs.31 */32static u32 __prci_readl(struct __prci_data *pd, u32 offs)33{34	return readl_relaxed(pd->va + offs);35}36 37static void __prci_writel(u32 v, u32 offs, struct __prci_data *pd)38{39	writel_relaxed(v, pd->va + offs);40}41 42/* WRPLL-related private functions */43 44/**45 * __prci_wrpll_unpack() - unpack WRPLL configuration registers into parameters46 * @c: ptr to a struct wrpll_cfg record to write config into47 * @r: value read from the PRCI PLL configuration register48 *49 * Given a value @r read from an FU740 PRCI PLL configuration register,50 * split it into fields and populate it into the WRPLL configuration record51 * pointed to by @c.52 *53 * The COREPLLCFG0 macros are used below, but the other *PLLCFG0 macros54 * have the same register layout.55 *56 * Context: Any context.57 */58static void __prci_wrpll_unpack(struct wrpll_cfg *c, u32 r)59{60	u32 v;61 62	v = r & PRCI_COREPLLCFG0_DIVR_MASK;63	v >>= PRCI_COREPLLCFG0_DIVR_SHIFT;64	c->divr = v;65 66	v = r & PRCI_COREPLLCFG0_DIVF_MASK;67	v >>= PRCI_COREPLLCFG0_DIVF_SHIFT;68	c->divf = v;69 70	v = r & PRCI_COREPLLCFG0_DIVQ_MASK;71	v >>= PRCI_COREPLLCFG0_DIVQ_SHIFT;72	c->divq = v;73 74	v = r & PRCI_COREPLLCFG0_RANGE_MASK;75	v >>= PRCI_COREPLLCFG0_RANGE_SHIFT;76	c->range = v;77 78	c->flags &=79	    (WRPLL_FLAGS_INT_FEEDBACK_MASK | WRPLL_FLAGS_EXT_FEEDBACK_MASK);80 81	/* external feedback mode not supported */82	c->flags |= WRPLL_FLAGS_INT_FEEDBACK_MASK;83}84 85/**86 * __prci_wrpll_pack() - pack PLL configuration parameters into a register value87 * @c: pointer to a struct wrpll_cfg record containing the PLL's cfg88 *89 * Using a set of WRPLL configuration values pointed to by @c,90 * assemble a PRCI PLL configuration register value, and return it to91 * the caller.92 *93 * Context: Any context.  Caller must ensure that the contents of the94 *          record pointed to by @c do not change during the execution95 *          of this function.96 *97 * Returns: a value suitable for writing into a PRCI PLL configuration98 *          register99 */100static u32 __prci_wrpll_pack(const struct wrpll_cfg *c)101{102	u32 r = 0;103 104	r |= c->divr << PRCI_COREPLLCFG0_DIVR_SHIFT;105	r |= c->divf << PRCI_COREPLLCFG0_DIVF_SHIFT;106	r |= c->divq << PRCI_COREPLLCFG0_DIVQ_SHIFT;107	r |= c->range << PRCI_COREPLLCFG0_RANGE_SHIFT;108 109	/* external feedback mode not supported */110	r |= PRCI_COREPLLCFG0_FSE_MASK;111 112	return r;113}114 115/**116 * __prci_wrpll_read_cfg0() - read the WRPLL configuration from the PRCI117 * @pd: PRCI context118 * @pwd: PRCI WRPLL metadata119 *120 * Read the current configuration of the PLL identified by @pwd from121 * the PRCI identified by @pd, and store it into the local configuration122 * cache in @pwd.123 *124 * Context: Any context.  Caller must prevent the records pointed to by125 *          @pd and @pwd from changing during execution.126 */127static void __prci_wrpll_read_cfg0(struct __prci_data *pd,128				   struct __prci_wrpll_data *pwd)129{130	__prci_wrpll_unpack(&pwd->c, __prci_readl(pd, pwd->cfg0_offs));131}132 133/**134 * __prci_wrpll_write_cfg0() - write WRPLL configuration into the PRCI135 * @pd: PRCI context136 * @pwd: PRCI WRPLL metadata137 * @c: WRPLL configuration record to write138 *139 * Write the WRPLL configuration described by @c into the WRPLL140 * configuration register identified by @pwd in the PRCI instance141 * described by @c.  Make a cached copy of the WRPLL's current142 * configuration so it can be used by other code.143 *144 * Context: Any context.  Caller must prevent the records pointed to by145 *          @pd and @pwd from changing during execution.146 */147static void __prci_wrpll_write_cfg0(struct __prci_data *pd,148				    struct __prci_wrpll_data *pwd,149				    struct wrpll_cfg *c)150{151	__prci_writel(__prci_wrpll_pack(c), pwd->cfg0_offs, pd);152 153	memcpy(&pwd->c, c, sizeof(*c));154}155 156/**157 * __prci_wrpll_write_cfg1() - write Clock enable/disable configuration158 * into the PRCI159 * @pd: PRCI context160 * @pwd: PRCI WRPLL metadata161 * @enable: Clock enable or disable value162 */163static void __prci_wrpll_write_cfg1(struct __prci_data *pd,164				    struct __prci_wrpll_data *pwd,165				    u32 enable)166{167	__prci_writel(enable, pwd->cfg1_offs, pd);168}169 170/*171 * Linux clock framework integration172 *173 * See the Linux clock framework documentation for more information on174 * these functions.175 */176 177unsigned long sifive_prci_wrpll_recalc_rate(struct clk_hw *hw,178					    unsigned long parent_rate)179{180	struct __prci_clock *pc = clk_hw_to_prci_clock(hw);181	struct __prci_wrpll_data *pwd = pc->pwd;182 183	return wrpll_calc_output_rate(&pwd->c, parent_rate);184}185 186long sifive_prci_wrpll_round_rate(struct clk_hw *hw,187				  unsigned long rate,188				  unsigned long *parent_rate)189{190	struct __prci_clock *pc = clk_hw_to_prci_clock(hw);191	struct __prci_wrpll_data *pwd = pc->pwd;192	struct wrpll_cfg c;193 194	memcpy(&c, &pwd->c, sizeof(c));195 196	wrpll_configure_for_rate(&c, rate, *parent_rate);197 198	return wrpll_calc_output_rate(&c, *parent_rate);199}200 201int sifive_prci_wrpll_set_rate(struct clk_hw *hw,202			       unsigned long rate, unsigned long parent_rate)203{204	struct __prci_clock *pc = clk_hw_to_prci_clock(hw);205	struct __prci_wrpll_data *pwd = pc->pwd;206	struct __prci_data *pd = pc->pd;207	int r;208 209	r = wrpll_configure_for_rate(&pwd->c, rate, parent_rate);210	if (r)211		return r;212 213	if (pwd->enable_bypass)214		pwd->enable_bypass(pd);215 216	__prci_wrpll_write_cfg0(pd, pwd, &pwd->c);217 218	udelay(wrpll_calc_max_lock_us(&pwd->c));219 220	return 0;221}222 223int sifive_clk_is_enabled(struct clk_hw *hw)224{225	struct __prci_clock *pc = clk_hw_to_prci_clock(hw);226	struct __prci_wrpll_data *pwd = pc->pwd;227	struct __prci_data *pd = pc->pd;228	u32 r;229 230	r = __prci_readl(pd, pwd->cfg1_offs);231 232	if (r & PRCI_COREPLLCFG1_CKE_MASK)233		return 1;234	else235		return 0;236}237 238int sifive_prci_clock_enable(struct clk_hw *hw)239{240	struct __prci_clock *pc = clk_hw_to_prci_clock(hw);241	struct __prci_wrpll_data *pwd = pc->pwd;242	struct __prci_data *pd = pc->pd;243 244	if (sifive_clk_is_enabled(hw))245		return 0;246 247	__prci_wrpll_write_cfg1(pd, pwd, PRCI_COREPLLCFG1_CKE_MASK);248 249	if (pwd->disable_bypass)250		pwd->disable_bypass(pd);251 252	return 0;253}254 255void sifive_prci_clock_disable(struct clk_hw *hw)256{257	struct __prci_clock *pc = clk_hw_to_prci_clock(hw);258	struct __prci_wrpll_data *pwd = pc->pwd;259	struct __prci_data *pd = pc->pd;260	u32 r;261 262	if (pwd->enable_bypass)263		pwd->enable_bypass(pd);264 265	r = __prci_readl(pd, pwd->cfg1_offs);266	r &= ~PRCI_COREPLLCFG1_CKE_MASK;267 268	__prci_wrpll_write_cfg1(pd, pwd, r);269}270 271/* TLCLKSEL clock integration */272 273unsigned long sifive_prci_tlclksel_recalc_rate(struct clk_hw *hw,274					       unsigned long parent_rate)275{276	struct __prci_clock *pc = clk_hw_to_prci_clock(hw);277	struct __prci_data *pd = pc->pd;278	u32 v;279	u8 div;280 281	v = __prci_readl(pd, PRCI_CLKMUXSTATUSREG_OFFSET);282	v &= PRCI_CLKMUXSTATUSREG_TLCLKSEL_STATUS_MASK;283	div = v ? 1 : 2;284 285	return div_u64(parent_rate, div);286}287 288/* HFPCLK clock integration */289 290unsigned long sifive_prci_hfpclkplldiv_recalc_rate(struct clk_hw *hw,291						   unsigned long parent_rate)292{293	struct __prci_clock *pc = clk_hw_to_prci_clock(hw);294	struct __prci_data *pd = pc->pd;295	u32 div = __prci_readl(pd, PRCI_HFPCLKPLLDIV_OFFSET);296 297	return div_u64(parent_rate, div + 2);298}299 300/*301 * Core clock mux control302 */303 304/**305 * sifive_prci_coreclksel_use_hfclk() - switch the CORECLK mux to output HFCLK306 * @pd: struct __prci_data * for the PRCI containing the CORECLK mux reg307 *308 * Switch the CORECLK mux to the HFCLK input source; return once complete.309 *310 * Context: Any context.  Caller must prevent concurrent changes to the311 *          PRCI_CORECLKSEL_OFFSET register.312 */313void sifive_prci_coreclksel_use_hfclk(struct __prci_data *pd)314{315	u32 r;316 317	r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET);318	r |= PRCI_CORECLKSEL_CORECLKSEL_MASK;319	__prci_writel(r, PRCI_CORECLKSEL_OFFSET, pd);320 321	r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET);	/* barrier */322}323 324/**325 * sifive_prci_coreclksel_use_corepll() - switch the CORECLK mux to output326 * COREPLL327 * @pd: struct __prci_data * for the PRCI containing the CORECLK mux reg328 *329 * Switch the CORECLK mux to the COREPLL output clock; return once complete.330 *331 * Context: Any context.  Caller must prevent concurrent changes to the332 *          PRCI_CORECLKSEL_OFFSET register.333 */334void sifive_prci_coreclksel_use_corepll(struct __prci_data *pd)335{336	u32 r;337 338	r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET);339	r &= ~PRCI_CORECLKSEL_CORECLKSEL_MASK;340	__prci_writel(r, PRCI_CORECLKSEL_OFFSET, pd);341 342	r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET);	/* barrier */343}344 345/**346 * sifive_prci_coreclksel_use_final_corepll() - switch the CORECLK mux to output347 * FINAL_COREPLL348 * @pd: struct __prci_data * for the PRCI containing the CORECLK mux reg349 *350 * Switch the CORECLK mux to the final COREPLL output clock; return once351 * complete.352 *353 * Context: Any context.  Caller must prevent concurrent changes to the354 *          PRCI_CORECLKSEL_OFFSET register.355 */356void sifive_prci_coreclksel_use_final_corepll(struct __prci_data *pd)357{358	u32 r;359 360	r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET);361	r &= ~PRCI_CORECLKSEL_CORECLKSEL_MASK;362	__prci_writel(r, PRCI_CORECLKSEL_OFFSET, pd);363 364	r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET);	/* barrier */365}366 367/**368 * sifive_prci_corepllsel_use_dvfscorepll() - switch the COREPLL mux to369 * output DVFS_COREPLL370 * @pd: struct __prci_data * for the PRCI containing the COREPLL mux reg371 *372 * Switch the COREPLL mux to the DVFSCOREPLL output clock; return once complete.373 *374 * Context: Any context.  Caller must prevent concurrent changes to the375 *          PRCI_COREPLLSEL_OFFSET register.376 */377void sifive_prci_corepllsel_use_dvfscorepll(struct __prci_data *pd)378{379	u32 r;380 381	r = __prci_readl(pd, PRCI_COREPLLSEL_OFFSET);382	r |= PRCI_COREPLLSEL_COREPLLSEL_MASK;383	__prci_writel(r, PRCI_COREPLLSEL_OFFSET, pd);384 385	r = __prci_readl(pd, PRCI_COREPLLSEL_OFFSET);	/* barrier */386}387 388/**389 * sifive_prci_corepllsel_use_corepll() - switch the COREPLL mux to390 * output COREPLL391 * @pd: struct __prci_data * for the PRCI containing the COREPLL mux reg392 *393 * Switch the COREPLL mux to the COREPLL output clock; return once complete.394 *395 * Context: Any context.  Caller must prevent concurrent changes to the396 *          PRCI_COREPLLSEL_OFFSET register.397 */398void sifive_prci_corepllsel_use_corepll(struct __prci_data *pd)399{400	u32 r;401 402	r = __prci_readl(pd, PRCI_COREPLLSEL_OFFSET);403	r &= ~PRCI_COREPLLSEL_COREPLLSEL_MASK;404	__prci_writel(r, PRCI_COREPLLSEL_OFFSET, pd);405 406	r = __prci_readl(pd, PRCI_COREPLLSEL_OFFSET);	/* barrier */407}408 409/**410 * sifive_prci_hfpclkpllsel_use_hfclk() - switch the HFPCLKPLL mux to411 * output HFCLK412 * @pd: struct __prci_data * for the PRCI containing the HFPCLKPLL mux reg413 *414 * Switch the HFPCLKPLL mux to the HFCLK input source; return once complete.415 *416 * Context: Any context.  Caller must prevent concurrent changes to the417 *          PRCI_HFPCLKPLLSEL_OFFSET register.418 */419void sifive_prci_hfpclkpllsel_use_hfclk(struct __prci_data *pd)420{421	u32 r;422 423	r = __prci_readl(pd, PRCI_HFPCLKPLLSEL_OFFSET);424	r |= PRCI_HFPCLKPLLSEL_HFPCLKPLLSEL_MASK;425	__prci_writel(r, PRCI_HFPCLKPLLSEL_OFFSET, pd);426 427	r = __prci_readl(pd, PRCI_HFPCLKPLLSEL_OFFSET);	/* barrier */428}429 430/**431 * sifive_prci_hfpclkpllsel_use_hfpclkpll() - switch the HFPCLKPLL mux to432 * output HFPCLKPLL433 * @pd: struct __prci_data * for the PRCI containing the HFPCLKPLL mux reg434 *435 * Switch the HFPCLKPLL mux to the HFPCLKPLL output clock; return once complete.436 *437 * Context: Any context.  Caller must prevent concurrent changes to the438 *          PRCI_HFPCLKPLLSEL_OFFSET register.439 */440void sifive_prci_hfpclkpllsel_use_hfpclkpll(struct __prci_data *pd)441{442	u32 r;443 444	r = __prci_readl(pd, PRCI_HFPCLKPLLSEL_OFFSET);445	r &= ~PRCI_HFPCLKPLLSEL_HFPCLKPLLSEL_MASK;446	__prci_writel(r, PRCI_HFPCLKPLLSEL_OFFSET, pd);447 448	r = __prci_readl(pd, PRCI_HFPCLKPLLSEL_OFFSET);	/* barrier */449}450 451/* PCIE AUX clock APIs for enable, disable. */452int sifive_prci_pcie_aux_clock_is_enabled(struct clk_hw *hw)453{454	struct __prci_clock *pc = clk_hw_to_prci_clock(hw);455	struct __prci_data *pd = pc->pd;456	u32 r;457 458	r = __prci_readl(pd, PRCI_PCIE_AUX_OFFSET);459 460	if (r & PRCI_PCIE_AUX_EN_MASK)461		return 1;462	else463		return 0;464}465 466int sifive_prci_pcie_aux_clock_enable(struct clk_hw *hw)467{468	struct __prci_clock *pc = clk_hw_to_prci_clock(hw);469	struct __prci_data *pd = pc->pd;470	u32 r __maybe_unused;471 472	if (sifive_prci_pcie_aux_clock_is_enabled(hw))473		return 0;474 475	__prci_writel(1, PRCI_PCIE_AUX_OFFSET, pd);476	r = __prci_readl(pd, PRCI_PCIE_AUX_OFFSET);	/* barrier */477 478	return 0;479}480 481void sifive_prci_pcie_aux_clock_disable(struct clk_hw *hw)482{483	struct __prci_clock *pc = clk_hw_to_prci_clock(hw);484	struct __prci_data *pd = pc->pd;485	u32 r __maybe_unused;486 487	__prci_writel(0, PRCI_PCIE_AUX_OFFSET, pd);488	r = __prci_readl(pd, PRCI_PCIE_AUX_OFFSET);	/* barrier */489 490}491 492/**493 * __prci_register_clocks() - register clock controls in the PRCI494 * @dev: Linux struct device495 * @pd: The pointer for PRCI per-device instance data496 * @desc: The pointer for the information of clocks of each SoCs497 *498 * Register the list of clock controls described in __prci_init_clocks[] with499 * the Linux clock framework.500 *501 * Return: 0 upon success or a negative error code upon failure.502 */503static int __prci_register_clocks(struct device *dev, struct __prci_data *pd,504				  const struct prci_clk_desc *desc)505{506	struct clk_init_data init = { };507	struct __prci_clock *pic;508	int parent_count, i, r;509 510	parent_count = of_clk_get_parent_count(dev->of_node);511	if (parent_count != EXPECTED_CLK_PARENT_COUNT) {512		dev_err(dev, "expected only two parent clocks, found %d\n",513			parent_count);514		return -EINVAL;515	}516 517	/* Register PLLs */518	for (i = 0; i < desc->num_clks; ++i) {519		pic = &(desc->clks[i]);520 521		init.name = pic->name;522		init.parent_names = &pic->parent_name;523		init.num_parents = 1;524		init.ops = pic->ops;525		pic->hw.init = &init;526 527		pic->pd = pd;528 529		if (pic->pwd)530			__prci_wrpll_read_cfg0(pd, pic->pwd);531 532		r = devm_clk_hw_register(dev, &pic->hw);533		if (r) {534			dev_warn(dev, "Failed to register clock %s: %d\n",535				 init.name, r);536			return r;537		}538 539		pd->hw_clks.hws[i] = &pic->hw;540	}541 542	pd->hw_clks.num = i;543 544	r = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,545					&pd->hw_clks);546	if (r) {547		dev_err(dev, "could not add hw_provider: %d\n", r);548		return r;549	}550 551	return 0;552}553 554/**555 * sifive_prci_probe() - initialize prci data and check parent count556 * @pdev: platform device pointer for the prci557 *558 * Return: 0 upon success or a negative error code upon failure.559 */560static int sifive_prci_probe(struct platform_device *pdev)561{562	struct device *dev = &pdev->dev;563	struct __prci_data *pd;564	const struct prci_clk_desc *desc;565	int r;566 567	desc = of_device_get_match_data(&pdev->dev);568 569	pd = devm_kzalloc(dev, struct_size(pd, hw_clks.hws, desc->num_clks), GFP_KERNEL);570	if (!pd)571		return -ENOMEM;572 573	pd->va = devm_platform_ioremap_resource(pdev, 0);574	if (IS_ERR(pd->va))575		return PTR_ERR(pd->va);576 577	pd->reset.rcdev.owner = THIS_MODULE;578	pd->reset.rcdev.nr_resets = PRCI_RST_NR;579	pd->reset.rcdev.ops = &reset_simple_ops;580	pd->reset.rcdev.of_node = pdev->dev.of_node;581	pd->reset.active_low = true;582	pd->reset.membase = pd->va + PRCI_DEVICESRESETREG_OFFSET;583	spin_lock_init(&pd->reset.lock);584 585	r = devm_reset_controller_register(&pdev->dev, &pd->reset.rcdev);586	if (r) {587		dev_err(dev, "could not register reset controller: %d\n", r);588		return r;589	}590	r = __prci_register_clocks(dev, pd, desc);591	if (r) {592		dev_err(dev, "could not register clocks: %d\n", r);593		return r;594	}595 596	dev_dbg(dev, "SiFive PRCI probed\n");597 598	return 0;599}600 601static const struct of_device_id sifive_prci_of_match[] = {602	{.compatible = "sifive,fu540-c000-prci", .data = &prci_clk_fu540},603	{.compatible = "sifive,fu740-c000-prci", .data = &prci_clk_fu740},604	{}605};606MODULE_DEVICE_TABLE(of, sifive_prci_of_match);607 608static struct platform_driver sifive_prci_driver = {609	.driver = {610		.name = "sifive-clk-prci",611		.of_match_table = sifive_prci_of_match,612	},613	.probe = sifive_prci_probe,614};615module_platform_driver(sifive_prci_driver);616 617MODULE_AUTHOR("Paul Walmsley <paul.walmsley@sifive.com>");618MODULE_DESCRIPTION("SiFive Power Reset Clock Interface (PRCI) driver");619MODULE_LICENSE("GPL");620