clk-fixup-div.c (3101B)
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 */ 23struct clk_fixup_div { 24 struct clk_divider divider; 25 const struct clk_ops *ops; 26 void (*fixup)(u32 *val); 27}; 28 29static 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 36static 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 44static 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 52static 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 82static 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 88struct 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}