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