/* * 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 #include #include #include #include #include #include #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) #define BIT32(nr) (1 << (nr)) static DEFINE_SPINLOCK(lock); extern bool clk_exynos_skip_hw; #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 < (unsigned int)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 * 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 < (unsigned int)nr_clks; ++i) clk_table[i] = ERR_PTR(-ENOENT); ctx->reg_base = base; ctx->clk_data.clks = clk_table; ctx->clk_data.clk_num = (unsigned int)nr_clks; spin_lock_init(&ctx->lock); return ctx; } void 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 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 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 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 &= ~BIT32(31); writel(pll_con, pll->con_reg); /* Set enable bit */ pll_con |= BIT32(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 &= ~BIT32(31); writel(pll_con0, pll->con_reg); /* Set enable bit */ pll_con0 |= BIT32(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 &= ~BIT32(31); writel(pll_con, pll->con_reg); /* Set enable bit */ pll_con |= BIT32(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 &= ~BIT32(31); writel(pll_con0, pll->con_reg); /* Set enable bit */ pll_con0 |= BIT32(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, }; /* 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 &= (BIT32(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 &= ~((BIT32(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 &= ((BIT32(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 &= ~((BIT32(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 &= ((BIT32(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, }; /* 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, now, best = 0; unsigned long parent_rate_saved = *best_parent_rate; int maxdiv; if (!rate) rate = 1; maxdiv = ((1 << (divider->rate_width)) - 1) + 1; 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(UINT_MAX / (unsigned int)rate, (unsigned int)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 &= BIT32(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, }; 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 * _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 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 &= BIT32(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 &= BIT32(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 &= BIT32(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 * _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; 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 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 (clk_exynos_skip_hw) return 0; if ((vclk->flags & VCLK_GATE) && !(vclk->flags & VCLK_QCH_DIS)) return 0; if (vclk->lock) spin_lock_irqsave(vclk->lock, flags); if ((vclk->flags & VCLK_GATE) && (vclk->flags & VCLK_QCH_DIS)) cal_qch_init(vclk->id, 0); 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 (clk_exynos_skip_hw) return ; if ((vclk->flags & VCLK_GATE) && !(vclk->flags & VCLK_QCH_DIS)) return ; if (vclk->lock) spin_lock_irqsave(vclk->lock, flags); if ((vclk->flags & VCLK_GATE) && (vclk->flags & VCLK_QCH_DIS)) cal_qch_init(vclk->id, 0); 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; if (clk_exynos_skip_hw) return 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; if (clk_exynos_skip_hw) return 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; if (clk_exynos_skip_hw) return 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 (clk_exynos_skip_hw) return 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; if (clk_exynos_skip_hw) return 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; if (clk_exynos_skip_hw) return 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 (clk_exynos_skip_hw) return 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 (clk_exynos_skip_hw) return 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 (clk_exynos_skip_hw) return 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 (clk_exynos_skip_hw) return 0; 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 (clk_exynos_skip_hw) return ; 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 * _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_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 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)); } } MODULE_LICENSE("GPL");