mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	clk: imx: Add fractional PLL output clock
This is a new fractional clock type introduced on i.MX8. The description of this fractional clock can be found here: https://www.nxp.com/docs/en/reference-manual/IMX8MDQLQRM.pdf#page=834 Signed-off-by: Lucas Stach <l.stach@pengutronix.de> Signed-off-by: Abel Vesa <abel.vesa@nxp.com> Reviewed-by: Sascha Hauer <s.hauer@pengutronix.de> Signed-off-by: Stephen Boyd <sboyd@kernel.org>
This commit is contained in:
		
							parent
							
								
									b80522040c
								
							
						
					
					
						commit
						6209624b9a
					
				
					 3 changed files with 236 additions and 0 deletions
				
			
		| 
						 | 
					@ -7,6 +7,7 @@ obj-y += \
 | 
				
			||||||
	clk-cpu.o \
 | 
						clk-cpu.o \
 | 
				
			||||||
	clk-fixup-div.o \
 | 
						clk-fixup-div.o \
 | 
				
			||||||
	clk-fixup-mux.o \
 | 
						clk-fixup-mux.o \
 | 
				
			||||||
 | 
						clk-frac-pll.o \
 | 
				
			||||||
	clk-gate-exclusive.o \
 | 
						clk-gate-exclusive.o \
 | 
				
			||||||
	clk-gate2.o \
 | 
						clk-gate2.o \
 | 
				
			||||||
	clk-pllv1.o \
 | 
						clk-pllv1.o \
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										232
									
								
								drivers/clk/imx/clk-frac-pll.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								drivers/clk/imx/clk-frac-pll.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,232 @@
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: GPL-2.0
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2018 NXP.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This driver supports the fractional plls found in the imx8m SOCs
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Documentation for this fractional pll can be found at:
 | 
				
			||||||
 | 
					 *   https://www.nxp.com/docs/en/reference-manual/IMX8MDQLQRM.pdf#page=834
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <linux/clk-provider.h>
 | 
				
			||||||
 | 
					#include <linux/err.h>
 | 
				
			||||||
 | 
					#include <linux/iopoll.h>
 | 
				
			||||||
 | 
					#include <linux/slab.h>
 | 
				
			||||||
 | 
					#include <linux/bitfield.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "clk.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define PLL_CFG0		0x0
 | 
				
			||||||
 | 
					#define PLL_CFG1		0x4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define PLL_LOCK_STATUS		BIT(31)
 | 
				
			||||||
 | 
					#define PLL_PD_MASK		BIT(19)
 | 
				
			||||||
 | 
					#define PLL_BYPASS_MASK		BIT(14)
 | 
				
			||||||
 | 
					#define PLL_NEWDIV_VAL		BIT(12)
 | 
				
			||||||
 | 
					#define PLL_NEWDIV_ACK		BIT(11)
 | 
				
			||||||
 | 
					#define PLL_FRAC_DIV_MASK	GENMASK(30, 7)
 | 
				
			||||||
 | 
					#define PLL_INT_DIV_MASK	GENMASK(6, 0)
 | 
				
			||||||
 | 
					#define PLL_OUTPUT_DIV_MASK	GENMASK(4, 0)
 | 
				
			||||||
 | 
					#define PLL_FRAC_DENOM		0x1000000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define PLL_FRAC_LOCK_TIMEOUT	10000
 | 
				
			||||||
 | 
					#define PLL_FRAC_ACK_TIMEOUT	500000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct clk_frac_pll {
 | 
				
			||||||
 | 
						struct clk_hw	hw;
 | 
				
			||||||
 | 
						void __iomem	*base;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define to_clk_frac_pll(_hw) container_of(_hw, struct clk_frac_pll, hw)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int clk_wait_lock(struct clk_frac_pll *pll)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u32 val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return readl_poll_timeout(pll->base, val, val & PLL_LOCK_STATUS, 0,
 | 
				
			||||||
 | 
										PLL_FRAC_LOCK_TIMEOUT);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int clk_wait_ack(struct clk_frac_pll *pll)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u32 val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* return directly if the pll is in powerdown or in bypass */
 | 
				
			||||||
 | 
						if (readl_relaxed(pll->base) & (PLL_PD_MASK | PLL_BYPASS_MASK))
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Wait for the pll's divfi and divff to be reloaded */
 | 
				
			||||||
 | 
						return readl_poll_timeout(pll->base, val, val & PLL_NEWDIV_ACK, 0,
 | 
				
			||||||
 | 
										PLL_FRAC_ACK_TIMEOUT);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int clk_pll_prepare(struct clk_hw *hw)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct clk_frac_pll *pll = to_clk_frac_pll(hw);
 | 
				
			||||||
 | 
						u32 val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						val = readl_relaxed(pll->base + PLL_CFG0);
 | 
				
			||||||
 | 
						val &= ~PLL_PD_MASK;
 | 
				
			||||||
 | 
						writel_relaxed(val, pll->base + PLL_CFG0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return clk_wait_lock(pll);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void clk_pll_unprepare(struct clk_hw *hw)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct clk_frac_pll *pll = to_clk_frac_pll(hw);
 | 
				
			||||||
 | 
						u32 val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						val = readl_relaxed(pll->base + PLL_CFG0);
 | 
				
			||||||
 | 
						val |= PLL_PD_MASK;
 | 
				
			||||||
 | 
						writel_relaxed(val, pll->base + PLL_CFG0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int clk_pll_is_prepared(struct clk_hw *hw)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct clk_frac_pll *pll = to_clk_frac_pll(hw);
 | 
				
			||||||
 | 
						u32 val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						val = readl_relaxed(pll->base + PLL_CFG0);
 | 
				
			||||||
 | 
						return (val & PLL_PD_MASK) ? 0 : 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static unsigned long clk_pll_recalc_rate(struct clk_hw *hw,
 | 
				
			||||||
 | 
										 unsigned long parent_rate)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct clk_frac_pll *pll = to_clk_frac_pll(hw);
 | 
				
			||||||
 | 
						u32 val, divff, divfi, divq;
 | 
				
			||||||
 | 
						u64 temp64 = parent_rate;
 | 
				
			||||||
 | 
						u64 rate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						val = readl_relaxed(pll->base + PLL_CFG0);
 | 
				
			||||||
 | 
						divq = (FIELD_GET(PLL_OUTPUT_DIV_MASK, val) + 1) * 2;
 | 
				
			||||||
 | 
						val = readl_relaxed(pll->base + PLL_CFG1);
 | 
				
			||||||
 | 
						divff = FIELD_GET(PLL_FRAC_DIV_MASK, val);
 | 
				
			||||||
 | 
						divfi = FIELD_GET(PLL_INT_DIV_MASK, val);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						temp64 *= 8;
 | 
				
			||||||
 | 
						temp64 *= divff;
 | 
				
			||||||
 | 
						do_div(temp64, PLL_FRAC_DENOM);
 | 
				
			||||||
 | 
						do_div(temp64, divq);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rate = parent_rate * 8 * (divfi + 1);
 | 
				
			||||||
 | 
						do_div(rate, divq);
 | 
				
			||||||
 | 
						rate += temp64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return rate;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static long clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
 | 
				
			||||||
 | 
								       unsigned long *prate)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u64 parent_rate = *prate;
 | 
				
			||||||
 | 
						u32 divff, divfi;
 | 
				
			||||||
 | 
						u64 temp64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						parent_rate *= 8;
 | 
				
			||||||
 | 
						rate *= 2;
 | 
				
			||||||
 | 
						temp64 = rate;
 | 
				
			||||||
 | 
						do_div(temp64, parent_rate);
 | 
				
			||||||
 | 
						divfi = temp64;
 | 
				
			||||||
 | 
						temp64 = rate - divfi * parent_rate;
 | 
				
			||||||
 | 
						temp64 *= PLL_FRAC_DENOM;
 | 
				
			||||||
 | 
						do_div(temp64, parent_rate);
 | 
				
			||||||
 | 
						divff = temp64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						temp64 = parent_rate;
 | 
				
			||||||
 | 
						temp64 *= divff;
 | 
				
			||||||
 | 
						do_div(temp64, PLL_FRAC_DENOM);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rate = parent_rate * divfi + temp64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return rate / 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * To simplify the clock calculation, we can keep the 'PLL_OUTPUT_VAL' at zero
 | 
				
			||||||
 | 
					 * (means the PLL output will be divided by 2). So the PLL output can use
 | 
				
			||||||
 | 
					 * the below formula:
 | 
				
			||||||
 | 
					 * pllout = parent_rate * 8 / 2 * DIVF_VAL;
 | 
				
			||||||
 | 
					 * where DIVF_VAL = 1 + DIVFI + DIVFF / 2^24.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
 | 
				
			||||||
 | 
								    unsigned long parent_rate)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct clk_frac_pll *pll = to_clk_frac_pll(hw);
 | 
				
			||||||
 | 
						u32 val, divfi, divff;
 | 
				
			||||||
 | 
						u64 temp64 = parent_rate;
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						parent_rate *= 8;
 | 
				
			||||||
 | 
						rate *= 2;
 | 
				
			||||||
 | 
						divfi = rate / parent_rate;
 | 
				
			||||||
 | 
						temp64 *= rate - divfi;
 | 
				
			||||||
 | 
						temp64 *= PLL_FRAC_DENOM;
 | 
				
			||||||
 | 
						do_div(temp64, parent_rate);
 | 
				
			||||||
 | 
						divff = temp64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						val = readl_relaxed(pll->base + PLL_CFG1);
 | 
				
			||||||
 | 
						val &= ~(PLL_FRAC_DIV_MASK | PLL_INT_DIV_MASK);
 | 
				
			||||||
 | 
						val |= (divff << 7) | (divfi - 1);
 | 
				
			||||||
 | 
						writel_relaxed(val, pll->base + PLL_CFG1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						val = readl_relaxed(pll->base + PLL_CFG0);
 | 
				
			||||||
 | 
						val &= ~0x1f;
 | 
				
			||||||
 | 
						writel_relaxed(val, pll->base + PLL_CFG0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Set the NEV_DIV_VAL to reload the DIVFI and DIVFF */
 | 
				
			||||||
 | 
						val = readl_relaxed(pll->base + PLL_CFG0);
 | 
				
			||||||
 | 
						val |= PLL_NEWDIV_VAL;
 | 
				
			||||||
 | 
						writel_relaxed(val, pll->base + PLL_CFG0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = clk_wait_ack(pll);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* clear the NEV_DIV_VAL */
 | 
				
			||||||
 | 
						val = readl_relaxed(pll->base + PLL_CFG0);
 | 
				
			||||||
 | 
						val &= ~PLL_NEWDIV_VAL;
 | 
				
			||||||
 | 
						writel_relaxed(val, pll->base + PLL_CFG0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct clk_ops clk_frac_pll_ops = {
 | 
				
			||||||
 | 
						.prepare	= clk_pll_prepare,
 | 
				
			||||||
 | 
						.unprepare	= clk_pll_unprepare,
 | 
				
			||||||
 | 
						.is_prepared	= clk_pll_is_prepared,
 | 
				
			||||||
 | 
						.recalc_rate	= clk_pll_recalc_rate,
 | 
				
			||||||
 | 
						.round_rate	= clk_pll_round_rate,
 | 
				
			||||||
 | 
						.set_rate	= clk_pll_set_rate,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct clk *imx_clk_frac_pll(const char *name, const char *parent_name,
 | 
				
			||||||
 | 
								     void __iomem *base)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct clk_init_data init;
 | 
				
			||||||
 | 
						struct clk_frac_pll *pll;
 | 
				
			||||||
 | 
						struct clk_hw *hw;
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pll = kzalloc(sizeof(*pll), GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!pll)
 | 
				
			||||||
 | 
							return ERR_PTR(-ENOMEM);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						init.name = name;
 | 
				
			||||||
 | 
						init.ops = &clk_frac_pll_ops;
 | 
				
			||||||
 | 
						init.flags = 0;
 | 
				
			||||||
 | 
						init.parent_names = &parent_name;
 | 
				
			||||||
 | 
						init.num_parents = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pll->base = base;
 | 
				
			||||||
 | 
						pll->hw.init = &init;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hw = &pll->hw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = clk_hw_register(NULL, hw);
 | 
				
			||||||
 | 
						if (ret) {
 | 
				
			||||||
 | 
							kfree(pll);
 | 
				
			||||||
 | 
							return ERR_PTR(ret);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return hw->clk;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,9 @@ struct clk *imx_clk_pllv1(enum imx_pllv1_type type, const char *name,
 | 
				
			||||||
struct clk *imx_clk_pllv2(const char *name, const char *parent,
 | 
					struct clk *imx_clk_pllv2(const char *name, const char *parent,
 | 
				
			||||||
		void __iomem *base);
 | 
							void __iomem *base);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct clk *imx_clk_frac_pll(const char *name, const char *parent_name,
 | 
				
			||||||
 | 
								     void __iomem *base);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum imx_pllv3_type {
 | 
					enum imx_pllv3_type {
 | 
				
			||||||
	IMX_PLLV3_GENERIC,
 | 
						IMX_PLLV3_GENERIC,
 | 
				
			||||||
	IMX_PLLV3_SYS,
 | 
						IMX_PLLV3_SYS,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue