| #include <linux/types.h> |
| #include <linux/kernel.h> |
| #include <linux/io.h> |
| #include <linux/delay.h> |
| #include <soc/samsung/ect_parser.h> |
| #include <soc/samsung/exynos-pmu.h> |
| |
| #include "cmucal.h" |
| #include "ra.h" |
| |
| static enum trans_opt ra_get_trans_opt(unsigned int to, unsigned int from) |
| { |
| if (from == to) |
| return TRANS_IGNORE; |
| |
| return to > from ? TRANS_HIGH : TRANS_LOW; |
| } |
| |
| static int ra_wait_done(void __iomem *reg, |
| unsigned char shift, |
| unsigned int done, |
| int usec) |
| { |
| unsigned int result; |
| |
| do { |
| result = get_bit(reg, shift); |
| |
| if (result == done) |
| return 0; |
| udelay(1); |
| } while (--usec > 0); |
| |
| return -EVCLKTIMEOUT; |
| } |
| |
| static unsigned int ra_get_fixed_rate(struct cmucal_clk *clk) |
| { |
| struct cmucal_clk_fixed_rate *frate = to_fixed_rate_clk(clk); |
| void __iomem *offset; |
| unsigned int rate; |
| |
| if (!clk->enable) |
| return frate->fixed_rate; |
| |
| offset = convert_pll_base(clk->enable); |
| |
| if (readl(offset) & PLL_MUX_SEL) |
| rate = frate->fixed_rate; |
| else |
| rate = FIN_HZ_26M; |
| |
| return rate; |
| } |
| |
| static unsigned int ra_get_fixed_factor(struct cmucal_clk *clk) |
| { |
| struct cmucal_clk_fixed_factor *ffacor = to_fixed_factor_clk(clk); |
| |
| return ffacor->ratio; |
| } |
| |
| static struct cmucal_pll_table *get_pll_table(struct cmucal_pll *pll_clk, |
| unsigned long rate) |
| { |
| struct cmucal_pll_table *prate_table = pll_clk->rate_table; |
| int i; |
| |
| for (i = 0; i < pll_clk->rate_count; i++) { |
| if (rate == prate_table[i].rate / 1000) |
| return &prate_table[i]; |
| } |
| |
| return NULL; |
| } |
| |
| static int ra_is_pll_enabled(struct cmucal_clk *clk) |
| { |
| return get_bit(clk->pll_con0, clk->e_shift); |
| } |
| |
| static int ra_enable_pll(struct cmucal_clk *clk, int enable) |
| { |
| unsigned int reg; |
| int ret = 0; |
| |
| reg = readl(clk->pll_con0); |
| if (!enable) { |
| reg &= ~(PLL_MUX_SEL); |
| writel(reg, clk->pll_con0); |
| |
| ret = ra_wait_done(clk->pll_con0, PLL_MUX_BUSY_SHIFT, 0, 100); |
| if (ret) |
| pr_err("pll mux change time out, \'%s\'\n", clk->name); |
| } |
| |
| if (enable) |
| reg |= 1 << clk->e_shift; |
| else |
| reg &= ~(1 << clk->e_shift); |
| |
| writel(reg, clk->pll_con0); |
| |
| if (enable) { |
| ret = ra_wait_done(clk->pll_con0, clk->s_shift, 1, 100); |
| if (ret) |
| pr_err("pll time out, \'%s\' %d\n", clk->name, enable); |
| } |
| |
| return ret; |
| } |
| |
| static int ra_pll_set_pmsk(struct cmucal_clk *clk, |
| struct cmucal_pll_table *rate_table) |
| { |
| struct cmucal_pll *pll = to_pll_clk(clk); |
| unsigned int mdiv, pdiv, sdiv, pll_con0, pll_con1; |
| signed short kdiv; |
| int ret = 0; |
| |
| pdiv = rate_table->pdiv; |
| mdiv = rate_table->mdiv; |
| sdiv = rate_table->sdiv; |
| |
| if (!clk->pll_con0) |
| return -EVCLKNOENT; |
| |
| pll_con0 = readl(clk->pll_con0); |
| pll_con0 &= ~(get_mask(pll->m_width, pll->m_shift) |
| | get_mask(pll->p_width, pll->p_shift) |
| | get_mask(pll->s_width, pll->s_shift)); |
| pll_con0 |= (mdiv << pll->m_shift |
| | pdiv << pll->p_shift |
| | sdiv << pll->s_shift); |
| |
| pll_con0 |= PLL_MUX_SEL | (1 << clk->e_shift); |
| |
| if (is_frac_pll(pll)) { |
| kdiv = rate_table->kdiv; |
| if (kdiv) |
| writel(pdiv * pll->flock_time, clk->lock); |
| else |
| writel(pdiv * pll->lock_time, clk->lock); |
| |
| if (clk->pll_con1) { |
| pll_con1 = readl(clk->pll_con1); |
| pll_con1 &= ~(get_mask(pll->k_width, pll->k_shift)); |
| pll_con1 |= (kdiv << pll->k_shift); |
| writel(pll_con1, clk->pll_con1); |
| } |
| } else { |
| writel(pdiv * pll->lock_time, clk->lock); |
| } |
| |
| writel(pll_con0, clk->pll_con0); |
| |
| ret = ra_wait_done(clk->pll_con0, clk->s_shift, 1, 100); |
| if (ret) |
| pr_err("time out, \'%s\'", clk->name); |
| |
| return ret; |
| } |
| |
| static int ra_get_div_mux(struct cmucal_clk *clk) |
| { |
| if (!clk->offset) |
| return 0; |
| |
| return get_value(clk->offset, clk->shift, clk->width); |
| } |
| |
| static int ra_set_div_mux(struct cmucal_clk *clk, unsigned int params) |
| { |
| unsigned int reg; |
| int ret = 0; |
| |
| if (!clk->offset) |
| return 0; |
| |
| reg = clear_value(clk->offset, clk->width, clk->shift); |
| writel(reg | (params << clk->shift), clk->offset); |
| |
| if (clk->status == NULL) |
| return 0; |
| |
| ret = ra_wait_done(clk->status, clk->s_shift, 0, 100); |
| if (ret) { |
| pr_err("time out, \'%s\' [%p]=%x [%p]=%x\n", |
| clk->name, |
| clk->offset, readl(clk->offset), |
| clk->status, readl(clk->status)); |
| } |
| |
| return ret; |
| } |
| |
| static int ra_set_mux_rate(struct cmucal_clk *clk, unsigned int rate) |
| { |
| struct cmucal_mux *mux; |
| unsigned int p_rate, sel = 0; |
| unsigned int diff, min_diff = 0xFFFFFFFF; |
| int i; |
| int ret = -EVCLKINVAL; |
| |
| if (rate == 0) |
| return ret; |
| |
| mux = to_mux_clk(clk); |
| |
| for (i = 0; i < mux->num_parents; i++) { |
| p_rate = ra_recalc_rate(mux->pid[i]); |
| |
| if (p_rate == rate) { |
| sel = i; |
| break; |
| } |
| |
| diff = abs(p_rate - rate); |
| if (diff < min_diff) { |
| min_diff = diff; |
| sel = i; |
| } |
| } |
| |
| if (i == mux->num_parents) |
| pr_debug("approximately select %s %u:%u:%u\n", |
| clk->name, rate, min_diff, sel); |
| ret = ra_set_div_mux(clk, sel); |
| |
| return ret; |
| } |
| |
| static int ra_set_div_rate(struct cmucal_clk *clk, unsigned int rate) |
| { |
| unsigned int p_rate; |
| unsigned int ratio, max_ratio; |
| unsigned int diff1, diff2; |
| int ret = -EVCLKINVAL; |
| |
| if (rate == 0) |
| return ret; |
| |
| p_rate = ra_recalc_rate(clk->pid); |
| |
| if (p_rate == 0) |
| return ret; |
| |
| max_ratio = width_to_mask(clk->width) + 1; |
| |
| ratio = p_rate / rate; |
| |
| if (ratio > 0 && ratio <= max_ratio) { |
| if (p_rate % rate) { |
| diff1 = p_rate - (ratio * rate); |
| diff2 = ratio * rate + rate - p_rate; |
| if (diff1 > diff2) { |
| ret = ra_set_div_mux(clk, ratio); |
| return ret; |
| } |
| } |
| |
| ret = ra_set_div_mux(clk, ratio - 1); |
| } else if (ratio == 0) { |
| ret = ra_set_div_mux(clk, ratio); |
| } else { |
| pr_err("failed div_rate %s %u:%u:%u:%u\n", |
| clk->name, p_rate, rate, ratio, max_ratio); |
| } |
| |
| return ret; |
| } |
| |
| static int ra_set_pll(struct cmucal_clk *clk, unsigned int rate) |
| { |
| struct cmucal_pll *pll; |
| struct cmucal_pll_table *rate_table; |
| struct cmucal_pll_table table; |
| struct cmucal_clk *umux; |
| unsigned int fin; |
| int ret = 0; |
| |
| pll = to_pll_clk(clk); |
| |
| if (rate == 0) { |
| if (pll->umux != EMPTY_CLK_ID) { |
| umux = cmucal_get_node(pll->umux); |
| if (umux) |
| ra_set_div_mux(umux, 0); |
| } |
| ra_enable_pll(clk, 0); |
| } else { |
| rate_table = get_pll_table(pll, rate); |
| if (!rate_table) { |
| if (IS_FIXED_RATE(clk->pid)) |
| fin = ra_get_value(clk->pid); |
| else |
| fin = FIN_HZ_26M; |
| |
| ret = pll_find_table(pll, &table, fin, rate); |
| if (ret) { |
| pr_err("failed %s table %u\n", clk->name, rate); |
| return ret; |
| } |
| rate_table = &table; |
| } |
| ra_enable_pll(clk, 0); |
| ret = ra_pll_set_pmsk(clk, rate_table); |
| |
| if (pll->umux != EMPTY_CLK_ID) { |
| umux = cmucal_get_node(pll->umux); |
| if (umux) |
| ra_set_div_mux(umux, 1); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static unsigned int ra_get_pll(struct cmucal_clk *clk) |
| { |
| struct cmucal_pll *pll; |
| unsigned int mdiv, pdiv, sdiv, pll_con0; |
| short kdiv; |
| unsigned long long fout; |
| |
| if (!ra_is_pll_enabled(clk)) { |
| pll_con0 = readl(clk->pll_con0); |
| if (pll_con0 & PLL_MUX_SEL) |
| return 0; |
| else |
| return FIN_HZ_26M; |
| } |
| |
| pll = to_pll_clk(clk); |
| |
| pll_con0 = readl(clk->pll_con0); |
| mdiv = (pll_con0 >> pll->m_shift) & width_to_mask(pll->m_width); |
| pdiv = (pll_con0 >> pll->p_shift) & width_to_mask(pll->p_width); |
| sdiv = (pll_con0 >> pll->s_shift) & width_to_mask(pll->s_width); |
| |
| if (IS_FIXED_RATE(clk->pid)) |
| fout = ra_get_value(clk->pid); |
| else |
| fout = FIN_HZ_26M; |
| |
| if (is_normal_pll(pll)) { |
| fout *= mdiv; |
| do_div(fout, (pdiv << sdiv)); |
| } else if (is_frac_pll(pll) && clk->pll_con1) { |
| kdiv = get_value(clk->pll_con1, pll->k_shift, pll->k_width); |
| fout *= (mdiv << 16) + kdiv; |
| do_div(fout, (pdiv << sdiv)); |
| fout >>= 16; |
| } else { |
| pr_err("Un-support PLL type\n"); |
| fout = 0; |
| } |
| |
| return fout; |
| } |
| |
| static unsigned int ra_get_pll_idx(struct cmucal_clk *clk) |
| { |
| struct cmucal_pll *pll; |
| struct cmucal_pll_table *prate_table; |
| unsigned int mdiv, pdiv, sdiv, pll_con0; |
| int i; |
| |
| pll = to_pll_clk(clk); |
| prate_table = pll->rate_table; |
| |
| pll_con0 = readl(clk->pll_con0); |
| mdiv = (pll_con0 >> pll->m_shift) & width_to_mask(pll->m_width); |
| pdiv = (pll_con0 >> pll->p_shift) & width_to_mask(pll->p_width); |
| sdiv = (pll_con0 >> pll->s_shift) & width_to_mask(pll->s_width); |
| |
| for (i = 0; i < pll->rate_count; i++) { |
| if (mdiv != prate_table[i].mdiv) |
| continue; |
| if (pdiv != prate_table[i].pdiv) |
| continue; |
| if (sdiv != prate_table[i].sdiv) |
| continue; |
| |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| static int ra_set_gate(struct cmucal_clk *clk, unsigned int pass) |
| { |
| unsigned int reg; |
| |
| /* |
| * MANUAL(status) 1 : CG_VALUE(offset) control |
| * 0 : ENABLE_AUTOMATIC_CLKGATING(enable) control |
| */ |
| if (!clk->status || get_bit(clk->status, clk->s_shift)) { |
| reg = readl(clk->offset); |
| reg &= ~(get_mask(clk->width, clk->shift)); |
| if (pass) |
| reg |= get_mask(clk->width, clk->shift); |
| |
| writel(reg, clk->offset); |
| } else { |
| reg = readl(clk->enable); |
| reg &= ~(get_mask(clk->e_width, clk->e_shift)); |
| if (!pass) |
| reg |= get_mask(clk->e_width, clk->e_shift); |
| |
| writel(reg, clk->offset); |
| } |
| return 0; |
| } |
| |
| static unsigned int ra_get_gate(struct cmucal_clk *clk) |
| { |
| unsigned int pass; |
| |
| /* |
| * MANUAL(status) 1 : CG_VALUE(offset) control |
| * 0 : ENABLE_AUTOMATIC_CLKGATING(enable) control |
| */ |
| if (!clk->status || get_bit(clk->status, clk->s_shift)) |
| pass = get_value(clk->offset, clk->shift, clk->width); |
| else |
| pass = !(get_value(clk->enable, clk->e_shift, clk->e_width)); |
| |
| return pass; |
| } |
| |
| /* |
| * en : qch enable bit |
| * req : qch request bit |
| * expire == 0 => default value |
| * expire != 0 => change value |
| */ |
| int ra_set_qch(unsigned int id, unsigned int en, |
| unsigned int req, unsigned int expire) |
| { |
| struct cmucal_qch *qch; |
| struct cmucal_clk *clk; |
| unsigned int reg; |
| |
| clk = cmucal_get_node(id); |
| if (!clk) { |
| pr_err("%s:[%x]\n", __func__, id); |
| return -EVCLKINVAL; |
| } |
| |
| if (!IS_QCH(clk->id)) { |
| if (IS_GATE(clk->id)) { |
| reg = readl(clk->status); |
| reg &= ~(get_mask(clk->s_width, clk->s_shift)); |
| if (!en) |
| reg |= get_mask(clk->s_width, clk->s_shift); |
| |
| writel(reg, clk->status); |
| return 0; |
| } |
| |
| pr_err("%s:cannot find qch [%x]\n", __func__, id); |
| return -EVCLKINVAL; |
| } |
| |
| if (expire) { |
| reg = ((en & width_to_mask(clk->width)) << clk->shift) | |
| ((req & width_to_mask(clk->s_width)) << clk->s_shift) | |
| ((expire & width_to_mask(clk->e_width)) << clk->e_shift); |
| } else { |
| reg = readl(clk->offset); |
| reg &= ~(get_mask(clk->width, clk->shift) | |
| get_mask(clk->s_width, clk->s_shift)); |
| reg |= (en << clk->shift) | (req << clk->s_shift); |
| } |
| |
| if (IS_ENABLED(CONFIG_CMUCAL_QCH_IGNORE_SUPPORT)) { |
| qch = to_qch(clk); |
| |
| if (en) |
| reg &= ~(0x1 << qch->ig_shift); |
| else |
| reg |= (0x1 << qch->ig_shift); |
| } |
| |
| writel(reg, clk->offset); |
| |
| return 0; |
| } |
| |
| static int ra_req_enable_qch(struct cmucal_clk *clk, unsigned int req) |
| { |
| unsigned int reg; |
| /* |
| * QH ENABLE(offset) 1 : Skip |
| * 0 : REQ(status) control |
| */ |
| if (!get_bit(clk->offset, clk->shift)) { |
| reg = readl(clk->status); |
| reg &= ~(get_mask(clk->s_width, clk->s_shift)); |
| if (req) |
| reg |= get_mask(clk->s_width, clk->s_shift); |
| |
| writel(reg, clk->status); |
| } |
| |
| return 0; |
| } |
| |
| int ra_enable_qch(struct cmucal_clk *clk, unsigned int en) |
| { |
| unsigned int reg; |
| /* |
| * QH ENABLE(offset) |
| */ |
| reg = readl(clk->offset); |
| reg &= ~(get_mask(clk->width, clk->shift)); |
| if (en) |
| reg |= get_mask(clk->width, clk->shift); |
| |
| writel(reg, clk->offset); |
| |
| return 0; |
| } |
| |
| int ra_set_enable_hwacg(struct cmucal_clk *clk, unsigned int en) |
| { |
| unsigned int reg; |
| |
| /* |
| * Automatic clkgating enable(enable) |
| */ |
| if (!clk->enable) |
| return 0; |
| |
| reg = readl(clk->enable); |
| reg &= ~(get_mask(clk->e_width, clk->e_shift)); |
| if (en) |
| reg |= get_mask(clk->s_width, clk->s_shift); |
| |
| writel(reg, clk->enable); |
| |
| return 0; |
| } |
| |
| static int ra_enable_fixed_rate(struct cmucal_clk *clk, unsigned int params) |
| { |
| unsigned int reg; |
| void __iomem *offset; |
| int ret; |
| |
| if (!clk->enable) |
| return 0; |
| |
| offset = convert_pll_base(clk->enable); |
| reg = readl(offset); |
| if (params) { |
| reg |= (PLL_ENABLE | PLL_MUX_SEL); |
| writel(reg, offset); |
| |
| ret = ra_wait_done(offset, PLL_STABLE_SHIFT, 1, 400); |
| if (ret) |
| pr_err("fixed pll enable time out, \'%s\'\n", clk->name); |
| } else { |
| reg &= ~(PLL_MUX_SEL); |
| writel(reg, offset); |
| ret = ra_wait_done(offset, PLL_MUX_BUSY_SHIFT, 0, 100); |
| if (ret) |
| pr_err("fixed pll mux change time out, \'%s\'\n", clk->name); |
| |
| reg &= ~(PLL_ENABLE); |
| writel(reg, offset); |
| } |
| |
| return 0; |
| } |
| |
| int ra_enable_clkout(struct cmucal_clk *clk, bool enable) |
| { |
| struct cmucal_clkout *clkout = to_clkout(clk); |
| |
| if (enable) { |
| exynos_pmu_update(clk->offset_idx, get_mask(clk->width, clk->shift), |
| clkout->sel << clk->shift); |
| exynos_pmu_update(clk->offset_idx, get_mask(clk->e_width, clk->e_shift), |
| clkout->en << clk->e_shift); |
| } else { |
| exynos_pmu_update(clk->offset_idx, get_mask(clk->e_width, clk->e_shift), |
| (!clkout->en) << clk->e_shift); |
| } |
| |
| return 0; |
| } |
| |
| int ra_set_enable(unsigned int id, unsigned int params) |
| { |
| struct cmucal_clk *clk; |
| unsigned type = GET_TYPE(id); |
| int ret = 0; |
| |
| clk = cmucal_get_node(id); |
| if (!clk) { |
| pr_err("%s:[%x]type : %x, params : %x\n", |
| __func__, id, type, params); |
| return -EVCLKINVAL; |
| } |
| |
| switch (type) { |
| case FIXED_RATE_TYPE: |
| ret = ra_enable_fixed_rate(clk, params); |
| break; |
| case PLL_TYPE: |
| ret = ra_enable_pll(clk, params); |
| break; |
| case MUX_TYPE: |
| if (IS_USER_MUX(clk->id)) |
| ret = ra_set_div_mux(clk, params); |
| break; |
| case GATE_TYPE: |
| ret = ra_set_gate(clk, params); |
| break; |
| case QCH_TYPE: |
| ret = ra_req_enable_qch(clk, params); |
| break; |
| case DIV_TYPE: |
| break; |
| case CLKOUT_TYPE: |
| ret = ra_enable_clkout(clk, params); |
| break; |
| default: |
| pr_err("Un-support clk type %x\n", id); |
| ret = -EVCLKINVAL; |
| } |
| |
| return ret; |
| } |
| |
| int ra_set_value(unsigned int id, unsigned int params) |
| { |
| struct cmucal_clk *clk; |
| unsigned type = GET_TYPE(id); |
| int ret; |
| |
| clk = cmucal_get_node(id); |
| if (!clk) { |
| pr_err("%s:[%x]type : %x, params : %x\n", |
| __func__, id, type, params); |
| return -EVCLKINVAL; |
| } |
| |
| pr_debug("%s:[%s:%x]type : %x, params : %x\n", |
| __func__, clk->name, id, type, params); |
| |
| switch (type) { |
| case DIV_TYPE: |
| ret = ra_set_div_mux(clk, params); |
| break; |
| case MUX_TYPE: |
| ret = ra_set_div_mux(clk, params); |
| break; |
| case PLL_TYPE: |
| ret = ra_set_pll(clk, params); |
| break; |
| case GATE_TYPE: |
| ret = ra_set_gate(clk, params); |
| break; |
| default: |
| pr_err("Un-support clk type %x\n", id); |
| ret = -EVCLKINVAL; |
| } |
| |
| return ret; |
| } |
| |
| unsigned int ra_get_value(unsigned int id) |
| { |
| struct cmucal_clk *clk; |
| unsigned type = GET_TYPE(id); |
| unsigned int val; |
| |
| clk = cmucal_get_node(id); |
| if (!clk) { |
| pr_err("%s:[%x]type : %x\n", __func__, id, type); |
| return 0; |
| } |
| switch (type) { |
| case DIV_TYPE: |
| val = ra_get_div_mux(clk); |
| break; |
| case MUX_TYPE: |
| val = ra_get_div_mux(clk); |
| break; |
| case PLL_TYPE: |
| val = ra_get_pll(clk); |
| break; |
| case GATE_TYPE: |
| val = ra_get_gate(clk); |
| break; |
| case FIXED_RATE_TYPE: |
| val = ra_get_fixed_rate(clk); |
| break; |
| case FIXED_FACTOR_TYPE: |
| val = ra_get_fixed_factor(clk); |
| break; |
| default: |
| pr_err("Un-support clk type %x\n", id); |
| val = 0; |
| } |
| |
| return val; |
| } |
| |
| static unsigned int __init ra_get_sfr_address(unsigned short idx, |
| void __iomem **addr, |
| unsigned char *shift, |
| unsigned char *width) |
| { |
| struct sfr_access *field; |
| struct sfr *reg; |
| struct sfr_block *block; |
| |
| field = cmucal_get_sfr_node(idx | SFR_ACCESS_TYPE); |
| if (!field) { |
| pr_info("%s:failed idx:%x\n", __func__, idx); |
| return 0; |
| } |
| *shift = field->shift; |
| *width = field->width; |
| |
| reg = cmucal_get_sfr_node(field->sfr | SFR_TYPE); |
| if (!reg) { |
| pr_info("%s:failed idx:%x sfr:%x\n", __func__, idx, field->sfr); |
| return 0; |
| } |
| |
| block = cmucal_get_sfr_node(reg->block | SFR_BLOCK_TYPE); |
| if (!reg || !block) { |
| pr_info("%s:failed idx:%x reg:%x\n", __func__, idx, reg->block); |
| return 0; |
| } |
| *addr = block->va + reg->offset; |
| |
| return block->pa + reg->offset; |
| } |
| |
| static void ra_get_pll_address(struct cmucal_clk *clk) |
| { |
| struct cmucal_pll *pll = to_pll_clk(clk); |
| |
| /* lock_div */ |
| ra_get_sfr_address(clk->offset_idx, |
| &clk->lock, |
| &clk->shift, |
| &clk->width); |
| /* enable_div */ |
| clk->paddr = ra_get_sfr_address(clk->enable_idx, |
| &clk->pll_con0, |
| &clk->e_shift, |
| &clk->e_width); |
| |
| /* status_div */ |
| ra_get_sfr_address(clk->status_idx, |
| &clk->pll_con0, |
| &clk->s_shift, |
| &clk->s_width); |
| |
| /* m_div */ |
| ra_get_sfr_address(pll->m_idx, |
| &clk->pll_con0, |
| &pll->m_shift, |
| &pll->m_width); |
| |
| /* p_div */ |
| ra_get_sfr_address(pll->p_idx, |
| &clk->pll_con0, |
| &pll->p_shift, |
| &pll->p_width); |
| |
| /* s_div */ |
| ra_get_sfr_address(pll->s_idx, |
| &clk->pll_con0, |
| &pll->s_shift, |
| &pll->s_width); |
| |
| /* k_div */ |
| if (pll->k_idx != EMPTY_CAL_ID) |
| ra_get_sfr_address(pll->k_idx, |
| &clk->pll_con1, |
| &pll->k_shift, |
| &pll->k_width); |
| else |
| clk->pll_con1 = NULL; |
| } |
| |
| static void ra_get_pll_rate_table(struct cmucal_clk *clk) |
| { |
| struct cmucal_pll *pll = to_pll_clk(clk); |
| void *pll_block; |
| struct cmucal_pll_table *table; |
| struct ect_pll *pll_unit; |
| struct ect_pll_frequency *pll_frequency; |
| int i; |
| |
| pll_block = ect_get_block(BLOCK_PLL); |
| if (!pll_block) |
| return; |
| |
| pll_unit = ect_pll_get_pll(pll_block, clk->name); |
| if (!pll_unit) |
| return; |
| |
| table = kzalloc(sizeof(struct cmucal_pll_table) * pll_unit->num_of_frequency, |
| GFP_KERNEL); |
| if (!table) |
| return; |
| |
| for (i = 0; i < pll_unit->num_of_frequency; ++i) { |
| pll_frequency = &pll_unit->frequency_list[i]; |
| |
| table[i].rate = pll_frequency->frequency; |
| table[i].pdiv = pll_frequency->p; |
| table[i].mdiv = pll_frequency->m; |
| table[i].sdiv = pll_frequency->s; |
| table[i].kdiv = pll_frequency->k; |
| } |
| |
| pll->rate_table = table; |
| pll->rate_count = pll_unit->num_of_frequency; |
| } |
| |
| int ra_set_list_enable(unsigned int *list, unsigned int num_list) |
| { |
| unsigned int id; |
| int i; |
| |
| for (i = 0; i < num_list; i++) { |
| id = list[i]; |
| if (IS_USER_MUX(id) || IS_GATE(id)) |
| ra_set_value(id, 1); |
| else if (IS_PLL(id)) |
| ra_set_enable(id, 1); |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(ra_set_list_enable); |
| |
| int ra_set_list_disable(unsigned int *list, unsigned int num_list) |
| { |
| unsigned int id; |
| int i; |
| |
| for (i = num_list ; i > 0; i--) { |
| id = list[i-1]; |
| if (IS_USER_MUX(id) || IS_GATE(id)) |
| ra_set_value(id, 0); |
| else if (IS_PLL(id)) |
| ra_set_enable(id, 0); |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(ra_set_list_disable); |
| |
| void ra_set_pll_ops(unsigned int *list, |
| struct vclk_lut *lut, |
| unsigned int num_list, |
| struct vclk_trans_ops *ops) |
| { |
| unsigned int from, to; |
| int i; |
| bool trans; |
| |
| for (i = 0; i < num_list; i++) { |
| if (GET_TYPE(list[i]) != PLL_TYPE) |
| continue; |
| |
| to = lut->params[i]; |
| if (ops && ops->get_pll) |
| from = ops->get_pll(list[i]); |
| else |
| from = ra_get_value(list[i]); |
| |
| trans = ra_get_trans_opt(to, from); |
| if (trans == TRANS_IGNORE) |
| continue; |
| |
| if (ops && ops->set_pll) |
| ops->set_pll(list[i], to); |
| else |
| ra_set_value(list[i], to); |
| } |
| } |
| EXPORT_SYMBOL_GPL(ra_set_pll_ops); |
| |
| void ra_set_clk_by_type(unsigned int *list, |
| struct vclk_lut *lut, |
| unsigned int num_list, |
| unsigned int type, |
| enum trans_opt opt) |
| { |
| unsigned int from, to; |
| int i; |
| bool trans; |
| |
| for (i = 0; i < num_list; i++) { |
| if (GET_TYPE(list[i]) != type) |
| continue; |
| |
| to = lut->params[i]; |
| from = ra_get_value(list[i]); |
| trans = ra_get_trans_opt(to, from); |
| if (trans == TRANS_IGNORE) |
| continue; |
| if (opt != TRANS_FORCE && trans != opt) |
| continue; |
| |
| ra_set_value(list[i], to); |
| } |
| } |
| EXPORT_SYMBOL_GPL(ra_set_clk_by_type); |
| |
| void ra_set_clk_by_seq(unsigned int *list, |
| struct vclk_lut *lut, |
| struct vclk_seq *seq, |
| unsigned int num_list) |
| { |
| unsigned int from, to; |
| unsigned int i, idx; |
| bool trans; |
| |
| for (i = 0; i < num_list; i++) { |
| from = lut->params[i]; |
| to = ra_get_value(list[i]); |
| trans = ra_get_trans_opt(to, from); |
| if (seq[i].opt & trans) { |
| idx = seq[i].idx; |
| ra_set_value(list[idx], to); |
| } |
| } |
| } |
| EXPORT_SYMBOL_GPL(ra_set_clk_by_seq); |
| |
| int ra_compare_clk_list(unsigned int *params, |
| unsigned int *list, |
| unsigned int num_list) |
| { |
| struct cmucal_clk *clk; |
| unsigned int i, type; |
| |
| for (i = 0; i < num_list; i++) { |
| type = GET_TYPE(list[i]); |
| clk = cmucal_get_node(list[i]); |
| if (!clk) { |
| pr_err("%s:[%x]type : %x\n", __func__, list[i], type); |
| return -EVCLKINVAL; |
| } |
| switch (type) { |
| case DIV_TYPE: |
| if (params[i] != ra_get_div_mux(clk)) |
| goto mismatch; |
| break; |
| case MUX_TYPE: |
| if (params[i] != ra_get_div_mux(clk)) |
| goto mismatch; |
| break; |
| case PLL_TYPE: |
| if (params[i] != ra_get_pll_idx(clk)) |
| goto mismatch; |
| break; |
| default: |
| pr_err("Un-support clk type %x\n", list[i]); |
| return -EVCLKINVAL; |
| } |
| } |
| |
| return 0; |
| mismatch: |
| pr_debug("mis-match %s <%u %u> \n", |
| clk->name, params[i], ra_get_value(list[i])); |
| return -EVCLKNOENT; |
| } |
| EXPORT_SYMBOL_GPL(ra_compare_clk_list); |
| |
| unsigned int ra_set_rate_switch(struct vclk_switch *info, unsigned int rate_max) |
| { |
| struct switch_lut *lut; |
| unsigned int switch_rate = rate_max; |
| int i; |
| |
| for (i = 0; i < info->num_switches; i++) { |
| lut = &info->lut[i]; |
| if (rate_max >= lut->rate) { |
| if (info->src_div != EMPTY_CLK_ID) |
| ra_set_value(info->src_div, lut->div_value); |
| if (info->src_mux != EMPTY_CLK_ID) |
| ra_set_value(info->src_mux, lut->mux_value); |
| |
| switch_rate = lut->rate; |
| break; |
| } |
| } |
| |
| if (i == info->num_switches) |
| switch_rate = rate_max; |
| |
| return switch_rate; |
| } |
| EXPORT_SYMBOL_GPL(ra_set_rate_switch); |
| |
| void ra_select_switch_pll(struct vclk_switch *info, unsigned int value) |
| { |
| if (value) { |
| if (info->src_gate != EMPTY_CLK_ID) |
| ra_set_value(info->src_gate, value); |
| if (info->src_umux != EMPTY_CLK_ID) |
| ra_set_value(info->src_umux, value); |
| } |
| |
| ra_set_value(info->switch_mux, value); |
| |
| if (!value) { |
| if (info->src_umux != EMPTY_CLK_ID) |
| ra_set_value(info->src_umux, value); |
| if (info->src_gate != EMPTY_CLK_ID) |
| ra_set_value(info->src_gate, value); |
| } |
| } |
| EXPORT_SYMBOL_GPL(ra_select_switch_pll); |
| |
| struct cmucal_clk *ra_get_parent(unsigned int id) |
| { |
| struct cmucal_clk *clk, *parent; |
| struct cmucal_mux *mux; |
| unsigned int val; |
| |
| clk = cmucal_get_node(id); |
| if (!clk) |
| return NULL; |
| |
| switch (GET_TYPE(clk->id)) { |
| case FIXED_RATE_TYPE: |
| case FIXED_FACTOR_TYPE: |
| case PLL_TYPE: |
| case DIV_TYPE: |
| case GATE_TYPE: |
| if (clk->pid == EMPTY_CLK_ID) |
| parent = NULL; |
| else |
| parent = cmucal_get_node(clk->pid); |
| break; |
| case MUX_TYPE: |
| mux = to_mux_clk(clk); |
| val = ra_get_div_mux(clk); |
| parent = cmucal_get_node(mux->pid[val]); |
| break; |
| default: |
| parent = NULL; |
| break; |
| } |
| |
| return parent; |
| } |
| EXPORT_SYMBOL_GPL(ra_get_parent); |
| |
| int ra_set_rate(unsigned int id, unsigned int rate) |
| { |
| struct cmucal_clk *clk; |
| int ret = 0; |
| |
| clk = cmucal_get_node(id); |
| if (!clk) |
| return -EVCLKINVAL; |
| |
| switch (GET_TYPE(clk->id)) { |
| case PLL_TYPE: |
| ret = ra_set_pll(clk, rate/1000); |
| break; |
| case DIV_TYPE: |
| ret = ra_set_div_rate(clk, rate); |
| break; |
| case MUX_TYPE: |
| ret = ra_set_mux_rate(clk, rate); |
| break; |
| default: |
| pr_err("Un-support clk type %x, rate = %u\n", id, rate); |
| ret = -EVCLKINVAL; |
| break; |
| } |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(ra_set_rate); |
| |
| unsigned int ra_recalc_rate(unsigned int id) |
| { |
| struct cmucal_clk *clk; |
| unsigned int cur; |
| unsigned int clk_path[RECALC_MAX]; |
| unsigned int depth, ratio; |
| unsigned long rate; |
| |
| if (GET_TYPE(id) > GATE_TYPE) |
| return 0; |
| |
| cur = id; |
| |
| for (depth = 0; depth < RECALC_MAX; depth++) { |
| clk_path[depth] = cur; |
| clk = ra_get_parent(cur); |
| if (!clk) |
| break; |
| cur = clk->id; |
| } |
| |
| if (depth == RECALC_MAX) { |
| pr_err("recalc_rate overflow id:%x\n", id); |
| return 0; |
| } |
| |
| /* get root clock rate */ |
| if (depth > 0 && IS_PLL(clk_path[depth-1])) |
| rate = ra_get_value(clk_path[--depth]); |
| else |
| rate = ra_get_value(clk_path[depth]); |
| |
| if (!rate) |
| return 0; |
| |
| /* calc request clock node rate */ |
| for (; depth > 0; --depth) { |
| cur = clk_path[depth-1]; |
| |
| if (IS_FIXED_FACTOR(cur) || IS_DIV(cur)) |
| ratio = ra_get_value(cur) + 1; |
| else |
| continue; |
| |
| do_div(rate, ratio); |
| } |
| |
| return rate; |
| } |
| EXPORT_SYMBOL_GPL(ra_recalc_rate); |
| |
| int __init ra_init(void) |
| { |
| struct cmucal_clk *clk; |
| struct sfr_block *block; |
| int i; |
| int size; |
| struct cmucal_qch *qch; |
| |
| /* convert physical address to virtual address */ |
| size = cmucal_get_list_size(SFR_BLOCK_TYPE); |
| for (i = 0; i < size; i++) { |
| block = cmucal_get_sfr_node(i | SFR_BLOCK_TYPE); |
| if (block && block->pa) |
| block->va = ioremap(block->pa, block->size); |
| } |
| |
| size = cmucal_get_list_size(PLL_TYPE); |
| for (i = 0; i < size; i++) { |
| clk = cmucal_get_node(i | PLL_TYPE); |
| if (!clk) |
| continue; |
| |
| ra_get_pll_address(clk); |
| |
| ra_get_pll_rate_table(clk); |
| |
| pll_get_locktime(to_pll_clk(clk)); |
| } |
| |
| size = cmucal_get_list_size(MUX_TYPE); |
| for (i = 0; i < size; i++) { |
| clk = cmucal_get_node(i | MUX_TYPE); |
| if (!clk) |
| continue; |
| |
| if (GET_IDX(clk->offset_idx) != EMPTY_CAL_ID) |
| clk->paddr = ra_get_sfr_address(clk->offset_idx, |
| &clk->offset, |
| &clk->shift, |
| &clk->width); |
| else |
| clk->offset = NULL; |
| |
| if (GET_IDX(clk->status_idx) != EMPTY_CAL_ID) |
| ra_get_sfr_address(clk->status_idx, |
| &clk->status, |
| &clk->s_shift, |
| &clk->s_width); |
| else |
| clk->status = NULL; |
| |
| if (GET_IDX(clk->enable_idx) != EMPTY_CAL_ID) |
| ra_get_sfr_address(clk->enable_idx, |
| &clk->enable, |
| &clk->e_shift, |
| &clk->e_width); |
| else |
| clk->enable = NULL; |
| } |
| |
| size = cmucal_get_list_size(DIV_TYPE); |
| for (i = 0; i < size; i++) { |
| clk = cmucal_get_node(i | DIV_TYPE); |
| if (!clk) |
| continue; |
| |
| if (GET_IDX(clk->offset_idx) != EMPTY_CAL_ID) |
| clk->paddr = ra_get_sfr_address(clk->offset_idx, |
| &clk->offset, |
| &clk->shift, |
| &clk->width); |
| else |
| clk->offset = NULL; |
| |
| if (GET_IDX(clk->status_idx) != EMPTY_CAL_ID) |
| ra_get_sfr_address(clk->status_idx, |
| &clk->status, |
| &clk->s_shift, |
| &clk->s_width); |
| else |
| clk->status = NULL; |
| |
| if (GET_IDX(clk->enable_idx) != EMPTY_CAL_ID) |
| ra_get_sfr_address(clk->enable_idx, |
| &clk->enable, |
| &clk->e_shift, |
| &clk->e_width); |
| else |
| clk->enable = NULL; |
| } |
| |
| size = cmucal_get_list_size(GATE_TYPE); |
| for (i = 0; i < size; i++) { |
| clk = cmucal_get_node(i | GATE_TYPE); |
| if (!clk) |
| continue; |
| |
| if (GET_IDX(clk->offset_idx) != EMPTY_CAL_ID) |
| clk->paddr = ra_get_sfr_address(clk->offset_idx, |
| &clk->offset, |
| &clk->shift, |
| &clk->width); |
| else |
| clk->offset = NULL; |
| |
| if (GET_IDX(clk->status_idx) != EMPTY_CAL_ID) |
| ra_get_sfr_address(clk->status_idx, |
| &clk->status, |
| &clk->s_shift, |
| &clk->s_width); |
| else |
| clk->status = NULL; |
| |
| if (GET_IDX(clk->enable_idx) != EMPTY_CAL_ID) |
| ra_get_sfr_address(clk->enable_idx, |
| &clk->enable, |
| &clk->e_shift, |
| &clk->e_width); |
| else |
| clk->enable = NULL; |
| } |
| |
| size = cmucal_get_list_size(FIXED_RATE_TYPE); |
| for (i = 0; i < size; i++) { |
| clk = cmucal_get_node(i | FIXED_RATE_TYPE); |
| if (!clk) |
| continue; |
| |
| if (GET_IDX(clk->enable_idx) != EMPTY_CAL_ID) |
| ra_get_sfr_address(clk->enable_idx, |
| &clk->enable, |
| &clk->e_shift, |
| &clk->e_width); |
| else |
| clk->enable = NULL; |
| } |
| |
| size = cmucal_get_list_size(FIXED_FACTOR_TYPE); |
| for (i = 0; i < size; i++) { |
| clk = cmucal_get_node(i | FIXED_FACTOR_TYPE); |
| if (!clk) |
| continue; |
| |
| if (GET_IDX(clk->enable_idx) != EMPTY_CAL_ID) |
| ra_get_sfr_address(clk->enable_idx, |
| &clk->enable, |
| &clk->e_shift, |
| &clk->e_width); |
| else |
| clk->enable = NULL; |
| } |
| |
| size = cmucal_get_list_size(QCH_TYPE); |
| for (i = 0; i < size; i++) { |
| clk = cmucal_get_node(i | QCH_TYPE); |
| if (!clk) |
| continue; |
| |
| clk->paddr = ra_get_sfr_address(clk->offset_idx, |
| &clk->offset, |
| &clk->shift, |
| &clk->width); |
| |
| if (GET_IDX(clk->status_idx) != EMPTY_CAL_ID) |
| ra_get_sfr_address(clk->status_idx, |
| &clk->status, |
| &clk->s_shift, |
| &clk->s_width); |
| else |
| clk->status = NULL; |
| |
| if (GET_IDX(clk->enable_idx) != EMPTY_CAL_ID) |
| ra_get_sfr_address(clk->enable_idx, |
| &clk->enable, |
| &clk->e_shift, |
| &clk->e_width); |
| else |
| clk->enable = NULL; |
| |
| qch = to_qch(clk); |
| if (GET_IDX(qch->ignore_idx) != EMPTY_CAL_ID) |
| ra_get_sfr_address(qch->ignore_idx, |
| &qch->ignore, |
| &qch->ig_shift, |
| &qch->ig_width); |
| else |
| qch->ignore= NULL; |
| } |
| |
| size = cmucal_get_list_size(OPTION_TYPE); |
| for (i = 0; i < size; i++) { |
| clk = cmucal_get_node(i | OPTION_TYPE); |
| if (!clk) |
| continue; |
| |
| ra_get_sfr_address(clk->offset_idx, |
| &clk->offset, |
| &clk->shift, |
| &clk->width); |
| |
| if (GET_IDX(clk->enable_idx) != EMPTY_CAL_ID) |
| ra_get_sfr_address(clk->enable_idx, |
| &clk->enable, |
| &clk->e_shift, |
| &clk->e_width); |
| else |
| clk->enable = NULL; |
| } |
| |
| return 0; |
| } |