| /* |
| * Copyright (C) 2016 Freescale Semiconductor, Inc. |
| * Copyright 2017-2018 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-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 gatable clock which can gate and ungate it's ouput |
| * |
| * Traits of this clock: |
| * prepare - clk_(un)prepare only ensures parent is (un)prepared |
| * enable - clk_enable and clk_disable are functional & control gating |
| * rate - inherits rate from parent. No clk_set_rate support |
| * parent - fixed parent. No clk_set_parent support |
| */ |
| |
| #define CLK_GATE_SCU_HW_SW_EN (BIT(0) | BIT(1)) |
| #define CLK_GATE_SCU_SW_EN BIT(1) |
| |
| struct clk_gate_scu { |
| struct clk_hw hw; |
| void __iomem *reg; |
| u8 bit_idx; |
| bool hw_gate; |
| u8 flags; |
| spinlock_t *lock; |
| sc_rsrc_t rsrc_id; |
| sc_pm_clk_t clk_type; |
| }; |
| |
| struct clk_gate2_scu { |
| struct clk_hw hw; |
| void __iomem *reg; |
| u8 bit_idx; |
| u8 flags; |
| spinlock_t *lock; |
| char *pd_name; |
| struct generic_pm_domain *pd; |
| }; |
| |
| struct clk_gate3_scu { |
| struct clk_hw hw; |
| spinlock_t *lock; |
| sc_rsrc_t rsrc_id; |
| sc_ctrl_t gpr_id; |
| bool invert; |
| }; |
| |
| #define to_clk_gate_scu(_hw) container_of(_hw, struct clk_gate_scu, hw) |
| #define to_clk_gate2_scu(_hw) container_of(_hw, struct clk_gate2_scu, hw) |
| #define to_clk_gate3_scu(_hw) container_of(_hw, struct clk_gate3_scu, hw) |
| |
| /* Write to the LPCG bits. */ |
| static int clk_gate_scu_enable(struct clk_hw *hw) |
| { |
| struct clk_gate_scu *gate = to_clk_gate_scu(hw); |
| u32 reg; |
| |
| if (!ccm_ipc_handle) |
| return -1; |
| |
| if (gate->reg) { |
| reg = readl(gate->reg); |
| if (gate->hw_gate) |
| reg |= (CLK_GATE_SCU_HW_SW_EN << gate->bit_idx); |
| else |
| reg |= (CLK_GATE_SCU_SW_EN << gate->bit_idx); |
| writel(reg, gate->reg); |
| } |
| |
| return 0; |
| } |
| |
| /* Write to the LPCG bits. */ |
| static void clk_gate_scu_disable(struct clk_hw *hw) |
| { |
| struct clk_gate_scu *gate = to_clk_gate_scu(hw); |
| u32 reg; |
| |
| if (!ccm_ipc_handle) |
| return; |
| |
| if (gate->reg) { |
| reg = readl(gate->reg); |
| if (gate->hw_gate) |
| reg &= ~(CLK_GATE_SCU_HW_SW_EN << gate->bit_idx); |
| else |
| reg &= ~(CLK_GATE_SCU_SW_EN << gate->bit_idx); |
| writel(reg, gate->reg); |
| } |
| } |
| |
| static int clk_gate_scu_prepare(struct clk_hw *hw) |
| { |
| struct clk_gate_scu *gate = to_clk_gate_scu(hw); |
| sc_err_t sci_err = SC_ERR_NONE; |
| |
| if (!ccm_ipc_handle) |
| return -1; |
| |
| /* Enable the clock at the DSC slice level */ |
| sci_err = sc_pm_clock_enable(ccm_ipc_handle, gate->rsrc_id, |
| gate->clk_type, true, gate->hw_gate); |
| |
| if (sci_err != SC_ERR_NONE) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static void clk_gate_scu_unprepare(struct clk_hw *hw) |
| { |
| struct clk_gate_scu *gate = to_clk_gate_scu(hw); |
| sc_err_t sci_err; |
| |
| if (!ccm_ipc_handle) |
| return; |
| |
| sci_err = sc_pm_clock_enable(ccm_ipc_handle, gate->rsrc_id, |
| gate->clk_type, false, false); |
| if (sci_err) |
| pr_err("clk gate scu unprepare clk fail!\n"); |
| } |
| |
| static unsigned long clk_gate_scu_recalc_rate(struct clk_hw *hw, |
| unsigned long parent_rate) |
| { |
| struct clk_gate_scu *clk = to_clk_gate_scu(hw); |
| sc_err_t sci_err; |
| sc_pm_clock_rate_t rate = 0; |
| |
| if (!ccm_ipc_handle) |
| return 0; |
| |
| sci_err = sc_pm_get_clock_rate(ccm_ipc_handle, clk->rsrc_id, |
| clk->clk_type, &rate); |
| |
| return sci_err ? 0 : rate; |
| } |
| |
| static struct clk_ops clk_gate_scu_ops = { |
| .prepare = clk_gate_scu_prepare, |
| .unprepare = clk_gate_scu_unprepare, |
| .enable = clk_gate_scu_enable, |
| .disable = clk_gate_scu_disable, |
| .recalc_rate = clk_gate_scu_recalc_rate, |
| }; |
| |
| struct clk *clk_register_gate_scu(struct device *dev, const char *name, |
| const char *parent_name, unsigned long flags, |
| u8 clk_gate_scu_flags, spinlock_t *lock, |
| sc_rsrc_t rsrc_id, sc_pm_clk_t clk_type, |
| void __iomem *reg, u8 bit_idx, bool hw_gate) |
| { |
| struct clk_gate_scu *gate; |
| 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); |
| } |
| |
| gate = kzalloc(sizeof(struct clk_gate_scu), GFP_KERNEL); |
| if (!gate) |
| return ERR_PTR(-ENOMEM); |
| |
| /* struct clk_gate_scu assignments */ |
| gate->flags = clk_gate_scu_flags; |
| gate->lock = lock; |
| gate->rsrc_id = rsrc_id; |
| gate->clk_type = clk_type; |
| if (reg != NULL) |
| gate->reg = ioremap((phys_addr_t)reg, SZ_64K); |
| else |
| gate->reg = NULL; |
| gate->bit_idx = bit_idx; |
| gate->hw_gate = hw_gate; |
| |
| init.name = name; |
| init.ops = &clk_gate_scu_ops; |
| init.flags = flags; |
| init.parent_names = parent_name ? &parent_name : NULL; |
| init.num_parents = parent_name ? 1 : 0; |
| |
| gate->hw.init = &init; |
| |
| clk = clk_register(dev, &gate->hw); |
| if (IS_ERR(clk)) { |
| iounmap(gate->reg); |
| kfree(gate); |
| } |
| |
| return clk; |
| } |
| |
| /* Get the power domain associated with the clock from the device tree. */ |
| static void populate_gate_pd(struct clk_gate2_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__); |
| } |
| } |
| |
| /* Write to the LPCG bits. */ |
| static int clk_gate2_scu_enable(struct clk_hw *hw) |
| { |
| struct clk_gate2_scu *gate = to_clk_gate2_scu(hw); |
| u32 reg; |
| |
| if (!ccm_ipc_handle) |
| return -1; |
| |
| if (gate->pd == NULL && gate->pd_name) |
| populate_gate_pd(gate); |
| |
| if (IS_ERR_OR_NULL(gate->pd)) |
| return -1; |
| |
| if (gate->pd->status != GPD_STATE_ACTIVE) |
| return -1; |
| |
| if (gate->reg) { |
| reg = readl(gate->reg); |
| reg |= (0x2 << gate->bit_idx); |
| writel(reg, gate->reg); |
| } |
| |
| return 0; |
| } |
| |
| /* Write to the LPCG bits. */ |
| static void clk_gate2_scu_disable(struct clk_hw *hw) |
| { |
| struct clk_gate2_scu *gate = to_clk_gate2_scu(hw); |
| u32 reg; |
| |
| if (!ccm_ipc_handle) |
| return; |
| |
| if (gate->pd == NULL && gate->pd_name) |
| populate_gate_pd(gate); |
| |
| if (IS_ERR_OR_NULL(gate->pd)) |
| return; |
| |
| if (gate->pd->status != GPD_STATE_ACTIVE) |
| return; |
| |
| if (gate->reg) { |
| reg = readl(gate->reg); |
| reg &= ~(0x2 << gate->bit_idx); |
| writel(reg, gate->reg); |
| } |
| } |
| |
| static int clk_gate2_scu_is_enabled(struct clk_hw *hw) |
| { |
| struct clk_gate2_scu *gate = to_clk_gate2_scu(hw); |
| u32 val; |
| |
| if (gate->pd == NULL && gate->pd_name) |
| populate_gate_pd(gate); |
| |
| if (IS_ERR_OR_NULL(gate->pd)) |
| return 0; |
| |
| if (gate->pd->status != GPD_STATE_ACTIVE) |
| return 0; |
| |
| if (gate->reg) { |
| val = readl(gate->reg); |
| |
| if (((val >> gate->bit_idx) & 2) == 2) |
| return 1; |
| } |
| return 0; |
| } |
| |
| |
| static struct clk_ops clk_gate2_scu_ops = { |
| .enable = clk_gate2_scu_enable, |
| .disable = clk_gate2_scu_disable, |
| .is_enabled = clk_gate2_scu_is_enabled, |
| }; |
| |
| struct clk *clk_register_gate2_scu(struct device *dev, const char *name, |
| const char *parent_name, unsigned long flags, |
| void __iomem *reg, u8 bit_idx, |
| u8 clk_gate_flags, spinlock_t *lock, const char *pd_name) |
| { |
| struct clk_gate2_scu *gate; |
| struct clk *clk; |
| struct clk_init_data init; |
| |
| gate = kzalloc(sizeof(struct clk_gate2_scu), GFP_KERNEL); |
| if (!gate) |
| return ERR_PTR(-ENOMEM); |
| |
| /* struct clk_gate_scu assignments */ |
| gate->flags = clk_gate_flags; |
| gate->lock = lock; |
| if (reg != NULL) |
| gate->reg = ioremap((phys_addr_t)reg, SZ_64K); |
| else |
| gate->reg = NULL; |
| gate->bit_idx = bit_idx; |
| gate->pd = NULL; |
| gate->pd_name = NULL; |
| if (pd_name) { |
| gate->pd_name = kzalloc(strlen(pd_name) + 1, GFP_KERNEL); |
| strcpy(gate->pd_name, pd_name); |
| } |
| |
| init.name = name; |
| init.ops = &clk_gate2_scu_ops; |
| init.flags = flags; |
| init.parent_names = parent_name ? &parent_name : NULL; |
| init.num_parents = parent_name ? 1 : 0; |
| |
| gate->hw.init = &init; |
| |
| clk = clk_register(dev, &gate->hw); |
| if (IS_ERR(clk)) { |
| iounmap(gate->reg); |
| kfree(gate->pd_name); |
| kfree(gate); |
| } |
| |
| return clk; |
| } |
| |
| static int clk_gate3_scu_prepare(struct clk_hw *hw) |
| { |
| struct clk_gate3_scu *gate = to_clk_gate3_scu(hw); |
| uint32_t val; |
| |
| if (!ccm_ipc_handle) |
| return -1; |
| |
| val = (gate->invert) ? 0 : 1; |
| |
| return sc_misc_set_control(ccm_ipc_handle, |
| gate->rsrc_id, gate->gpr_id, val); |
| } |
| |
| /* Write to the LPCG bits. */ |
| static void clk_gate3_scu_unprepare(struct clk_hw *hw) |
| { |
| struct clk_gate3_scu *gate = to_clk_gate3_scu(hw); |
| uint32_t val; |
| |
| if (!ccm_ipc_handle) |
| return; |
| |
| val = (gate->invert) ? 1 : 0; |
| sc_misc_set_control(ccm_ipc_handle, gate->rsrc_id, gate->gpr_id, val); |
| } |
| |
| static int clk_gate3_scu_is_prepared(struct clk_hw *hw) |
| { |
| struct clk_gate3_scu *gate = to_clk_gate3_scu(hw); |
| uint32_t val; |
| |
| if (!ccm_ipc_handle) |
| return -1; |
| |
| sc_misc_get_control(ccm_ipc_handle, gate->rsrc_id, gate->gpr_id, &val); |
| val &= 1; |
| |
| if (gate->invert) |
| return 1 - val; |
| |
| return val; |
| } |
| |
| static struct clk_ops clk_gate3_scu_ops = { |
| .prepare = clk_gate3_scu_prepare, |
| .unprepare = clk_gate3_scu_unprepare, |
| .is_prepared = clk_gate3_scu_is_prepared, |
| }; |
| |
| struct clk *clk_register_gate3_scu(struct device *dev, const char *name, |
| const char *parent_name, spinlock_t *lock, |
| sc_rsrc_t rsrc_id, sc_ctrl_t gpr_id, bool invert_flag) |
| { |
| struct clk_gate3_scu *gate; |
| 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); |
| } |
| |
| gate = kzalloc(sizeof(struct clk_gate_scu), GFP_KERNEL); |
| if (!gate) |
| return ERR_PTR(-ENOMEM); |
| |
| /* struct clk_gate_scu assignments */ |
| gate->lock = lock; |
| gate->rsrc_id = rsrc_id; |
| gate->gpr_id = gpr_id; |
| gate->invert = invert_flag; |
| |
| init.name = name; |
| init.ops = &clk_gate3_scu_ops; |
| init.flags = 0; |
| init.parent_names = parent_name ? &parent_name : NULL; |
| init.num_parents = parent_name ? 1 : 0; |
| |
| gate->hw.init = &init; |
| |
| clk = clk_register(dev, &gate->hw); |
| if (IS_ERR(clk)) |
| kfree(gate); |
| |
| return clk; |
| } |