162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright 2018 NXP 462306a36Sopenharmony_ci * Dong Aisheng <aisheng.dong@nxp.com> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/bits.h> 862306a36Sopenharmony_ci#include <linux/clk-provider.h> 962306a36Sopenharmony_ci#include <linux/err.h> 1062306a36Sopenharmony_ci#include <linux/io.h> 1162306a36Sopenharmony_ci#include <linux/slab.h> 1262306a36Sopenharmony_ci#include <linux/spinlock.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include "clk-scu.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistatic DEFINE_SPINLOCK(imx_lpcg_scu_lock); 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define CLK_GATE_SCU_LPCG_MASK 0x3 1962306a36Sopenharmony_ci#define CLK_GATE_SCU_LPCG_HW_SEL BIT(0) 2062306a36Sopenharmony_ci#define CLK_GATE_SCU_LPCG_SW_SEL BIT(1) 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci/* 2362306a36Sopenharmony_ci * struct clk_lpcg_scu - Description of LPCG clock 2462306a36Sopenharmony_ci * 2562306a36Sopenharmony_ci * @hw: clk_hw of this LPCG 2662306a36Sopenharmony_ci * @reg: register of this LPCG clock 2762306a36Sopenharmony_ci * @bit_idx: bit index of this LPCG clock 2862306a36Sopenharmony_ci * @hw_gate: HW auto gate enable 2962306a36Sopenharmony_ci * 3062306a36Sopenharmony_ci * This structure describes one LPCG clock 3162306a36Sopenharmony_ci */ 3262306a36Sopenharmony_cistruct clk_lpcg_scu { 3362306a36Sopenharmony_ci struct clk_hw hw; 3462306a36Sopenharmony_ci void __iomem *reg; 3562306a36Sopenharmony_ci u8 bit_idx; 3662306a36Sopenharmony_ci bool hw_gate; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci /* for state save&restore */ 3962306a36Sopenharmony_ci u32 state; 4062306a36Sopenharmony_ci}; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define to_clk_lpcg_scu(_hw) container_of(_hw, struct clk_lpcg_scu, hw) 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic int clk_lpcg_scu_enable(struct clk_hw *hw) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci struct clk_lpcg_scu *clk = to_clk_lpcg_scu(hw); 4762306a36Sopenharmony_ci unsigned long flags; 4862306a36Sopenharmony_ci u32 reg, val; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci spin_lock_irqsave(&imx_lpcg_scu_lock, flags); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci reg = readl_relaxed(clk->reg); 5362306a36Sopenharmony_ci reg &= ~(CLK_GATE_SCU_LPCG_MASK << clk->bit_idx); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci val = CLK_GATE_SCU_LPCG_SW_SEL; 5662306a36Sopenharmony_ci if (clk->hw_gate) 5762306a36Sopenharmony_ci val |= CLK_GATE_SCU_LPCG_HW_SEL; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci reg |= val << clk->bit_idx; 6062306a36Sopenharmony_ci writel(reg, clk->reg); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci spin_unlock_irqrestore(&imx_lpcg_scu_lock, flags); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci return 0; 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic void clk_lpcg_scu_disable(struct clk_hw *hw) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci struct clk_lpcg_scu *clk = to_clk_lpcg_scu(hw); 7062306a36Sopenharmony_ci unsigned long flags; 7162306a36Sopenharmony_ci u32 reg; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci spin_lock_irqsave(&imx_lpcg_scu_lock, flags); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci reg = readl_relaxed(clk->reg); 7662306a36Sopenharmony_ci reg &= ~(CLK_GATE_SCU_LPCG_MASK << clk->bit_idx); 7762306a36Sopenharmony_ci writel(reg, clk->reg); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci spin_unlock_irqrestore(&imx_lpcg_scu_lock, flags); 8062306a36Sopenharmony_ci} 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic const struct clk_ops clk_lpcg_scu_ops = { 8362306a36Sopenharmony_ci .enable = clk_lpcg_scu_enable, 8462306a36Sopenharmony_ci .disable = clk_lpcg_scu_disable, 8562306a36Sopenharmony_ci}; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistruct clk_hw *__imx_clk_lpcg_scu(struct device *dev, const char *name, 8862306a36Sopenharmony_ci const char *parent_name, unsigned long flags, 8962306a36Sopenharmony_ci void __iomem *reg, u8 bit_idx, bool hw_gate) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci struct clk_lpcg_scu *clk; 9262306a36Sopenharmony_ci struct clk_init_data init; 9362306a36Sopenharmony_ci struct clk_hw *hw; 9462306a36Sopenharmony_ci int ret; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci clk = kzalloc(sizeof(*clk), GFP_KERNEL); 9762306a36Sopenharmony_ci if (!clk) 9862306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci clk->reg = reg; 10162306a36Sopenharmony_ci clk->bit_idx = bit_idx; 10262306a36Sopenharmony_ci clk->hw_gate = hw_gate; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci init.name = name; 10562306a36Sopenharmony_ci init.ops = &clk_lpcg_scu_ops; 10662306a36Sopenharmony_ci init.flags = CLK_SET_RATE_PARENT | flags; 10762306a36Sopenharmony_ci init.parent_names = parent_name ? &parent_name : NULL; 10862306a36Sopenharmony_ci init.num_parents = parent_name ? 1 : 0; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci clk->hw.init = &init; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci hw = &clk->hw; 11362306a36Sopenharmony_ci ret = clk_hw_register(dev, hw); 11462306a36Sopenharmony_ci if (ret) { 11562306a36Sopenharmony_ci kfree(clk); 11662306a36Sopenharmony_ci hw = ERR_PTR(ret); 11762306a36Sopenharmony_ci return hw; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci if (dev) 12162306a36Sopenharmony_ci dev_set_drvdata(dev, clk); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci return hw; 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_civoid imx_clk_lpcg_scu_unregister(struct clk_hw *hw) 12762306a36Sopenharmony_ci{ 12862306a36Sopenharmony_ci struct clk_lpcg_scu *clk = to_clk_lpcg_scu(hw); 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci clk_hw_unregister(&clk->hw); 13162306a36Sopenharmony_ci kfree(clk); 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic int __maybe_unused imx_clk_lpcg_scu_suspend(struct device *dev) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci struct clk_lpcg_scu *clk = dev_get_drvdata(dev); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci clk->state = readl_relaxed(clk->reg); 13962306a36Sopenharmony_ci dev_dbg(dev, "save lpcg state 0x%x\n", clk->state); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci return 0; 14262306a36Sopenharmony_ci} 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic int __maybe_unused imx_clk_lpcg_scu_resume(struct device *dev) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci struct clk_lpcg_scu *clk = dev_get_drvdata(dev); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci /* 14962306a36Sopenharmony_ci * FIXME: Sometimes writes don't work unless the CPU issues 15062306a36Sopenharmony_ci * them twice 15162306a36Sopenharmony_ci */ 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci writel(clk->state, clk->reg); 15462306a36Sopenharmony_ci writel(clk->state, clk->reg); 15562306a36Sopenharmony_ci dev_dbg(dev, "restore lpcg state 0x%x\n", clk->state); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci return 0; 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ciconst struct dev_pm_ops imx_clk_lpcg_scu_pm_ops = { 16162306a36Sopenharmony_ci SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_clk_lpcg_scu_suspend, 16262306a36Sopenharmony_ci imx_clk_lpcg_scu_resume) 16362306a36Sopenharmony_ci}; 164