Commit | Line | Data |
---|---|---|
8c56df37 RB |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* cavium_ptp.c - PTP 1588 clock on Cavium hardware | |
3 | * Copyright (c) 2003-2015, 2017 Cavium, Inc. | |
4 | */ | |
5 | ||
6 | #include <linux/device.h> | |
7 | #include <linux/module.h> | |
8 | #include <linux/timecounter.h> | |
9 | #include <linux/pci.h> | |
10 | ||
11 | #include "cavium_ptp.h" | |
12 | ||
34343410 | 13 | #define DRV_NAME "cavium_ptp" |
8c56df37 RB |
14 | |
15 | #define PCI_DEVICE_ID_CAVIUM_PTP 0xA00C | |
16 | #define PCI_DEVICE_ID_CAVIUM_RST 0xA00E | |
17 | ||
18 | #define PCI_PTP_BAR_NO 0 | |
19 | #define PCI_RST_BAR_NO 0 | |
20 | ||
21 | #define PTP_CLOCK_CFG 0xF00ULL | |
22 | #define PTP_CLOCK_CFG_PTP_EN BIT(0) | |
23 | #define PTP_CLOCK_LO 0xF08ULL | |
24 | #define PTP_CLOCK_HI 0xF10ULL | |
25 | #define PTP_CLOCK_COMP 0xF18ULL | |
26 | ||
27 | #define RST_BOOT 0x1600ULL | |
28 | #define CLOCK_BASE_RATE 50000000ULL | |
29 | ||
30 | static u64 ptp_cavium_clock_get(void) | |
31 | { | |
32 | struct pci_dev *pdev; | |
33 | void __iomem *base; | |
34 | u64 ret = CLOCK_BASE_RATE * 16; | |
35 | ||
36 | pdev = pci_get_device(PCI_VENDOR_ID_CAVIUM, | |
37 | PCI_DEVICE_ID_CAVIUM_RST, NULL); | |
38 | if (!pdev) | |
39 | goto error; | |
40 | ||
41 | base = pci_ioremap_bar(pdev, PCI_RST_BAR_NO); | |
42 | if (!base) | |
43 | goto error_put_pdev; | |
44 | ||
45 | ret = CLOCK_BASE_RATE * ((readq(base + RST_BOOT) >> 33) & 0x3f); | |
46 | ||
47 | iounmap(base); | |
48 | ||
49 | error_put_pdev: | |
50 | pci_dev_put(pdev); | |
51 | ||
52 | error: | |
53 | return ret; | |
54 | } | |
55 | ||
56 | struct cavium_ptp *cavium_ptp_get(void) | |
57 | { | |
58 | struct cavium_ptp *ptp; | |
59 | struct pci_dev *pdev; | |
60 | ||
61 | pdev = pci_get_device(PCI_VENDOR_ID_CAVIUM, | |
62 | PCI_DEVICE_ID_CAVIUM_PTP, NULL); | |
63 | if (!pdev) | |
64 | return ERR_PTR(-ENODEV); | |
65 | ||
66 | ptp = pci_get_drvdata(pdev); | |
67 | if (!ptp) | |
68 | ptp = ERR_PTR(-EPROBE_DEFER); | |
69 | if (IS_ERR(ptp)) | |
70 | pci_dev_put(pdev); | |
71 | ||
72 | return ptp; | |
73 | } | |
74 | EXPORT_SYMBOL(cavium_ptp_get); | |
75 | ||
76 | void cavium_ptp_put(struct cavium_ptp *ptp) | |
77 | { | |
07a2e1cf JG |
78 | if (!ptp) |
79 | return; | |
8c56df37 RB |
80 | pci_dev_put(ptp->pdev); |
81 | } | |
82 | EXPORT_SYMBOL(cavium_ptp_put); | |
83 | ||
84 | /** | |
85 | * cavium_ptp_adjfine() - Adjust ptp frequency | |
86 | * @ptp: PTP clock info | |
87 | * @scaled_ppm: how much to adjust by, in parts per million, but with a | |
88 | * 16 bit binary fractional field | |
89 | */ | |
90 | static int cavium_ptp_adjfine(struct ptp_clock_info *ptp_info, long scaled_ppm) | |
91 | { | |
92 | struct cavium_ptp *clock = | |
93 | container_of(ptp_info, struct cavium_ptp, ptp_info); | |
94 | unsigned long flags; | |
95 | u64 comp; | |
96 | u64 adj; | |
97 | bool neg_adj = false; | |
98 | ||
99 | if (scaled_ppm < 0) { | |
100 | neg_adj = true; | |
101 | scaled_ppm = -scaled_ppm; | |
102 | } | |
103 | ||
104 | /* The hardware adds the clock compensation value to the PTP clock | |
105 | * on every coprocessor clock cycle. Typical convention is that it | |
106 | * represent number of nanosecond betwen each cycle. In this | |
107 | * convention compensation value is in 64 bit fixed-point | |
108 | * representation where upper 32 bits are number of nanoseconds | |
109 | * and lower is fractions of nanosecond. | |
110 | * The scaled_ppm represent the ratio in "parts per bilion" by which the | |
111 | * compensation value should be corrected. | |
112 | * To calculate new compenstation value we use 64bit fixed point | |
113 | * arithmetic on following formula | |
114 | * comp = tbase + tbase * scaled_ppm / (1M * 2^16) | |
115 | * where tbase is the basic compensation value calculated initialy | |
116 | * in cavium_ptp_init() -> tbase = 1/Hz. Then we use endian | |
117 | * independent structure definition to write data to PTP register. | |
118 | */ | |
119 | comp = ((u64)1000000000ull << 32) / clock->clock_rate; | |
120 | adj = comp * scaled_ppm; | |
121 | adj >>= 16; | |
122 | adj = div_u64(adj, 1000000ull); | |
123 | comp = neg_adj ? comp - adj : comp + adj; | |
124 | ||
125 | spin_lock_irqsave(&clock->spin_lock, flags); | |
126 | writeq(comp, clock->reg_base + PTP_CLOCK_COMP); | |
127 | spin_unlock_irqrestore(&clock->spin_lock, flags); | |
128 | ||
129 | return 0; | |
130 | } | |
131 | ||
132 | /** | |
133 | * cavium_ptp_adjtime() - Adjust ptp time | |
134 | * @ptp: PTP clock info | |
135 | * @delta: how much to adjust by, in nanosecs | |
136 | */ | |
137 | static int cavium_ptp_adjtime(struct ptp_clock_info *ptp_info, s64 delta) | |
138 | { | |
139 | struct cavium_ptp *clock = | |
140 | container_of(ptp_info, struct cavium_ptp, ptp_info); | |
141 | unsigned long flags; | |
142 | ||
143 | spin_lock_irqsave(&clock->spin_lock, flags); | |
144 | timecounter_adjtime(&clock->time_counter, delta); | |
145 | spin_unlock_irqrestore(&clock->spin_lock, flags); | |
146 | ||
147 | /* Sync, for network driver to get latest value */ | |
148 | smp_mb(); | |
149 | ||
150 | return 0; | |
151 | } | |
152 | ||
153 | /** | |
154 | * cavium_ptp_gettime() - Get hardware clock time with adjustment | |
155 | * @ptp: PTP clock info | |
156 | * @ts: timespec | |
157 | */ | |
158 | static int cavium_ptp_gettime(struct ptp_clock_info *ptp_info, | |
159 | struct timespec64 *ts) | |
160 | { | |
161 | struct cavium_ptp *clock = | |
162 | container_of(ptp_info, struct cavium_ptp, ptp_info); | |
163 | unsigned long flags; | |
164 | u64 nsec; | |
165 | ||
166 | spin_lock_irqsave(&clock->spin_lock, flags); | |
167 | nsec = timecounter_read(&clock->time_counter); | |
168 | spin_unlock_irqrestore(&clock->spin_lock, flags); | |
169 | ||
170 | *ts = ns_to_timespec64(nsec); | |
171 | ||
172 | return 0; | |
173 | } | |
174 | ||
175 | /** | |
176 | * cavium_ptp_settime() - Set hardware clock time. Reset adjustment | |
177 | * @ptp: PTP clock info | |
178 | * @ts: timespec | |
179 | */ | |
180 | static int cavium_ptp_settime(struct ptp_clock_info *ptp_info, | |
181 | const struct timespec64 *ts) | |
182 | { | |
183 | struct cavium_ptp *clock = | |
184 | container_of(ptp_info, struct cavium_ptp, ptp_info); | |
185 | unsigned long flags; | |
186 | u64 nsec; | |
187 | ||
188 | nsec = timespec64_to_ns(ts); | |
189 | ||
190 | spin_lock_irqsave(&clock->spin_lock, flags); | |
191 | timecounter_init(&clock->time_counter, &clock->cycle_counter, nsec); | |
192 | spin_unlock_irqrestore(&clock->spin_lock, flags); | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
197 | /** | |
198 | * cavium_ptp_enable() - Request to enable or disable an ancillary feature. | |
199 | * @ptp: PTP clock info | |
200 | * @rq: request | |
201 | * @on: is it on | |
202 | */ | |
203 | static int cavium_ptp_enable(struct ptp_clock_info *ptp_info, | |
204 | struct ptp_clock_request *rq, int on) | |
205 | { | |
206 | return -EOPNOTSUPP; | |
207 | } | |
208 | ||
209 | static u64 cavium_ptp_cc_read(const struct cyclecounter *cc) | |
210 | { | |
211 | struct cavium_ptp *clock = | |
212 | container_of(cc, struct cavium_ptp, cycle_counter); | |
213 | ||
214 | return readq(clock->reg_base + PTP_CLOCK_HI); | |
215 | } | |
216 | ||
217 | static int cavium_ptp_probe(struct pci_dev *pdev, | |
218 | const struct pci_device_id *ent) | |
219 | { | |
220 | struct device *dev = &pdev->dev; | |
221 | struct cavium_ptp *clock; | |
222 | struct cyclecounter *cc; | |
223 | u64 clock_cfg; | |
224 | u64 clock_comp; | |
225 | int err; | |
226 | ||
227 | clock = devm_kzalloc(dev, sizeof(*clock), GFP_KERNEL); | |
228 | if (!clock) { | |
229 | err = -ENOMEM; | |
230 | goto error; | |
231 | } | |
232 | ||
233 | clock->pdev = pdev; | |
234 | ||
235 | err = pcim_enable_device(pdev); | |
236 | if (err) | |
237 | goto error_free; | |
238 | ||
239 | err = pcim_iomap_regions(pdev, 1 << PCI_PTP_BAR_NO, pci_name(pdev)); | |
240 | if (err) | |
241 | goto error_free; | |
242 | ||
243 | clock->reg_base = pcim_iomap_table(pdev)[PCI_PTP_BAR_NO]; | |
244 | ||
245 | spin_lock_init(&clock->spin_lock); | |
246 | ||
247 | cc = &clock->cycle_counter; | |
248 | cc->read = cavium_ptp_cc_read; | |
249 | cc->mask = CYCLECOUNTER_MASK(64); | |
250 | cc->mult = 1; | |
251 | cc->shift = 0; | |
252 | ||
253 | timecounter_init(&clock->time_counter, &clock->cycle_counter, | |
254 | ktime_to_ns(ktime_get_real())); | |
255 | ||
256 | clock->clock_rate = ptp_cavium_clock_get(); | |
257 | ||
258 | clock->ptp_info = (struct ptp_clock_info) { | |
259 | .owner = THIS_MODULE, | |
260 | .name = "ThunderX PTP", | |
261 | .max_adj = 1000000000ull, | |
262 | .n_ext_ts = 0, | |
263 | .n_pins = 0, | |
264 | .pps = 0, | |
265 | .adjfine = cavium_ptp_adjfine, | |
266 | .adjtime = cavium_ptp_adjtime, | |
267 | .gettime64 = cavium_ptp_gettime, | |
268 | .settime64 = cavium_ptp_settime, | |
269 | .enable = cavium_ptp_enable, | |
270 | }; | |
271 | ||
272 | clock_cfg = readq(clock->reg_base + PTP_CLOCK_CFG); | |
273 | clock_cfg |= PTP_CLOCK_CFG_PTP_EN; | |
274 | writeq(clock_cfg, clock->reg_base + PTP_CLOCK_CFG); | |
275 | ||
276 | clock_comp = ((u64)1000000000ull << 32) / clock->clock_rate; | |
277 | writeq(clock_comp, clock->reg_base + PTP_CLOCK_COMP); | |
278 | ||
279 | clock->ptp_clock = ptp_clock_register(&clock->ptp_info, dev); | |
8c56df37 RB |
280 | if (IS_ERR(clock->ptp_clock)) { |
281 | err = PTR_ERR(clock->ptp_clock); | |
282 | goto error_stop; | |
283 | } | |
284 | ||
285 | pci_set_drvdata(pdev, clock); | |
286 | return 0; | |
287 | ||
288 | error_stop: | |
289 | clock_cfg = readq(clock->reg_base + PTP_CLOCK_CFG); | |
290 | clock_cfg &= ~PTP_CLOCK_CFG_PTP_EN; | |
291 | writeq(clock_cfg, clock->reg_base + PTP_CLOCK_CFG); | |
292 | pcim_iounmap_regions(pdev, 1 << PCI_PTP_BAR_NO); | |
293 | ||
294 | error_free: | |
295 | devm_kfree(dev, clock); | |
296 | ||
297 | error: | |
298 | /* For `cavium_ptp_get()` we need to differentiate between the case | |
299 | * when the core has not tried to probe this device and the case when | |
300 | * the probe failed. In the later case we pretend that the | |
301 | * initialization was successful and keep the error in | |
302 | * `dev->driver_data`. | |
303 | */ | |
304 | pci_set_drvdata(pdev, ERR_PTR(err)); | |
305 | return 0; | |
306 | } | |
307 | ||
308 | static void cavium_ptp_remove(struct pci_dev *pdev) | |
309 | { | |
310 | struct cavium_ptp *clock = pci_get_drvdata(pdev); | |
311 | u64 clock_cfg; | |
312 | ||
313 | if (IS_ERR_OR_NULL(clock)) | |
314 | return; | |
315 | ||
316 | ptp_clock_unregister(clock->ptp_clock); | |
317 | ||
318 | clock_cfg = readq(clock->reg_base + PTP_CLOCK_CFG); | |
319 | clock_cfg &= ~PTP_CLOCK_CFG_PTP_EN; | |
320 | writeq(clock_cfg, clock->reg_base + PTP_CLOCK_CFG); | |
321 | } | |
322 | ||
323 | static const struct pci_device_id cavium_ptp_id_table[] = { | |
324 | { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, PCI_DEVICE_ID_CAVIUM_PTP) }, | |
325 | { 0, } | |
326 | }; | |
327 | ||
328 | static struct pci_driver cavium_ptp_driver = { | |
329 | .name = DRV_NAME, | |
330 | .id_table = cavium_ptp_id_table, | |
331 | .probe = cavium_ptp_probe, | |
332 | .remove = cavium_ptp_remove, | |
333 | }; | |
334 | ||
75498aa1 | 335 | module_pci_driver(cavium_ptp_driver); |
8c56df37 RB |
336 | |
337 | MODULE_DESCRIPTION(DRV_NAME); | |
338 | MODULE_AUTHOR("Cavium Networks <support@cavium.com>"); | |
339 | MODULE_LICENSE("GPL v2"); | |
340 | MODULE_DEVICE_TABLE(pci, cavium_ptp_id_table); |