Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v4.16 144 lines 3.2 kB view raw
1/* 2 * Copyright 2012 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/** 19 * struct clk_frac - mxs fractional divider clock 20 * @hw: clk_hw for the fractional divider clock 21 * @reg: register address 22 * @shift: the divider bit shift 23 * @width: the divider bit width 24 * @busy: busy bit shift 25 * 26 * The clock is an adjustable fractional divider with a busy bit to wait 27 * when the divider is adjusted. 28 */ 29struct clk_frac { 30 struct clk_hw hw; 31 void __iomem *reg; 32 u8 shift; 33 u8 width; 34 u8 busy; 35}; 36 37#define to_clk_frac(_hw) container_of(_hw, struct clk_frac, hw) 38 39static unsigned long clk_frac_recalc_rate(struct clk_hw *hw, 40 unsigned long parent_rate) 41{ 42 struct clk_frac *frac = to_clk_frac(hw); 43 u32 div; 44 u64 tmp_rate; 45 46 div = readl_relaxed(frac->reg) >> frac->shift; 47 div &= (1 << frac->width) - 1; 48 49 tmp_rate = (u64)parent_rate * div; 50 return tmp_rate >> frac->width; 51} 52 53static long clk_frac_round_rate(struct clk_hw *hw, unsigned long rate, 54 unsigned long *prate) 55{ 56 struct clk_frac *frac = to_clk_frac(hw); 57 unsigned long parent_rate = *prate; 58 u32 div; 59 u64 tmp, tmp_rate, result; 60 61 if (rate > parent_rate) 62 return -EINVAL; 63 64 tmp = rate; 65 tmp <<= frac->width; 66 do_div(tmp, parent_rate); 67 div = tmp; 68 69 if (!div) 70 return -EINVAL; 71 72 tmp_rate = (u64)parent_rate * div; 73 result = tmp_rate >> frac->width; 74 if ((result << frac->width) < tmp_rate) 75 result += 1; 76 return result; 77} 78 79static int clk_frac_set_rate(struct clk_hw *hw, unsigned long rate, 80 unsigned long parent_rate) 81{ 82 struct clk_frac *frac = to_clk_frac(hw); 83 unsigned long flags; 84 u32 div, val; 85 u64 tmp; 86 87 if (rate > parent_rate) 88 return -EINVAL; 89 90 tmp = rate; 91 tmp <<= frac->width; 92 do_div(tmp, parent_rate); 93 div = tmp; 94 95 if (!div) 96 return -EINVAL; 97 98 spin_lock_irqsave(&mxs_lock, flags); 99 100 val = readl_relaxed(frac->reg); 101 val &= ~(((1 << frac->width) - 1) << frac->shift); 102 val |= div << frac->shift; 103 writel_relaxed(val, frac->reg); 104 105 spin_unlock_irqrestore(&mxs_lock, flags); 106 107 return mxs_clk_wait(frac->reg, frac->busy); 108} 109 110static const struct clk_ops clk_frac_ops = { 111 .recalc_rate = clk_frac_recalc_rate, 112 .round_rate = clk_frac_round_rate, 113 .set_rate = clk_frac_set_rate, 114}; 115 116struct clk *mxs_clk_frac(const char *name, const char *parent_name, 117 void __iomem *reg, u8 shift, u8 width, u8 busy) 118{ 119 struct clk_frac *frac; 120 struct clk *clk; 121 struct clk_init_data init; 122 123 frac = kzalloc(sizeof(*frac), GFP_KERNEL); 124 if (!frac) 125 return ERR_PTR(-ENOMEM); 126 127 init.name = name; 128 init.ops = &clk_frac_ops; 129 init.flags = CLK_SET_RATE_PARENT; 130 init.parent_names = (parent_name ? &parent_name: NULL); 131 init.num_parents = (parent_name ? 1 : 0); 132 133 frac->reg = reg; 134 frac->shift = shift; 135 frac->width = width; 136 frac->busy = busy; 137 frac->hw.init = &init; 138 139 clk = clk_register(NULL, &frac->hw); 140 if (IS_ERR(clk)) 141 kfree(frac); 142 143 return clk; 144}