1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * PTP 1588 clock using the IXP46X |
4 | * |
5 | * Copyright (C) 2010 OMICRON electronics GmbH |
6 | */ |
7 | #include <linux/device.h> |
8 | #include <linux/module.h> |
9 | #include <linux/mod_devicetable.h> |
10 | #include <linux/err.h> |
11 | #include <linux/init.h> |
12 | #include <linux/interrupt.h> |
13 | #include <linux/io.h> |
14 | #include <linux/irq.h> |
15 | #include <linux/kernel.h> |
16 | #include <linux/ptp_clock_kernel.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/soc/ixp4xx/cpu.h> |
19 | |
20 | #include "ixp46x_ts.h" |
21 | |
22 | #define DRIVER "ptp_ixp46x" |
23 | #define N_EXT_TS 2 |
24 | |
25 | struct ixp_clock { |
26 | struct ixp46x_ts_regs *regs; |
27 | struct ptp_clock *ptp_clock; |
28 | struct ptp_clock_info caps; |
29 | int exts0_enabled; |
30 | int exts1_enabled; |
31 | int slave_irq; |
32 | int master_irq; |
33 | }; |
34 | |
35 | static DEFINE_SPINLOCK(register_lock); |
36 | |
37 | /* |
38 | * Register access functions |
39 | */ |
40 | |
41 | static u64 ixp_systime_read(struct ixp46x_ts_regs *regs) |
42 | { |
43 | u64 ns; |
44 | u32 lo, hi; |
45 | |
46 | lo = __raw_readl(addr: ®s->systime_lo); |
47 | hi = __raw_readl(addr: ®s->systime_hi); |
48 | |
49 | ns = ((u64) hi) << 32; |
50 | ns |= lo; |
51 | ns <<= TICKS_NS_SHIFT; |
52 | |
53 | return ns; |
54 | } |
55 | |
56 | static void ixp_systime_write(struct ixp46x_ts_regs *regs, u64 ns) |
57 | { |
58 | u32 hi, lo; |
59 | |
60 | ns >>= TICKS_NS_SHIFT; |
61 | hi = ns >> 32; |
62 | lo = ns & 0xffffffff; |
63 | |
64 | __raw_writel(val: lo, addr: ®s->systime_lo); |
65 | __raw_writel(val: hi, addr: ®s->systime_hi); |
66 | } |
67 | |
68 | /* |
69 | * Interrupt service routine |
70 | */ |
71 | |
72 | static irqreturn_t isr(int irq, void *priv) |
73 | { |
74 | struct ixp_clock *ixp_clock = priv; |
75 | struct ixp46x_ts_regs *regs = ixp_clock->regs; |
76 | struct ptp_clock_event event; |
77 | u32 ack = 0, lo, hi, val; |
78 | |
79 | val = __raw_readl(addr: ®s->event); |
80 | |
81 | if (val & TSER_SNS) { |
82 | ack |= TSER_SNS; |
83 | if (ixp_clock->exts0_enabled) { |
84 | hi = __raw_readl(addr: ®s->asms_hi); |
85 | lo = __raw_readl(addr: ®s->asms_lo); |
86 | event.type = PTP_CLOCK_EXTTS; |
87 | event.index = 0; |
88 | event.timestamp = ((u64) hi) << 32; |
89 | event.timestamp |= lo; |
90 | event.timestamp <<= TICKS_NS_SHIFT; |
91 | ptp_clock_event(ptp: ixp_clock->ptp_clock, event: &event); |
92 | } |
93 | } |
94 | |
95 | if (val & TSER_SNM) { |
96 | ack |= TSER_SNM; |
97 | if (ixp_clock->exts1_enabled) { |
98 | hi = __raw_readl(addr: ®s->amms_hi); |
99 | lo = __raw_readl(addr: ®s->amms_lo); |
100 | event.type = PTP_CLOCK_EXTTS; |
101 | event.index = 1; |
102 | event.timestamp = ((u64) hi) << 32; |
103 | event.timestamp |= lo; |
104 | event.timestamp <<= TICKS_NS_SHIFT; |
105 | ptp_clock_event(ptp: ixp_clock->ptp_clock, event: &event); |
106 | } |
107 | } |
108 | |
109 | if (val & TTIPEND) |
110 | ack |= TTIPEND; /* this bit seems to be always set */ |
111 | |
112 | if (ack) { |
113 | __raw_writel(val: ack, addr: ®s->event); |
114 | return IRQ_HANDLED; |
115 | } else |
116 | return IRQ_NONE; |
117 | } |
118 | |
119 | /* |
120 | * PTP clock operations |
121 | */ |
122 | |
123 | static int ptp_ixp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) |
124 | { |
125 | u32 addend; |
126 | struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); |
127 | struct ixp46x_ts_regs *regs = ixp_clock->regs; |
128 | |
129 | addend = adjust_by_scaled_ppm(DEFAULT_ADDEND, scaled_ppm); |
130 | |
131 | __raw_writel(val: addend, addr: ®s->addend); |
132 | |
133 | return 0; |
134 | } |
135 | |
136 | static int ptp_ixp_adjtime(struct ptp_clock_info *ptp, s64 delta) |
137 | { |
138 | s64 now; |
139 | unsigned long flags; |
140 | struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); |
141 | struct ixp46x_ts_regs *regs = ixp_clock->regs; |
142 | |
143 | spin_lock_irqsave(®ister_lock, flags); |
144 | |
145 | now = ixp_systime_read(regs); |
146 | now += delta; |
147 | ixp_systime_write(regs, ns: now); |
148 | |
149 | spin_unlock_irqrestore(lock: ®ister_lock, flags); |
150 | |
151 | return 0; |
152 | } |
153 | |
154 | static int ptp_ixp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) |
155 | { |
156 | u64 ns; |
157 | unsigned long flags; |
158 | struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); |
159 | struct ixp46x_ts_regs *regs = ixp_clock->regs; |
160 | |
161 | spin_lock_irqsave(®ister_lock, flags); |
162 | |
163 | ns = ixp_systime_read(regs); |
164 | |
165 | spin_unlock_irqrestore(lock: ®ister_lock, flags); |
166 | |
167 | *ts = ns_to_timespec64(nsec: ns); |
168 | return 0; |
169 | } |
170 | |
171 | static int ptp_ixp_settime(struct ptp_clock_info *ptp, |
172 | const struct timespec64 *ts) |
173 | { |
174 | u64 ns; |
175 | unsigned long flags; |
176 | struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); |
177 | struct ixp46x_ts_regs *regs = ixp_clock->regs; |
178 | |
179 | ns = timespec64_to_ns(ts); |
180 | |
181 | spin_lock_irqsave(®ister_lock, flags); |
182 | |
183 | ixp_systime_write(regs, ns); |
184 | |
185 | spin_unlock_irqrestore(lock: ®ister_lock, flags); |
186 | |
187 | return 0; |
188 | } |
189 | |
190 | static int ptp_ixp_enable(struct ptp_clock_info *ptp, |
191 | struct ptp_clock_request *rq, int on) |
192 | { |
193 | struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); |
194 | |
195 | switch (rq->type) { |
196 | case PTP_CLK_REQ_EXTTS: |
197 | switch (rq->extts.index) { |
198 | case 0: |
199 | ixp_clock->exts0_enabled = on ? 1 : 0; |
200 | break; |
201 | case 1: |
202 | ixp_clock->exts1_enabled = on ? 1 : 0; |
203 | break; |
204 | default: |
205 | return -EINVAL; |
206 | } |
207 | return 0; |
208 | default: |
209 | break; |
210 | } |
211 | |
212 | return -EOPNOTSUPP; |
213 | } |
214 | |
215 | static const struct ptp_clock_info ptp_ixp_caps = { |
216 | .owner = THIS_MODULE, |
217 | .name = "IXP46X timer" , |
218 | .max_adj = 66666655, |
219 | .n_ext_ts = N_EXT_TS, |
220 | .n_pins = 0, |
221 | .pps = 0, |
222 | .adjfine = ptp_ixp_adjfine, |
223 | .adjtime = ptp_ixp_adjtime, |
224 | .gettime64 = ptp_ixp_gettime, |
225 | .settime64 = ptp_ixp_settime, |
226 | .enable = ptp_ixp_enable, |
227 | }; |
228 | |
229 | /* module operations */ |
230 | |
231 | static struct ixp_clock ixp_clock; |
232 | |
233 | int ixp46x_ptp_find(struct ixp46x_ts_regs *__iomem *regs, int *phc_index) |
234 | { |
235 | *regs = ixp_clock.regs; |
236 | *phc_index = ptp_clock_index(ptp: ixp_clock.ptp_clock); |
237 | |
238 | if (!ixp_clock.ptp_clock) |
239 | return -EPROBE_DEFER; |
240 | |
241 | return 0; |
242 | } |
243 | EXPORT_SYMBOL_GPL(ixp46x_ptp_find); |
244 | |
245 | /* Called from the registered devm action */ |
246 | static void ptp_ixp_unregister_action(void *d) |
247 | { |
248 | struct ptp_clock *ptp_clock = d; |
249 | |
250 | ptp_clock_unregister(ptp: ptp_clock); |
251 | ixp_clock.ptp_clock = NULL; |
252 | } |
253 | |
254 | static int ptp_ixp_probe(struct platform_device *pdev) |
255 | { |
256 | struct device *dev = &pdev->dev; |
257 | int ret; |
258 | |
259 | ixp_clock.regs = devm_platform_ioremap_resource(pdev, index: 0); |
260 | ixp_clock.master_irq = platform_get_irq(pdev, 0); |
261 | ixp_clock.slave_irq = platform_get_irq(pdev, 1); |
262 | if (IS_ERR(ptr: ixp_clock.regs) || |
263 | ixp_clock.master_irq < 0 || ixp_clock.slave_irq < 0) |
264 | return -ENXIO; |
265 | |
266 | ixp_clock.caps = ptp_ixp_caps; |
267 | |
268 | ixp_clock.ptp_clock = ptp_clock_register(info: &ixp_clock.caps, NULL); |
269 | |
270 | if (IS_ERR(ptr: ixp_clock.ptp_clock)) |
271 | return PTR_ERR(ptr: ixp_clock.ptp_clock); |
272 | |
273 | ret = devm_add_action_or_reset(dev, ptp_ixp_unregister_action, |
274 | ixp_clock.ptp_clock); |
275 | if (ret) { |
276 | dev_err(dev, "failed to install clock removal handler\n" ); |
277 | return ret; |
278 | } |
279 | |
280 | __raw_writel(DEFAULT_ADDEND, addr: &ixp_clock.regs->addend); |
281 | __raw_writel(val: 1, addr: &ixp_clock.regs->trgt_lo); |
282 | __raw_writel(val: 0, addr: &ixp_clock.regs->trgt_hi); |
283 | __raw_writel(TTIPEND, addr: &ixp_clock.regs->event); |
284 | |
285 | ret = devm_request_irq(dev, irq: ixp_clock.master_irq, handler: isr, |
286 | irqflags: 0, DRIVER, dev_id: &ixp_clock); |
287 | if (ret) |
288 | return dev_err_probe(dev, err: ret, |
289 | fmt: "request_irq failed for irq %d\n" , |
290 | ixp_clock.master_irq); |
291 | |
292 | ret = devm_request_irq(dev, irq: ixp_clock.slave_irq, handler: isr, |
293 | irqflags: 0, DRIVER, dev_id: &ixp_clock); |
294 | if (ret) |
295 | return dev_err_probe(dev, err: ret, |
296 | fmt: "request_irq failed for irq %d\n" , |
297 | ixp_clock.slave_irq); |
298 | |
299 | return 0; |
300 | } |
301 | |
302 | static const struct of_device_id ptp_ixp_match[] = { |
303 | { |
304 | .compatible = "intel,ixp46x-ptp-timer" , |
305 | }, |
306 | { }, |
307 | }; |
308 | |
309 | static struct platform_driver ptp_ixp_driver = { |
310 | .driver = { |
311 | .name = "ptp-ixp46x" , |
312 | .of_match_table = ptp_ixp_match, |
313 | .suppress_bind_attrs = true, |
314 | }, |
315 | .probe = ptp_ixp_probe, |
316 | }; |
317 | module_platform_driver(ptp_ixp_driver); |
318 | |
319 | MODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>" ); |
320 | MODULE_DESCRIPTION("PTP clock using the IXP46X timer" ); |
321 | MODULE_LICENSE("GPL" ); |
322 | |