| rjw | 1f88458 | 2022-01-06 17:20:42 +0800 | [diff] [blame] | 1 | /* | 
|  | 2 | * Copyright (C) 2013 Freescale Semiconductor, Inc. | 
|  | 3 | * | 
|  | 4 | * The code contained herein is licensed under the GNU General Public | 
|  | 5 | * License. You may obtain a copy of the GNU General Public License | 
|  | 6 | * Version 2 or later at the following locations: | 
|  | 7 | * | 
|  | 8 | * http://www.opensource.org/licenses/gpl-license.html | 
|  | 9 | * http://www.gnu.org/copyleft/gpl.html | 
|  | 10 | */ | 
|  | 11 |  | 
|  | 12 | #include <linux/clk-provider.h> | 
|  | 13 | #include <linux/err.h> | 
|  | 14 | #include <linux/io.h> | 
|  | 15 | #include <linux/slab.h> | 
|  | 16 | #include "clk.h" | 
|  | 17 |  | 
|  | 18 | #define div_mask(d)	((1 << (d->width)) - 1) | 
|  | 19 |  | 
|  | 20 | /** | 
|  | 21 | * struct clk_fixup_div - imx integer fixup divider clock | 
|  | 22 | * @divider: the parent class | 
|  | 23 | * @ops: pointer to clk_ops of parent class | 
|  | 24 | * @fixup: a hook to fixup the write value | 
|  | 25 | * | 
|  | 26 | * The imx fixup divider clock is a subclass of basic clk_divider | 
|  | 27 | * with an addtional fixup hook. | 
|  | 28 | */ | 
|  | 29 | struct clk_fixup_div { | 
|  | 30 | struct clk_divider divider; | 
|  | 31 | const struct clk_ops *ops; | 
|  | 32 | void (*fixup)(u32 *val); | 
|  | 33 | }; | 
|  | 34 |  | 
|  | 35 | static inline struct clk_fixup_div *to_clk_fixup_div(struct clk_hw *hw) | 
|  | 36 | { | 
|  | 37 | struct clk_divider *divider = to_clk_divider(hw); | 
|  | 38 |  | 
|  | 39 | return container_of(divider, struct clk_fixup_div, divider); | 
|  | 40 | } | 
|  | 41 |  | 
|  | 42 | static unsigned long clk_fixup_div_recalc_rate(struct clk_hw *hw, | 
|  | 43 | unsigned long parent_rate) | 
|  | 44 | { | 
|  | 45 | struct clk_fixup_div *fixup_div = to_clk_fixup_div(hw); | 
|  | 46 |  | 
|  | 47 | return fixup_div->ops->recalc_rate(&fixup_div->divider.hw, parent_rate); | 
|  | 48 | } | 
|  | 49 |  | 
|  | 50 | static long clk_fixup_div_round_rate(struct clk_hw *hw, unsigned long rate, | 
|  | 51 | unsigned long *prate) | 
|  | 52 | { | 
|  | 53 | struct clk_fixup_div *fixup_div = to_clk_fixup_div(hw); | 
|  | 54 |  | 
|  | 55 | return fixup_div->ops->round_rate(&fixup_div->divider.hw, rate, prate); | 
|  | 56 | } | 
|  | 57 |  | 
|  | 58 | static int clk_fixup_div_set_rate(struct clk_hw *hw, unsigned long rate, | 
|  | 59 | unsigned long parent_rate) | 
|  | 60 | { | 
|  | 61 | struct clk_fixup_div *fixup_div = to_clk_fixup_div(hw); | 
|  | 62 | struct clk_divider *div = to_clk_divider(hw); | 
|  | 63 | unsigned int divider, value; | 
|  | 64 | unsigned long flags = 0; | 
|  | 65 | u32 val; | 
|  | 66 |  | 
|  | 67 | divider = parent_rate / rate; | 
|  | 68 |  | 
|  | 69 | /* Zero based divider */ | 
|  | 70 | value = divider - 1; | 
|  | 71 |  | 
|  | 72 | if (value > div_mask(div)) | 
|  | 73 | value = div_mask(div); | 
|  | 74 |  | 
|  | 75 | spin_lock_irqsave(div->lock, flags); | 
|  | 76 |  | 
|  | 77 | val = readl(div->reg); | 
|  | 78 | val &= ~(div_mask(div) << div->shift); | 
|  | 79 | val |= value << div->shift; | 
|  | 80 | fixup_div->fixup(&val); | 
|  | 81 | writel(val, div->reg); | 
|  | 82 |  | 
|  | 83 | spin_unlock_irqrestore(div->lock, flags); | 
|  | 84 |  | 
|  | 85 | return 0; | 
|  | 86 | } | 
|  | 87 |  | 
|  | 88 | static const struct clk_ops clk_fixup_div_ops = { | 
|  | 89 | .recalc_rate = clk_fixup_div_recalc_rate, | 
|  | 90 | .round_rate = clk_fixup_div_round_rate, | 
|  | 91 | .set_rate = clk_fixup_div_set_rate, | 
|  | 92 | }; | 
|  | 93 |  | 
|  | 94 | struct clk *imx_clk_fixup_divider(const char *name, const char *parent, | 
|  | 95 | void __iomem *reg, u8 shift, u8 width, | 
|  | 96 | void (*fixup)(u32 *val)) | 
|  | 97 | { | 
|  | 98 | struct clk_fixup_div *fixup_div; | 
|  | 99 | struct clk *clk; | 
|  | 100 | struct clk_init_data init; | 
|  | 101 |  | 
|  | 102 | if (!fixup) | 
|  | 103 | return ERR_PTR(-EINVAL); | 
|  | 104 |  | 
|  | 105 | fixup_div = kzalloc(sizeof(*fixup_div), GFP_KERNEL); | 
|  | 106 | if (!fixup_div) | 
|  | 107 | return ERR_PTR(-ENOMEM); | 
|  | 108 |  | 
|  | 109 | init.name = name; | 
|  | 110 | init.ops = &clk_fixup_div_ops; | 
|  | 111 | init.flags = CLK_SET_RATE_PARENT; | 
|  | 112 | init.parent_names = parent ? &parent : NULL; | 
|  | 113 | init.num_parents = parent ? 1 : 0; | 
|  | 114 |  | 
|  | 115 | fixup_div->divider.reg = reg; | 
|  | 116 | fixup_div->divider.shift = shift; | 
|  | 117 | fixup_div->divider.width = width; | 
|  | 118 | fixup_div->divider.lock = &imx_ccm_lock; | 
|  | 119 | fixup_div->divider.hw.init = &init; | 
|  | 120 | fixup_div->ops = &clk_divider_ops; | 
|  | 121 | fixup_div->fixup = fixup; | 
|  | 122 |  | 
|  | 123 | clk = clk_register(NULL, &fixup_div->divider.hw); | 
|  | 124 | if (IS_ERR(clk)) | 
|  | 125 | kfree(fixup_div); | 
|  | 126 |  | 
|  | 127 | return clk; | 
|  | 128 | } |