diff options
Diffstat (limited to 'drivers/clk/imx/clk-mux-scu.c')
-rw-r--r-- | drivers/clk/imx/clk-mux-scu.c | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/drivers/clk/imx/clk-mux-scu.c b/drivers/clk/imx/clk-mux-scu.c new file mode 100644 index 000000000000..ac1c8bc385bd --- /dev/null +++ b/drivers/clk/imx/clk-mux-scu.c @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pm_domain.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <soc/imx8/sc/sci.h> + +#include "clk-imx8.h" + +/* + * DOC: basic adjustable multiplexer clock that cannot gate + * + * Traits of this clock: + * prepare - clk_prepare only ensures that parents are prepared + * enable - clk_enable only ensures that parents are enabled + * rate - rate is only affected by parent switching. No clk_set_rate support + * parent - parent is adjustable through clk_set_parent + */ + +struct clk_mux_scu { + struct clk_hw hw; + void __iomem *reg; + u32 *table; + u32 mask; + u8 shift; + u8 flags; + u32 val; + bool update; + spinlock_t *lock; + char *pd_name; + struct generic_pm_domain *pd; +}; + +struct clk_mux_gpr_scu { + struct clk_hw hw; + sc_rsrc_t rsrc_id; + sc_ctrl_t gpr_id; +}; + +struct clk_mux2_scu { + struct clk_hw hw; + sc_rsrc_t rsrc_id; + sc_pm_clk_t clk_type; +}; + +#define to_clk_mux_scu(_hw) container_of(_hw, struct clk_mux_scu, hw) +#define to_clk_mux_gpr_scu(_hw) container_of(_hw, struct clk_mux_gpr_scu, hw) +#define to_clk_mux2_scu(_hw) container_of(_hw, struct clk_mux2_scu, hw) + +/* Get the power domain associated with the clock from the device tree. */ +static void populate_mux_pd(struct clk_mux_scu *clk) +{ + struct device_node *np; + struct of_phandle_args pd_args; + + np = of_find_node_by_name(NULL, clk->pd_name); + if (np) { + pd_args.np = np; + pd_args.args_count = 0; + clk->pd = genpd_get_from_provider(&pd_args); + if (IS_ERR(clk->pd)) + pr_warn("%s: failed to get pd\n", __func__); + } +} + +static int check_mux_pd(struct clk_mux_scu *mux) +{ + if (!ccm_ipc_handle) + return -1; + + if (mux->pd == NULL && mux->pd_name) + populate_mux_pd(mux); + + if (IS_ERR_OR_NULL(mux->pd)) + return -1; + + if (mux->pd->status != GPD_STATE_ACTIVE) + return -1; + + return 0; +} + +static u8 clk_mux_get_parent_scu(struct clk_hw *hw) +{ + struct clk_mux_scu *mux = to_clk_mux_scu(hw); + int num_parents = clk_hw_get_num_parents(hw); + u32 val; + + /* + * FIXME need a mux-specific flag to determine if val is bitwise or numeric + * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1 + * to 0x7 (index starts at one) + * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so + * val = 0x4 really means "bit 2, index starts at bit 0" + */ + val = mux->val >> mux->shift; + val &= mux->mask; + + if (mux->table) { + int i; + + for (i = 0; i < num_parents; i++) + if (mux->table[i] == val) + return i; + return -EINVAL; + } + + if (val && (mux->flags & CLK_MUX_INDEX_BIT)) + val = ffs(val) - 1; + + if (val && (mux->flags & CLK_MUX_INDEX_ONE)) + val--; + + if (val >= num_parents) + return -EINVAL; + + return val; +} + +static int clk_mux_prepare_scu(struct clk_hw *hw) +{ + struct clk_mux_scu *mux = to_clk_mux_scu(hw); + unsigned long flags = 0; + int ret; + + ret = check_mux_pd(mux); + if (ret) + return ret; + + if (mux->lock) + spin_lock_irqsave(mux->lock, flags); + + if (mux->update) { + clk_writel(mux->val, mux->reg); + mux->update = 0; + } + + if (mux->lock) + spin_unlock_irqrestore(mux->lock, flags); + + return 0; +} + +static int clk_mux_set_parent_scu(struct clk_hw *hw, u8 index) +{ + struct clk_mux_scu *mux = to_clk_mux_scu(hw); + unsigned long flags = 0; + int ret; + + ret = check_mux_pd(mux); + + if (mux->table) { + index = mux->table[index]; + } else { + if (mux->flags & CLK_MUX_INDEX_BIT) + index = 1 << index; + if (mux->flags & CLK_MUX_INDEX_ONE) + index++; + } + + if (mux->lock) + spin_lock_irqsave(mux->lock, flags); + + if (mux->flags & CLK_MUX_HIWORD_MASK) { + mux->val = mux->mask << (mux->shift + 16); + } else { + mux->val &= ~(mux->mask << mux->shift); + } + mux->val |= index << mux->shift; + mux->update = (ret != 0); + + if (ret == 0) + clk_writel(mux->val, mux->reg); + + if (mux->lock) + spin_unlock_irqrestore(mux->lock, flags); + + return 0; +} + +const struct clk_ops clk_mux_scu_ops = { + .prepare = clk_mux_prepare_scu, + .get_parent = clk_mux_get_parent_scu, + .set_parent = clk_mux_set_parent_scu, + .determine_rate = __clk_mux_determine_rate, +}; + +const struct clk_ops clk_mux_ro_scu_ops = { + .get_parent = clk_mux_get_parent_scu, +}; + +struct clk *clk_register_mux_table_scu(struct device *dev, const char *name, + const char **parent_names, u8 num_parents, unsigned long flags, + void __iomem *reg, u8 shift, u32 mask, + u8 clk_mux_flags, u32 *table, spinlock_t *lock, + const char *pd_name) +{ + struct clk_mux_scu *mux; + struct clk *clk; + struct clk_init_data init; + u8 width = 0; + + if (clk_mux_flags & CLK_MUX_HIWORD_MASK) { + width = fls(mask) - ffs(mask) + 1; + if (width + shift > 16) { + pr_err("mux value exceeds LOWORD field\n"); + return ERR_PTR(-EINVAL); + } + } + + /* allocate the mux */ + mux = kzalloc(sizeof(struct clk_mux_scu), GFP_KERNEL); + if (!mux) { + pr_err("%s: could not allocate mux clk\n", __func__); + return ERR_PTR(-ENOMEM); + } + + init.name = name; + if (clk_mux_flags & CLK_MUX_READ_ONLY) + init.ops = &clk_mux_ro_scu_ops; + else + init.ops = &clk_mux_scu_ops; + init.flags = flags | CLK_IS_BASIC; + init.parent_names = parent_names; + init.num_parents = num_parents; + + /* struct clk_mux_scu assignments */ + mux->reg = reg; + mux->shift = shift; + mux->mask = mask; + mux->flags = clk_mux_flags; + mux->lock = lock; + mux->table = table; + mux->hw.init = &init; + mux->pd_name = NULL; + if (pd_name) { + mux->pd_name = kzalloc(strlen(pd_name) + 1, GFP_KERNEL); + strcpy(mux->pd_name, pd_name); + } + + clk = clk_register(dev, &mux->hw); + + if (IS_ERR(clk)) { + kfree(mux->pd_name); + kfree(mux); + } + + return clk; +} + +struct clk *clk_register_mux_scu(struct device *dev, const char *name, + const char **parent_names, u8 num_parents, unsigned long flags, + void __iomem *reg, u8 shift, u8 width, + u8 clk_mux_flags, spinlock_t *lock, + const char *pd_name) +{ + u32 mask = BIT(width) - 1; + + return clk_register_mux_table_scu(dev, name, parent_names, num_parents, + flags, reg, shift, mask, clk_mux_flags, + NULL, lock, pd_name); +} + +void clk_unregister_mux_scu(struct clk *clk) +{ + struct clk_mux_scu *mux; + struct clk_hw *hw; + + hw = __clk_get_hw(clk); + if (!hw) + return; + + mux = to_clk_mux_scu(hw); + + clk_unregister(clk); + kfree(mux); +} + +static u8 clk_mux_gpr_scu_get_parent(struct clk_hw *hw) +{ + struct clk_mux_gpr_scu *gpr_mux = to_clk_mux_gpr_scu(hw); + u32 val = 0; + + if (!ccm_ipc_handle) + return 0; + + sc_misc_get_control(ccm_ipc_handle, + gpr_mux->rsrc_id, gpr_mux->gpr_id, &val); + + return (u8)val; +} + +static int clk_mux_gpr_scu_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_mux_gpr_scu *gpr_mux = to_clk_mux_gpr_scu(hw); + + if (!ccm_ipc_handle) + return -1; + + sc_misc_set_control(ccm_ipc_handle, + gpr_mux->rsrc_id, gpr_mux->gpr_id, index); + + return 0; +} + +static const struct clk_ops clk_mux_gpr_scu_ops = { + .get_parent = clk_mux_gpr_scu_get_parent, + .set_parent = clk_mux_gpr_scu_set_parent, +}; + +struct clk *clk_register_mux_gpr_scu(struct device *dev, const char *name, + const char **parents, int num_parents, spinlock_t *lock, + sc_rsrc_t rsrc_id, sc_ctrl_t gpr_id) +{ + struct clk_mux_gpr_scu *gpr_scu_mux; + struct clk *clk; + struct clk_init_data init; + + if (!imx8_clk_is_resource_owned(rsrc_id)) { + pr_debug("skip clk %s rsrc %d not owned\n", name, rsrc_id); + return ERR_PTR(-ENODEV); + } + + if (rsrc_id >= SC_R_LAST) + return NULL; + + if (gpr_id >= SC_C_LAST) + return NULL; + + gpr_scu_mux = kzalloc(sizeof(struct clk_mux_gpr_scu), GFP_KERNEL); + if (!gpr_scu_mux) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &clk_mux_gpr_scu_ops; + init.parent_names = parents; + init.num_parents = num_parents; + init.flags |= CLK_SET_PARENT_NOCACHE; + + gpr_scu_mux->hw.init = &init; + gpr_scu_mux->rsrc_id = rsrc_id; + gpr_scu_mux->gpr_id = gpr_id; + + clk = clk_register(NULL, &gpr_scu_mux->hw); + if (IS_ERR(clk)) + kfree(gpr_scu_mux); + + return clk; +} + +static u8 clk_mux2_scu_get_parent(struct clk_hw *hw) +{ + struct clk_mux2_scu *mux = to_clk_mux2_scu(hw); + sc_pm_clk_parent_t parent; + sc_err_t ret; + + if (!ccm_ipc_handle) + return -EBUSY; + + ret = sc_pm_get_clock_parent(ccm_ipc_handle, mux->rsrc_id, + mux->clk_type, &parent); + if (ret != SC_ERR_NONE) + return -EINVAL; + + return (u8)parent; +} + +static int clk_mux2_scu_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_mux2_scu *mux = to_clk_mux2_scu(hw); + sc_err_t ret; + + if (!ccm_ipc_handle) + return -EBUSY; + + ret = sc_pm_set_clock_parent(ccm_ipc_handle, mux->rsrc_id, + mux->clk_type, index); + if (ret != SC_ERR_NONE) + return -EINVAL; + + return 0; +} + +static const struct clk_ops clk_mux2_scu_ops = { + .get_parent = clk_mux2_scu_get_parent, + .set_parent = clk_mux2_scu_set_parent, +}; + + +struct clk *clk_register_mux2_scu(struct device *dev, const char *name, + const char **parents, int num_parents, + unsigned long flags, sc_rsrc_t rsrc_id, + sc_pm_clk_t clk_type) +{ + struct clk_mux2_scu *mux; + struct clk *clk; + struct clk_init_data init; + + if (!imx8_clk_is_resource_owned(rsrc_id)) { + pr_debug("skip clk %s rsrc %d not owned\n", name, rsrc_id); + return ERR_PTR(-ENODEV); + } + + if (rsrc_id >= SC_R_LAST) + return ERR_PTR(-EINVAL); + + mux = kzalloc(sizeof(struct clk_mux2_scu), GFP_KERNEL); + if (!mux) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &clk_mux2_scu_ops; + init.parent_names = parents; + init.num_parents = num_parents; + init.flags = flags |= CLK_SET_PARENT_NOCACHE; + + mux->hw.init = &init; + mux->rsrc_id = rsrc_id; + mux->clk_type = clk_type; + + clk = clk_register(NULL, &mux->hw); + if (IS_ERR(clk)) + kfree(mux); + + return clk; +} |