File Index
Files and directories
Sign in
FILE

clk-lpcg-scu.c

File preview with syntax highlighting.

linux-6.19.14/drivers/clk/imx/clk-lpcg-scu.c C
  1// SPDX-License-Identifier: GPL-2.0+
  2/*
  3 * Copyright 2018 NXP
  4 *	Dong Aisheng <aisheng.dong@nxp.com>
  5 */
  6
  7#include <linux/bits.h>
  8#include <linux/clk-provider.h>
  9#include <linux/delay.h>
 10#include <linux/err.h>
 11#include <linux/io.h>
 12#include <linux/slab.h>
 13#include <linux/spinlock.h>
 14#include <linux/units.h>
 15
 16#include "clk-scu.h"
 17
 18static DEFINE_SPINLOCK(imx_lpcg_scu_lock);
 19
 20#define CLK_GATE_SCU_LPCG_MASK		0x3
 21#define CLK_GATE_SCU_LPCG_HW_SEL	BIT(0)
 22#define CLK_GATE_SCU_LPCG_SW_SEL	BIT(1)
 23
 24/*
 25 * struct clk_lpcg_scu - Description of LPCG clock
 26 *
 27 * @hw: clk_hw of this LPCG
 28 * @reg: register of this LPCG clock
 29 * @bit_idx: bit index of this LPCG clock
 30 * @hw_gate: HW auto gate enable
 31 *
 32 * This structure describes one LPCG clock
 33 */
 34struct clk_lpcg_scu {
 35	struct clk_hw hw;
 36	void __iomem *reg;
 37	u8 bit_idx;
 38	bool hw_gate;
 39
 40	/* for state save&restore */
 41	u32 state;
 42};
 43
 44#define to_clk_lpcg_scu(_hw) container_of(_hw, struct clk_lpcg_scu, hw)
 45
 46/* e10858 -LPCG clock gating register synchronization errata */
 47static void lpcg_e10858_writel(unsigned long rate, void __iomem *reg, u32 val)
 48{
 49	writel(val, reg);
 50
 51	if (rate >= 24 * HZ_PER_MHZ || rate == 0) {
 52		/*
 53		 * The time taken to access the LPCG registers from the AP core
 54		 * through the interconnect is longer than the minimum delay
 55		 * of 4 clock cycles required by the errata.
 56		 * Adding a readl will provide sufficient delay to prevent
 57		 * back-to-back writes.
 58		 */
 59		readl(reg);
 60	} else {
 61		/*
 62		 * For clocks running below 24MHz, wait a minimum of
 63		 * 4 clock cycles.
 64		 */
 65		ndelay(4 * (DIV_ROUND_UP(1000 * HZ_PER_MHZ, rate)));
 66	}
 67}
 68
 69static int clk_lpcg_scu_enable(struct clk_hw *hw)
 70{
 71	struct clk_lpcg_scu *clk = to_clk_lpcg_scu(hw);
 72	unsigned long flags;
 73	u32 reg, val;
 74
 75	spin_lock_irqsave(&imx_lpcg_scu_lock, flags);
 76
 77	reg = readl_relaxed(clk->reg);
 78	reg &= ~(CLK_GATE_SCU_LPCG_MASK << clk->bit_idx);
 79
 80	val = CLK_GATE_SCU_LPCG_SW_SEL;
 81	if (clk->hw_gate)
 82		val |= CLK_GATE_SCU_LPCG_HW_SEL;
 83
 84	reg |= val << clk->bit_idx;
 85
 86	lpcg_e10858_writel(clk_hw_get_rate(hw), clk->reg, reg);
 87
 88	spin_unlock_irqrestore(&imx_lpcg_scu_lock, flags);
 89
 90	return 0;
 91}
 92
 93static void clk_lpcg_scu_disable(struct clk_hw *hw)
 94{
 95	struct clk_lpcg_scu *clk = to_clk_lpcg_scu(hw);
 96	unsigned long flags;
 97	u32 reg;
 98
 99	spin_lock_irqsave(&imx_lpcg_scu_lock, flags);
100
101	reg = readl_relaxed(clk->reg);
102	reg &= ~(CLK_GATE_SCU_LPCG_MASK << clk->bit_idx);
103	lpcg_e10858_writel(clk_hw_get_rate(hw), clk->reg, reg);
104
105	spin_unlock_irqrestore(&imx_lpcg_scu_lock, flags);
106}
107
108static const struct clk_ops clk_lpcg_scu_ops = {
109	.enable = clk_lpcg_scu_enable,
110	.disable = clk_lpcg_scu_disable,
111};
112
113struct clk_hw *__imx_clk_lpcg_scu(struct device *dev, const char *name,
114				  const char *parent_name, unsigned long flags,
115				  void __iomem *reg, u8 bit_idx, bool hw_gate)
116{
117	struct clk_lpcg_scu *clk;
118	struct clk_init_data init;
119	struct clk_hw *hw;
120	int ret;
121
122	clk = kzalloc(sizeof(*clk), GFP_KERNEL);
123	if (!clk)
124		return ERR_PTR(-ENOMEM);
125
126	clk->reg = reg;
127	clk->bit_idx = bit_idx;
128	clk->hw_gate = hw_gate;
129
130	init.name = name;
131	init.ops = &clk_lpcg_scu_ops;
132	init.flags = CLK_SET_RATE_PARENT | flags;
133	init.parent_names = parent_name ? &parent_name : NULL;
134	init.num_parents = parent_name ? 1 : 0;
135
136	clk->hw.init = &init;
137
138	hw = &clk->hw;
139	ret = clk_hw_register(dev, hw);
140	if (ret) {
141		kfree(clk);
142		hw = ERR_PTR(ret);
143		return hw;
144	}
145
146	if (dev)
147		dev_set_drvdata(dev, clk);
148
149	return hw;
150}
151
152void imx_clk_lpcg_scu_unregister(struct clk_hw *hw)
153{
154	struct clk_lpcg_scu *clk = to_clk_lpcg_scu(hw);
155
156	clk_hw_unregister(&clk->hw);
157	kfree(clk);
158}
159
160static int __maybe_unused imx_clk_lpcg_scu_suspend(struct device *dev)
161{
162	struct clk_lpcg_scu *clk = dev_get_drvdata(dev);
163
164	if (!strncmp("hdmi_lpcg", clk_hw_get_name(&clk->hw), strlen("hdmi_lpcg")))
165		return 0;
166
167	clk->state = readl_relaxed(clk->reg);
168	dev_dbg(dev, "save lpcg state 0x%x\n", clk->state);
169
170	return 0;
171}
172
173static int __maybe_unused imx_clk_lpcg_scu_resume(struct device *dev)
174{
175	struct clk_lpcg_scu *clk = dev_get_drvdata(dev);
176
177	if (!strncmp("hdmi_lpcg", clk_hw_get_name(&clk->hw), strlen("hdmi_lpcg")))
178		return 0;
179
180	writel(clk->state, clk->reg);
181	lpcg_e10858_writel(0, clk->reg, clk->state);
182	dev_dbg(dev, "restore lpcg state 0x%x\n", clk->state);
183
184	return 0;
185}
186
187const struct dev_pm_ops imx_clk_lpcg_scu_pm_ops = {
188	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_clk_lpcg_scu_suspend,
189				      imx_clk_lpcg_scu_resume)
190};
~/ Read-only file preview.