1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright (C) 2016 Freescale Semiconductor, Inc. |
4 | * Copyright 2017~2018 NXP |
5 | * |
6 | */ |
7 | |
8 | #include <linux/bits.h> |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/err.h> |
11 | #include <linux/io.h> |
12 | #include <linux/slab.h> |
13 | |
14 | #include "../clk-fractional-divider.h" |
15 | #include "clk.h" |
16 | |
17 | #define PCG_PCS_SHIFT 24 |
18 | #define PCG_PCS_MASK 0x7 |
19 | #define PCG_CGC_SHIFT 30 |
20 | #define PCG_FRAC_SHIFT 3 |
21 | #define PCG_FRAC_WIDTH 1 |
22 | #define PCG_PCD_SHIFT 0 |
23 | #define PCG_PCD_WIDTH 3 |
24 | |
25 | #define SW_RST BIT(28) |
26 | |
27 | static int pcc_gate_enable(struct clk_hw *hw) |
28 | { |
29 | struct clk_gate *gate = to_clk_gate(hw); |
30 | unsigned long flags; |
31 | u32 val; |
32 | int ret; |
33 | |
34 | ret = clk_gate_ops.enable(hw); |
35 | if (ret) |
36 | return ret; |
37 | |
38 | spin_lock_irqsave(gate->lock, flags); |
39 | /* |
40 | * release the sw reset for peripherals associated with |
41 | * with this pcc clock. |
42 | */ |
43 | val = readl(addr: gate->reg); |
44 | val |= SW_RST; |
45 | writel(val, addr: gate->reg); |
46 | |
47 | spin_unlock_irqrestore(lock: gate->lock, flags); |
48 | |
49 | return 0; |
50 | } |
51 | |
52 | static void pcc_gate_disable(struct clk_hw *hw) |
53 | { |
54 | clk_gate_ops.disable(hw); |
55 | } |
56 | |
57 | static int pcc_gate_is_enabled(struct clk_hw *hw) |
58 | { |
59 | return clk_gate_ops.is_enabled(hw); |
60 | } |
61 | |
62 | static const struct clk_ops pcc_gate_ops = { |
63 | .enable = pcc_gate_enable, |
64 | .disable = pcc_gate_disable, |
65 | .is_enabled = pcc_gate_is_enabled, |
66 | }; |
67 | |
68 | static struct clk_hw *imx_ulp_clk_hw_composite(const char *name, |
69 | const char * const *parent_names, |
70 | int num_parents, bool mux_present, |
71 | bool rate_present, bool gate_present, |
72 | void __iomem *reg, bool has_swrst) |
73 | { |
74 | struct clk_hw *mux_hw = NULL, *fd_hw = NULL, *gate_hw = NULL; |
75 | struct clk_fractional_divider *fd = NULL; |
76 | struct clk_gate *gate = NULL; |
77 | struct clk_mux *mux = NULL; |
78 | struct clk_hw *hw; |
79 | u32 val; |
80 | |
81 | if (mux_present) { |
82 | mux = kzalloc(size: sizeof(*mux), GFP_KERNEL); |
83 | if (!mux) |
84 | return ERR_PTR(error: -ENOMEM); |
85 | mux_hw = &mux->hw; |
86 | mux->reg = reg; |
87 | mux->shift = PCG_PCS_SHIFT; |
88 | mux->mask = PCG_PCS_MASK; |
89 | if (has_swrst) |
90 | mux->lock = &imx_ccm_lock; |
91 | } |
92 | |
93 | if (rate_present) { |
94 | fd = kzalloc(size: sizeof(*fd), GFP_KERNEL); |
95 | if (!fd) { |
96 | kfree(objp: mux); |
97 | return ERR_PTR(error: -ENOMEM); |
98 | } |
99 | fd_hw = &fd->hw; |
100 | fd->reg = reg; |
101 | fd->mshift = PCG_FRAC_SHIFT; |
102 | fd->mwidth = PCG_FRAC_WIDTH; |
103 | fd->nshift = PCG_PCD_SHIFT; |
104 | fd->nwidth = PCG_PCD_WIDTH; |
105 | fd->flags = CLK_FRAC_DIVIDER_ZERO_BASED; |
106 | if (has_swrst) |
107 | fd->lock = &imx_ccm_lock; |
108 | } |
109 | |
110 | if (gate_present) { |
111 | gate = kzalloc(size: sizeof(*gate), GFP_KERNEL); |
112 | if (!gate) { |
113 | kfree(objp: mux); |
114 | kfree(objp: fd); |
115 | return ERR_PTR(error: -ENOMEM); |
116 | } |
117 | gate_hw = &gate->hw; |
118 | gate->reg = reg; |
119 | gate->bit_idx = PCG_CGC_SHIFT; |
120 | if (has_swrst) |
121 | gate->lock = &imx_ccm_lock; |
122 | /* |
123 | * make sure clock is gated during clock tree initialization, |
124 | * the HW ONLY allow clock parent/rate changed with clock gated, |
125 | * during clock tree initialization, clocks could be enabled |
126 | * by bootloader, so the HW status will mismatch with clock tree |
127 | * prepare count, then clock core driver will allow parent/rate |
128 | * change since the prepare count is zero, but HW actually |
129 | * prevent the parent/rate change due to the clock is enabled. |
130 | */ |
131 | val = readl_relaxed(reg); |
132 | val &= ~(1 << PCG_CGC_SHIFT); |
133 | writel_relaxed(val, reg); |
134 | } |
135 | |
136 | hw = clk_hw_register_composite(NULL, name, parent_names, num_parents, |
137 | mux_hw, mux_ops: &clk_mux_ops, rate_hw: fd_hw, |
138 | rate_ops: &clk_fractional_divider_ops, gate_hw, |
139 | gate_ops: has_swrst ? &pcc_gate_ops : &clk_gate_ops, CLK_SET_RATE_GATE | |
140 | CLK_SET_PARENT_GATE | CLK_SET_RATE_NO_REPARENT); |
141 | if (IS_ERR(ptr: hw)) { |
142 | kfree(objp: mux); |
143 | kfree(objp: fd); |
144 | kfree(objp: gate); |
145 | } |
146 | |
147 | return hw; |
148 | } |
149 | |
150 | struct clk_hw *imx7ulp_clk_hw_composite(const char *name, const char * const *parent_names, |
151 | int num_parents, bool mux_present, bool rate_present, |
152 | bool gate_present, void __iomem *reg) |
153 | { |
154 | return imx_ulp_clk_hw_composite(name, parent_names, num_parents, mux_present, rate_present, |
155 | gate_present, reg, has_swrst: false); |
156 | } |
157 | |
158 | struct clk_hw *imx8ulp_clk_hw_composite(const char *name, const char * const *parent_names, |
159 | int num_parents, bool mux_present, bool rate_present, |
160 | bool gate_present, void __iomem *reg, bool has_swrst) |
161 | { |
162 | return imx_ulp_clk_hw_composite(name, parent_names, num_parents, mux_present, rate_present, |
163 | gate_present, reg, has_swrst); |
164 | } |
165 | EXPORT_SYMBOL_GPL(imx8ulp_clk_hw_composite); |
166 | |