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/err.h> |
10 | #include <linux/io.h> |
11 | #include <linux/slab.h> |
12 | #include <linux/spinlock.h> |
13 | |
14 | #include "clk-scu.h" |
15 | |
16 | static DEFINE_SPINLOCK(imx_lpcg_scu_lock); |
17 | |
18 | #define CLK_GATE_SCU_LPCG_MASK 0x3 |
19 | #define CLK_GATE_SCU_LPCG_HW_SEL BIT(0) |
20 | #define CLK_GATE_SCU_LPCG_SW_SEL BIT(1) |
21 | |
22 | /* |
23 | * struct clk_lpcg_scu - Description of LPCG clock |
24 | * |
25 | * @hw: clk_hw of this LPCG |
26 | * @reg: register of this LPCG clock |
27 | * @bit_idx: bit index of this LPCG clock |
28 | * @hw_gate: HW auto gate enable |
29 | * |
30 | * This structure describes one LPCG clock |
31 | */ |
32 | struct clk_lpcg_scu { |
33 | struct clk_hw hw; |
34 | void __iomem *reg; |
35 | u8 bit_idx; |
36 | bool hw_gate; |
37 | |
38 | /* for state save&restore */ |
39 | u32 state; |
40 | }; |
41 | |
42 | #define to_clk_lpcg_scu(_hw) container_of(_hw, struct clk_lpcg_scu, hw) |
43 | |
44 | static int clk_lpcg_scu_enable(struct clk_hw *hw) |
45 | { |
46 | struct clk_lpcg_scu *clk = to_clk_lpcg_scu(hw); |
47 | unsigned long flags; |
48 | u32 reg, val; |
49 | |
50 | spin_lock_irqsave(&imx_lpcg_scu_lock, flags); |
51 | |
52 | reg = readl_relaxed(clk->reg); |
53 | reg &= ~(CLK_GATE_SCU_LPCG_MASK << clk->bit_idx); |
54 | |
55 | val = CLK_GATE_SCU_LPCG_SW_SEL; |
56 | if (clk->hw_gate) |
57 | val |= CLK_GATE_SCU_LPCG_HW_SEL; |
58 | |
59 | reg |= val << clk->bit_idx; |
60 | writel(val: reg, addr: clk->reg); |
61 | |
62 | spin_unlock_irqrestore(lock: &imx_lpcg_scu_lock, flags); |
63 | |
64 | return 0; |
65 | } |
66 | |
67 | static void clk_lpcg_scu_disable(struct clk_hw *hw) |
68 | { |
69 | struct clk_lpcg_scu *clk = to_clk_lpcg_scu(hw); |
70 | unsigned long flags; |
71 | u32 reg; |
72 | |
73 | spin_lock_irqsave(&imx_lpcg_scu_lock, flags); |
74 | |
75 | reg = readl_relaxed(clk->reg); |
76 | reg &= ~(CLK_GATE_SCU_LPCG_MASK << clk->bit_idx); |
77 | writel(val: reg, addr: clk->reg); |
78 | |
79 | spin_unlock_irqrestore(lock: &imx_lpcg_scu_lock, flags); |
80 | } |
81 | |
82 | static const struct clk_ops clk_lpcg_scu_ops = { |
83 | .enable = clk_lpcg_scu_enable, |
84 | .disable = clk_lpcg_scu_disable, |
85 | }; |
86 | |
87 | struct clk_hw *__imx_clk_lpcg_scu(struct device *dev, const char *name, |
88 | const char *parent_name, unsigned long flags, |
89 | void __iomem *reg, u8 bit_idx, bool hw_gate) |
90 | { |
91 | struct clk_lpcg_scu *clk; |
92 | struct clk_init_data init; |
93 | struct clk_hw *hw; |
94 | int ret; |
95 | |
96 | clk = kzalloc(size: sizeof(*clk), GFP_KERNEL); |
97 | if (!clk) |
98 | return ERR_PTR(error: -ENOMEM); |
99 | |
100 | clk->reg = reg; |
101 | clk->bit_idx = bit_idx; |
102 | clk->hw_gate = hw_gate; |
103 | |
104 | init.name = name; |
105 | init.ops = &clk_lpcg_scu_ops; |
106 | init.flags = CLK_SET_RATE_PARENT | flags; |
107 | init.parent_names = parent_name ? &parent_name : NULL; |
108 | init.num_parents = parent_name ? 1 : 0; |
109 | |
110 | clk->hw.init = &init; |
111 | |
112 | hw = &clk->hw; |
113 | ret = clk_hw_register(dev, hw); |
114 | if (ret) { |
115 | kfree(objp: clk); |
116 | hw = ERR_PTR(error: ret); |
117 | return hw; |
118 | } |
119 | |
120 | if (dev) |
121 | dev_set_drvdata(dev, data: clk); |
122 | |
123 | return hw; |
124 | } |
125 | |
126 | void imx_clk_lpcg_scu_unregister(struct clk_hw *hw) |
127 | { |
128 | struct clk_lpcg_scu *clk = to_clk_lpcg_scu(hw); |
129 | |
130 | clk_hw_unregister(hw: &clk->hw); |
131 | kfree(objp: clk); |
132 | } |
133 | |
134 | static int __maybe_unused imx_clk_lpcg_scu_suspend(struct device *dev) |
135 | { |
136 | struct clk_lpcg_scu *clk = dev_get_drvdata(dev); |
137 | |
138 | clk->state = readl_relaxed(clk->reg); |
139 | dev_dbg(dev, "save lpcg state 0x%x\n" , clk->state); |
140 | |
141 | return 0; |
142 | } |
143 | |
144 | static int __maybe_unused imx_clk_lpcg_scu_resume(struct device *dev) |
145 | { |
146 | struct clk_lpcg_scu *clk = dev_get_drvdata(dev); |
147 | |
148 | /* |
149 | * FIXME: Sometimes writes don't work unless the CPU issues |
150 | * them twice |
151 | */ |
152 | |
153 | writel(val: clk->state, addr: clk->reg); |
154 | writel(val: clk->state, addr: clk->reg); |
155 | dev_dbg(dev, "restore lpcg state 0x%x\n" , clk->state); |
156 | |
157 | return 0; |
158 | } |
159 | |
160 | const struct dev_pm_ops imx_clk_lpcg_scu_pm_ops = { |
161 | SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_clk_lpcg_scu_suspend, |
162 | imx_clk_lpcg_scu_resume) |
163 | }; |
164 | |