xref: /openbmc/linux/drivers/clk/mediatek/clk-pll.c (revision 5a938500)
11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
29741b1a6SJames Liao /*
39741b1a6SJames Liao  * Copyright (c) 2014 MediaTek Inc.
49741b1a6SJames Liao  * Author: James Liao <jamesjj.liao@mediatek.com>
59741b1a6SJames Liao  */
69741b1a6SJames Liao 
710174b50SChen-Yu Tsai #include <linux/clk-provider.h>
810174b50SChen-Yu Tsai #include <linux/container_of.h>
910174b50SChen-Yu Tsai #include <linux/delay.h>
1010174b50SChen-Yu Tsai #include <linux/err.h>
119741b1a6SJames Liao #include <linux/io.h>
1232b028fbSMiles Chen #include <linux/module.h>
1310174b50SChen-Yu Tsai #include <linux/of_address.h>
149741b1a6SJames Liao #include <linux/slab.h>
159741b1a6SJames Liao 
1639691fb6SChen-Yu Tsai #include "clk-pll.h"
179741b1a6SJames Liao 
1810174b50SChen-Yu Tsai #define MHZ			(1000 * 1000)
1910174b50SChen-Yu Tsai 
209741b1a6SJames Liao #define REG_CON0		0
219741b1a6SJames Liao #define REG_CON1		4
229741b1a6SJames Liao 
239741b1a6SJames Liao #define CON0_BASE_EN		BIT(0)
249741b1a6SJames Liao #define CON0_PWR_ON		BIT(0)
259741b1a6SJames Liao #define CON0_ISO_EN		BIT(1)
2623fe31deSWeiyi Lu #define PCW_CHG_MASK		BIT(31)
279741b1a6SJames Liao 
289741b1a6SJames Liao #define AUDPLL_TUNER_EN		BIT(31)
299741b1a6SJames Liao 
309d7e1a82SOwen Chen /* default 7 bits integer, can be overridden with pcwibits. */
319741b1a6SJames Liao #define INTEGER_BITS		7
329741b1a6SJames Liao 
mtk_pll_is_prepared(struct clk_hw * hw)33029c936aSJohnson Wang int mtk_pll_is_prepared(struct clk_hw *hw)
349741b1a6SJames Liao {
359741b1a6SJames Liao 	struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
369741b1a6SJames Liao 
37f384c447SChun-Jie Chen 	return (readl(pll->en_addr) & BIT(pll->data->pll_en_bit)) != 0;
389741b1a6SJames Liao }
399741b1a6SJames Liao 
__mtk_pll_recalc_rate(struct mtk_clk_pll * pll,u32 fin,u32 pcw,int postdiv)409741b1a6SJames Liao static unsigned long __mtk_pll_recalc_rate(struct mtk_clk_pll *pll, u32 fin,
419741b1a6SJames Liao 		u32 pcw, int postdiv)
429741b1a6SJames Liao {
439741b1a6SJames Liao 	int pcwbits = pll->data->pcwbits;
449d7e1a82SOwen Chen 	int pcwfbits = 0;
459d7e1a82SOwen Chen 	int ibits;
469741b1a6SJames Liao 	u64 vco;
479741b1a6SJames Liao 	u8 c = 0;
489741b1a6SJames Liao 
499741b1a6SJames Liao 	/* The fractional part of the PLL divider. */
509d7e1a82SOwen Chen 	ibits = pll->data->pcwibits ? pll->data->pcwibits : INTEGER_BITS;
519d7e1a82SOwen Chen 	if (pcwbits > ibits)
529d7e1a82SOwen Chen 		pcwfbits = pcwbits - ibits;
539741b1a6SJames Liao 
549741b1a6SJames Liao 	vco = (u64)fin * pcw;
559741b1a6SJames Liao 
569741b1a6SJames Liao 	if (pcwfbits && (vco & GENMASK(pcwfbits - 1, 0)))
579741b1a6SJames Liao 		c = 1;
589741b1a6SJames Liao 
599741b1a6SJames Liao 	vco >>= pcwfbits;
609741b1a6SJames Liao 
619741b1a6SJames Liao 	if (c)
629741b1a6SJames Liao 		vco++;
639741b1a6SJames Liao 
649741b1a6SJames Liao 	return ((unsigned long)vco + postdiv - 1) / postdiv;
659741b1a6SJames Liao }
669741b1a6SJames Liao 
__mtk_pll_tuner_enable(struct mtk_clk_pll * pll)67be17ca6aSOwen Chen static void __mtk_pll_tuner_enable(struct mtk_clk_pll *pll)
68be17ca6aSOwen Chen {
69be17ca6aSOwen Chen 	u32 r;
70be17ca6aSOwen Chen 
71be17ca6aSOwen Chen 	if (pll->tuner_en_addr) {
72be17ca6aSOwen Chen 		r = readl(pll->tuner_en_addr) | BIT(pll->data->tuner_en_bit);
73be17ca6aSOwen Chen 		writel(r, pll->tuner_en_addr);
74be17ca6aSOwen Chen 	} else if (pll->tuner_addr) {
75be17ca6aSOwen Chen 		r = readl(pll->tuner_addr) | AUDPLL_TUNER_EN;
76be17ca6aSOwen Chen 		writel(r, pll->tuner_addr);
77be17ca6aSOwen Chen 	}
78be17ca6aSOwen Chen }
79be17ca6aSOwen Chen 
__mtk_pll_tuner_disable(struct mtk_clk_pll * pll)80be17ca6aSOwen Chen static void __mtk_pll_tuner_disable(struct mtk_clk_pll *pll)
81be17ca6aSOwen Chen {
82be17ca6aSOwen Chen 	u32 r;
83be17ca6aSOwen Chen 
84be17ca6aSOwen Chen 	if (pll->tuner_en_addr) {
85be17ca6aSOwen Chen 		r = readl(pll->tuner_en_addr) & ~BIT(pll->data->tuner_en_bit);
86be17ca6aSOwen Chen 		writel(r, pll->tuner_en_addr);
87be17ca6aSOwen Chen 	} else if (pll->tuner_addr) {
88be17ca6aSOwen Chen 		r = readl(pll->tuner_addr) & ~AUDPLL_TUNER_EN;
89be17ca6aSOwen Chen 		writel(r, pll->tuner_addr);
90be17ca6aSOwen Chen 	}
91be17ca6aSOwen Chen }
92be17ca6aSOwen Chen 
mtk_pll_set_rate_regs(struct mtk_clk_pll * pll,u32 pcw,int postdiv)939741b1a6SJames Liao static void mtk_pll_set_rate_regs(struct mtk_clk_pll *pll, u32 pcw,
949741b1a6SJames Liao 		int postdiv)
959741b1a6SJames Liao {
9623fe31deSWeiyi Lu 	u32 chg, val;
979741b1a6SJames Liao 
98be17ca6aSOwen Chen 	/* disable tuner */
99be17ca6aSOwen Chen 	__mtk_pll_tuner_disable(pll);
100be17ca6aSOwen Chen 
101b3be457eSJames Liao 	/* set postdiv */
102b3be457eSJames Liao 	val = readl(pll->pd_addr);
103b3be457eSJames Liao 	val &= ~(POSTDIV_MASK << pll->data->pd_shift);
104b3be457eSJames Liao 	val |= (ffs(postdiv) - 1) << pll->data->pd_shift;
1059741b1a6SJames Liao 
106b3be457eSJames Liao 	/* postdiv and pcw need to set at the same time if on same register */
107b3be457eSJames Liao 	if (pll->pd_addr != pll->pcw_addr) {
108b3be457eSJames Liao 		writel(val, pll->pd_addr);
109b3be457eSJames Liao 		val = readl(pll->pcw_addr);
110b3be457eSJames Liao 	}
111b3be457eSJames Liao 
112b3be457eSJames Liao 	/* set pcw */
1139741b1a6SJames Liao 	val &= ~GENMASK(pll->data->pcw_shift + pll->data->pcwbits - 1,
1149741b1a6SJames Liao 			pll->data->pcw_shift);
1159741b1a6SJames Liao 	val |= pcw << pll->data->pcw_shift;
1169741b1a6SJames Liao 	writel(val, pll->pcw_addr);
117dac5d672SJames Liao 	chg = readl(pll->pcw_chg_addr) | PCW_CHG_MASK;
11823fe31deSWeiyi Lu 	writel(chg, pll->pcw_chg_addr);
1199741b1a6SJames Liao 	if (pll->tuner_addr)
12023fe31deSWeiyi Lu 		writel(val + 1, pll->tuner_addr);
1219741b1a6SJames Liao 
122be17ca6aSOwen Chen 	/* restore tuner_en */
123be17ca6aSOwen Chen 	__mtk_pll_tuner_enable(pll);
124be17ca6aSOwen Chen 
1259741b1a6SJames Liao 	udelay(20);
1269741b1a6SJames Liao }
1279741b1a6SJames Liao 
1289741b1a6SJames Liao /*
1299741b1a6SJames Liao  * mtk_pll_calc_values - calculate good values for a given input frequency.
1309741b1a6SJames Liao  * @pll:	The pll
1319741b1a6SJames Liao  * @pcw:	The pcw value (output)
1329741b1a6SJames Liao  * @postdiv:	The post divider (output)
1339741b1a6SJames Liao  * @freq:	The desired target frequency
1349741b1a6SJames Liao  * @fin:	The input frequency
1359741b1a6SJames Liao  *
1369741b1a6SJames Liao  */
mtk_pll_calc_values(struct mtk_clk_pll * pll,u32 * pcw,u32 * postdiv,u32 freq,u32 fin)137029c936aSJohnson Wang void mtk_pll_calc_values(struct mtk_clk_pll *pll, u32 *pcw, u32 *postdiv,
1389741b1a6SJames Liao 			 u32 freq, u32 fin)
1399741b1a6SJames Liao {
1409d7e1a82SOwen Chen 	unsigned long fmin = pll->data->fmin ? pll->data->fmin : (1000 * MHZ);
14175ce0cdbSJames Liao 	const struct mtk_pll_div_table *div_table = pll->data->div_table;
1429741b1a6SJames Liao 	u64 _pcw;
1439d7e1a82SOwen Chen 	int ibits;
1449741b1a6SJames Liao 	u32 val;
1459741b1a6SJames Liao 
1469741b1a6SJames Liao 	if (freq > pll->data->fmax)
1479741b1a6SJames Liao 		freq = pll->data->fmax;
1489741b1a6SJames Liao 
14975ce0cdbSJames Liao 	if (div_table) {
15075ce0cdbSJames Liao 		if (freq > div_table[0].freq)
15175ce0cdbSJames Liao 			freq = div_table[0].freq;
15275ce0cdbSJames Liao 
15375ce0cdbSJames Liao 		for (val = 0; div_table[val + 1].freq != 0; val++) {
15475ce0cdbSJames Liao 			if (freq > div_table[val + 1].freq)
15575ce0cdbSJames Liao 				break;
15675ce0cdbSJames Liao 		}
15775ce0cdbSJames Liao 		*postdiv = 1 << val;
15875ce0cdbSJames Liao 	} else {
159196de71aSJames Liao 		for (val = 0; val < 5; val++) {
1609741b1a6SJames Liao 			*postdiv = 1 << val;
161196de71aSJames Liao 			if ((u64)freq * *postdiv >= fmin)
1629741b1a6SJames Liao 				break;
1639741b1a6SJames Liao 		}
16475ce0cdbSJames Liao 	}
1659741b1a6SJames Liao 
1669741b1a6SJames Liao 	/* _pcw = freq * postdiv / fin * 2^pcwfbits */
1679d7e1a82SOwen Chen 	ibits = pll->data->pcwibits ? pll->data->pcwibits : INTEGER_BITS;
1689d7e1a82SOwen Chen 	_pcw = ((u64)freq << val) << (pll->data->pcwbits - ibits);
1699741b1a6SJames Liao 	do_div(_pcw, fin);
1709741b1a6SJames Liao 
1719741b1a6SJames Liao 	*pcw = (u32)_pcw;
1729741b1a6SJames Liao }
1739741b1a6SJames Liao 
mtk_pll_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)174029c936aSJohnson Wang int mtk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
1759741b1a6SJames Liao 		     unsigned long parent_rate)
1769741b1a6SJames Liao {
1779741b1a6SJames Liao 	struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
1789741b1a6SJames Liao 	u32 pcw = 0;
1799741b1a6SJames Liao 	u32 postdiv;
1809741b1a6SJames Liao 
1819741b1a6SJames Liao 	mtk_pll_calc_values(pll, &pcw, &postdiv, rate, parent_rate);
1829741b1a6SJames Liao 	mtk_pll_set_rate_regs(pll, pcw, postdiv);
1839741b1a6SJames Liao 
1849741b1a6SJames Liao 	return 0;
1859741b1a6SJames Liao }
1869741b1a6SJames Liao 
mtk_pll_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)187029c936aSJohnson Wang unsigned long mtk_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
1889741b1a6SJames Liao {
1899741b1a6SJames Liao 	struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
1909741b1a6SJames Liao 	u32 postdiv;
1919741b1a6SJames Liao 	u32 pcw;
1929741b1a6SJames Liao 
1939741b1a6SJames Liao 	postdiv = (readl(pll->pd_addr) >> pll->data->pd_shift) & POSTDIV_MASK;
1949741b1a6SJames Liao 	postdiv = 1 << postdiv;
1959741b1a6SJames Liao 
1969741b1a6SJames Liao 	pcw = readl(pll->pcw_addr) >> pll->data->pcw_shift;
1979741b1a6SJames Liao 	pcw &= GENMASK(pll->data->pcwbits - 1, 0);
1989741b1a6SJames Liao 
1999741b1a6SJames Liao 	return __mtk_pll_recalc_rate(pll, parent_rate, pcw, postdiv);
2009741b1a6SJames Liao }
2019741b1a6SJames Liao 
mtk_pll_round_rate(struct clk_hw * hw,unsigned long rate,unsigned long * prate)202029c936aSJohnson Wang long mtk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
2039741b1a6SJames Liao 			unsigned long *prate)
2049741b1a6SJames Liao {
2059741b1a6SJames Liao 	struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
2069741b1a6SJames Liao 	u32 pcw = 0;
2079741b1a6SJames Liao 	int postdiv;
2089741b1a6SJames Liao 
2099741b1a6SJames Liao 	mtk_pll_calc_values(pll, &pcw, &postdiv, rate, *prate);
2109741b1a6SJames Liao 
2119741b1a6SJames Liao 	return __mtk_pll_recalc_rate(pll, *prate, pcw, postdiv);
2129741b1a6SJames Liao }
2139741b1a6SJames Liao 
mtk_pll_prepare(struct clk_hw * hw)214029c936aSJohnson Wang int mtk_pll_prepare(struct clk_hw *hw)
2159741b1a6SJames Liao {
2169741b1a6SJames Liao 	struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
2179741b1a6SJames Liao 	u32 r;
2189741b1a6SJames Liao 
2199741b1a6SJames Liao 	r = readl(pll->pwr_addr) | CON0_PWR_ON;
2209741b1a6SJames Liao 	writel(r, pll->pwr_addr);
2219741b1a6SJames Liao 	udelay(1);
2229741b1a6SJames Liao 
2239741b1a6SJames Liao 	r = readl(pll->pwr_addr) & ~CON0_ISO_EN;
2249741b1a6SJames Liao 	writel(r, pll->pwr_addr);
2259741b1a6SJames Liao 	udelay(1);
2269741b1a6SJames Liao 
227f384c447SChun-Jie Chen 	r = readl(pll->en_addr) | BIT(pll->data->pll_en_bit);
228f384c447SChun-Jie Chen 	writel(r, pll->en_addr);
2299741b1a6SJames Liao 
230e1fd35f5SChun-Jie Chen 	if (pll->data->en_mask) {
231e1fd35f5SChun-Jie Chen 		r = readl(pll->base_addr + REG_CON0) | pll->data->en_mask;
2327cc4e1bbSChun-Jie Chen 		writel(r, pll->base_addr + REG_CON0);
2337cc4e1bbSChun-Jie Chen 	}
2347cc4e1bbSChun-Jie Chen 
235be17ca6aSOwen Chen 	__mtk_pll_tuner_enable(pll);
2369741b1a6SJames Liao 
2379741b1a6SJames Liao 	udelay(20);
2389741b1a6SJames Liao 
2399741b1a6SJames Liao 	if (pll->data->flags & HAVE_RST_BAR) {
2409741b1a6SJames Liao 		r = readl(pll->base_addr + REG_CON0);
2419741b1a6SJames Liao 		r |= pll->data->rst_bar_mask;
2429741b1a6SJames Liao 		writel(r, pll->base_addr + REG_CON0);
2439741b1a6SJames Liao 	}
2449741b1a6SJames Liao 
2459741b1a6SJames Liao 	return 0;
2469741b1a6SJames Liao }
2479741b1a6SJames Liao 
mtk_pll_unprepare(struct clk_hw * hw)248029c936aSJohnson Wang void mtk_pll_unprepare(struct clk_hw *hw)
2499741b1a6SJames Liao {
2509741b1a6SJames Liao 	struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
2519741b1a6SJames Liao 	u32 r;
2529741b1a6SJames Liao 
2539741b1a6SJames Liao 	if (pll->data->flags & HAVE_RST_BAR) {
2549741b1a6SJames Liao 		r = readl(pll->base_addr + REG_CON0);
2559741b1a6SJames Liao 		r &= ~pll->data->rst_bar_mask;
2569741b1a6SJames Liao 		writel(r, pll->base_addr + REG_CON0);
2579741b1a6SJames Liao 	}
2589741b1a6SJames Liao 
259be17ca6aSOwen Chen 	__mtk_pll_tuner_disable(pll);
2609741b1a6SJames Liao 
261e1fd35f5SChun-Jie Chen 	if (pll->data->en_mask) {
262e1fd35f5SChun-Jie Chen 		r = readl(pll->base_addr + REG_CON0) & ~pll->data->en_mask;
2637cc4e1bbSChun-Jie Chen 		writel(r, pll->base_addr + REG_CON0);
2647cc4e1bbSChun-Jie Chen 	}
2657cc4e1bbSChun-Jie Chen 
266f384c447SChun-Jie Chen 	r = readl(pll->en_addr) & ~BIT(pll->data->pll_en_bit);
267f384c447SChun-Jie Chen 	writel(r, pll->en_addr);
2689741b1a6SJames Liao 
2699741b1a6SJames Liao 	r = readl(pll->pwr_addr) | CON0_ISO_EN;
2709741b1a6SJames Liao 	writel(r, pll->pwr_addr);
2719741b1a6SJames Liao 
2729741b1a6SJames Liao 	r = readl(pll->pwr_addr) & ~CON0_PWR_ON;
2739741b1a6SJames Liao 	writel(r, pll->pwr_addr);
2749741b1a6SJames Liao }
2759741b1a6SJames Liao 
276029c936aSJohnson Wang const struct clk_ops mtk_pll_ops = {
2779741b1a6SJames Liao 	.is_prepared	= mtk_pll_is_prepared,
2789741b1a6SJames Liao 	.prepare	= mtk_pll_prepare,
2799741b1a6SJames Liao 	.unprepare	= mtk_pll_unprepare,
2809741b1a6SJames Liao 	.recalc_rate	= mtk_pll_recalc_rate,
2819741b1a6SJames Liao 	.round_rate	= mtk_pll_round_rate,
2829741b1a6SJames Liao 	.set_rate	= mtk_pll_set_rate,
2839741b1a6SJames Liao };
2849741b1a6SJames Liao 
mtk_clk_register_pll_ops(struct mtk_clk_pll * pll,const struct mtk_pll_data * data,void __iomem * base,const struct clk_ops * pll_ops)285029c936aSJohnson Wang struct clk_hw *mtk_clk_register_pll_ops(struct mtk_clk_pll *pll,
286029c936aSJohnson Wang 					const struct mtk_pll_data *data,
287029c936aSJohnson Wang 					void __iomem *base,
288029c936aSJohnson Wang 					const struct clk_ops *pll_ops)
2899741b1a6SJames Liao {
29095f58981SRicky Liang 	struct clk_init_data init = {};
2916f691a58SChen-Yu Tsai 	int ret;
2929741b1a6SJames Liao 	const char *parent_name = "clk26m";
2939741b1a6SJames Liao 
2949741b1a6SJames Liao 	pll->base_addr = base + data->reg;
2959741b1a6SJames Liao 	pll->pwr_addr = base + data->pwr_reg;
2969741b1a6SJames Liao 	pll->pd_addr = base + data->pd_reg;
2979741b1a6SJames Liao 	pll->pcw_addr = base + data->pcw_reg;
29823fe31deSWeiyi Lu 	if (data->pcw_chg_reg)
29923fe31deSWeiyi Lu 		pll->pcw_chg_addr = base + data->pcw_chg_reg;
30023fe31deSWeiyi Lu 	else
30123fe31deSWeiyi Lu 		pll->pcw_chg_addr = pll->base_addr + REG_CON1;
3029741b1a6SJames Liao 	if (data->tuner_reg)
3039741b1a6SJames Liao 		pll->tuner_addr = base + data->tuner_reg;
304cb95c169SChun-Jie Chen 	if (data->tuner_en_reg || data->tuner_en_bit)
305e2f744a8Sweiyi.lu@mediatek.com 		pll->tuner_en_addr = base + data->tuner_en_reg;
306f384c447SChun-Jie Chen 	if (data->en_reg)
307f384c447SChun-Jie Chen 		pll->en_addr = base + data->en_reg;
308f384c447SChun-Jie Chen 	else
309f384c447SChun-Jie Chen 		pll->en_addr = pll->base_addr + REG_CON0;
3109741b1a6SJames Liao 	pll->hw.init = &init;
3119741b1a6SJames Liao 	pll->data = data;
3129741b1a6SJames Liao 
3139741b1a6SJames Liao 	init.name = data->name;
314e9862118SShunli Wang 	init.flags = (data->flags & PLL_AO) ? CLK_IS_CRITICAL : 0;
315029c936aSJohnson Wang 	init.ops = pll_ops;
316c955bf39SChen Zhong 	if (data->parent_name)
317c955bf39SChen Zhong 		init.parent_names = &data->parent_name;
318c955bf39SChen Zhong 	else
3199741b1a6SJames Liao 		init.parent_names = &parent_name;
3209741b1a6SJames Liao 	init.num_parents = 1;
3219741b1a6SJames Liao 
3226f691a58SChen-Yu Tsai 	ret = clk_hw_register(NULL, &pll->hw);
3239741b1a6SJames Liao 
324*5a938500SDan Carpenter 	if (ret)
3256f691a58SChen-Yu Tsai 		return ERR_PTR(ret);
3269741b1a6SJames Liao 
3276f691a58SChen-Yu Tsai 	return &pll->hw;
3286f691a58SChen-Yu Tsai }
3296f691a58SChen-Yu Tsai 
mtk_clk_register_pll(const struct mtk_pll_data * data,void __iomem * base)330029c936aSJohnson Wang struct clk_hw *mtk_clk_register_pll(const struct mtk_pll_data *data,
331029c936aSJohnson Wang 				    void __iomem *base)
332029c936aSJohnson Wang {
333029c936aSJohnson Wang 	struct mtk_clk_pll *pll;
334029c936aSJohnson Wang 	struct clk_hw *hw;
335029c936aSJohnson Wang 
336029c936aSJohnson Wang 	pll = kzalloc(sizeof(*pll), GFP_KERNEL);
337029c936aSJohnson Wang 	if (!pll)
338029c936aSJohnson Wang 		return ERR_PTR(-ENOMEM);
339029c936aSJohnson Wang 
340029c936aSJohnson Wang 	hw = mtk_clk_register_pll_ops(pll, data, base, &mtk_pll_ops);
341*5a938500SDan Carpenter 	if (IS_ERR(hw))
342*5a938500SDan Carpenter 		kfree(pll);
343029c936aSJohnson Wang 
344029c936aSJohnson Wang 	return hw;
345029c936aSJohnson Wang }
346029c936aSJohnson Wang 
mtk_clk_unregister_pll(struct clk_hw * hw)347029c936aSJohnson Wang void mtk_clk_unregister_pll(struct clk_hw *hw)
3486dd19906SChen-Yu Tsai {
3496dd19906SChen-Yu Tsai 	struct mtk_clk_pll *pll;
3506dd19906SChen-Yu Tsai 
3516dd19906SChen-Yu Tsai 	if (!hw)
3526dd19906SChen-Yu Tsai 		return;
3536dd19906SChen-Yu Tsai 
3546dd19906SChen-Yu Tsai 	pll = to_mtk_clk_pll(hw);
3556dd19906SChen-Yu Tsai 
3566f691a58SChen-Yu Tsai 	clk_hw_unregister(hw);
3576dd19906SChen-Yu Tsai 	kfree(pll);
3586dd19906SChen-Yu Tsai }
3596dd19906SChen-Yu Tsai 
mtk_clk_register_plls(struct device_node * node,const struct mtk_pll_data * plls,int num_plls,struct clk_hw_onecell_data * clk_data)3606ae34f2bSChen-Yu Tsai int mtk_clk_register_plls(struct device_node *node,
3616ae34f2bSChen-Yu Tsai 			  const struct mtk_pll_data *plls, int num_plls,
362609cc5e1SChen-Yu Tsai 			  struct clk_hw_onecell_data *clk_data)
3639741b1a6SJames Liao {
3649741b1a6SJames Liao 	void __iomem *base;
365cdb2bab7SJames Liao 	int i;
3666f691a58SChen-Yu Tsai 	struct clk_hw *hw;
3679741b1a6SJames Liao 
3689741b1a6SJames Liao 	base = of_iomap(node, 0);
3699741b1a6SJames Liao 	if (!base) {
3709741b1a6SJames Liao 		pr_err("%s(): ioremap failed\n", __func__);
3716ae34f2bSChen-Yu Tsai 		return -EINVAL;
3729741b1a6SJames Liao 	}
3739741b1a6SJames Liao 
3749741b1a6SJames Liao 	for (i = 0; i < num_plls; i++) {
3759741b1a6SJames Liao 		const struct mtk_pll_data *pll = &plls[i];
3769741b1a6SJames Liao 
377609cc5e1SChen-Yu Tsai 		if (!IS_ERR_OR_NULL(clk_data->hws[pll->id])) {
378d54bb86bSChen-Yu Tsai 			pr_warn("%pOF: Trying to register duplicate clock ID: %d\n",
379d54bb86bSChen-Yu Tsai 				node, pll->id);
380d54bb86bSChen-Yu Tsai 			continue;
381d54bb86bSChen-Yu Tsai 		}
382d54bb86bSChen-Yu Tsai 
3836f691a58SChen-Yu Tsai 		hw = mtk_clk_register_pll(pll, base);
3849741b1a6SJames Liao 
3856f691a58SChen-Yu Tsai 		if (IS_ERR(hw)) {
3866f691a58SChen-Yu Tsai 			pr_err("Failed to register clk %s: %pe\n", pll->name,
3876f691a58SChen-Yu Tsai 			       hw);
3886ae34f2bSChen-Yu Tsai 			goto err;
3899741b1a6SJames Liao 		}
3909741b1a6SJames Liao 
3916f691a58SChen-Yu Tsai 		clk_data->hws[pll->id] = hw;
3929741b1a6SJames Liao 	}
3936ae34f2bSChen-Yu Tsai 
3946ae34f2bSChen-Yu Tsai 	return 0;
3956ae34f2bSChen-Yu Tsai 
3966ae34f2bSChen-Yu Tsai err:
3976ae34f2bSChen-Yu Tsai 	while (--i >= 0) {
3986ae34f2bSChen-Yu Tsai 		const struct mtk_pll_data *pll = &plls[i];
3996ae34f2bSChen-Yu Tsai 
4006f691a58SChen-Yu Tsai 		mtk_clk_unregister_pll(clk_data->hws[pll->id]);
401609cc5e1SChen-Yu Tsai 		clk_data->hws[pll->id] = ERR_PTR(-ENOENT);
4026ae34f2bSChen-Yu Tsai 	}
4036ae34f2bSChen-Yu Tsai 
4046ae34f2bSChen-Yu Tsai 	iounmap(base);
4056ae34f2bSChen-Yu Tsai 
4066f691a58SChen-Yu Tsai 	return PTR_ERR(hw);
4079741b1a6SJames Liao }
40832b028fbSMiles Chen EXPORT_SYMBOL_GPL(mtk_clk_register_plls);
40932b028fbSMiles Chen 
mtk_clk_pll_get_base(struct clk_hw * hw,const struct mtk_pll_data * data)410029c936aSJohnson Wang __iomem void *mtk_clk_pll_get_base(struct clk_hw *hw,
4116dd19906SChen-Yu Tsai 				   const struct mtk_pll_data *data)
4126dd19906SChen-Yu Tsai {
4136dd19906SChen-Yu Tsai 	struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
4146dd19906SChen-Yu Tsai 
4156dd19906SChen-Yu Tsai 	return pll->base_addr - data->reg;
4166dd19906SChen-Yu Tsai }
4176dd19906SChen-Yu Tsai 
mtk_clk_unregister_plls(const struct mtk_pll_data * plls,int num_plls,struct clk_hw_onecell_data * clk_data)4186dd19906SChen-Yu Tsai void mtk_clk_unregister_plls(const struct mtk_pll_data *plls, int num_plls,
419609cc5e1SChen-Yu Tsai 			     struct clk_hw_onecell_data *clk_data)
4206dd19906SChen-Yu Tsai {
4216dd19906SChen-Yu Tsai 	__iomem void *base = NULL;
4226dd19906SChen-Yu Tsai 	int i;
4236dd19906SChen-Yu Tsai 
4246dd19906SChen-Yu Tsai 	if (!clk_data)
4256dd19906SChen-Yu Tsai 		return;
4266dd19906SChen-Yu Tsai 
4276dd19906SChen-Yu Tsai 	for (i = num_plls; i > 0; i--) {
4286dd19906SChen-Yu Tsai 		const struct mtk_pll_data *pll = &plls[i - 1];
4296dd19906SChen-Yu Tsai 
430609cc5e1SChen-Yu Tsai 		if (IS_ERR_OR_NULL(clk_data->hws[pll->id]))
4316dd19906SChen-Yu Tsai 			continue;
4326dd19906SChen-Yu Tsai 
4336dd19906SChen-Yu Tsai 		/*
4346dd19906SChen-Yu Tsai 		 * This is quite ugly but unfortunately the clks don't have
4356dd19906SChen-Yu Tsai 		 * any device tied to them, so there's no place to store the
4366dd19906SChen-Yu Tsai 		 * pointer to the I/O region base address. We have to fetch
4376dd19906SChen-Yu Tsai 		 * it from one of the registered clks.
4386dd19906SChen-Yu Tsai 		 */
439609cc5e1SChen-Yu Tsai 		base = mtk_clk_pll_get_base(clk_data->hws[pll->id], pll);
4406dd19906SChen-Yu Tsai 
4416f691a58SChen-Yu Tsai 		mtk_clk_unregister_pll(clk_data->hws[pll->id]);
442609cc5e1SChen-Yu Tsai 		clk_data->hws[pll->id] = ERR_PTR(-ENOENT);
4436dd19906SChen-Yu Tsai 	}
4446dd19906SChen-Yu Tsai 
4456dd19906SChen-Yu Tsai 	iounmap(base);
4466dd19906SChen-Yu Tsai }
4476dd19906SChen-Yu Tsai EXPORT_SYMBOL_GPL(mtk_clk_unregister_plls);
4486dd19906SChen-Yu Tsai 
44932b028fbSMiles Chen MODULE_LICENSE("GPL");
450