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