brintos

brintos / linux-shallow public Read only

0
0
Text · 9.4 KiB · ce4efcd Raw
372 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (c) 2022, 2023 Linaro Ltd.4 */5#include <linux/bitfield.h>6#include <linux/clk.h>7#include <linux/clk-provider.h>8#include <linux/interconnect-clk.h>9#include <linux/interconnect-provider.h>10#include <linux/of.h>11#include <linux/module.h>12#include <linux/platform_device.h>13#include <linux/regmap.h>14 15#include <dt-bindings/interconnect/qcom,msm8996-cbf.h>16 17#include "clk-alpha-pll.h"18#include "clk-regmap.h"19 20/* Need to match the order of clocks in DT binding */21enum {22	DT_XO,23	DT_APCS_AUX,24};25 26enum {27	CBF_XO_INDEX,28	CBF_PLL_INDEX,29	CBF_DIV_INDEX,30	CBF_APCS_AUX_INDEX,31};32 33#define DIV_THRESHOLD		60000000034 35#define CBF_MUX_OFFSET		0x1836#define CBF_MUX_PARENT_MASK		GENMASK(1, 0)37#define CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_MASK GENMASK(5, 4)38#define CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_GPLL0_SEL \39	FIELD_PREP(CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_MASK, 0x03)40#define CBF_MUX_AUTO_CLK_SEL_BIT	BIT(6)41 42#define CBF_PLL_OFFSET 0xf00043 44static struct alpha_pll_config cbfpll_config = {45	.l = 72,46	.config_ctl_val = 0x200d4828,47	.config_ctl_hi_val = 0x006,48	.test_ctl_val = 0x1c000000,49	.test_ctl_hi_val = 0x00004000,50	.pre_div_mask = BIT(12),51	.post_div_mask = 0x3 << 8,52	.post_div_val = 0x1 << 8,53	.main_output_mask = BIT(0),54	.early_output_mask = BIT(3),55};56 57static struct clk_alpha_pll cbf_pll = {58	.offset = CBF_PLL_OFFSET,59	.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_HUAYRA_APSS],60	.flags = SUPPORTS_DYNAMIC_UPDATE | SUPPORTS_FSM_MODE,61	.clkr.hw.init = &(struct clk_init_data){62		.name = "cbf_pll",63		.parent_data = (const struct clk_parent_data[]) {64			{ .index = DT_XO, },65		},66		.num_parents = 1,67		.ops = &clk_alpha_pll_hwfsm_ops,68	},69};70 71static struct clk_fixed_factor cbf_pll_postdiv = {72	.mult = 1,73	.div = 2,74	.hw.init = &(struct clk_init_data){75		.name = "cbf_pll_postdiv",76		.parent_hws = (const struct clk_hw*[]){77			&cbf_pll.clkr.hw78		},79		.num_parents = 1,80		.ops = &clk_fixed_factor_ops,81		.flags = CLK_SET_RATE_PARENT,82	},83};84 85static const struct clk_parent_data cbf_mux_parent_data[] = {86	{ .index = DT_XO },87	{ .hw = &cbf_pll.clkr.hw },88	{ .hw = &cbf_pll_postdiv.hw },89	{ .index = DT_APCS_AUX },90};91 92struct clk_cbf_8996_mux {93	u32 reg;94	struct notifier_block nb;95	struct clk_regmap clkr;96};97 98static struct clk_cbf_8996_mux *to_clk_cbf_8996_mux(struct clk_regmap *clkr)99{100	return container_of(clkr, struct clk_cbf_8996_mux, clkr);101}102 103static int cbf_clk_notifier_cb(struct notifier_block *nb, unsigned long event,104			       void *data);105 106static u8 clk_cbf_8996_mux_get_parent(struct clk_hw *hw)107{108	struct clk_regmap *clkr = to_clk_regmap(hw);109	struct clk_cbf_8996_mux *mux = to_clk_cbf_8996_mux(clkr);110	u32 val;111 112	regmap_read(clkr->regmap, mux->reg, &val);113 114	return FIELD_GET(CBF_MUX_PARENT_MASK, val);115}116 117static int clk_cbf_8996_mux_set_parent(struct clk_hw *hw, u8 index)118{119	struct clk_regmap *clkr = to_clk_regmap(hw);120	struct clk_cbf_8996_mux *mux = to_clk_cbf_8996_mux(clkr);121	u32 val;122 123	val = FIELD_PREP(CBF_MUX_PARENT_MASK, index);124 125	return regmap_update_bits(clkr->regmap, mux->reg, CBF_MUX_PARENT_MASK, val);126}127 128static int clk_cbf_8996_mux_determine_rate(struct clk_hw *hw,129					   struct clk_rate_request *req)130{131	struct clk_hw *parent;132 133	if (req->rate < (DIV_THRESHOLD / cbf_pll_postdiv.div))134		return -EINVAL;135 136	if (req->rate < DIV_THRESHOLD)137		parent = clk_hw_get_parent_by_index(hw, CBF_DIV_INDEX);138	else139		parent = clk_hw_get_parent_by_index(hw, CBF_PLL_INDEX);140 141	if (!parent)142		return -EINVAL;143 144	req->best_parent_rate = clk_hw_round_rate(parent, req->rate);145	req->best_parent_hw = parent;146 147	return 0;148}149 150static const struct clk_ops clk_cbf_8996_mux_ops = {151	.set_parent = clk_cbf_8996_mux_set_parent,152	.get_parent = clk_cbf_8996_mux_get_parent,153	.determine_rate = clk_cbf_8996_mux_determine_rate,154};155 156static struct clk_cbf_8996_mux cbf_mux = {157	.reg = CBF_MUX_OFFSET,158	.nb.notifier_call = cbf_clk_notifier_cb,159	.clkr.hw.init = &(struct clk_init_data) {160		.name = "cbf_mux",161		.parent_data = cbf_mux_parent_data,162		.num_parents = ARRAY_SIZE(cbf_mux_parent_data),163		.ops = &clk_cbf_8996_mux_ops,164		/* CPU clock is critical and should never be gated */165		.flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,166	},167};168 169static int cbf_clk_notifier_cb(struct notifier_block *nb, unsigned long event,170			       void *data)171{172	struct clk_notifier_data *cnd = data;173 174	switch (event) {175	case PRE_RATE_CHANGE:176		/*177		 * Avoid overvolting. clk_core_set_rate_nolock() walks from top178		 * to bottom, so it will change the rate of the PLL before179		 * chaging the parent of PMUX. This can result in pmux getting180		 * clocked twice the expected rate.181		 *182		 * Manually switch to PLL/2 here.183		 */184		if (cnd->old_rate > DIV_THRESHOLD &&185		    cnd->new_rate < DIV_THRESHOLD)186			clk_cbf_8996_mux_set_parent(&cbf_mux.clkr.hw, CBF_DIV_INDEX);187		break;188	case ABORT_RATE_CHANGE:189		/* Revert manual change */190		if (cnd->new_rate < DIV_THRESHOLD &&191		    cnd->old_rate > DIV_THRESHOLD)192			clk_cbf_8996_mux_set_parent(&cbf_mux.clkr.hw, CBF_PLL_INDEX);193		break;194	default:195		break;196	}197 198	return notifier_from_errno(0);199};200 201static struct clk_hw *cbf_msm8996_hw_clks[] = {202	&cbf_pll_postdiv.hw,203};204 205static struct clk_regmap *cbf_msm8996_clks[] = {206	&cbf_pll.clkr,207	&cbf_mux.clkr,208};209 210static const struct regmap_config cbf_msm8996_regmap_config = {211	.reg_bits		= 32,212	.reg_stride		= 4,213	.val_bits		= 32,214	.max_register		= 0x10000,215	.fast_io		= true,216	.val_format_endian	= REGMAP_ENDIAN_LITTLE,217};218 219#ifdef CONFIG_INTERCONNECT220 221/* Random ID that doesn't clash with main qnoc and OSM */222#define CBF_MASTER_NODE 2000223 224static int qcom_msm8996_cbf_icc_register(struct platform_device *pdev, struct clk_hw *cbf_hw)225{226	struct device *dev = &pdev->dev;227	struct clk *clk = devm_clk_hw_get_clk(dev, cbf_hw, "cbf");228	const struct icc_clk_data data[] = {229		{230			.clk = clk,231			.name = "cbf",232			.master_id = MASTER_CBF_M4M,233			.slave_id = SLAVE_CBF_M4M,234		},235	};236	struct icc_provider *provider;237 238	provider = icc_clk_register(dev, CBF_MASTER_NODE, ARRAY_SIZE(data), data);239	if (IS_ERR(provider))240		return PTR_ERR(provider);241 242	platform_set_drvdata(pdev, provider);243 244	return 0;245}246 247static void qcom_msm8996_cbf_icc_remove(struct platform_device *pdev)248{249	struct icc_provider *provider = platform_get_drvdata(pdev);250 251	icc_clk_unregister(provider);252}253#define qcom_msm8996_cbf_icc_sync_state icc_sync_state254#else255static int qcom_msm8996_cbf_icc_register(struct platform_device *pdev,  struct clk_hw *cbf_hw)256{257	dev_warn(&pdev->dev, "CONFIG_INTERCONNECT is disabled, CBF clock is fixed\n");258 259	return 0;260}261#define qcom_msm8996_cbf_icc_remove(pdev) { }262#define qcom_msm8996_cbf_icc_sync_state NULL263#endif264 265static int qcom_msm8996_cbf_probe(struct platform_device *pdev)266{267	void __iomem *base;268	struct regmap *regmap;269	struct device *dev = &pdev->dev;270	int i, ret;271 272	base = devm_platform_ioremap_resource(pdev, 0);273	if (IS_ERR(base))274		return PTR_ERR(base);275 276	regmap = devm_regmap_init_mmio(dev, base, &cbf_msm8996_regmap_config);277	if (IS_ERR(regmap))278		return PTR_ERR(regmap);279 280	/* Select GPLL0 for 300MHz for the CBF clock */281	regmap_write(regmap, CBF_MUX_OFFSET, 0x3);282 283	/* Ensure write goes through before PLLs are reconfigured */284	udelay(5);285 286	/* Set the auto clock sel always-on source to GPLL0/2 (300MHz) */287	regmap_update_bits(regmap, CBF_MUX_OFFSET,288			   CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_MASK,289			   CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_GPLL0_SEL);290 291	clk_alpha_pll_configure(&cbf_pll, regmap, &cbfpll_config);292 293	/* Wait for PLL(s) to lock */294	udelay(50);295 296	/* Enable auto clock selection for CBF */297	regmap_update_bits(regmap, CBF_MUX_OFFSET,298			   CBF_MUX_AUTO_CLK_SEL_BIT,299			   CBF_MUX_AUTO_CLK_SEL_BIT);300 301	/* Ensure write goes through before muxes are switched */302	udelay(5);303 304	/* Switch CBF to use the primary PLL */305	regmap_update_bits(regmap, CBF_MUX_OFFSET, CBF_MUX_PARENT_MASK, 0x1);306 307	if (of_device_is_compatible(dev->of_node, "qcom,msm8996pro-cbf")) {308		cbfpll_config.post_div_val = 0x3 << 8;309		cbf_pll_postdiv.div = 4;310	}311 312	for (i = 0; i < ARRAY_SIZE(cbf_msm8996_hw_clks); i++) {313		ret = devm_clk_hw_register(dev, cbf_msm8996_hw_clks[i]);314		if (ret)315			return ret;316	}317 318	for (i = 0; i < ARRAY_SIZE(cbf_msm8996_clks); i++) {319		ret = devm_clk_register_regmap(dev, cbf_msm8996_clks[i]);320		if (ret)321			return ret;322	}323 324	ret = devm_clk_notifier_register(dev, cbf_mux.clkr.hw.clk, &cbf_mux.nb);325	if (ret)326		return ret;327 328	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &cbf_mux.clkr.hw);329	if (ret)330		return ret;331 332	return qcom_msm8996_cbf_icc_register(pdev, &cbf_mux.clkr.hw);333}334 335static void qcom_msm8996_cbf_remove(struct platform_device *pdev)336{337	qcom_msm8996_cbf_icc_remove(pdev);338}339 340static const struct of_device_id qcom_msm8996_cbf_match_table[] = {341	{ .compatible = "qcom,msm8996-cbf" },342	{ .compatible = "qcom,msm8996pro-cbf" },343	{ /* sentinel */ },344};345MODULE_DEVICE_TABLE(of, qcom_msm8996_cbf_match_table);346 347static struct platform_driver qcom_msm8996_cbf_driver = {348	.probe = qcom_msm8996_cbf_probe,349	.remove = qcom_msm8996_cbf_remove,350	.driver = {351		.name = "qcom-msm8996-cbf",352		.of_match_table = qcom_msm8996_cbf_match_table,353		.sync_state = qcom_msm8996_cbf_icc_sync_state,354	},355};356 357/* Register early enough to fix the clock to be used for other cores */358static int __init qcom_msm8996_cbf_init(void)359{360	return platform_driver_register(&qcom_msm8996_cbf_driver);361}362postcore_initcall(qcom_msm8996_cbf_init);363 364static void __exit qcom_msm8996_cbf_exit(void)365{366	platform_driver_unregister(&qcom_msm8996_cbf_driver);367}368module_exit(qcom_msm8996_cbf_exit);369 370MODULE_DESCRIPTION("QCOM MSM8996 CPU Bus Fabric Clock Driver");371MODULE_LICENSE("GPL");372