diff options
Diffstat (limited to 'drivers/clk/qcom/clk-rcg2.c')
-rw-r--r-- | drivers/clk/qcom/clk-rcg2.c | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c new file mode 100644 index 000000000000..00f878a04d3f --- /dev/null +++ b/drivers/clk/qcom/clk-rcg2.c @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/bug.h> +#include <linux/export.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/regmap.h> + +#include <asm/div64.h> + +#include "clk-rcg.h" + +#define CMD_REG 0x0 +#define CMD_UPDATE BIT(0) +#define CMD_ROOT_EN BIT(1) +#define CMD_DIRTY_CFG BIT(4) +#define CMD_DIRTY_N BIT(5) +#define CMD_DIRTY_M BIT(6) +#define CMD_DIRTY_D BIT(7) +#define CMD_ROOT_OFF BIT(31) + +#define CFG_REG 0x4 +#define CFG_SRC_DIV_SHIFT 0 +#define CFG_SRC_SEL_SHIFT 8 +#define CFG_SRC_SEL_MASK (0x7 << CFG_SRC_SEL_SHIFT) +#define CFG_MODE_SHIFT 12 +#define CFG_MODE_MASK (0x3 << CFG_MODE_SHIFT) +#define CFG_MODE_DUAL_EDGE (0x2 << CFG_MODE_SHIFT) + +#define M_REG 0x8 +#define N_REG 0xc +#define D_REG 0x10 + +static int clk_rcg2_is_enabled(struct clk_hw *hw) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + u32 cmd; + int ret; + + ret = regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CMD_REG, &cmd); + if (ret) + return ret; + + return (cmd & CMD_ROOT_OFF) != 0; +} + +static u8 clk_rcg2_get_parent(struct clk_hw *hw) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + int num_parents = __clk_get_num_parents(hw->clk); + u32 cfg; + int i, ret; + + ret = regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, &cfg); + if (ret) + return ret; + + cfg &= CFG_SRC_SEL_MASK; + cfg >>= CFG_SRC_SEL_SHIFT; + + for (i = 0; i < num_parents; i++) + if (cfg == rcg->parent_map[i]) + return i; + + return -EINVAL; +} + +static int update_config(struct clk_rcg2 *rcg) +{ + int count, ret; + u32 cmd; + struct clk_hw *hw = &rcg->clkr.hw; + const char *name = __clk_get_name(hw->clk); + + ret = regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + CMD_REG, + CMD_UPDATE, CMD_UPDATE); + if (ret) + return ret; + + /* Wait for update to take effect */ + for (count = 500; count > 0; count--) { + ret = regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CMD_REG, &cmd); + if (ret) + return ret; + if (!(cmd & CMD_UPDATE)) + return 0; + udelay(1); + } + + WARN(1, "%s: rcg didn't update its configuration.", name); + return 0; +} + +static int clk_rcg2_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + int ret; + + ret = regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, + CFG_SRC_SEL_MASK, + rcg->parent_map[index] << CFG_SRC_SEL_SHIFT); + if (ret) + return ret; + + return update_config(rcg); +} + +/* + * Calculate m/n:d rate + * + * parent_rate m + * rate = ----------- x --- + * hid_div n + */ +static unsigned long +calc_rate(unsigned long rate, u32 m, u32 n, u32 mode, u32 hid_div) +{ + if (hid_div) { + rate *= 2; + rate /= hid_div + 1; + } + + if (mode) { + u64 tmp = rate; + tmp *= m; + do_div(tmp, n); + rate = tmp; + } + + return rate; +} + +static unsigned long +clk_rcg2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + u32 cfg, hid_div, m = 0, n = 0, mode = 0, mask; + + regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, &cfg); + + if (rcg->mnd_width) { + mask = BIT(rcg->mnd_width) - 1; + regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + M_REG, &m); + m &= mask; + regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + N_REG, &n); + n = ~n; + n &= mask; + n += m; + mode = cfg & CFG_MODE_MASK; + mode >>= CFG_MODE_SHIFT; + } + + mask = BIT(rcg->hid_width) - 1; + hid_div = cfg >> CFG_SRC_DIV_SHIFT; + hid_div &= mask; + + return calc_rate(parent_rate, m, n, mode, hid_div); +} + +static const +struct freq_tbl *find_freq(const struct freq_tbl *f, unsigned long rate) +{ + if (!f) + return NULL; + + for (; f->freq; f++) + if (rate <= f->freq) + return f; + + return NULL; +} + +static long _freq_tbl_determine_rate(struct clk_hw *hw, + const struct freq_tbl *f, unsigned long rate, + unsigned long *p_rate, struct clk **p) +{ + unsigned long clk_flags; + + f = find_freq(f, rate); + if (!f) + return -EINVAL; + + clk_flags = __clk_get_flags(hw->clk); + *p = clk_get_parent_by_index(hw->clk, f->src); + if (clk_flags & CLK_SET_RATE_PARENT) { + if (f->pre_div) { + rate /= 2; + rate *= f->pre_div + 1; + } + + if (f->n) { + u64 tmp = rate; + tmp = tmp * f->n; + do_div(tmp, f->m); + rate = tmp; + } + } else { + rate = __clk_get_rate(*p); + } + *p_rate = rate; + + return f->freq; +} + +static long clk_rcg2_determine_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *p_rate, struct clk **p) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + + return _freq_tbl_determine_rate(hw, rcg->freq_tbl, rate, p_rate, p); +} + +static int __clk_rcg2_set_rate(struct clk_hw *hw, unsigned long rate) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + const struct freq_tbl *f; + u32 cfg, mask; + int ret; + + f = find_freq(rcg->freq_tbl, rate); + if (!f) + return -EINVAL; + + if (rcg->mnd_width && f->n) { + mask = BIT(rcg->mnd_width) - 1; + ret = regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + M_REG, + mask, f->m); + if (ret) + return ret; + + ret = regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + N_REG, + mask, ~(f->n - f->m)); + if (ret) + return ret; + + ret = regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + D_REG, + mask, ~f->n); + if (ret) + return ret; + } + + mask = BIT(rcg->hid_width) - 1; + mask |= CFG_SRC_SEL_MASK | CFG_MODE_MASK; + cfg = f->pre_div << CFG_SRC_DIV_SHIFT; + cfg |= rcg->parent_map[f->src] << CFG_SRC_SEL_SHIFT; + if (rcg->mnd_width && f->n) + cfg |= CFG_MODE_DUAL_EDGE; + ret = regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, mask, + cfg); + if (ret) + return ret; + + return update_config(rcg); +} + +static int clk_rcg2_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + return __clk_rcg2_set_rate(hw, rate); +} + +static int clk_rcg2_set_rate_and_parent(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate, u8 index) +{ + return __clk_rcg2_set_rate(hw, rate); +} + +const struct clk_ops clk_rcg2_ops = { + .is_enabled = clk_rcg2_is_enabled, + .get_parent = clk_rcg2_get_parent, + .set_parent = clk_rcg2_set_parent, + .recalc_rate = clk_rcg2_recalc_rate, + .determine_rate = clk_rcg2_determine_rate, + .set_rate = clk_rcg2_set_rate, + .set_rate_and_parent = clk_rcg2_set_rate_and_parent, +}; +EXPORT_SYMBOL_GPL(clk_rcg2_ops); |