xref: /openbmc/linux/drivers/clk/clk-mux.c (revision 5834fd75)
1e1bd55e5SStephen Boyd // SPDX-License-Identifier: GPL-2.0
29d9f78edSMike Turquette /*
39d9f78edSMike Turquette  * Copyright (C) 2011 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
49d9f78edSMike Turquette  * Copyright (C) 2011 Richard Zhao, Linaro <richard.zhao@linaro.org>
59d9f78edSMike Turquette  * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd <mturquette@linaro.org>
69d9f78edSMike Turquette  *
79d9f78edSMike Turquette  * Simple multiplexer clock implementation
89d9f78edSMike Turquette  */
99d9f78edSMike Turquette 
109d9f78edSMike Turquette #include <linux/clk-provider.h>
119d9f78edSMike Turquette #include <linux/module.h>
129d9f78edSMike Turquette #include <linux/slab.h>
139d9f78edSMike Turquette #include <linux/io.h>
149d9f78edSMike Turquette #include <linux/err.h>
159d9f78edSMike Turquette 
169d9f78edSMike Turquette /*
179d9f78edSMike Turquette  * DOC: basic adjustable multiplexer clock that cannot gate
189d9f78edSMike Turquette  *
199d9f78edSMike Turquette  * Traits of this clock:
209d9f78edSMike Turquette  * prepare - clk_prepare only ensures that parents are prepared
219d9f78edSMike Turquette  * enable - clk_enable only ensures that parents are enabled
229d9f78edSMike Turquette  * rate - rate is only affected by parent switching.  No clk_set_rate support
239d9f78edSMike Turquette  * parent - parent is adjustable through clk_set_parent
249d9f78edSMike Turquette  */
259d9f78edSMike Turquette 
263a727519SJonas Gorski static inline u32 clk_mux_readl(struct clk_mux *mux)
273a727519SJonas Gorski {
283a727519SJonas Gorski 	if (mux->flags & CLK_MUX_BIG_ENDIAN)
293a727519SJonas Gorski 		return ioread32be(mux->reg);
303a727519SJonas Gorski 
315834fd75SJonas Gorski 	return readl(mux->reg);
323a727519SJonas Gorski }
333a727519SJonas Gorski 
343a727519SJonas Gorski static inline void clk_mux_writel(struct clk_mux *mux, u32 val)
353a727519SJonas Gorski {
363a727519SJonas Gorski 	if (mux->flags & CLK_MUX_BIG_ENDIAN)
373a727519SJonas Gorski 		iowrite32be(val, mux->reg);
383a727519SJonas Gorski 	else
395834fd75SJonas Gorski 		writel(val, mux->reg);
403a727519SJonas Gorski }
413a727519SJonas Gorski 
4277deb66dSJerome Brunet int clk_mux_val_to_index(struct clk_hw *hw, u32 *table, unsigned int flags,
4377deb66dSJerome Brunet 			 unsigned int val)
449d9f78edSMike Turquette {
45497295afSStephen Boyd 	int num_parents = clk_hw_get_num_parents(hw);
469d9f78edSMike Turquette 
4777deb66dSJerome Brunet 	if (table) {
48ce4f3313SPeter De Schrijver 		int i;
49ce4f3313SPeter De Schrijver 
50ce4f3313SPeter De Schrijver 		for (i = 0; i < num_parents; i++)
5177deb66dSJerome Brunet 			if (table[i] == val)
52ce4f3313SPeter De Schrijver 				return i;
53ce4f3313SPeter De Schrijver 		return -EINVAL;
54ce4f3313SPeter De Schrijver 	}
559d9f78edSMike Turquette 
5677deb66dSJerome Brunet 	if (val && (flags & CLK_MUX_INDEX_BIT))
579d9f78edSMike Turquette 		val = ffs(val) - 1;
589d9f78edSMike Turquette 
5977deb66dSJerome Brunet 	if (val && (flags & CLK_MUX_INDEX_ONE))
609d9f78edSMike Turquette 		val--;
619d9f78edSMike Turquette 
62ce4f3313SPeter De Schrijver 	if (val >= num_parents)
639d9f78edSMike Turquette 		return -EINVAL;
649d9f78edSMike Turquette 
659d9f78edSMike Turquette 	return val;
669d9f78edSMike Turquette }
6777deb66dSJerome Brunet EXPORT_SYMBOL_GPL(clk_mux_val_to_index);
6877deb66dSJerome Brunet 
6977deb66dSJerome Brunet unsigned int clk_mux_index_to_val(u32 *table, unsigned int flags, u8 index)
7077deb66dSJerome Brunet {
7177deb66dSJerome Brunet 	unsigned int val = index;
7277deb66dSJerome Brunet 
7377deb66dSJerome Brunet 	if (table) {
7477deb66dSJerome Brunet 		val = table[index];
7577deb66dSJerome Brunet 	} else {
7677deb66dSJerome Brunet 		if (flags & CLK_MUX_INDEX_BIT)
7777deb66dSJerome Brunet 			val = 1 << index;
7877deb66dSJerome Brunet 
7977deb66dSJerome Brunet 		if (flags & CLK_MUX_INDEX_ONE)
8077deb66dSJerome Brunet 			val++;
8177deb66dSJerome Brunet 	}
8277deb66dSJerome Brunet 
8377deb66dSJerome Brunet 	return val;
8477deb66dSJerome Brunet }
8577deb66dSJerome Brunet EXPORT_SYMBOL_GPL(clk_mux_index_to_val);
8677deb66dSJerome Brunet 
8777deb66dSJerome Brunet static u8 clk_mux_get_parent(struct clk_hw *hw)
8877deb66dSJerome Brunet {
8977deb66dSJerome Brunet 	struct clk_mux *mux = to_clk_mux(hw);
9077deb66dSJerome Brunet 	u32 val;
9177deb66dSJerome Brunet 
923a727519SJonas Gorski 	val = clk_mux_readl(mux) >> mux->shift;
9377deb66dSJerome Brunet 	val &= mux->mask;
9477deb66dSJerome Brunet 
9577deb66dSJerome Brunet 	return clk_mux_val_to_index(hw, mux->table, mux->flags, val);
9677deb66dSJerome Brunet }
979d9f78edSMike Turquette 
989d9f78edSMike Turquette static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
999d9f78edSMike Turquette {
1009d9f78edSMike Turquette 	struct clk_mux *mux = to_clk_mux(hw);
10177deb66dSJerome Brunet 	u32 val = clk_mux_index_to_val(mux->table, mux->flags, index);
1029d9f78edSMike Turquette 	unsigned long flags = 0;
10377deb66dSJerome Brunet 	u32 reg;
1049d9f78edSMike Turquette 
1059d9f78edSMike Turquette 	if (mux->lock)
1069d9f78edSMike Turquette 		spin_lock_irqsave(mux->lock, flags);
107661e2180SStephen Boyd 	else
108661e2180SStephen Boyd 		__acquire(mux->lock);
1099d9f78edSMike Turquette 
110ba492e90SHaojian Zhuang 	if (mux->flags & CLK_MUX_HIWORD_MASK) {
11177deb66dSJerome Brunet 		reg = mux->mask << (mux->shift + 16);
112ba492e90SHaojian Zhuang 	} else {
1133a727519SJonas Gorski 		reg = clk_mux_readl(mux);
11477deb66dSJerome Brunet 		reg &= ~(mux->mask << mux->shift);
115ba492e90SHaojian Zhuang 	}
11677deb66dSJerome Brunet 	val = val << mux->shift;
11777deb66dSJerome Brunet 	reg |= val;
1183a727519SJonas Gorski 	clk_mux_writel(mux, reg);
1199d9f78edSMike Turquette 
1209d9f78edSMike Turquette 	if (mux->lock)
1219d9f78edSMike Turquette 		spin_unlock_irqrestore(mux->lock, flags);
122661e2180SStephen Boyd 	else
123661e2180SStephen Boyd 		__release(mux->lock);
1249d9f78edSMike Turquette 
1259d9f78edSMike Turquette 	return 0;
1269d9f78edSMike Turquette }
1279d9f78edSMike Turquette 
1284ad69b80SJerome Brunet static int clk_mux_determine_rate(struct clk_hw *hw,
1294ad69b80SJerome Brunet 				  struct clk_rate_request *req)
1304ad69b80SJerome Brunet {
1314ad69b80SJerome Brunet 	struct clk_mux *mux = to_clk_mux(hw);
1324ad69b80SJerome Brunet 
1334ad69b80SJerome Brunet 	return clk_mux_determine_rate_flags(hw, req, mux->flags);
1344ad69b80SJerome Brunet }
1354ad69b80SJerome Brunet 
136822c250eSShawn Guo const struct clk_ops clk_mux_ops = {
1379d9f78edSMike Turquette 	.get_parent = clk_mux_get_parent,
1389d9f78edSMike Turquette 	.set_parent = clk_mux_set_parent,
1394ad69b80SJerome Brunet 	.determine_rate = clk_mux_determine_rate,
1409d9f78edSMike Turquette };
1419d9f78edSMike Turquette EXPORT_SYMBOL_GPL(clk_mux_ops);
1429d9f78edSMike Turquette 
143c57acd14STomasz Figa const struct clk_ops clk_mux_ro_ops = {
144c57acd14STomasz Figa 	.get_parent = clk_mux_get_parent,
145c57acd14STomasz Figa };
146c57acd14STomasz Figa EXPORT_SYMBOL_GPL(clk_mux_ro_ops);
147c57acd14STomasz Figa 
148264b3171SStephen Boyd struct clk_hw *clk_hw_register_mux_table(struct device *dev, const char *name,
1492893c379SSascha Hauer 		const char * const *parent_names, u8 num_parents,
1502893c379SSascha Hauer 		unsigned long flags,
151ce4f3313SPeter De Schrijver 		void __iomem *reg, u8 shift, u32 mask,
152ce4f3313SPeter De Schrijver 		u8 clk_mux_flags, u32 *table, spinlock_t *lock)
1539d9f78edSMike Turquette {
1549d9f78edSMike Turquette 	struct clk_mux *mux;
155264b3171SStephen Boyd 	struct clk_hw *hw;
1560197b3eaSSaravana Kannan 	struct clk_init_data init;
157ba492e90SHaojian Zhuang 	u8 width = 0;
158264b3171SStephen Boyd 	int ret;
159ba492e90SHaojian Zhuang 
160ba492e90SHaojian Zhuang 	if (clk_mux_flags & CLK_MUX_HIWORD_MASK) {
161ba492e90SHaojian Zhuang 		width = fls(mask) - ffs(mask) + 1;
162ba492e90SHaojian Zhuang 		if (width + shift > 16) {
163ba492e90SHaojian Zhuang 			pr_err("mux value exceeds LOWORD field\n");
164ba492e90SHaojian Zhuang 			return ERR_PTR(-EINVAL);
165ba492e90SHaojian Zhuang 		}
166ba492e90SHaojian Zhuang 	}
1679d9f78edSMike Turquette 
16827d54591SMike Turquette 	/* allocate the mux */
1691e28733eSMarkus Elfring 	mux = kzalloc(sizeof(*mux), GFP_KERNEL);
1700b910402SMarkus Elfring 	if (!mux)
1719d9f78edSMike Turquette 		return ERR_PTR(-ENOMEM);
1729d9f78edSMike Turquette 
1730197b3eaSSaravana Kannan 	init.name = name;
174c57acd14STomasz Figa 	if (clk_mux_flags & CLK_MUX_READ_ONLY)
175c57acd14STomasz Figa 		init.ops = &clk_mux_ro_ops;
176c57acd14STomasz Figa 	else
1770197b3eaSSaravana Kannan 		init.ops = &clk_mux_ops;
178f7d8caadSRajendra Nayak 	init.flags = flags | CLK_IS_BASIC;
1790197b3eaSSaravana Kannan 	init.parent_names = parent_names;
1800197b3eaSSaravana Kannan 	init.num_parents = num_parents;
1810197b3eaSSaravana Kannan 
1829d9f78edSMike Turquette 	/* struct clk_mux assignments */
1839d9f78edSMike Turquette 	mux->reg = reg;
1849d9f78edSMike Turquette 	mux->shift = shift;
185ce4f3313SPeter De Schrijver 	mux->mask = mask;
1869d9f78edSMike Turquette 	mux->flags = clk_mux_flags;
1879d9f78edSMike Turquette 	mux->lock = lock;
188ce4f3313SPeter De Schrijver 	mux->table = table;
18931df9db9SMike Turquette 	mux->hw.init = &init;
1909d9f78edSMike Turquette 
191264b3171SStephen Boyd 	hw = &mux->hw;
192264b3171SStephen Boyd 	ret = clk_hw_register(dev, hw);
193264b3171SStephen Boyd 	if (ret) {
19427d54591SMike Turquette 		kfree(mux);
195264b3171SStephen Boyd 		hw = ERR_PTR(ret);
196264b3171SStephen Boyd 	}
19727d54591SMike Turquette 
198264b3171SStephen Boyd 	return hw;
199264b3171SStephen Boyd }
200264b3171SStephen Boyd EXPORT_SYMBOL_GPL(clk_hw_register_mux_table);
201264b3171SStephen Boyd 
202264b3171SStephen Boyd struct clk *clk_register_mux_table(struct device *dev, const char *name,
203264b3171SStephen Boyd 		const char * const *parent_names, u8 num_parents,
204264b3171SStephen Boyd 		unsigned long flags,
205264b3171SStephen Boyd 		void __iomem *reg, u8 shift, u32 mask,
206264b3171SStephen Boyd 		u8 clk_mux_flags, u32 *table, spinlock_t *lock)
207264b3171SStephen Boyd {
208264b3171SStephen Boyd 	struct clk_hw *hw;
209264b3171SStephen Boyd 
210264b3171SStephen Boyd 	hw = clk_hw_register_mux_table(dev, name, parent_names, num_parents,
211264b3171SStephen Boyd 				       flags, reg, shift, mask, clk_mux_flags,
212264b3171SStephen Boyd 				       table, lock);
213264b3171SStephen Boyd 	if (IS_ERR(hw))
214264b3171SStephen Boyd 		return ERR_CAST(hw);
215264b3171SStephen Boyd 	return hw->clk;
2169d9f78edSMike Turquette }
2175cfe10bbSMike Turquette EXPORT_SYMBOL_GPL(clk_register_mux_table);
218ce4f3313SPeter De Schrijver 
219ce4f3313SPeter De Schrijver struct clk *clk_register_mux(struct device *dev, const char *name,
2202893c379SSascha Hauer 		const char * const *parent_names, u8 num_parents,
2212893c379SSascha Hauer 		unsigned long flags,
222ce4f3313SPeter De Schrijver 		void __iomem *reg, u8 shift, u8 width,
223ce4f3313SPeter De Schrijver 		u8 clk_mux_flags, spinlock_t *lock)
224ce4f3313SPeter De Schrijver {
225ce4f3313SPeter De Schrijver 	u32 mask = BIT(width) - 1;
226ce4f3313SPeter De Schrijver 
227ce4f3313SPeter De Schrijver 	return clk_register_mux_table(dev, name, parent_names, num_parents,
228ce4f3313SPeter De Schrijver 				      flags, reg, shift, mask, clk_mux_flags,
229ce4f3313SPeter De Schrijver 				      NULL, lock);
230ce4f3313SPeter De Schrijver }
2315cfe10bbSMike Turquette EXPORT_SYMBOL_GPL(clk_register_mux);
2324e3c021fSKrzysztof Kozlowski 
233264b3171SStephen Boyd struct clk_hw *clk_hw_register_mux(struct device *dev, const char *name,
234264b3171SStephen Boyd 		const char * const *parent_names, u8 num_parents,
235264b3171SStephen Boyd 		unsigned long flags,
236264b3171SStephen Boyd 		void __iomem *reg, u8 shift, u8 width,
237264b3171SStephen Boyd 		u8 clk_mux_flags, spinlock_t *lock)
238264b3171SStephen Boyd {
239264b3171SStephen Boyd 	u32 mask = BIT(width) - 1;
240264b3171SStephen Boyd 
241264b3171SStephen Boyd 	return clk_hw_register_mux_table(dev, name, parent_names, num_parents,
242264b3171SStephen Boyd 				      flags, reg, shift, mask, clk_mux_flags,
243264b3171SStephen Boyd 				      NULL, lock);
244264b3171SStephen Boyd }
245264b3171SStephen Boyd EXPORT_SYMBOL_GPL(clk_hw_register_mux);
246264b3171SStephen Boyd 
2474e3c021fSKrzysztof Kozlowski void clk_unregister_mux(struct clk *clk)
2484e3c021fSKrzysztof Kozlowski {
2494e3c021fSKrzysztof Kozlowski 	struct clk_mux *mux;
2504e3c021fSKrzysztof Kozlowski 	struct clk_hw *hw;
2514e3c021fSKrzysztof Kozlowski 
2524e3c021fSKrzysztof Kozlowski 	hw = __clk_get_hw(clk);
2534e3c021fSKrzysztof Kozlowski 	if (!hw)
2544e3c021fSKrzysztof Kozlowski 		return;
2554e3c021fSKrzysztof Kozlowski 
2564e3c021fSKrzysztof Kozlowski 	mux = to_clk_mux(hw);
2574e3c021fSKrzysztof Kozlowski 
2584e3c021fSKrzysztof Kozlowski 	clk_unregister(clk);
2594e3c021fSKrzysztof Kozlowski 	kfree(mux);
2604e3c021fSKrzysztof Kozlowski }
2614e3c021fSKrzysztof Kozlowski EXPORT_SYMBOL_GPL(clk_unregister_mux);
262264b3171SStephen Boyd 
263264b3171SStephen Boyd void clk_hw_unregister_mux(struct clk_hw *hw)
264264b3171SStephen Boyd {
265264b3171SStephen Boyd 	struct clk_mux *mux;
266264b3171SStephen Boyd 
267264b3171SStephen Boyd 	mux = to_clk_mux(hw);
268264b3171SStephen Boyd 
269264b3171SStephen Boyd 	clk_hw_unregister(hw);
270264b3171SStephen Boyd 	kfree(mux);
271264b3171SStephen Boyd }
272264b3171SStephen Boyd EXPORT_SYMBOL_GPL(clk_hw_unregister_mux);
273