summaryrefslogtreecommitdiff
path: root/drivers/clk/imx/clk-mux-scu.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/imx/clk-mux-scu.c')
-rw-r--r--drivers/clk/imx/clk-mux-scu.c445
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;
+}