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