1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * mmp APB clock operation source file |
4 | * |
5 | * Copyright (C) 2012 Marvell |
6 | * Chao Xie <xiechao.mail@gmail.com> |
7 | */ |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/io.h> |
11 | #include <linux/err.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/slab.h> |
14 | |
15 | #include "clk.h" |
16 | |
17 | /* Common APB clock register bit definitions */ |
18 | #define APBC_APBCLK (1 << 0) /* APB Bus Clock Enable */ |
19 | #define APBC_FNCLK (1 << 1) /* Functional Clock Enable */ |
20 | #define APBC_RST (1 << 2) /* Reset Generation */ |
21 | #define APBC_POWER (1 << 7) /* Reset Generation */ |
22 | |
23 | #define to_clk_apbc(hw) container_of(hw, struct clk_apbc, hw) |
24 | struct clk_apbc { |
25 | struct clk_hw hw; |
26 | void __iomem *base; |
27 | unsigned int delay; |
28 | unsigned int flags; |
29 | spinlock_t *lock; |
30 | }; |
31 | |
32 | static int clk_apbc_prepare(struct clk_hw *hw) |
33 | { |
34 | struct clk_apbc *apbc = to_clk_apbc(hw); |
35 | unsigned int data; |
36 | unsigned long flags = 0; |
37 | |
38 | /* |
39 | * It may share same register as MUX clock, |
40 | * and it will impact FNCLK enable. Spinlock is needed |
41 | */ |
42 | if (apbc->lock) |
43 | spin_lock_irqsave(apbc->lock, flags); |
44 | |
45 | data = readl_relaxed(apbc->base); |
46 | if (apbc->flags & APBC_POWER_CTRL) |
47 | data |= APBC_POWER; |
48 | data |= APBC_FNCLK; |
49 | writel_relaxed(data, apbc->base); |
50 | |
51 | if (apbc->lock) |
52 | spin_unlock_irqrestore(lock: apbc->lock, flags); |
53 | |
54 | udelay(apbc->delay); |
55 | |
56 | if (apbc->lock) |
57 | spin_lock_irqsave(apbc->lock, flags); |
58 | |
59 | data = readl_relaxed(apbc->base); |
60 | data |= APBC_APBCLK; |
61 | writel_relaxed(data, apbc->base); |
62 | |
63 | if (apbc->lock) |
64 | spin_unlock_irqrestore(lock: apbc->lock, flags); |
65 | |
66 | udelay(apbc->delay); |
67 | |
68 | if (!(apbc->flags & APBC_NO_BUS_CTRL)) { |
69 | if (apbc->lock) |
70 | spin_lock_irqsave(apbc->lock, flags); |
71 | |
72 | data = readl_relaxed(apbc->base); |
73 | data &= ~APBC_RST; |
74 | writel_relaxed(data, apbc->base); |
75 | |
76 | if (apbc->lock) |
77 | spin_unlock_irqrestore(lock: apbc->lock, flags); |
78 | } |
79 | |
80 | return 0; |
81 | } |
82 | |
83 | static void clk_apbc_unprepare(struct clk_hw *hw) |
84 | { |
85 | struct clk_apbc *apbc = to_clk_apbc(hw); |
86 | unsigned long data; |
87 | unsigned long flags = 0; |
88 | |
89 | if (apbc->lock) |
90 | spin_lock_irqsave(apbc->lock, flags); |
91 | |
92 | data = readl_relaxed(apbc->base); |
93 | if (apbc->flags & APBC_POWER_CTRL) |
94 | data &= ~APBC_POWER; |
95 | data &= ~APBC_FNCLK; |
96 | writel_relaxed(data, apbc->base); |
97 | |
98 | if (apbc->lock) |
99 | spin_unlock_irqrestore(lock: apbc->lock, flags); |
100 | |
101 | udelay(10); |
102 | |
103 | if (apbc->lock) |
104 | spin_lock_irqsave(apbc->lock, flags); |
105 | |
106 | data = readl_relaxed(apbc->base); |
107 | data &= ~APBC_APBCLK; |
108 | writel_relaxed(data, apbc->base); |
109 | |
110 | if (apbc->lock) |
111 | spin_unlock_irqrestore(lock: apbc->lock, flags); |
112 | } |
113 | |
114 | static const struct clk_ops clk_apbc_ops = { |
115 | .prepare = clk_apbc_prepare, |
116 | .unprepare = clk_apbc_unprepare, |
117 | }; |
118 | |
119 | struct clk *mmp_clk_register_apbc(const char *name, const char *parent_name, |
120 | void __iomem *base, unsigned int delay, |
121 | unsigned int apbc_flags, spinlock_t *lock) |
122 | { |
123 | struct clk_apbc *apbc; |
124 | struct clk *clk; |
125 | struct clk_init_data init; |
126 | |
127 | apbc = kzalloc(size: sizeof(*apbc), GFP_KERNEL); |
128 | if (!apbc) |
129 | return NULL; |
130 | |
131 | init.name = name; |
132 | init.ops = &clk_apbc_ops; |
133 | init.flags = CLK_SET_RATE_PARENT; |
134 | init.parent_names = (parent_name ? &parent_name : NULL); |
135 | init.num_parents = (parent_name ? 1 : 0); |
136 | |
137 | apbc->base = base; |
138 | apbc->delay = delay; |
139 | apbc->flags = apbc_flags; |
140 | apbc->lock = lock; |
141 | apbc->hw.init = &init; |
142 | |
143 | clk = clk_register(NULL, hw: &apbc->hw); |
144 | if (IS_ERR(ptr: clk)) |
145 | kfree(objp: apbc); |
146 | |
147 | return clk; |
148 | } |
149 | |