xref: /openbmc/linux/drivers/clk/clk-mux.c (revision 14364fca)
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>
11b3084079SDmitry Baryshkov #include <linux/device.h>
129d9f78edSMike Turquette #include <linux/module.h>
139d9f78edSMike Turquette #include <linux/slab.h>
149d9f78edSMike Turquette #include <linux/io.h>
159d9f78edSMike Turquette #include <linux/err.h>
169d9f78edSMike Turquette 
179d9f78edSMike Turquette /*
189d9f78edSMike Turquette  * DOC: basic adjustable multiplexer clock that cannot gate
199d9f78edSMike Turquette  *
209d9f78edSMike Turquette  * Traits of this clock:
219d9f78edSMike Turquette  * prepare - clk_prepare only ensures that parents are prepared
229d9f78edSMike Turquette  * enable - clk_enable only ensures that parents are enabled
239d9f78edSMike Turquette  * rate - rate is only affected by parent switching.  No clk_set_rate support
249d9f78edSMike Turquette  * parent - parent is adjustable through clk_set_parent
259d9f78edSMike Turquette  */
269d9f78edSMike Turquette 
clk_mux_readl(struct clk_mux * mux)273a727519SJonas Gorski static inline u32 clk_mux_readl(struct clk_mux *mux)
283a727519SJonas Gorski {
293a727519SJonas Gorski 	if (mux->flags & CLK_MUX_BIG_ENDIAN)
303a727519SJonas Gorski 		return ioread32be(mux->reg);
313a727519SJonas Gorski 
325834fd75SJonas Gorski 	return readl(mux->reg);
333a727519SJonas Gorski }
343a727519SJonas Gorski 
clk_mux_writel(struct clk_mux * mux,u32 val)353a727519SJonas Gorski static inline void clk_mux_writel(struct clk_mux *mux, u32 val)
363a727519SJonas Gorski {
373a727519SJonas Gorski 	if (mux->flags & CLK_MUX_BIG_ENDIAN)
383a727519SJonas Gorski 		iowrite32be(val, mux->reg);
393a727519SJonas Gorski 	else
405834fd75SJonas Gorski 		writel(val, mux->reg);
413a727519SJonas Gorski }
423a727519SJonas Gorski 
clk_mux_val_to_index(struct clk_hw * hw,const u32 * table,unsigned int flags,unsigned int val)43891b7023SJonathan Neuschäfer int clk_mux_val_to_index(struct clk_hw *hw, const u32 *table, unsigned int flags,
4477deb66dSJerome Brunet 			 unsigned int val)
459d9f78edSMike Turquette {
46497295afSStephen Boyd 	int num_parents = clk_hw_get_num_parents(hw);
479d9f78edSMike Turquette 
4877deb66dSJerome Brunet 	if (table) {
49ce4f3313SPeter De Schrijver 		int i;
50ce4f3313SPeter De Schrijver 
51ce4f3313SPeter De Schrijver 		for (i = 0; i < num_parents; i++)
5277deb66dSJerome Brunet 			if (table[i] == val)
53ce4f3313SPeter De Schrijver 				return i;
54ce4f3313SPeter De Schrijver 		return -EINVAL;
55ce4f3313SPeter De Schrijver 	}
569d9f78edSMike Turquette 
5777deb66dSJerome Brunet 	if (val && (flags & CLK_MUX_INDEX_BIT))
589d9f78edSMike Turquette 		val = ffs(val) - 1;
599d9f78edSMike Turquette 
6077deb66dSJerome Brunet 	if (val && (flags & CLK_MUX_INDEX_ONE))
619d9f78edSMike Turquette 		val--;
629d9f78edSMike Turquette 
63ce4f3313SPeter De Schrijver 	if (val >= num_parents)
649d9f78edSMike Turquette 		return -EINVAL;
659d9f78edSMike Turquette 
669d9f78edSMike Turquette 	return val;
679d9f78edSMike Turquette }
6877deb66dSJerome Brunet EXPORT_SYMBOL_GPL(clk_mux_val_to_index);
6977deb66dSJerome Brunet 
clk_mux_index_to_val(const u32 * table,unsigned int flags,u8 index)70891b7023SJonathan Neuschäfer unsigned int clk_mux_index_to_val(const u32 *table, unsigned int flags, u8 index)
7177deb66dSJerome Brunet {
7277deb66dSJerome Brunet 	unsigned int val = index;
7377deb66dSJerome Brunet 
7477deb66dSJerome Brunet 	if (table) {
7577deb66dSJerome Brunet 		val = table[index];
7677deb66dSJerome Brunet 	} else {
7777deb66dSJerome Brunet 		if (flags & CLK_MUX_INDEX_BIT)
7877deb66dSJerome Brunet 			val = 1 << index;
7977deb66dSJerome Brunet 
8077deb66dSJerome Brunet 		if (flags & CLK_MUX_INDEX_ONE)
8177deb66dSJerome Brunet 			val++;
8277deb66dSJerome Brunet 	}
8377deb66dSJerome Brunet 
8477deb66dSJerome Brunet 	return val;
8577deb66dSJerome Brunet }
8677deb66dSJerome Brunet EXPORT_SYMBOL_GPL(clk_mux_index_to_val);
8777deb66dSJerome Brunet 
clk_mux_get_parent(struct clk_hw * hw)8877deb66dSJerome Brunet static u8 clk_mux_get_parent(struct clk_hw *hw)
8977deb66dSJerome Brunet {
9077deb66dSJerome Brunet 	struct clk_mux *mux = to_clk_mux(hw);
9177deb66dSJerome Brunet 	u32 val;
9277deb66dSJerome Brunet 
933a727519SJonas Gorski 	val = clk_mux_readl(mux) >> mux->shift;
9477deb66dSJerome Brunet 	val &= mux->mask;
9577deb66dSJerome Brunet 
9677deb66dSJerome Brunet 	return clk_mux_val_to_index(hw, mux->table, mux->flags, val);
9777deb66dSJerome Brunet }
989d9f78edSMike Turquette 
clk_mux_set_parent(struct clk_hw * hw,u8 index)999d9f78edSMike Turquette static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
1009d9f78edSMike Turquette {
1019d9f78edSMike Turquette 	struct clk_mux *mux = to_clk_mux(hw);
10277deb66dSJerome Brunet 	u32 val = clk_mux_index_to_val(mux->table, mux->flags, index);
1039d9f78edSMike Turquette 	unsigned long flags = 0;
10477deb66dSJerome Brunet 	u32 reg;
1059d9f78edSMike Turquette 
1069d9f78edSMike Turquette 	if (mux->lock)
1079d9f78edSMike Turquette 		spin_lock_irqsave(mux->lock, flags);
108661e2180SStephen Boyd 	else
109661e2180SStephen Boyd 		__acquire(mux->lock);
1109d9f78edSMike Turquette 
111ba492e90SHaojian Zhuang 	if (mux->flags & CLK_MUX_HIWORD_MASK) {
11277deb66dSJerome Brunet 		reg = mux->mask << (mux->shift + 16);
113ba492e90SHaojian Zhuang 	} else {
1143a727519SJonas Gorski 		reg = clk_mux_readl(mux);
11577deb66dSJerome Brunet 		reg &= ~(mux->mask << mux->shift);
116ba492e90SHaojian Zhuang 	}
11777deb66dSJerome Brunet 	val = val << mux->shift;
11877deb66dSJerome Brunet 	reg |= val;
1193a727519SJonas Gorski 	clk_mux_writel(mux, reg);
1209d9f78edSMike Turquette 
1219d9f78edSMike Turquette 	if (mux->lock)
1229d9f78edSMike Turquette 		spin_unlock_irqrestore(mux->lock, flags);
123661e2180SStephen Boyd 	else
124661e2180SStephen Boyd 		__release(mux->lock);
1259d9f78edSMike Turquette 
1269d9f78edSMike Turquette 	return 0;
1279d9f78edSMike Turquette }
1289d9f78edSMike Turquette 
clk_mux_determine_rate(struct clk_hw * hw,struct clk_rate_request * req)1294ad69b80SJerome Brunet static int clk_mux_determine_rate(struct clk_hw *hw,
1304ad69b80SJerome Brunet 				  struct clk_rate_request *req)
1314ad69b80SJerome Brunet {
1324ad69b80SJerome Brunet 	struct clk_mux *mux = to_clk_mux(hw);
1334ad69b80SJerome Brunet 
1344ad69b80SJerome Brunet 	return clk_mux_determine_rate_flags(hw, req, mux->flags);
1354ad69b80SJerome Brunet }
1364ad69b80SJerome Brunet 
137822c250eSShawn Guo const struct clk_ops clk_mux_ops = {
1389d9f78edSMike Turquette 	.get_parent = clk_mux_get_parent,
1399d9f78edSMike Turquette 	.set_parent = clk_mux_set_parent,
1404ad69b80SJerome Brunet 	.determine_rate = clk_mux_determine_rate,
1419d9f78edSMike Turquette };
1429d9f78edSMike Turquette EXPORT_SYMBOL_GPL(clk_mux_ops);
1439d9f78edSMike Turquette 
144c57acd14STomasz Figa const struct clk_ops clk_mux_ro_ops = {
145c57acd14STomasz Figa 	.get_parent = clk_mux_get_parent,
146c57acd14STomasz Figa };
147c57acd14STomasz Figa EXPORT_SYMBOL_GPL(clk_mux_ro_ops);
148c57acd14STomasz Figa 
__clk_hw_register_mux(struct device * dev,struct device_node * np,const char * name,u8 num_parents,const char * const * parent_names,const struct clk_hw ** parent_hws,const struct clk_parent_data * parent_data,unsigned long flags,void __iomem * reg,u8 shift,u32 mask,u8 clk_mux_flags,const u32 * table,spinlock_t * lock)1499611b3aaSStephen Boyd struct clk_hw *__clk_hw_register_mux(struct device *dev, struct device_node *np,
1509611b3aaSStephen Boyd 		const char *name, u8 num_parents,
1519611b3aaSStephen Boyd 		const char * const *parent_names,
1529611b3aaSStephen Boyd 		const struct clk_hw **parent_hws,
1539611b3aaSStephen Boyd 		const struct clk_parent_data *parent_data,
1549611b3aaSStephen Boyd 		unsigned long flags, void __iomem *reg, u8 shift, u32 mask,
155891b7023SJonathan Neuschäfer 		u8 clk_mux_flags, const u32 *table, spinlock_t *lock)
1569d9f78edSMike Turquette {
1579d9f78edSMike Turquette 	struct clk_mux *mux;
158264b3171SStephen Boyd 	struct clk_hw *hw;
159cc819cf8SManivannan Sadhasivam 	struct clk_init_data init = {};
1609611b3aaSStephen Boyd 	int ret = -EINVAL;
161ba492e90SHaojian Zhuang 
162ba492e90SHaojian Zhuang 	if (clk_mux_flags & CLK_MUX_HIWORD_MASK) {
163*14364fcaSColin Ian King 		u8 width = fls(mask) - ffs(mask) + 1;
164*14364fcaSColin Ian King 
165ba492e90SHaojian Zhuang 		if (width + shift > 16) {
166ba492e90SHaojian Zhuang 			pr_err("mux value exceeds LOWORD field\n");
167ba492e90SHaojian Zhuang 			return ERR_PTR(-EINVAL);
168ba492e90SHaojian Zhuang 		}
169ba492e90SHaojian Zhuang 	}
1709d9f78edSMike Turquette 
17127d54591SMike Turquette 	/* allocate the mux */
1721e28733eSMarkus Elfring 	mux = kzalloc(sizeof(*mux), GFP_KERNEL);
1730b910402SMarkus Elfring 	if (!mux)
1749d9f78edSMike Turquette 		return ERR_PTR(-ENOMEM);
1759d9f78edSMike Turquette 
1760197b3eaSSaravana Kannan 	init.name = name;
177c57acd14STomasz Figa 	if (clk_mux_flags & CLK_MUX_READ_ONLY)
178c57acd14STomasz Figa 		init.ops = &clk_mux_ro_ops;
179c57acd14STomasz Figa 	else
1800197b3eaSSaravana Kannan 		init.ops = &clk_mux_ops;
18190b6c5c7SStephen Boyd 	init.flags = flags;
1820197b3eaSSaravana Kannan 	init.parent_names = parent_names;
1839611b3aaSStephen Boyd 	init.parent_data = parent_data;
1849611b3aaSStephen Boyd 	init.parent_hws = parent_hws;
1850197b3eaSSaravana Kannan 	init.num_parents = num_parents;
1860197b3eaSSaravana Kannan 
1879d9f78edSMike Turquette 	/* struct clk_mux assignments */
1889d9f78edSMike Turquette 	mux->reg = reg;
1899d9f78edSMike Turquette 	mux->shift = shift;
190ce4f3313SPeter De Schrijver 	mux->mask = mask;
1919d9f78edSMike Turquette 	mux->flags = clk_mux_flags;
1929d9f78edSMike Turquette 	mux->lock = lock;
193ce4f3313SPeter De Schrijver 	mux->table = table;
19431df9db9SMike Turquette 	mux->hw.init = &init;
1959d9f78edSMike Turquette 
196264b3171SStephen Boyd 	hw = &mux->hw;
1979611b3aaSStephen Boyd 	if (dev || !np)
198264b3171SStephen Boyd 		ret = clk_hw_register(dev, hw);
1999611b3aaSStephen Boyd 	else if (np)
2009611b3aaSStephen Boyd 		ret = of_clk_hw_register(np, hw);
201264b3171SStephen Boyd 	if (ret) {
20227d54591SMike Turquette 		kfree(mux);
203264b3171SStephen Boyd 		hw = ERR_PTR(ret);
204264b3171SStephen Boyd 	}
20527d54591SMike Turquette 
206264b3171SStephen Boyd 	return hw;
207264b3171SStephen Boyd }
2089611b3aaSStephen Boyd EXPORT_SYMBOL_GPL(__clk_hw_register_mux);
209264b3171SStephen Boyd 
devm_clk_hw_release_mux(struct device * dev,void * res)210b3084079SDmitry Baryshkov static void devm_clk_hw_release_mux(struct device *dev, void *res)
211b3084079SDmitry Baryshkov {
212b3084079SDmitry Baryshkov 	clk_hw_unregister_mux(*(struct clk_hw **)res);
213b3084079SDmitry Baryshkov }
214b3084079SDmitry Baryshkov 
__devm_clk_hw_register_mux(struct device * dev,struct device_node * np,const char * name,u8 num_parents,const char * const * parent_names,const struct clk_hw ** parent_hws,const struct clk_parent_data * parent_data,unsigned long flags,void __iomem * reg,u8 shift,u32 mask,u8 clk_mux_flags,const u32 * table,spinlock_t * lock)215b3084079SDmitry Baryshkov struct clk_hw *__devm_clk_hw_register_mux(struct device *dev, struct device_node *np,
216b3084079SDmitry Baryshkov 		const char *name, u8 num_parents,
217b3084079SDmitry Baryshkov 		const char * const *parent_names,
218b3084079SDmitry Baryshkov 		const struct clk_hw **parent_hws,
219b3084079SDmitry Baryshkov 		const struct clk_parent_data *parent_data,
220b3084079SDmitry Baryshkov 		unsigned long flags, void __iomem *reg, u8 shift, u32 mask,
221891b7023SJonathan Neuschäfer 		u8 clk_mux_flags, const u32 *table, spinlock_t *lock)
222b3084079SDmitry Baryshkov {
223b3084079SDmitry Baryshkov 	struct clk_hw **ptr, *hw;
224b3084079SDmitry Baryshkov 
225b3084079SDmitry Baryshkov 	ptr = devres_alloc(devm_clk_hw_release_mux, sizeof(*ptr), GFP_KERNEL);
226b3084079SDmitry Baryshkov 	if (!ptr)
227b3084079SDmitry Baryshkov 		return ERR_PTR(-ENOMEM);
228b3084079SDmitry Baryshkov 
229b3084079SDmitry Baryshkov 	hw = __clk_hw_register_mux(dev, np, name, num_parents, parent_names, parent_hws,
230b3084079SDmitry Baryshkov 				       parent_data, flags, reg, shift, mask,
231b3084079SDmitry Baryshkov 				       clk_mux_flags, table, lock);
232b3084079SDmitry Baryshkov 
233b3084079SDmitry Baryshkov 	if (!IS_ERR(hw)) {
234b3084079SDmitry Baryshkov 		*ptr = hw;
235b3084079SDmitry Baryshkov 		devres_add(dev, ptr);
236b3084079SDmitry Baryshkov 	} else {
237b3084079SDmitry Baryshkov 		devres_free(ptr);
238b3084079SDmitry Baryshkov 	}
239b3084079SDmitry Baryshkov 
240b3084079SDmitry Baryshkov 	return hw;
241b3084079SDmitry Baryshkov }
242b3084079SDmitry Baryshkov EXPORT_SYMBOL_GPL(__devm_clk_hw_register_mux);
243b3084079SDmitry Baryshkov 
clk_register_mux_table(struct device * dev,const char * name,const char * const * parent_names,u8 num_parents,unsigned long flags,void __iomem * reg,u8 shift,u32 mask,u8 clk_mux_flags,const u32 * table,spinlock_t * lock)244264b3171SStephen Boyd struct clk *clk_register_mux_table(struct device *dev, const char *name,
245264b3171SStephen Boyd 		const char * const *parent_names, u8 num_parents,
2469611b3aaSStephen Boyd 		unsigned long flags, void __iomem *reg, u8 shift, u32 mask,
247891b7023SJonathan Neuschäfer 		u8 clk_mux_flags, const u32 *table, spinlock_t *lock)
248264b3171SStephen Boyd {
249264b3171SStephen Boyd 	struct clk_hw *hw;
250264b3171SStephen Boyd 
2519611b3aaSStephen Boyd 	hw = clk_hw_register_mux_table(dev, name, parent_names,
2529611b3aaSStephen Boyd 				       num_parents, flags, reg, shift, mask,
2539611b3aaSStephen Boyd 				       clk_mux_flags, table, lock);
254264b3171SStephen Boyd 	if (IS_ERR(hw))
255264b3171SStephen Boyd 		return ERR_CAST(hw);
256264b3171SStephen Boyd 	return hw->clk;
2579d9f78edSMike Turquette }
2585cfe10bbSMike Turquette EXPORT_SYMBOL_GPL(clk_register_mux_table);
259ce4f3313SPeter De Schrijver 
clk_unregister_mux(struct clk * clk)2604e3c021fSKrzysztof Kozlowski void clk_unregister_mux(struct clk *clk)
2614e3c021fSKrzysztof Kozlowski {
2624e3c021fSKrzysztof Kozlowski 	struct clk_mux *mux;
2634e3c021fSKrzysztof Kozlowski 	struct clk_hw *hw;
2644e3c021fSKrzysztof Kozlowski 
2654e3c021fSKrzysztof Kozlowski 	hw = __clk_get_hw(clk);
2664e3c021fSKrzysztof Kozlowski 	if (!hw)
2674e3c021fSKrzysztof Kozlowski 		return;
2684e3c021fSKrzysztof Kozlowski 
2694e3c021fSKrzysztof Kozlowski 	mux = to_clk_mux(hw);
2704e3c021fSKrzysztof Kozlowski 
2714e3c021fSKrzysztof Kozlowski 	clk_unregister(clk);
2724e3c021fSKrzysztof Kozlowski 	kfree(mux);
2734e3c021fSKrzysztof Kozlowski }
2744e3c021fSKrzysztof Kozlowski EXPORT_SYMBOL_GPL(clk_unregister_mux);
275264b3171SStephen Boyd 
clk_hw_unregister_mux(struct clk_hw * hw)276264b3171SStephen Boyd void clk_hw_unregister_mux(struct clk_hw *hw)
277264b3171SStephen Boyd {
278264b3171SStephen Boyd 	struct clk_mux *mux;
279264b3171SStephen Boyd 
280264b3171SStephen Boyd 	mux = to_clk_mux(hw);
281264b3171SStephen Boyd 
282264b3171SStephen Boyd 	clk_hw_unregister(hw);
283264b3171SStephen Boyd 	kfree(mux);
284264b3171SStephen Boyd }
285264b3171SStephen Boyd EXPORT_SYMBOL_GPL(clk_hw_unregister_mux);
286