| // SPDX-License-Identifier: GPL-2.0 | 
 | // Copyright (c) 2018, The Linux Foundation. All rights reserved. | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/export.h> | 
 | #include <linux/regmap.h> | 
 | #include <linux/delay.h> | 
 | #include <linux/err.h> | 
 | #include <linux/clk-provider.h> | 
 | #include <linux/spinlock.h> | 
 |  | 
 | #include "clk-regmap.h" | 
 | #include "clk-hfpll.h" | 
 |  | 
 | #define PLL_OUTCTRL	BIT(0) | 
 | #define PLL_BYPASSNL	BIT(1) | 
 | #define PLL_RESET_N	BIT(2) | 
 |  | 
 | /* Initialize a HFPLL at a given rate and enable it. */ | 
 | static void __clk_hfpll_init_once(struct clk_hw *hw) | 
 | { | 
 | 	struct clk_hfpll *h = to_clk_hfpll(hw); | 
 | 	struct hfpll_data const *hd = h->d; | 
 | 	struct regmap *regmap = h->clkr.regmap; | 
 |  | 
 | 	if (likely(h->init_done)) | 
 | 		return; | 
 |  | 
 | 	/* Configure PLL parameters for integer mode. */ | 
 | 	if (hd->config_val) | 
 | 		regmap_write(regmap, hd->config_reg, hd->config_val); | 
 | 	regmap_write(regmap, hd->m_reg, 0); | 
 | 	regmap_write(regmap, hd->n_reg, 1); | 
 |  | 
 | 	if (hd->user_reg) { | 
 | 		u32 regval = hd->user_val; | 
 | 		unsigned long rate; | 
 |  | 
 | 		rate = clk_hw_get_rate(hw); | 
 |  | 
 | 		/* Pick the right VCO. */ | 
 | 		if (hd->user_vco_mask && rate > hd->low_vco_max_rate) | 
 | 			regval |= hd->user_vco_mask; | 
 | 		regmap_write(regmap, hd->user_reg, regval); | 
 | 	} | 
 |  | 
 | 	/* Write L_VAL from conf if it exist */ | 
 | 	if (hd->l_val) | 
 | 		regmap_write(regmap, hd->l_reg, hd->l_val); | 
 |  | 
 | 	if (hd->droop_reg) | 
 | 		regmap_write(regmap, hd->droop_reg, hd->droop_val); | 
 |  | 
 | 	h->init_done = true; | 
 | } | 
 |  | 
 | static void __clk_hfpll_enable(struct clk_hw *hw) | 
 | { | 
 | 	struct clk_hfpll *h = to_clk_hfpll(hw); | 
 | 	struct hfpll_data const *hd = h->d; | 
 | 	struct regmap *regmap = h->clkr.regmap; | 
 | 	u32 val; | 
 |  | 
 | 	__clk_hfpll_init_once(hw); | 
 |  | 
 | 	/* Disable PLL bypass mode. */ | 
 | 	regmap_update_bits(regmap, hd->mode_reg, PLL_BYPASSNL, PLL_BYPASSNL); | 
 |  | 
 | 	/* | 
 | 	 * H/W requires a 5us delay between disabling the bypass and | 
 | 	 * de-asserting the reset. Delay 10us just to be safe. | 
 | 	 */ | 
 | 	udelay(10); | 
 |  | 
 | 	/* De-assert active-low PLL reset. */ | 
 | 	regmap_update_bits(regmap, hd->mode_reg, PLL_RESET_N, PLL_RESET_N); | 
 |  | 
 | 	/* Wait for PLL to lock. */ | 
 | 	if (hd->status_reg) | 
 | 		/* | 
 | 		 * Busy wait. Should never timeout, we add a timeout to | 
 | 		 * prevent any sort of stall. | 
 | 		 */ | 
 | 		regmap_read_poll_timeout(regmap, hd->status_reg, val, | 
 | 					 !(val & BIT(hd->lock_bit)), 0, | 
 | 					 100 * USEC_PER_MSEC); | 
 | 	else | 
 | 		udelay(60); | 
 |  | 
 | 	/* Enable PLL output. */ | 
 | 	regmap_update_bits(regmap, hd->mode_reg, PLL_OUTCTRL, PLL_OUTCTRL); | 
 | } | 
 |  | 
 | /* Enable an already-configured HFPLL. */ | 
 | static int clk_hfpll_enable(struct clk_hw *hw) | 
 | { | 
 | 	unsigned long flags; | 
 | 	struct clk_hfpll *h = to_clk_hfpll(hw); | 
 | 	struct hfpll_data const *hd = h->d; | 
 | 	struct regmap *regmap = h->clkr.regmap; | 
 | 	u32 mode; | 
 |  | 
 | 	spin_lock_irqsave(&h->lock, flags); | 
 | 	regmap_read(regmap, hd->mode_reg, &mode); | 
 | 	if (!(mode & (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL))) | 
 | 		__clk_hfpll_enable(hw); | 
 | 	spin_unlock_irqrestore(&h->lock, flags); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void __clk_hfpll_disable(struct clk_hfpll *h) | 
 | { | 
 | 	struct hfpll_data const *hd = h->d; | 
 | 	struct regmap *regmap = h->clkr.regmap; | 
 |  | 
 | 	/* | 
 | 	 * Disable the PLL output, disable test mode, enable the bypass mode, | 
 | 	 * and assert the reset. | 
 | 	 */ | 
 | 	regmap_update_bits(regmap, hd->mode_reg, | 
 | 			   PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL, 0); | 
 | } | 
 |  | 
 | static void clk_hfpll_disable(struct clk_hw *hw) | 
 | { | 
 | 	struct clk_hfpll *h = to_clk_hfpll(hw); | 
 | 	unsigned long flags; | 
 |  | 
 | 	spin_lock_irqsave(&h->lock, flags); | 
 | 	__clk_hfpll_disable(h); | 
 | 	spin_unlock_irqrestore(&h->lock, flags); | 
 | } | 
 |  | 
 | static int clk_hfpll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) | 
 | { | 
 | 	struct clk_hfpll *h = to_clk_hfpll(hw); | 
 | 	struct hfpll_data const *hd = h->d; | 
 | 	unsigned long rrate; | 
 |  | 
 | 	req->rate = clamp(req->rate, hd->min_rate, hd->max_rate); | 
 |  | 
 | 	rrate = DIV_ROUND_UP(req->rate, req->best_parent_rate) * req->best_parent_rate; | 
 | 	if (rrate > hd->max_rate) | 
 | 		rrate -= req->best_parent_rate; | 
 |  | 
 | 	req->rate = rrate; | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  * For optimization reasons, assumes no downstream clocks are actively using | 
 |  * it. | 
 |  */ | 
 | static int clk_hfpll_set_rate(struct clk_hw *hw, unsigned long rate, | 
 | 			      unsigned long parent_rate) | 
 | { | 
 | 	struct clk_hfpll *h = to_clk_hfpll(hw); | 
 | 	struct hfpll_data const *hd = h->d; | 
 | 	struct regmap *regmap = h->clkr.regmap; | 
 | 	unsigned long flags; | 
 | 	u32 l_val, val; | 
 | 	bool enabled; | 
 |  | 
 | 	l_val = rate / parent_rate; | 
 |  | 
 | 	spin_lock_irqsave(&h->lock, flags); | 
 |  | 
 | 	enabled = __clk_is_enabled(hw->clk); | 
 | 	if (enabled) | 
 | 		__clk_hfpll_disable(h); | 
 |  | 
 | 	/* Pick the right VCO. */ | 
 | 	if (hd->user_reg && hd->user_vco_mask) { | 
 | 		regmap_read(regmap, hd->user_reg, &val); | 
 | 		if (rate <= hd->low_vco_max_rate) | 
 | 			val &= ~hd->user_vco_mask; | 
 | 		else | 
 | 			val |= hd->user_vco_mask; | 
 | 		regmap_write(regmap, hd->user_reg, val); | 
 | 	} | 
 |  | 
 | 	regmap_write(regmap, hd->l_reg, l_val); | 
 |  | 
 | 	if (enabled) | 
 | 		__clk_hfpll_enable(hw); | 
 |  | 
 | 	spin_unlock_irqrestore(&h->lock, flags); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static unsigned long clk_hfpll_recalc_rate(struct clk_hw *hw, | 
 | 					   unsigned long parent_rate) | 
 | { | 
 | 	struct clk_hfpll *h = to_clk_hfpll(hw); | 
 | 	struct hfpll_data const *hd = h->d; | 
 | 	struct regmap *regmap = h->clkr.regmap; | 
 | 	u32 l_val; | 
 |  | 
 | 	regmap_read(regmap, hd->l_reg, &l_val); | 
 |  | 
 | 	return l_val * parent_rate; | 
 | } | 
 |  | 
 | static int clk_hfpll_init(struct clk_hw *hw) | 
 | { | 
 | 	struct clk_hfpll *h = to_clk_hfpll(hw); | 
 | 	struct hfpll_data const *hd = h->d; | 
 | 	struct regmap *regmap = h->clkr.regmap; | 
 | 	u32 mode, status; | 
 |  | 
 | 	regmap_read(regmap, hd->mode_reg, &mode); | 
 | 	if (mode != (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)) { | 
 | 		__clk_hfpll_init_once(hw); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	if (hd->status_reg) { | 
 | 		regmap_read(regmap, hd->status_reg, &status); | 
 | 		if (!(status & BIT(hd->lock_bit))) { | 
 | 			WARN(1, "HFPLL %s is ON, but not locked!\n", | 
 | 			     __clk_get_name(hw->clk)); | 
 | 			clk_hfpll_disable(hw); | 
 | 			__clk_hfpll_init_once(hw); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int hfpll_is_enabled(struct clk_hw *hw) | 
 | { | 
 | 	struct clk_hfpll *h = to_clk_hfpll(hw); | 
 | 	struct hfpll_data const *hd = h->d; | 
 | 	struct regmap *regmap = h->clkr.regmap; | 
 | 	u32 mode; | 
 |  | 
 | 	regmap_read(regmap, hd->mode_reg, &mode); | 
 | 	mode &= 0x7; | 
 | 	return mode == (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL); | 
 | } | 
 |  | 
 | const struct clk_ops clk_ops_hfpll = { | 
 | 	.enable = clk_hfpll_enable, | 
 | 	.disable = clk_hfpll_disable, | 
 | 	.is_enabled = hfpll_is_enabled, | 
 | 	.determine_rate = clk_hfpll_determine_rate, | 
 | 	.set_rate = clk_hfpll_set_rate, | 
 | 	.recalc_rate = clk_hfpll_recalc_rate, | 
 | 	.init = clk_hfpll_init, | 
 | }; | 
 | EXPORT_SYMBOL_GPL(clk_ops_hfpll); |