xref: /openbmc/linux/drivers/clk/clk-mux.c (revision 2893c379)
19d9f78edSMike Turquette /*
29d9f78edSMike Turquette  * Copyright (C) 2011 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
39d9f78edSMike Turquette  * Copyright (C) 2011 Richard Zhao, Linaro <richard.zhao@linaro.org>
49d9f78edSMike Turquette  * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd <mturquette@linaro.org>
59d9f78edSMike Turquette  *
69d9f78edSMike Turquette  * This program is free software; you can redistribute it and/or modify
79d9f78edSMike Turquette  * it under the terms of the GNU General Public License version 2 as
89d9f78edSMike Turquette  * published by the Free Software Foundation.
99d9f78edSMike Turquette  *
109d9f78edSMike Turquette  * Simple multiplexer clock implementation
119d9f78edSMike Turquette  */
129d9f78edSMike Turquette 
139d9f78edSMike Turquette #include <linux/clk.h>
149d9f78edSMike Turquette #include <linux/clk-provider.h>
159d9f78edSMike Turquette #include <linux/module.h>
169d9f78edSMike Turquette #include <linux/slab.h>
179d9f78edSMike Turquette #include <linux/io.h>
189d9f78edSMike Turquette #include <linux/err.h>
199d9f78edSMike Turquette 
209d9f78edSMike Turquette /*
219d9f78edSMike Turquette  * DOC: basic adjustable multiplexer clock that cannot gate
229d9f78edSMike Turquette  *
239d9f78edSMike Turquette  * Traits of this clock:
249d9f78edSMike Turquette  * prepare - clk_prepare only ensures that parents are prepared
259d9f78edSMike Turquette  * enable - clk_enable only ensures that parents are enabled
269d9f78edSMike Turquette  * rate - rate is only affected by parent switching.  No clk_set_rate support
279d9f78edSMike Turquette  * parent - parent is adjustable through clk_set_parent
289d9f78edSMike Turquette  */
299d9f78edSMike Turquette 
309d9f78edSMike Turquette #define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw)
319d9f78edSMike Turquette 
329d9f78edSMike Turquette static u8 clk_mux_get_parent(struct clk_hw *hw)
339d9f78edSMike Turquette {
349d9f78edSMike Turquette 	struct clk_mux *mux = to_clk_mux(hw);
35ce4f3313SPeter De Schrijver 	int num_parents = __clk_get_num_parents(hw->clk);
369d9f78edSMike Turquette 	u32 val;
379d9f78edSMike Turquette 
389d9f78edSMike Turquette 	/*
399d9f78edSMike Turquette 	 * FIXME need a mux-specific flag to determine if val is bitwise or numeric
409d9f78edSMike Turquette 	 * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1
419d9f78edSMike Turquette 	 * to 0x7 (index starts at one)
429d9f78edSMike Turquette 	 * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so
439d9f78edSMike Turquette 	 * val = 0x4 really means "bit 2, index starts at bit 0"
449d9f78edSMike Turquette 	 */
45aa514ce3SGerhard Sittig 	val = clk_readl(mux->reg) >> mux->shift;
46ce4f3313SPeter De Schrijver 	val &= mux->mask;
47ce4f3313SPeter De Schrijver 
48ce4f3313SPeter De Schrijver 	if (mux->table) {
49ce4f3313SPeter De Schrijver 		int i;
50ce4f3313SPeter De Schrijver 
51ce4f3313SPeter De Schrijver 		for (i = 0; i < num_parents; i++)
52ce4f3313SPeter De Schrijver 			if (mux->table[i] == val)
53ce4f3313SPeter De Schrijver 				return i;
54ce4f3313SPeter De Schrijver 		return -EINVAL;
55ce4f3313SPeter De Schrijver 	}
569d9f78edSMike Turquette 
579d9f78edSMike Turquette 	if (val && (mux->flags & CLK_MUX_INDEX_BIT))
589d9f78edSMike Turquette 		val = ffs(val) - 1;
599d9f78edSMike Turquette 
609d9f78edSMike Turquette 	if (val && (mux->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 }
689d9f78edSMike Turquette 
699d9f78edSMike Turquette static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
709d9f78edSMike Turquette {
719d9f78edSMike Turquette 	struct clk_mux *mux = to_clk_mux(hw);
729d9f78edSMike Turquette 	u32 val;
739d9f78edSMike Turquette 	unsigned long flags = 0;
749d9f78edSMike Turquette 
75ce4f3313SPeter De Schrijver 	if (mux->table)
76ce4f3313SPeter De Schrijver 		index = mux->table[index];
77ce4f3313SPeter De Schrijver 
78ce4f3313SPeter De Schrijver 	else {
799d9f78edSMike Turquette 		if (mux->flags & CLK_MUX_INDEX_BIT)
806793b3cdSHans de Goede 			index = 1 << index;
819d9f78edSMike Turquette 
829d9f78edSMike Turquette 		if (mux->flags & CLK_MUX_INDEX_ONE)
839d9f78edSMike Turquette 			index++;
84ce4f3313SPeter De Schrijver 	}
859d9f78edSMike Turquette 
869d9f78edSMike Turquette 	if (mux->lock)
879d9f78edSMike Turquette 		spin_lock_irqsave(mux->lock, flags);
889d9f78edSMike Turquette 
89ba492e90SHaojian Zhuang 	if (mux->flags & CLK_MUX_HIWORD_MASK) {
90ba492e90SHaojian Zhuang 		val = mux->mask << (mux->shift + 16);
91ba492e90SHaojian Zhuang 	} else {
92aa514ce3SGerhard Sittig 		val = clk_readl(mux->reg);
93ce4f3313SPeter De Schrijver 		val &= ~(mux->mask << mux->shift);
94ba492e90SHaojian Zhuang 	}
959d9f78edSMike Turquette 	val |= index << mux->shift;
96aa514ce3SGerhard Sittig 	clk_writel(val, mux->reg);
979d9f78edSMike Turquette 
989d9f78edSMike Turquette 	if (mux->lock)
999d9f78edSMike Turquette 		spin_unlock_irqrestore(mux->lock, flags);
1009d9f78edSMike Turquette 
1019d9f78edSMike Turquette 	return 0;
1029d9f78edSMike Turquette }
1039d9f78edSMike Turquette 
104822c250eSShawn Guo const struct clk_ops clk_mux_ops = {
1059d9f78edSMike Turquette 	.get_parent = clk_mux_get_parent,
1069d9f78edSMike Turquette 	.set_parent = clk_mux_set_parent,
107e366fdd7SJames Hogan 	.determine_rate = __clk_mux_determine_rate,
1089d9f78edSMike Turquette };
1099d9f78edSMike Turquette EXPORT_SYMBOL_GPL(clk_mux_ops);
1109d9f78edSMike Turquette 
111c57acd14STomasz Figa const struct clk_ops clk_mux_ro_ops = {
112c57acd14STomasz Figa 	.get_parent = clk_mux_get_parent,
113c57acd14STomasz Figa };
114c57acd14STomasz Figa EXPORT_SYMBOL_GPL(clk_mux_ro_ops);
115c57acd14STomasz Figa 
116ce4f3313SPeter De Schrijver struct clk *clk_register_mux_table(struct device *dev, const char *name,
1172893c379SSascha Hauer 		const char * const *parent_names, u8 num_parents,
1182893c379SSascha Hauer 		unsigned long flags,
119ce4f3313SPeter De Schrijver 		void __iomem *reg, u8 shift, u32 mask,
120ce4f3313SPeter De Schrijver 		u8 clk_mux_flags, u32 *table, spinlock_t *lock)
1219d9f78edSMike Turquette {
1229d9f78edSMike Turquette 	struct clk_mux *mux;
12327d54591SMike Turquette 	struct clk *clk;
1240197b3eaSSaravana Kannan 	struct clk_init_data init;
125ba492e90SHaojian Zhuang 	u8 width = 0;
126ba492e90SHaojian Zhuang 
127ba492e90SHaojian Zhuang 	if (clk_mux_flags & CLK_MUX_HIWORD_MASK) {
128ba492e90SHaojian Zhuang 		width = fls(mask) - ffs(mask) + 1;
129ba492e90SHaojian Zhuang 		if (width + shift > 16) {
130ba492e90SHaojian Zhuang 			pr_err("mux value exceeds LOWORD field\n");
131ba492e90SHaojian Zhuang 			return ERR_PTR(-EINVAL);
132ba492e90SHaojian Zhuang 		}
133ba492e90SHaojian Zhuang 	}
1349d9f78edSMike Turquette 
13527d54591SMike Turquette 	/* allocate the mux */
13610363b58SShawn Guo 	mux = kzalloc(sizeof(struct clk_mux), GFP_KERNEL);
1379d9f78edSMike Turquette 	if (!mux) {
1389d9f78edSMike Turquette 		pr_err("%s: could not allocate mux clk\n", __func__);
1399d9f78edSMike Turquette 		return ERR_PTR(-ENOMEM);
1409d9f78edSMike Turquette 	}
1419d9f78edSMike Turquette 
1420197b3eaSSaravana Kannan 	init.name = name;
143c57acd14STomasz Figa 	if (clk_mux_flags & CLK_MUX_READ_ONLY)
144c57acd14STomasz Figa 		init.ops = &clk_mux_ro_ops;
145c57acd14STomasz Figa 	else
1460197b3eaSSaravana Kannan 		init.ops = &clk_mux_ops;
147f7d8caadSRajendra Nayak 	init.flags = flags | CLK_IS_BASIC;
1480197b3eaSSaravana Kannan 	init.parent_names = parent_names;
1490197b3eaSSaravana Kannan 	init.num_parents = num_parents;
1500197b3eaSSaravana Kannan 
1519d9f78edSMike Turquette 	/* struct clk_mux assignments */
1529d9f78edSMike Turquette 	mux->reg = reg;
1539d9f78edSMike Turquette 	mux->shift = shift;
154ce4f3313SPeter De Schrijver 	mux->mask = mask;
1559d9f78edSMike Turquette 	mux->flags = clk_mux_flags;
1569d9f78edSMike Turquette 	mux->lock = lock;
157ce4f3313SPeter De Schrijver 	mux->table = table;
15831df9db9SMike Turquette 	mux->hw.init = &init;
1599d9f78edSMike Turquette 
1600197b3eaSSaravana Kannan 	clk = clk_register(dev, &mux->hw);
16127d54591SMike Turquette 
16227d54591SMike Turquette 	if (IS_ERR(clk))
16327d54591SMike Turquette 		kfree(mux);
16427d54591SMike Turquette 
16527d54591SMike Turquette 	return clk;
1669d9f78edSMike Turquette }
1675cfe10bbSMike Turquette EXPORT_SYMBOL_GPL(clk_register_mux_table);
168ce4f3313SPeter De Schrijver 
169ce4f3313SPeter De Schrijver struct clk *clk_register_mux(struct device *dev, const char *name,
1702893c379SSascha Hauer 		const char * const *parent_names, u8 num_parents,
1712893c379SSascha Hauer 		unsigned long flags,
172ce4f3313SPeter De Schrijver 		void __iomem *reg, u8 shift, u8 width,
173ce4f3313SPeter De Schrijver 		u8 clk_mux_flags, spinlock_t *lock)
174ce4f3313SPeter De Schrijver {
175ce4f3313SPeter De Schrijver 	u32 mask = BIT(width) - 1;
176ce4f3313SPeter De Schrijver 
177ce4f3313SPeter De Schrijver 	return clk_register_mux_table(dev, name, parent_names, num_parents,
178ce4f3313SPeter De Schrijver 				      flags, reg, shift, mask, clk_mux_flags,
179ce4f3313SPeter De Schrijver 				      NULL, lock);
180ce4f3313SPeter De Schrijver }
1815cfe10bbSMike Turquette EXPORT_SYMBOL_GPL(clk_register_mux);
1824e3c021fSKrzysztof Kozlowski 
1834e3c021fSKrzysztof Kozlowski void clk_unregister_mux(struct clk *clk)
1844e3c021fSKrzysztof Kozlowski {
1854e3c021fSKrzysztof Kozlowski 	struct clk_mux *mux;
1864e3c021fSKrzysztof Kozlowski 	struct clk_hw *hw;
1874e3c021fSKrzysztof Kozlowski 
1884e3c021fSKrzysztof Kozlowski 	hw = __clk_get_hw(clk);
1894e3c021fSKrzysztof Kozlowski 	if (!hw)
1904e3c021fSKrzysztof Kozlowski 		return;
1914e3c021fSKrzysztof Kozlowski 
1924e3c021fSKrzysztof Kozlowski 	mux = to_clk_mux(hw);
1934e3c021fSKrzysztof Kozlowski 
1944e3c021fSKrzysztof Kozlowski 	clk_unregister(clk);
1954e3c021fSKrzysztof Kozlowski 	kfree(mux);
1964e3c021fSKrzysztof Kozlowski }
1974e3c021fSKrzysztof Kozlowski EXPORT_SYMBOL_GPL(clk_unregister_mux);
198