| /* |
| * Copyright (c) 2014 Samsung Electronics Co., Ltd. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This file contains the utility functions for composite clocks. |
| */ |
| |
| #include <linux/syscore_ops.h> |
| #include <linux/errno.h> |
| #include <linux/log2.h> |
| #include <linux/of.h> |
| #include <linux/delay.h> |
| #include <soc/samsung/cal-if.h> |
| |
| #include "composite.h" |
| |
| #define PLL_STAT_OSC (0x0) |
| #define PLL_STAT_PLL (0x1) |
| #define PLL_STAT_CHANGE (0x4) |
| #define PLL_STAT_MASK (0x7) |
| |
| #define to_comp_pll(_hw) container_of(_hw, struct samsung_composite_pll, hw) |
| #define to_comp_mux(_hw) container_of(_hw, struct samsung_composite_mux, hw) |
| #define to_comp_divider(_hw) container_of(_hw, struct samsung_composite_divider, hw) |
| #define to_usermux(_hw) container_of(_hw, struct clk_samsung_usermux, hw) |
| #define to_vclk(_hw) container_of(_hw, struct samsung_vclk, hw) |
| |
| static DEFINE_SPINLOCK(lock); |
| |
| #ifdef CONFIG_PM_SLEEP |
| void samsung_clk_save(void __iomem *base, |
| struct samsung_clk_reg_dump *rd, |
| unsigned int num_regs) |
| { |
| for (; num_regs > 0; --num_regs, ++rd) |
| rd->value = readl(base + rd->offset); |
| } |
| |
| void samsung_clk_restore(void __iomem *base, |
| const struct samsung_clk_reg_dump *rd, |
| unsigned int num_regs) |
| { |
| for (; num_regs > 0; --num_regs, ++rd) |
| writel(rd->value, base + rd->offset); |
| } |
| |
| struct samsung_clk_reg_dump *samsung_clk_alloc_reg_dump( |
| const unsigned long *rdump, |
| unsigned long nr_rdump) |
| { |
| struct samsung_clk_reg_dump *rd; |
| unsigned int i; |
| |
| rd = kcalloc(nr_rdump, sizeof(*rd), GFP_KERNEL); |
| if (!rd) |
| return NULL; |
| |
| for (i = 0; i < nr_rdump; ++i) |
| rd[i].offset = rdump[i]; |
| |
| return rd; |
| } |
| #endif /* CONFIG_PM_SLEEP */ |
| |
| /* setup the essentials required to support clock lookup using ccf */ |
| struct samsung_clk_provider *__init samsung_clk_init(struct device_node *np, |
| void __iomem *base, unsigned long nr_clks) |
| { |
| struct samsung_clk_provider *ctx = NULL; |
| struct clk **clk_table; |
| int i; |
| |
| if (!np) |
| return ctx; |
| |
| ctx = kzalloc(sizeof(struct samsung_clk_provider), GFP_KERNEL); |
| if (!ctx) |
| panic("could not allocate clock provider context.\n"); |
| |
| clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL); |
| if (!clk_table) |
| panic("could not allocate clock lookup table\n"); |
| |
| for (i = 0; i < nr_clks; ++i) |
| clk_table[i] = ERR_PTR(-ENOENT); |
| |
| ctx->reg_base = base; |
| ctx->clk_data.clks = clk_table; |
| ctx->clk_data.clk_num = nr_clks; |
| spin_lock_init(&ctx->lock); |
| |
| return ctx; |
| } |
| |
| void __init samsung_clk_of_add_provider(struct device_node *np, |
| struct samsung_clk_provider *ctx) |
| { |
| if (np) { |
| if (of_clk_add_provider(np, of_clk_src_onecell_get, |
| &ctx->clk_data)) |
| panic("could not register clk provider\n"); |
| } |
| } |
| |
| /* add a clock instance to the clock lookup table used for dt based lookup */ |
| static void samsung_clk_add_lookup(struct samsung_clk_provider *ctx, struct clk *clk, |
| unsigned int id) |
| { |
| if (ctx->clk_data.clks && id) |
| ctx->clk_data.clks[id] = clk; |
| } |
| |
| /* register a list of fixed clocks */ |
| void __init samsung_register_fixed_rate(struct samsung_clk_provider *ctx, |
| struct samsung_fixed_rate *list, unsigned int nr_clk) |
| { |
| struct clk *clk; |
| unsigned int idx, ret; |
| |
| for (idx = 0; idx < nr_clk; idx++, list++) { |
| clk = clk_register_fixed_rate(NULL, list->name, |
| list->parent_name, list->flags, list->fixed_rate); |
| if (IS_ERR(clk)) { |
| pr_err("%s: failed to register clock %s\n", __func__, |
| list->name); |
| continue; |
| } |
| |
| samsung_clk_add_lookup(ctx, clk, list->id); |
| |
| /* |
| * Unconditionally add a clock lookup for the fixed rate clocks. |
| * There are not many of these on any of Samsung platforms. |
| */ |
| ret = clk_register_clkdev(clk, list->name, NULL); |
| if (ret) |
| pr_err("%s: failed to register clock lookup for %s", |
| __func__, list->name); |
| } |
| } |
| |
| /* register a list of fixed factor clocks */ |
| void __init samsung_register_fixed_factor(struct samsung_clk_provider *ctx, |
| struct samsung_fixed_factor *list, unsigned int nr_clk) |
| { |
| struct clk *clk; |
| unsigned int idx, ret; |
| |
| for (idx = 0; idx < nr_clk; idx++, list++) { |
| clk = clk_register_fixed_factor(NULL, list->name, |
| list->parent_name, list->flags, list->mult, list->div); |
| if (IS_ERR(clk)) { |
| pr_err("%s: failed to register clock %s\n", __func__, |
| list->name); |
| continue; |
| } |
| |
| samsung_clk_add_lookup(ctx, clk, list->id); |
| |
| ret = clk_register_clkdev(clk, list->name, NULL); |
| if (ret) |
| pr_err("%s: failed to register clock lookup for %s", |
| __func__, list->name); |
| } |
| } |
| |
| /* |
| * obtain the clock speed of all external fixed clock sources from device |
| * tree and register it |
| */ |
| void __init samsung_register_of_fixed_ext(struct samsung_clk_provider *ctx, |
| struct samsung_fixed_rate *fixed_rate_clk, |
| unsigned int nr_fixed_rate_clk, |
| struct of_device_id *clk_matches) |
| { |
| const struct of_device_id *match; |
| struct device_node *np; |
| u32 freq; |
| |
| for_each_matching_node_and_match(np, clk_matches, &match) { |
| if (of_property_read_u32(np, "clock-frequency", &freq)) |
| continue; |
| fixed_rate_clk[(unsigned long)match->data].fixed_rate = freq; |
| } |
| samsung_register_fixed_rate(ctx, fixed_rate_clk, nr_fixed_rate_clk); |
| } |
| |
| /* operation functions for pll clocks */ |
| static const struct samsung_pll_rate_table *samsung_get_pll_settings( |
| struct samsung_composite_pll *pll, unsigned long rate) |
| { |
| const struct samsung_pll_rate_table *rate_table = pll->rate_table; |
| int i; |
| |
| for (i = 0; i < pll->rate_count; i++) { |
| if (rate == rate_table[i].rate) |
| return &rate_table[i]; |
| } |
| |
| return NULL; |
| } |
| |
| static long samsung_pll_round_rate(struct clk_hw *hw, |
| unsigned long drate, unsigned long *prate) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| const struct samsung_pll_rate_table *rate_table = pll->rate_table; |
| int i; |
| |
| /* Assumming rate_table is in descending order */ |
| for (i = 0; i < pll->rate_count; i++) { |
| if (drate >= rate_table[i].rate) |
| return rate_table[i].rate; |
| } |
| |
| /* return minimum supported value */ |
| return rate_table[i - 1].rate; |
| } |
| |
| static int samsung_composite_pll_is_enabled(struct clk_hw *hw) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| int set = pll->pll_flag & PLL_BYPASS ? 0 : 1; |
| unsigned int reg; |
| |
| reg = readl(pll->enable_reg); |
| |
| return (((reg >> pll->enable_bit) & 1) == set) ? 1 : 0; |
| } |
| |
| static int samsung_composite_pll_enable(struct clk_hw *hw) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| int set = pll->pll_flag & PLL_BYPASS ? 0 : 1; |
| unsigned int reg; |
| |
| /* Setting Enable register */ |
| reg = readl(pll->enable_reg); |
| if (set) |
| reg |= (1 << pll->enable_bit); |
| else |
| reg &= ~(1 << pll->enable_bit); |
| writel(reg, pll->enable_reg); |
| |
| /* setting CTRL mux register to 1 */ |
| reg = readl(pll->sel_reg); |
| reg |= (1 << pll->sel_bit); |
| writel(reg, pll->sel_reg); |
| |
| /* check status for mux setting */ |
| do { |
| cpu_relax(); |
| reg = readl(pll->stat_reg); |
| } while (((reg >> pll->stat_bit) & PLL_STAT_MASK) != PLL_STAT_PLL); |
| |
| return 0; |
| } |
| |
| static void samsung_composite_pll_disable(struct clk_hw *hw) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| int set = pll->pll_flag & PLL_BYPASS ? 0 : 1; |
| unsigned int reg; |
| |
| /* setting CTRL mux register to 0 */ |
| reg = readl(pll->sel_reg); |
| reg &= ~(1 << pll->sel_bit); |
| writel(reg, pll->sel_reg); |
| |
| /* check status for mux setting */ |
| do { |
| cpu_relax(); |
| reg = readl(pll->stat_reg); |
| } while (((reg >> pll->stat_bit) & PLL_STAT_MASK) != PLL_STAT_OSC); |
| |
| /* Setting Register */ |
| reg = readl(pll->enable_reg); |
| if (set) |
| reg &= ~(1 << pll->enable_bit); |
| else |
| reg |= (1 << pll->enable_bit); |
| |
| writel(reg, pll->enable_reg); |
| } |
| |
| static unsigned long samsung_pll145xx_recalc_rate(struct clk_hw *hw, |
| unsigned long parent_rate) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| u32 mdiv, pdiv, sdiv, pll_con; |
| u64 fvco = parent_rate; |
| |
| pll_con = readl(pll->con_reg); |
| mdiv = (pll_con >> PLL145XX_MDIV_SHIFT) & PLL145XX_MDIV_MASK; |
| pdiv = (pll_con >> PLL145XX_PDIV_SHIFT) & PLL145XX_PDIV_MASK; |
| sdiv = (pll_con >> PLL145XX_SDIV_SHIFT) & PLL145XX_SDIV_MASK; |
| /* Do calculation */ |
| fvco *= mdiv; |
| do_div(fvco, (pdiv << sdiv)); |
| |
| return (unsigned long)fvco; |
| } |
| |
| static inline bool samsung_pll145xx_mp_check(u32 mdiv, u32 pdiv, u32 pll_con) |
| { |
| return ((mdiv != ((pll_con >> PLL145XX_MDIV_SHIFT) & PLL145XX_MDIV_MASK)) || |
| (pdiv != ((pll_con >> PLL145XX_PDIV_SHIFT) & PLL145XX_PDIV_MASK))); |
| } |
| |
| static int samsung_pll145xx_set_rate(struct clk_hw *hw, unsigned long drate, |
| unsigned long prate) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| const struct samsung_pll_rate_table *rate; |
| u32 pll_con; |
| |
| rate = samsung_get_pll_settings(pll, drate); |
| if (!rate) { |
| pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__, |
| drate, __clk_get_name(hw->clk)); |
| return -EINVAL; |
| } |
| |
| pll_con = readl(pll->con_reg); |
| if (!(samsung_pll145xx_mp_check(rate->mdiv, rate->pdiv, pll_con))) { |
| if ((rate->sdiv) == ((pll_con >> PLL145XX_SDIV_SHIFT) & PLL145XX_SDIV_MASK)) |
| return 0; |
| /* In the case of changing S value only */ |
| pll_con &= ~(PLL145XX_SDIV_MASK << PLL145XX_SDIV_SHIFT); |
| pll_con |= rate->sdiv << PLL145XX_SDIV_SHIFT; |
| writel(pll_con, pll->con_reg); |
| |
| return 0; |
| } |
| |
| /* Set PLL lock time */ |
| writel(rate->pdiv * PLL145XX_LOCK_FACTOR, pll->lock_reg); |
| /* Change PLL PMS values */ |
| pll_con &= ~((PLL145XX_MDIV_MASK << PLL145XX_MDIV_SHIFT) | |
| (PLL145XX_PDIV_MASK << PLL145XX_PDIV_SHIFT) | |
| (PLL145XX_SDIV_MASK << PLL145XX_SDIV_SHIFT)); |
| pll_con |= (rate->mdiv << PLL145XX_MDIV_SHIFT) | |
| (rate->pdiv << PLL145XX_PDIV_SHIFT) | |
| (rate->sdiv << PLL145XX_SDIV_SHIFT); |
| /* To prevent instable PLL operation, preset ENABLE bit with 0 */ |
| pll_con &= ~BIT(31); |
| writel(pll_con, pll->con_reg); |
| |
| /* Set enable bit */ |
| pll_con |= BIT(31); |
| writel(pll_con, pll->con_reg); |
| |
| do { |
| cpu_relax(); |
| pll_con = readl(pll->con_reg); |
| } while (!(pll_con & (PLL145XX_LOCKED_MASK << PLL145XX_LOCKED_SHIFT))); |
| |
| return 0; |
| } |
| |
| |
| static unsigned long samsung_pll1460x_recalc_rate(struct clk_hw *hw, |
| unsigned long parent_rate) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| u32 mdiv, pdiv, sdiv, pll_con0, pll_con1; |
| s16 kdiv; |
| u64 fvco = parent_rate; |
| |
| pll_con0 = readl(pll->con_reg); |
| pll_con1 = readl(pll->con_reg + 4); |
| mdiv = (pll_con0 >> PLL1460X_MDIV_SHIFT) & PLL1460X_MDIV_MASK; |
| pdiv = (pll_con0 >> PLL1460X_PDIV_SHIFT) & PLL1460X_PDIV_MASK; |
| sdiv = (pll_con0 >> PLL1460X_SDIV_SHIFT) & PLL1460X_SDIV_MASK; |
| kdiv = (s16)((pll_con1 >> PLL1460X_KDIV_SHIFT) & PLL1460X_KDIV_MASK); |
| /* Do calculation */ |
| fvco *= (mdiv << 16) + kdiv; |
| do_div(fvco, (pdiv << sdiv)); |
| fvco >>= 16; |
| |
| return (unsigned long)fvco; |
| } |
| |
| static inline bool samsung_pll1460x_mpk_check(u32 mdiv, u32 pdiv, u32 kdiv, u32 pll_con0, u32 pll_con1) |
| { |
| return ((mdiv != ((pll_con0 >> PLL1460X_MDIV_SHIFT) & PLL1460X_MDIV_MASK)) || |
| (pdiv != ((pll_con0 >> PLL1460X_PDIV_SHIFT) & PLL1460X_PDIV_MASK)) || |
| (kdiv != ((pll_con1 >> PLL1460X_KDIV_SHIFT) & PLL1460X_KDIV_MASK))); |
| } |
| |
| static int samsung_pll1460x_set_rate(struct clk_hw *hw, unsigned long drate, |
| unsigned long parent_rate) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| u32 pll_con0, pll_con1; |
| const struct samsung_pll_rate_table *rate; |
| |
| rate = samsung_get_pll_settings(pll, drate); |
| if (!rate) { |
| pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__, |
| drate, __clk_get_name(hw->clk)); |
| return -EINVAL; |
| } |
| |
| pll_con0 = readl(pll->con_reg); |
| pll_con1 = readl(pll->con_reg + 4); |
| if (!(samsung_pll1460x_mpk_check(rate->mdiv, rate->pdiv, rate->kdiv, pll_con0, pll_con1))) { |
| if ((rate->sdiv) == ((pll_con0 >> PLL1460X_SDIV_SHIFT) & PLL1460X_SDIV_MASK)) |
| return 0; |
| /* In the case of changing S value only */ |
| pll_con0 &= ~(PLL1460X_SDIV_MASK << PLL1460X_SDIV_SHIFT); |
| pll_con0 |= (rate->sdiv << PLL1460X_SDIV_SHIFT); |
| writel(pll_con0, pll->con_reg); |
| |
| return 0; |
| } |
| |
| /* Set PLL lock time */ |
| writel(rate->pdiv * PLL1460X_LOCK_FACTOR, pll->lock_reg); |
| |
| pll_con1 &= ~(PLL1460X_KDIV_MASK << PLL1460X_KDIV_SHIFT); |
| pll_con1 |= (rate->kdiv << PLL1460X_KDIV_SHIFT); |
| writel(pll_con1, pll->con_reg + 4); |
| |
| pll_con0 &= ~((PLL1460X_MDIV_MASK << PLL1460X_MDIV_SHIFT) | |
| (PLL1460X_PDIV_MASK << PLL1460X_PDIV_SHIFT) | |
| (PLL1460X_SDIV_MASK << PLL1460X_SDIV_SHIFT)); |
| pll_con0 |= (rate->mdiv << PLL1460X_MDIV_SHIFT) | |
| (rate->pdiv << PLL1460X_PDIV_SHIFT) | |
| (rate->sdiv << PLL1460X_SDIV_SHIFT); |
| /* To prevent instable PLL operation, preset ENABLE bit with 0 */ |
| pll_con0 &= ~BIT(31); |
| writel(pll_con0, pll->con_reg); |
| |
| /* Set enable bit */ |
| pll_con0 |= BIT(31); |
| writel(pll_con0, pll->con_reg); |
| |
| /* Wait lock time */ |
| do { |
| cpu_relax(); |
| pll_con0 = readl(pll->con_reg); |
| } while (!(pll_con0 & (PLL1460X_LOCKED_MASK << PLL1460X_LOCKED_SHIFT))); |
| |
| return 0; |
| } |
| |
| static const struct clk_ops samsung_pll145xx_clk_ops = { |
| .recalc_rate = samsung_pll145xx_recalc_rate, |
| .set_rate = samsung_pll145xx_set_rate, |
| .round_rate = samsung_pll_round_rate, |
| .enable = samsung_composite_pll_enable, |
| .disable = samsung_composite_pll_disable, |
| .is_enabled = samsung_composite_pll_is_enabled, |
| }; |
| |
| static const struct clk_ops samsung_pll1460x_clk_ops = { |
| .recalc_rate = samsung_pll1460x_recalc_rate, |
| .set_rate = samsung_pll1460x_set_rate, |
| .round_rate = samsung_pll_round_rate, |
| .enable = samsung_composite_pll_enable, |
| .disable = samsung_composite_pll_disable, |
| .is_enabled = samsung_composite_pll_is_enabled, |
| }; |
| |
| static int samsung_composite_pll_enable_onchange(struct clk_hw *hw) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| int set = pll->pll_flag & PLL_BYPASS ? 0 : 1; |
| unsigned int reg; |
| |
| /* Setting Enable register */ |
| reg = readl(pll->enable_reg); |
| if (set) |
| reg |= (1 << pll->enable_bit); |
| else |
| reg &= ~(1 << pll->enable_bit); |
| writel(reg, pll->enable_reg); |
| |
| /* setting CTRL mux register to 1 */ |
| reg = readl(pll->sel_reg); |
| reg |= (1 << pll->sel_bit); |
| writel(reg, pll->sel_reg); |
| |
| /* check status for mux setting */ |
| do { |
| cpu_relax(); |
| reg = readl(pll->stat_reg); |
| } while (((reg >> pll->stat_bit) & PLL_STAT_MASK) == PLL_STAT_CHANGE); |
| |
| return 0; |
| } |
| |
| static void samsung_composite_pll_disable_onchange(struct clk_hw *hw) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| int set = pll->pll_flag & PLL_BYPASS ? 0 : 1; |
| unsigned int reg; |
| |
| /* setting CTRL mux register to 0 */ |
| reg = readl(pll->sel_reg); |
| reg &= ~(1 << pll->sel_bit); |
| writel(reg, pll->sel_reg); |
| |
| /* check status for mux setting */ |
| do { |
| cpu_relax(); |
| reg = readl(pll->stat_reg); |
| } while (((reg >> pll->stat_bit) & PLL_STAT_MASK) == PLL_STAT_CHANGE); |
| |
| /* Setting Register */ |
| reg = readl(pll->enable_reg); |
| if (set) |
| reg &= ~(1 << pll->enable_bit); |
| else |
| reg |= (1 << pll->enable_bit); |
| |
| writel(reg, pll->enable_reg); |
| } |
| |
| static unsigned long samsung_pll255xx_recalc_rate(struct clk_hw *hw, |
| unsigned long parent_rate) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| u32 mdiv, pdiv, sdiv, pll_con; |
| u64 fvco = parent_rate; |
| |
| pll_con = readl(pll->con_reg); |
| mdiv = (pll_con >> PLL255XX_MDIV_SHIFT) & PLL255XX_MDIV_MASK; |
| pdiv = (pll_con >> PLL255XX_PDIV_SHIFT) & PLL255XX_PDIV_MASK; |
| sdiv = (pll_con >> PLL255XX_SDIV_SHIFT) & PLL255XX_SDIV_MASK; |
| /* Do calculation */ |
| fvco *= mdiv; |
| do_div(fvco, (pdiv << sdiv)); |
| |
| return (unsigned long)fvco; |
| } |
| |
| static inline bool samsung_pll255xx_mp_check(u32 mdiv, u32 pdiv, u32 pll_con) |
| { |
| return ((mdiv != ((pll_con >> PLL255XX_MDIV_SHIFT) & PLL255XX_MDIV_MASK)) || |
| (pdiv != ((pll_con >> PLL255XX_PDIV_SHIFT) & PLL255XX_PDIV_MASK))); |
| } |
| |
| static int samsung_pll255xx_set_rate(struct clk_hw *hw, unsigned long drate, |
| unsigned long prate) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| const struct samsung_pll_rate_table *rate; |
| u32 pll_con; |
| |
| rate = samsung_get_pll_settings(pll, drate); |
| if (!rate) { |
| pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__, |
| drate, __clk_get_name(hw->clk)); |
| return -EINVAL; |
| } |
| |
| pll_con = readl(pll->con_reg); |
| if (!(samsung_pll255xx_mp_check(rate->mdiv, rate->pdiv, pll_con))) { |
| if ((rate->sdiv) == ((pll_con >> PLL255XX_SDIV_SHIFT) & PLL255XX_SDIV_MASK)) |
| return 0; |
| /* In the case of changing S value only */ |
| pll_con &= ~(PLL255XX_SDIV_MASK << PLL255XX_SDIV_SHIFT); |
| pll_con |= rate->sdiv << PLL255XX_SDIV_SHIFT; |
| writel(pll_con, pll->con_reg); |
| |
| return 0; |
| } |
| |
| /* Set PLL lock time */ |
| writel(rate->pdiv * PLL255XX_LOCK_FACTOR, pll->lock_reg); |
| /* Change PLL PMS values */ |
| pll_con &= ~((PLL255XX_MDIV_MASK << PLL255XX_MDIV_SHIFT) | |
| (PLL255XX_PDIV_MASK << PLL255XX_PDIV_SHIFT) | |
| (PLL255XX_SDIV_MASK << PLL255XX_SDIV_SHIFT)); |
| pll_con |= (rate->mdiv << PLL255XX_MDIV_SHIFT) | |
| (rate->pdiv << PLL255XX_PDIV_SHIFT) | |
| (rate->sdiv << PLL255XX_SDIV_SHIFT); |
| |
| /* To prevent unstable PLL operation, preset enable bit with 0 */ |
| pll_con &= ~BIT(31); |
| writel(pll_con, pll->con_reg); |
| |
| /* Set enable bit */ |
| pll_con |= BIT(31); |
| writel(pll_con, pll->con_reg); |
| |
| do { |
| cpu_relax(); |
| pll_con = readl(pll->con_reg); |
| } while (!(pll_con & (PLL255XX_LOCKED_MASK << PLL255XX_LOCKED_SHIFT))); |
| |
| return 0; |
| } |
| |
| /* register function for pll clocks */ |
| static const struct clk_ops samsung_pll255xx_clk_ops = { |
| .recalc_rate = samsung_pll255xx_recalc_rate, |
| .set_rate = samsung_pll255xx_set_rate, |
| .round_rate = samsung_pll_round_rate, |
| .enable = samsung_composite_pll_enable_onchange, |
| .disable = samsung_composite_pll_disable_onchange, |
| .is_enabled = samsung_composite_pll_is_enabled, |
| }; |
| |
| static unsigned long samsung_pll2650x_recalc_rate(struct clk_hw *hw, |
| unsigned long parent_rate) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| u32 mdiv, pdiv, sdiv, pll_con0, pll_con1; |
| s16 kdiv; |
| u64 fvco = parent_rate; |
| |
| pll_con0 = readl(pll->con_reg); |
| pll_con1 = readl(pll->con_reg + 4); |
| mdiv = (pll_con0 >> PLL2650X_MDIV_SHIFT) & PLL2650X_MDIV_MASK; |
| pdiv = (pll_con0 >> PLL2650X_PDIV_SHIFT) & PLL2650X_PDIV_MASK; |
| sdiv = (pll_con0 >> PLL2650X_SDIV_SHIFT) & PLL2650X_SDIV_MASK; |
| kdiv = (s16)((pll_con1 >> PLL2650X_KDIV_SHIFT) & PLL2650X_KDIV_MASK); |
| /* Do calculation */ |
| fvco *= (mdiv << 16) + kdiv; |
| do_div(fvco, (pdiv << sdiv)); |
| fvco >>= 16; |
| |
| return (unsigned long)fvco; |
| } |
| |
| static inline bool samsung_pll2650x_mpk_check(u32 mdiv, u32 pdiv, u32 kdiv, u32 pll_con0, u32 pll_con1) |
| { |
| return ((mdiv != ((pll_con0 >> PLL2650X_MDIV_SHIFT) & PLL2650X_MDIV_MASK)) || |
| (pdiv != ((pll_con0 >> PLL2650X_PDIV_SHIFT) & PLL2650X_PDIV_MASK)) || |
| (kdiv != ((pll_con1 >> PLL2650X_KDIV_SHIFT) & PLL2650X_KDIV_MASK))); |
| } |
| |
| static int samsung_pll2650x_set_rate(struct clk_hw *hw, unsigned long drate, |
| unsigned long parent_rate) |
| { |
| struct samsung_composite_pll *pll = to_comp_pll(hw); |
| u32 pll_con0, pll_con1; |
| const struct samsung_pll_rate_table *rate; |
| |
| rate = samsung_get_pll_settings(pll, drate); |
| if (!rate) { |
| pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__, |
| drate, __clk_get_name(hw->clk)); |
| return -EINVAL; |
| } |
| |
| pll_con0 = readl(pll->con_reg); |
| pll_con1 = readl(pll->con_reg + 4); |
| if (!(samsung_pll2650x_mpk_check(rate->mdiv, rate->pdiv, rate->kdiv, pll_con0, pll_con1))) { |
| if ((rate->sdiv) == ((pll_con0 >> PLL2650X_SDIV_SHIFT) & PLL2650X_SDIV_MASK)) |
| return 0; |
| /* In the case of changing S value only */ |
| pll_con0 &= ~(PLL2650X_SDIV_MASK << PLL2650X_SDIV_SHIFT); |
| pll_con0 |= (rate->sdiv << PLL2650X_SDIV_SHIFT); |
| writel(pll_con0, pll->con_reg); |
| |
| return 0; |
| } |
| |
| /* Set PLL lock time */ |
| writel(rate->pdiv * PLL2650X_LOCK_FACTOR, pll->lock_reg); |
| |
| pll_con1 &= ~(PLL2650X_KDIV_MASK << PLL2650X_KDIV_SHIFT); |
| pll_con1 |= (rate->kdiv << PLL2650X_KDIV_SHIFT); |
| writel(pll_con1, pll->con_reg + 4); |
| |
| pll_con0 &= ~((PLL2650X_MDIV_MASK << PLL2650X_MDIV_SHIFT) | |
| (PLL2650X_PDIV_MASK << PLL2650X_PDIV_SHIFT) | |
| (PLL2650X_SDIV_MASK << PLL2650X_SDIV_SHIFT)); |
| pll_con0 |= (rate->mdiv << PLL2650X_MDIV_SHIFT) | |
| (rate->pdiv << PLL2650X_PDIV_SHIFT) | |
| (rate->sdiv << PLL2650X_SDIV_SHIFT); |
| |
| /* To prevent unstable PLL operation, preset enable bit with 0 */ |
| pll_con0 &= ~BIT(31); |
| writel(pll_con0, pll->con_reg); |
| |
| /* Set enable bit */ |
| pll_con0 |= BIT(31); |
| writel(pll_con0, pll->con_reg); |
| |
| /* |
| * Wait lock time |
| * unit address translation : us to ms for mdelay |
| */ |
| mdelay(rate->pdiv * PLL2650X_LOCK_FACTOR / 1000); |
| |
| return 0; |
| } |
| |
| static const struct clk_ops samsung_pll2650x_clk_ops = { |
| .recalc_rate = samsung_pll2650x_recalc_rate, |
| .set_rate = samsung_pll2650x_set_rate, |
| .round_rate = samsung_pll_round_rate, |
| .enable = samsung_composite_pll_enable_onchange, |
| .disable = samsung_composite_pll_disable_onchange, |
| .is_enabled = samsung_composite_pll_is_enabled, |
| }; |
| |
| /* register function for pll clocks */ |
| static void _samsung_register_comp_pll(struct samsung_clk_provider *ctx, |
| struct samsung_composite_pll *list) |
| { |
| struct clk *clk; |
| static const char *pname[1] = {"fin_pll"}; |
| int len; |
| unsigned int ret = 0; |
| |
| if (list->rate_table) { |
| /* find count of rates in rate_table */ |
| for (len = 0; list->rate_table[len].rate != 0; ) |
| len++; |
| list->rate_count = len; } |
| else |
| list->rate_count = 0; |
| |
| if (list->type == pll_1450x) |
| clk = clk_register_composite(NULL, list->name, pname, 1, |
| NULL, NULL, |
| &list->hw, &samsung_pll145xx_clk_ops, |
| &list->hw, &samsung_pll145xx_clk_ops, list->flag); |
| else if (list->type == pll_1451x || list->type == pll_1452x) |
| clk = clk_register_composite(NULL, list->name, pname, 1, |
| NULL, NULL, |
| &list->hw, &samsung_pll145xx_clk_ops, |
| &list->hw, &samsung_pll145xx_clk_ops, list->flag); |
| else if (list->type == pll_1460x) |
| clk = clk_register_composite(NULL, list->name, pname, 1, |
| NULL, NULL, |
| &list->hw, &samsung_pll1460x_clk_ops, |
| &list->hw, &samsung_pll1460x_clk_ops, list->flag); |
| else if (list->type == pll_2551x || list->type == pll_2555x) |
| clk = clk_register_composite(NULL, list->name, pname, 1, |
| NULL, NULL, |
| &list->hw, &samsung_pll255xx_clk_ops, |
| &list->hw, &samsung_pll255xx_clk_ops, list->flag); |
| else if (list->type == pll_2650x) |
| clk = clk_register_composite(NULL, list->name, pname, 1, |
| NULL, NULL, |
| &list->hw, &samsung_pll2650x_clk_ops, |
| &list->hw, &samsung_pll2650x_clk_ops, list->flag); |
| else { |
| pr_err("%s: invalid pll type %d\n", __func__, list->type); |
| return; |
| } |
| |
| if (IS_ERR(clk)) { |
| pr_err("%s: failed to register pll clock %s\n", |
| __func__, list->name); |
| return; |
| } |
| |
| samsung_clk_add_lookup(ctx, clk, list->id); |
| |
| /* register a clock lookup only if a clock alias is specified */ |
| if (list->alias) { |
| ret = clk_register_clkdev(clk, list->alias, NULL); |
| if (ret) |
| pr_err("%s: failed to register lookup %s\n", |
| __func__, list->alias); |
| } |
| } |
| |
| void samsung_register_comp_pll(struct samsung_clk_provider *ctx, |
| struct samsung_composite_pll *list, unsigned int nr_pll) |
| { |
| int cnt; |
| |
| for (cnt = 0; cnt < nr_pll; cnt++) |
| _samsung_register_comp_pll(ctx, &list[cnt]); |
| } |
| |
| /* operation functions for mux clocks */ |
| static u8 samsung_mux_get_parent(struct clk_hw *hw) |
| { |
| struct samsung_composite_mux *mux = to_comp_mux(hw); |
| u32 val; |
| |
| val = readl(mux->sel_reg) >> mux->sel_bit; |
| val &= (BIT(mux->sel_width) - 1); |
| |
| return (u8)val; |
| } |
| |
| static int samsung_mux_set_parent(struct clk_hw *hw, u8 index) |
| { |
| struct samsung_composite_mux *mux = to_comp_mux(hw); |
| u32 val; |
| unsigned long flags = 0; |
| unsigned int timeout = 1000; |
| |
| if (mux->lock) |
| spin_lock_irqsave(mux->lock, flags); |
| |
| val = readl(mux->sel_reg); |
| val &= ~((BIT(mux->sel_width) - 1) << mux->sel_bit); |
| val |= index << mux->sel_bit; |
| writel(val, mux->sel_reg); |
| |
| if (mux->stat_reg) |
| do { |
| --timeout; |
| if (!timeout) { |
| pr_err("%s: failed to set parent %s.\n", |
| __func__, clk_hw_get_name(hw)); |
| pr_err("MUX_REG: %08x, MUX_STAT_REG: %08x\n", |
| readl(mux->sel_reg), readl(mux->stat_reg)); |
| if (mux->lock) |
| spin_unlock_irqrestore(mux->lock, flags); |
| return -ETIMEDOUT; |
| } |
| val = readl(mux->stat_reg); |
| val &= ((BIT(mux->stat_width) - 1) << mux->stat_bit); |
| } while (val != (index << mux->stat_bit)); |
| |
| if (mux->lock) |
| spin_unlock_irqrestore(mux->lock, flags); |
| |
| return 0; |
| } |
| |
| static const struct clk_ops samsung_composite_mux_ops = { |
| .get_parent = samsung_mux_get_parent, |
| .set_parent = samsung_mux_set_parent, |
| }; |
| |
| /* operation functions for mux clocks checking status with "on changing" */ |
| |
| static int samsung_mux_set_parent_onchange(struct clk_hw *hw, u8 index) |
| { |
| struct samsung_composite_mux *mux = to_comp_mux(hw); |
| u32 val; |
| unsigned long flags = 0; |
| unsigned int timeout = 1000; |
| |
| if (mux->lock) |
| spin_lock_irqsave(mux->lock, flags); |
| |
| val = readl(mux->sel_reg); |
| val &= ~((BIT(mux->sel_width) - 1) << mux->sel_bit); |
| val |= index << mux->sel_bit; |
| writel(val, mux->sel_reg); |
| |
| if (mux->stat_reg) |
| do { |
| --timeout; |
| if (!timeout) { |
| pr_err("%s: failed to set parent %s.\n", |
| __func__, clk_hw_get_name(hw)); |
| pr_err("MUX_REG: %08x, MUX_STAT_REG: %08x\n", |
| readl(mux->sel_reg), readl(mux->stat_reg)); |
| if (mux->lock) |
| spin_unlock_irqrestore(mux->lock, flags); |
| return -ETIMEDOUT; |
| } |
| val = readl(mux->stat_reg); |
| val &= ((BIT(mux->stat_width) - 1) << mux->stat_bit); |
| } while (val == PLL_STAT_CHANGE); |
| |
| if (mux->lock) |
| spin_unlock_irqrestore(mux->lock, flags); |
| |
| return 0; |
| } |
| static const struct clk_ops samsung_composite_mux_ops_onchange = { |
| .get_parent = samsung_mux_get_parent, |
| .set_parent = samsung_mux_set_parent_onchange, |
| }; |
| |
| /* register function for mux clock */ |
| static void _samsung_register_comp_mux(struct samsung_clk_provider *ctx, |
| struct samsung_composite_mux *list) |
| { |
| struct clk *clk; |
| unsigned int ret = 0; |
| |
| list->lock = &lock; |
| |
| if (!(list->flag & CLK_ON_CHANGING)) |
| clk = clk_register_composite(NULL, list->name, list->parents, list->num_parents, |
| &list->hw, &samsung_composite_mux_ops, |
| NULL, NULL, |
| NULL, NULL, list->flag); |
| else |
| clk = clk_register_composite(NULL, list->name, list->parents, list->num_parents, |
| &list->hw, &samsung_composite_mux_ops_onchange, |
| NULL, NULL, |
| NULL, NULL, list->flag); |
| |
| if (IS_ERR(clk)) { |
| pr_err("%s: failed to register mux clock %s\n", |
| __func__, list->name); |
| return; |
| } |
| |
| samsung_clk_add_lookup(ctx, clk, list->id); |
| |
| /* register a clock lookup only if a clock alias is specified */ |
| if (list->alias) { |
| ret = clk_register_clkdev(clk, list->alias, NULL); |
| if (ret) |
| pr_err("%s: failed to register lookup %s\n", |
| __func__, list->alias); |
| } |
| } |
| |
| void samsung_register_comp_mux(struct samsung_clk_provider *ctx, |
| struct samsung_composite_mux *list, unsigned int nr_mux) |
| { |
| int cnt; |
| |
| for (cnt = 0; cnt < nr_mux; cnt++) |
| _samsung_register_comp_mux(ctx, &list[cnt]); |
| } |
| |
| /* operation functions for divider clocks */ |
| static unsigned long samsung_divider_recalc_rate(struct clk_hw *hw, |
| unsigned long parent_rate) |
| { |
| struct samsung_composite_divider *divider = to_comp_divider(hw); |
| unsigned int val; |
| |
| val = readl(divider->rate_reg) >> divider->rate_bit; |
| val &= (1 << divider->rate_width) - 1; |
| val += 1; |
| if (!val) |
| return parent_rate; |
| |
| return parent_rate / val; |
| } |
| |
| static int samsung_divider_bestdiv(struct clk_hw *hw, unsigned long rate, |
| unsigned long *best_parent_rate) |
| { |
| struct samsung_composite_divider *divider = to_comp_divider(hw); |
| int i, bestdiv = 0; |
| unsigned long parent_rate, maxdiv, now, best = 0; |
| unsigned long parent_rate_saved = *best_parent_rate; |
| |
| if (!rate) |
| rate = 1; |
| |
| maxdiv = ((unsigned long)(1UL << (divider->rate_width)) - 1UL) + 1UL; |
| |
| if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) { |
| parent_rate = *best_parent_rate; |
| bestdiv = (parent_rate + rate - 1) / rate; |
| bestdiv = bestdiv == 0 ? 1 : bestdiv; |
| bestdiv = bestdiv > maxdiv ? maxdiv : bestdiv; |
| return bestdiv; |
| } |
| |
| maxdiv = min(ULONG_MAX / rate, maxdiv); |
| |
| for (i = 1; i <= maxdiv; i++) { |
| if (rate * i == parent_rate_saved) { |
| /* |
| * It's the most ideal case if the requested rate can be |
| * divided from parent clock without needing to change |
| * parent rate, so return the divider immediately. |
| */ |
| *best_parent_rate = parent_rate_saved; |
| return i; |
| } |
| parent_rate = clk_round_rate(clk_get_parent(hw->clk), |
| ((rate * i) + i - 1)); |
| now = parent_rate / i; |
| if (now <= rate && now > best) { |
| bestdiv = i; |
| best = now; |
| *best_parent_rate = parent_rate; |
| } |
| } |
| |
| if (!bestdiv) { |
| bestdiv = ((1 << (divider->rate_width)) - 1) + 1; |
| *best_parent_rate = clk_round_rate(clk_get_parent(hw->clk), 1); |
| } |
| |
| return bestdiv; |
| } |
| |
| static long samsung_divider_round_rate(struct clk_hw *hw, unsigned long rate, |
| unsigned long *prate) |
| { |
| int div = 1; |
| |
| div = samsung_divider_bestdiv(hw, rate, prate); |
| if (div == 0) { |
| pr_err("%s: divider value should not be %d\n", __func__, div); |
| div = 1; |
| } |
| |
| return *prate / div; |
| } |
| |
| static int samsung_divider_set_rate(struct clk_hw *hw, unsigned long rate, |
| unsigned long parent_rate) |
| { |
| struct samsung_composite_divider *divider = to_comp_divider(hw); |
| unsigned int div; |
| u32 val; |
| unsigned long flags = 0; |
| unsigned int timeout = 1000; |
| |
| div = (parent_rate / rate) - 1; |
| |
| if (div > ((1 << divider->rate_width) - 1)) |
| div = (1 << divider->rate_width) - 1; |
| |
| if (divider->lock) |
| spin_lock_irqsave(divider->lock, flags); |
| |
| val = readl(divider->rate_reg); |
| val &= ~(((1 << divider->rate_width) - 1) << divider->rate_bit); |
| val |= div << divider->rate_bit; |
| writel(val, divider->rate_reg); |
| |
| if (divider->stat_reg) |
| do { |
| --timeout; |
| if (!timeout) { |
| pr_err("%s: faild to set rate %s.\n", |
| __func__, clk_hw_get_name(hw)); |
| pr_err("DIV_REG: %08x, MUX_STAT_REG: %08x\n", |
| readl(divider->rate_reg), readl(divider->stat_reg)); |
| if (divider->lock) |
| spin_unlock_irqrestore(divider->lock, flags); |
| return -ETIMEDOUT; |
| } |
| val = readl(divider->stat_reg); |
| val &= BIT(divider->stat_width - 1) << divider->stat_bit; |
| } while (val); |
| |
| if (divider->lock) |
| spin_unlock_irqrestore(divider->lock, flags); |
| |
| return 0; |
| } |
| |
| static const struct clk_ops samsung_composite_divider_ops = { |
| .recalc_rate = samsung_divider_recalc_rate, |
| .round_rate = samsung_divider_round_rate, |
| .set_rate = samsung_divider_set_rate, |
| }; |
| |
| /* register function for divider clocks */ |
| static void _samsung_register_comp_divider(struct samsung_clk_provider *ctx, |
| struct samsung_composite_divider *list) |
| { |
| struct clk *clk; |
| unsigned int ret = 0; |
| |
| list->lock = &lock; |
| |
| clk = clk_register_composite(NULL, list->name, &list->parent_name, 1, |
| NULL, NULL, |
| &list->hw, &samsung_composite_divider_ops, |
| NULL, NULL, list->flag); |
| |
| if (IS_ERR(clk)) { |
| pr_err("%s: failed to register mux clock %s\n", |
| __func__, list->name); |
| return; |
| } |
| |
| samsung_clk_add_lookup(ctx, clk, list->id); |
| |
| /* register a clock lookup only if a clock alias is specified */ |
| if (list->alias) { |
| ret = clk_register_clkdev(clk, list->alias, NULL); |
| if (ret) |
| pr_err("%s: failed to register lookup %s\n", |
| __func__, list->alias); |
| } |
| } |
| |
| void samsung_register_comp_divider(struct samsung_clk_provider *ctx, |
| struct samsung_composite_divider *list, unsigned int nr_div) |
| { |
| int cnt; |
| |
| for (cnt = 0; cnt < nr_div; cnt++) |
| _samsung_register_comp_divider(ctx, &list[cnt]); |
| } |
| |
| struct dummy_gate_clk { |
| unsigned long offset; |
| u8 bit_idx; |
| struct clk *clk; |
| }; |
| |
| static struct dummy_gate_clk **gate_clk_list; |
| static unsigned int gate_clk_nr; |
| |
| int samsung_add_clk_gate_list(struct clk *clk, unsigned long offset, u8 bit_idx, const char *name) |
| { |
| struct dummy_gate_clk *tmp_clk; |
| |
| if (!clk || !offset) |
| return -EINVAL; |
| |
| tmp_clk = kzalloc(sizeof(struct dummy_gate_clk), GFP_KERNEL); |
| if (!tmp_clk) { |
| pr_err("%s: fail to alloc for gate_clk\n", __func__); |
| return -ENOMEM; |
| } |
| |
| tmp_clk->offset = offset; |
| tmp_clk->bit_idx = bit_idx; |
| tmp_clk->clk = clk; |
| |
| gate_clk_list[gate_clk_nr] = tmp_clk; |
| |
| gate_clk_nr++; |
| |
| return 0; |
| } |
| |
| struct clk *samsung_clk_get_by_reg(unsigned long offset, u8 bit_idx) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < gate_clk_nr; i++) { |
| if (gate_clk_list[i]->offset == offset) { |
| if (gate_clk_list[i]->bit_idx == bit_idx) |
| return gate_clk_list[i]->clk; |
| } |
| } |
| |
| pr_err("%s: Fail to get clk by register offset\n", __func__); |
| |
| return 0; |
| } |
| |
| /* existing register function for gate clocks */ |
| static struct clk * __init _samsung_register_gate( |
| struct samsung_clk_provider *ctx, struct samsung_gate *list) |
| { |
| struct clk *clk; |
| unsigned int ret = 0; |
| |
| clk = clk_register_gate(NULL, list->name, list->parent_name, |
| list->flag, list->reg, list->bit, |
| list->flag, &lock); |
| |
| if (IS_ERR(clk)) { |
| pr_err("%s: failed to register clock %s\n", __func__, |
| list->name); |
| return 0; |
| } |
| |
| samsung_clk_add_lookup(ctx, clk, list->id); |
| |
| if (list->alias) { |
| ret = clk_register_clkdev(clk, list->alias, NULL); |
| if (ret) |
| pr_err("%s: failed to register lookup %s\n", |
| __func__, list->alias); |
| } |
| |
| return clk; |
| } |
| |
| void __init samsung_register_gate(struct samsung_clk_provider *ctx, |
| struct samsung_gate *list, unsigned int nr_gate) |
| { |
| int cnt; |
| struct clk *clk; |
| bool gate_list_fail = false; |
| unsigned int gate_enable_nr = 0; |
| struct clk **gate_enable_list; |
| |
| gate_clk_list = kzalloc(sizeof(struct dummy_gate_clk *) * nr_gate, GFP_KERNEL); |
| if (!gate_clk_list) { |
| pr_err("%s: can not alloc for gate clock list\n", __func__); |
| gate_list_fail = true; |
| } |
| |
| gate_enable_list = kzalloc(sizeof(struct clk *) * nr_gate, GFP_KERNEL); |
| |
| if (!gate_enable_list) { |
| pr_err("%s: can not alloc for enable gate clock list\n", __func__); |
| return ; |
| } |
| |
| for (cnt = 0; cnt < nr_gate; cnt++) { |
| clk = _samsung_register_gate(ctx, &list[cnt]); |
| |
| if (((&list[cnt])->flag & CLK_GATE_ENABLE) && gate_enable_list) { |
| gate_enable_list[gate_enable_nr] = clk; |
| gate_enable_nr++; |
| } |
| |
| /* Make list for gate clk to used by samsung_clk_get_by_reg */ |
| if (!gate_list_fail) |
| samsung_add_clk_gate_list(clk, (unsigned long)(list[cnt].reg), list[cnt].bit, list[cnt].name); |
| } |
| |
| /* |
| * Enable for not controlling gate clocks |
| */ |
| for (cnt = 0; cnt < gate_enable_nr; cnt++) |
| clk_prepare_enable(gate_enable_list[cnt]); |
| |
| if (gate_enable_list) |
| kfree(gate_enable_list); |
| } |
| |
| /* operation functions for usermux clocks */ |
| static int samsung_usermux_is_enabled(struct clk_hw *hw) |
| { |
| struct clk_samsung_usermux *usermux = to_usermux(hw); |
| u32 val; |
| |
| val = readl(usermux->sel_reg); |
| val &= BIT(usermux->sel_bit); |
| |
| return val ? 1 : 0; |
| } |
| |
| static int samsung_usermux_enable(struct clk_hw *hw) |
| { |
| struct clk_samsung_usermux *usermux = to_usermux(hw); |
| u32 val; |
| unsigned long flags = 0; |
| unsigned int timeout = 1000; |
| |
| if (usermux->lock) |
| spin_lock_irqsave(usermux->lock, flags); |
| |
| val = readl(usermux->sel_reg); |
| val &= ~(1 << usermux->sel_bit); |
| val |= (1 << usermux->sel_bit); |
| writel(val, usermux->sel_reg); |
| |
| if (usermux->stat_reg) |
| do { |
| --timeout; |
| if (!timeout) { |
| pr_err("%s: failed to enable clock %s.\n", |
| __func__, clk_hw_get_name(hw)); |
| if (usermux->lock) |
| spin_unlock_irqrestore(usermux->lock, flags); |
| return -ETIMEDOUT; |
| } |
| val = readl(usermux->stat_reg); |
| val &= BIT(2) << usermux->stat_bit; |
| } while (val); |
| |
| if (usermux->lock) |
| spin_unlock_irqrestore(usermux->lock, flags); |
| |
| return 0; |
| } |
| |
| static void samsung_usermux_disable(struct clk_hw *hw) |
| { |
| struct clk_samsung_usermux *usermux = to_usermux(hw); |
| u32 val; |
| unsigned long flags = 0; |
| unsigned int timeout = 1000; |
| |
| if (usermux->lock) |
| spin_lock_irqsave(usermux->lock, flags); |
| |
| val = readl(usermux->sel_reg); |
| val &= ~(1 << usermux->sel_bit); |
| writel(val, usermux->sel_reg); |
| |
| if (usermux->stat_reg) |
| do { |
| --timeout; |
| if (!timeout) { |
| pr_err("%s: failed to disable clock %s.\n", |
| __func__, clk_hw_get_name(hw)); |
| if (usermux->lock) |
| spin_unlock_irqrestore(usermux->lock, flags); |
| return; |
| } |
| val = readl(usermux->stat_reg); |
| val &= BIT(2) << usermux->stat_bit; |
| } while (val); |
| |
| if (usermux->lock) |
| spin_unlock_irqrestore(usermux->lock, flags); |
| } |
| |
| static const struct clk_ops samsung_usermux_ops = { |
| .enable = samsung_usermux_enable, |
| .disable = samsung_usermux_disable, |
| .is_enabled = samsung_usermux_is_enabled, |
| }; |
| |
| /* register function for usermux clocks */ |
| static struct clk * __init _samsung_register_comp_usermux(struct samsung_usermux *list) |
| { |
| struct clk_samsung_usermux *usermux; |
| struct clk *clk; |
| struct clk_init_data init; |
| |
| usermux = kzalloc(sizeof(struct clk_samsung_usermux), GFP_KERNEL); |
| if (!usermux) { |
| pr_err("%s: could not allocate usermux clk\n", __func__); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| init.name = list->name; |
| init.ops = &samsung_usermux_ops; |
| init.flags = list->flag | CLK_IS_BASIC; |
| init.parent_names = (list->parent_name ? &list->parent_name : NULL); |
| init.num_parents = (list->parent_name ? 1 : 0); |
| |
| usermux->sel_reg = list->sel_reg; |
| usermux->sel_bit = list->sel_bit; |
| usermux->stat_reg = list->stat_reg; |
| usermux->stat_bit = list->stat_bit; |
| usermux->flag = 0; |
| usermux->lock = &lock; |
| usermux->hw.init = &init; |
| |
| clk = clk_register(NULL, &usermux->hw); |
| |
| if (IS_ERR(clk)) |
| kfree(usermux); |
| |
| return clk; |
| } |
| |
| void __init samsung_register_usermux(struct samsung_clk_provider *ctx, |
| struct samsung_usermux *list, unsigned int nr_usermux) |
| { |
| struct clk *clk; |
| int cnt; |
| unsigned int ret = 0; |
| |
| for (cnt = 0; cnt < nr_usermux; cnt++) { |
| clk = _samsung_register_comp_usermux(&list[cnt]); |
| if (IS_ERR(clk)) { |
| pr_err("%s: failed to register clock %s\n", __func__, |
| (&list[cnt])->name); |
| return; |
| } |
| |
| samsung_clk_add_lookup(ctx, clk, (&list[cnt])->id); |
| |
| if ((&list[cnt])->alias) { |
| ret = clk_register_clkdev(clk, (&list[cnt])->alias, NULL); |
| if (ret) |
| pr_err("%s: failed to register lookup %s\n", |
| __func__, (&list[cnt])->alias); |
| } |
| } |
| } |
| |
| /** |
| * Operations for virtual clock used in cal |
| * When cal is used to set clocks, following operations will be executed. |
| */ |
| int cal_vclk_enable(struct clk_hw *hw) |
| { |
| struct samsung_vclk *vclk = to_vclk(hw); |
| unsigned long flags = 0; |
| int ret = 0; |
| |
| if ((vclk->flags & VCLK_GATE) && !(vclk->flags & VCLK_QCH_DIS)) |
| return 0; |
| |
| if (vclk->lock) |
| spin_lock_irqsave(vclk->lock, flags); |
| |
| dbg_snapshot_clk(hw, __func__, 1, DSS_FLAG_IN); |
| /* Call cal api to enable virtual clock */ |
| ret = cal_clk_enable(vclk->id); |
| if (ret) { |
| pr_err("[CAL]%s failed %d %d.\n", __func__, vclk->id, ret); |
| dbg_snapshot_clk(hw, __func__, 1, DSS_FLAG_ON); |
| if (vclk->lock) |
| spin_unlock_irqrestore(vclk->lock, flags); |
| return -EAGAIN; |
| } |
| dbg_snapshot_clk(hw, __func__, 1, DSS_FLAG_OUT); |
| |
| if (vclk->lock) |
| spin_unlock_irqrestore(vclk->lock, flags); |
| |
| return 0; |
| } |
| |
| void cal_vclk_disable(struct clk_hw *hw) |
| { |
| struct samsung_vclk *vclk = to_vclk(hw); |
| unsigned long flags = 0; |
| int ret = 0; |
| |
| if ((vclk->flags & VCLK_GATE) && !(vclk->flags & VCLK_QCH_DIS)) |
| return ; |
| |
| if (vclk->lock) |
| spin_lock_irqsave(vclk->lock, flags); |
| |
| dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_IN); |
| /* Call cal api to disable virtual clock */ |
| ret = cal_clk_disable(vclk->id); |
| if (ret) { |
| pr_err("[CAL]%s failed %d %d.\n", __func__, vclk->id, ret); |
| dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_ON); |
| if (vclk->lock) |
| spin_unlock_irqrestore(vclk->lock, flags); |
| return; |
| } |
| dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_OUT); |
| |
| if (vclk->lock) |
| spin_unlock_irqrestore(vclk->lock, flags); |
| } |
| |
| int cal_vclk_is_enabled(struct clk_hw *hw) |
| { |
| struct samsung_vclk *vclk = to_vclk(hw); |
| int ret = 0; |
| |
| /* |
| * Call cal api to check whether clock is enabled or not |
| * Spinlock is not needed because only read operation will |
| * be executed |
| */ |
| ret = cal_clk_is_enabled(vclk->id); |
| |
| return ret; |
| } |
| |
| unsigned long cal_vclk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) |
| { |
| struct samsung_vclk *vclk = to_vclk(hw); |
| unsigned long ret = 0; |
| |
| dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_IN); |
| /* Call cal api to recalculate rate */ |
| ret = cal_clk_getrate(vclk->id); |
| dbg_snapshot_clk(hw, __func__, ret, DSS_FLAG_OUT); |
| |
| return ret; |
| } |
| |
| unsigned long cal_vclk_gate_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) |
| { |
| struct samsung_vclk *vclk; |
| struct clk_hw *parent; |
| unsigned long ret = 0; |
| |
| dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_IN); |
| parent = clk_hw_get_parent(hw); |
| if (parent) { |
| vclk = to_vclk(parent); |
| /* call cal api to recalculate rate */ |
| ret = cal_clk_getrate(vclk->id); |
| } |
| dbg_snapshot_clk(hw, __func__, ret, DSS_FLAG_OUT); |
| |
| return ret; |
| } |
| |
| long cal_vclk_round_rate(struct clk_hw *hw, unsigned long rate, |
| unsigned long *prate) |
| { |
| /* round_rate ops is not needed when using cal */ |
| return (long)rate; |
| } |
| |
| int cal_vclk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long prate) |
| { |
| struct samsung_vclk *vclk = to_vclk(hw); |
| unsigned long flags = 0; |
| int ret = 0; |
| |
| if (vclk->lock) |
| spin_lock_irqsave(vclk->lock, flags); |
| |
| dbg_snapshot_clk(hw, __func__, rate, DSS_FLAG_IN); |
| /* Call cal api to set rate of clock */ |
| ret = cal_clk_setrate(vclk->id, rate); |
| if (ret) { |
| pr_err("[CAL]%s failed %d %lu %d.\n", __func__, |
| vclk->id, rate, ret); |
| dbg_snapshot_clk(hw, __func__, rate, DSS_FLAG_ON); |
| if (vclk->lock) |
| spin_unlock_irqrestore(vclk->lock, flags); |
| return -EAGAIN; |
| } |
| dbg_snapshot_clk(hw, __func__, rate, DSS_FLAG_OUT); |
| |
| if (vclk->lock) |
| spin_unlock_irqrestore(vclk->lock, flags); |
| |
| return ret; |
| } |
| |
| unsigned long cal_vclk_dfs_sw_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) |
| { |
| struct samsung_vclk *vclk = to_vclk(hw); |
| unsigned long ret = 0; |
| |
| /* Call cal api to recalculate rate */ |
| ret = cal_dfs_cached_get_rate(vclk->id); |
| |
| return ret; |
| } |
| unsigned long cal_vclk_dfs_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) |
| { |
| struct samsung_vclk *vclk = to_vclk(hw); |
| unsigned long ret = 0; |
| |
| /* Call cal api to recalculate rate */ |
| ret = cal_dfs_get_rate(vclk->id); |
| |
| return ret; |
| } |
| |
| int cal_vclk_dfs_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long prate) |
| { |
| struct samsung_vclk *vclk = to_vclk(hw); |
| unsigned long flags = 0; |
| int ret = 0; |
| |
| if (vclk->lock) |
| spin_lock_irqsave(vclk->lock, flags); |
| |
| dbg_snapshot_clk(hw, __func__, rate, DSS_FLAG_IN); |
| /* Call cal api to set rate of clock */ |
| ret = cal_dfs_set_rate(vclk->id, rate); |
| if (ret) { |
| pr_err("[CAL]%s failed %d %lu %d.\n", __func__, |
| vclk->id, rate, ret); |
| dbg_snapshot_clk(hw, __func__, rate, DSS_FLAG_ON); |
| if (vclk->lock) |
| spin_unlock_irqrestore(vclk->lock, flags); |
| return -EAGAIN; |
| } |
| dbg_snapshot_clk(hw, __func__, rate, DSS_FLAG_OUT); |
| |
| if (vclk->lock) |
| spin_unlock_irqrestore(vclk->lock, flags); |
| |
| return ret; |
| } |
| |
| int cal_vclk_dfs_set_rate_switch(struct clk_hw *hw, unsigned long rate, unsigned long prate) |
| { |
| struct samsung_vclk *vclk = to_vclk(hw); |
| unsigned long flags = 0; |
| int ret = 0; |
| |
| if (vclk->lock) |
| spin_lock_irqsave(vclk->lock, flags); |
| |
| /* Call cal api to set rate of clock */ |
| ret = cal_dfs_set_rate_switch(vclk->id, rate); |
| if (ret) { |
| pr_err("[CAL]%s failed.\n", __func__); |
| if (vclk->lock) |
| spin_unlock_irqrestore(vclk->lock, flags); |
| return -EAGAIN; |
| } |
| |
| if (vclk->lock) |
| spin_unlock_irqrestore(vclk->lock, flags); |
| |
| return ret; |
| } |
| |
| static int cal_vclk_qch_init(struct clk_hw *hw) |
| { |
| struct samsung_vclk *vclk = to_vclk(hw); |
| int ret = 0; |
| |
| if (vclk->flags & VCLK_QCH_EN) |
| ret = cal_qch_init(vclk->id, 1); |
| else if (vclk->flags & VCLK_QCH_DIS) |
| ret = cal_qch_init(vclk->id, 0); |
| |
| return ret; |
| } |
| |
| int cal_vclk_qactive_enable(struct clk_hw *hw) |
| { |
| struct samsung_vclk *vclk = to_vclk(hw); |
| unsigned long flags = 0; |
| unsigned int reg; |
| |
| if (!(vclk->flags & VCLK_QACTIVE)) |
| return 0; |
| |
| if (!vclk->addr) |
| return 0; |
| |
| if (vclk->lock) |
| spin_lock_irqsave(vclk->lock, flags); |
| |
| dbg_snapshot_clk(hw, __func__, 1, DSS_FLAG_IN); |
| |
| reg = readl(vclk->addr); |
| reg &= ~(vclk->mask); |
| reg |= vclk->val; |
| writel(reg, vclk->addr); |
| |
| dbg_snapshot_clk(hw, __func__, 1, DSS_FLAG_OUT); |
| |
| if (vclk->lock) |
| spin_unlock_irqrestore(vclk->lock, flags); |
| |
| return 0; |
| } |
| |
| void cal_vclk_qactive_disable(struct clk_hw *hw) |
| { |
| struct samsung_vclk *vclk = to_vclk(hw); |
| unsigned long flags = 0; |
| unsigned int reg; |
| |
| if (!(vclk->flags & VCLK_QACTIVE)) |
| return ; |
| |
| if (!vclk->addr) |
| return ;; |
| |
| if (vclk->lock) |
| spin_lock_irqsave(vclk->lock, flags); |
| |
| dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_IN); |
| |
| reg = readl(vclk->addr); |
| reg &= ~(vclk->mask); |
| writel(reg, vclk->addr); |
| |
| dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_OUT); |
| |
| if (vclk->lock) |
| spin_unlock_irqrestore(vclk->lock, flags); |
| } |
| |
| static const struct clk_ops samsung_vclk_ops = { |
| .enable = cal_vclk_enable, |
| .disable = cal_vclk_disable, |
| .is_enabled = cal_vclk_is_enabled, |
| .recalc_rate = cal_vclk_recalc_rate, |
| .round_rate = cal_vclk_round_rate, |
| .set_rate = cal_vclk_set_rate, |
| }; |
| |
| static const struct clk_ops samsung_vclk_dfs_ops = { |
| .recalc_rate = cal_vclk_dfs_recalc_rate, |
| .round_rate = cal_vclk_round_rate, |
| .set_rate = cal_vclk_dfs_set_rate, |
| }; |
| |
| static const struct clk_ops samsung_vclk_dfs_sw_ops = { |
| .recalc_rate = cal_vclk_dfs_sw_recalc_rate, |
| .round_rate = cal_vclk_round_rate, |
| .set_rate = cal_vclk_dfs_set_rate_switch, |
| }; |
| |
| static const struct clk_ops samsung_vclk_gate_ops = { |
| .enable = cal_vclk_enable, |
| .disable = cal_vclk_disable, |
| .is_enabled = cal_vclk_is_enabled, |
| .recalc_rate = cal_vclk_gate_recalc_rate, |
| }; |
| |
| static const struct clk_ops samsung_vclk_qactive_ops = { |
| .enable = cal_vclk_qactive_enable, |
| .disable = cal_vclk_qactive_disable, |
| }; |
| |
| static struct clk * __init _samsung_register_vclk(struct init_vclk *list) |
| { |
| struct samsung_vclk *vclk; |
| struct clk *clk; |
| struct clk_init_data init; |
| |
| vclk = kzalloc(sizeof(struct samsung_vclk), GFP_KERNEL); |
| if (!vclk) { |
| pr_err("%s: could not allocate struct vclk\n", __func__); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| init.name = list->name; |
| if (list->vclk_flags & VCLK_DFS) |
| init.ops = &samsung_vclk_dfs_ops; |
| else if (list->vclk_flags & VCLK_DFS_SWITCH) |
| init.ops = &samsung_vclk_dfs_sw_ops; |
| else if (list->vclk_flags & VCLK_GATE) |
| init.ops = &samsung_vclk_gate_ops; |
| else if (list->vclk_flags & VCLK_QACTIVE) { |
| init.ops = &samsung_vclk_qactive_ops; |
| vclk->addr = ioremap(list->addr, 4); |
| vclk->mask = list->mask; |
| vclk->val = list->val; |
| } else |
| init.ops = &samsung_vclk_ops; |
| init.flags = list->flags | (CLK_IS_BASIC | CLK_GET_RATE_NOCACHE | CLK_IGNORE_UNUSED); |
| init.parent_names = (list->parent ? &list->parent : NULL); |
| init.num_parents = (list->parent ? 1 : 0); |
| vclk->id = list->calid; |
| /* Flags for vclk are not defined yet */ |
| vclk->flags = list->vclk_flags; |
| vclk->lock = &lock; |
| vclk->hw.init = &init; |
| clk = clk_register(NULL, &vclk->hw); |
| |
| if (IS_ERR(clk)) |
| kfree(vclk); |
| |
| return clk; |
| } |
| |
| void __init samsung_register_vclk(struct samsung_clk_provider *ctx, |
| struct init_vclk *list, unsigned int nr_vclk) |
| { |
| struct clk *clk; |
| int cnt; |
| unsigned int ret = 0; |
| |
| for (cnt = 0; cnt < nr_vclk; cnt++) { |
| clk = _samsung_register_vclk(&list[cnt]); |
| if (IS_ERR(clk)) { |
| pr_err("%s: failed to register virtual clock %s\n", |
| __func__, (&list[cnt])->name); |
| continue; |
| } |
| |
| samsung_clk_add_lookup(ctx, clk, (&list[cnt])->id); |
| |
| /* Additional array of clocks for finding struct clk */ |
| if ((&list[cnt])->alias) { |
| ret = clk_register_clkdev(clk, (&list[cnt])->alias, NULL); |
| if (ret) |
| pr_err("%s: failed to register lookup %s\n", |
| __func__, (&list[cnt])->alias); |
| } |
| |
| cal_vclk_qch_init(__clk_get_hw(clk)); |
| } |
| } |