1 // SPDX-License-Identifier: (GPL-2.0 OR MIT)
4 * Hirschmann Hellcreek TSN switch.
6 * Copyright (C) 2019,2020 Hochschule Offenburg
7 * Copyright (C) 2019,2020 Linutronix GmbH
8 * Authors: Kamil Alkhouri <kamil.alkhouri@hs-offenburg.de>
9 * Kurt Kanzenbach <kurt@linutronix.de>
13 #include <linux/ptp_clock_kernel.h>
14 #include "hellcreek.h"
15 #include "hellcreek_ptp.h"
16 #include "hellcreek_hwtstamp.h"
18 u16
hellcreek_ptp_read(struct hellcreek
*hellcreek
, unsigned int offset
)
20 return readw(hellcreek
->ptp_base
+ offset
);
23 void hellcreek_ptp_write(struct hellcreek
*hellcreek
, u16 data
,
26 writew(data
, hellcreek
->ptp_base
+ offset
);
29 /* Get nanoseconds from PTP clock */
30 static u64
hellcreek_ptp_clock_read(struct hellcreek
*hellcreek
,
31 struct ptp_system_timestamp
*sts
)
36 hellcreek_ptp_write(hellcreek
, PR_COMMAND_C_SS
, PR_COMMAND_C
);
38 /* The time of the day is saved as 96 bits. However, due to hardware
39 * limitations the seconds are not or only partly kept in the PTP
40 * core. Currently only three bits for the seconds are available. That's
41 * why only the nanoseconds are used and the seconds are tracked in
42 * software. Anyway due to internal locking all five registers should be
45 nsh
= hellcreek_ptp_read(hellcreek
, PR_SS_SYNC_DATA_C
);
46 nsh
= hellcreek_ptp_read(hellcreek
, PR_SS_SYNC_DATA_C
);
47 nsh
= hellcreek_ptp_read(hellcreek
, PR_SS_SYNC_DATA_C
);
48 nsh
= hellcreek_ptp_read(hellcreek
, PR_SS_SYNC_DATA_C
);
49 ptp_read_system_prets(sts
);
50 nsl
= hellcreek_ptp_read(hellcreek
, PR_SS_SYNC_DATA_C
);
51 ptp_read_system_postts(sts
);
53 return (u64
)nsl
| ((u64
)nsh
<< 16);
56 static u64
__hellcreek_ptp_gettime(struct hellcreek
*hellcreek
,
57 struct ptp_system_timestamp
*sts
)
61 ns
= hellcreek_ptp_clock_read(hellcreek
, sts
);
62 if (ns
< hellcreek
->last_ts
)
64 hellcreek
->last_ts
= ns
;
65 ns
+= hellcreek
->seconds
* NSEC_PER_SEC
;
70 /* Retrieve the seconds parts in nanoseconds for a packet timestamped with @ns.
71 * There has to be a check whether an overflow occurred between the packet
72 * arrival and now. If so use the correct seconds (-1) for calculating the
73 * packet arrival time.
75 u64
hellcreek_ptp_gettime_seconds(struct hellcreek
*hellcreek
, u64 ns
)
79 __hellcreek_ptp_gettime(hellcreek
, NULL
);
80 if (hellcreek
->last_ts
> ns
)
81 s
= hellcreek
->seconds
* NSEC_PER_SEC
;
83 s
= (hellcreek
->seconds
- 1) * NSEC_PER_SEC
;
88 static int hellcreek_ptp_gettimex(struct ptp_clock_info
*ptp
,
89 struct timespec64
*ts
,
90 struct ptp_system_timestamp
*sts
)
92 struct hellcreek
*hellcreek
= ptp_to_hellcreek(ptp
);
95 mutex_lock(&hellcreek
->ptp_lock
);
96 ns
= __hellcreek_ptp_gettime(hellcreek
, sts
);
97 mutex_unlock(&hellcreek
->ptp_lock
);
99 *ts
= ns_to_timespec64(ns
);
104 static int hellcreek_ptp_settime(struct ptp_clock_info
*ptp
,
105 const struct timespec64
*ts
)
107 struct hellcreek
*hellcreek
= ptp_to_hellcreek(ptp
);
110 secl
= ts
->tv_sec
& 0xffff;
111 nsh
= ((u32
)ts
->tv_nsec
& 0xffff0000) >> 16;
112 nsl
= ts
->tv_nsec
& 0xffff;
114 mutex_lock(&hellcreek
->ptp_lock
);
116 /* Update overflow data structure */
117 hellcreek
->seconds
= ts
->tv_sec
;
118 hellcreek
->last_ts
= ts
->tv_nsec
;
120 /* Set time in clock */
121 hellcreek_ptp_write(hellcreek
, 0x00, PR_CLOCK_WRITE_C
);
122 hellcreek_ptp_write(hellcreek
, 0x00, PR_CLOCK_WRITE_C
);
123 hellcreek_ptp_write(hellcreek
, secl
, PR_CLOCK_WRITE_C
);
124 hellcreek_ptp_write(hellcreek
, nsh
, PR_CLOCK_WRITE_C
);
125 hellcreek_ptp_write(hellcreek
, nsl
, PR_CLOCK_WRITE_C
);
127 mutex_unlock(&hellcreek
->ptp_lock
);
132 static int hellcreek_ptp_adjfine(struct ptp_clock_info
*ptp
, long scaled_ppm
)
134 struct hellcreek
*hellcreek
= ptp_to_hellcreek(ptp
);
135 u16 negative
= 0, addendh
, addendl
;
139 if (scaled_ppm
< 0) {
141 scaled_ppm
= -scaled_ppm
;
144 /* IP-Core adjusts the nominal frequency by adding or subtracting 1 ns
145 * from the 8 ns (period of the oscillator) every time the accumulator
146 * register overflows. The value stored in the addend register is added
147 * to the accumulator register every 8 ns.
149 * addend value = (2^30 * accumulator_overflow_rate) /
150 * oscillator_frequency
153 * oscillator_frequency = 125 MHz
154 * accumulator_overflow_rate = 125 MHz * scaled_ppm * 2^-16 * 10^-6 * 8
158 addend
= (u32
)div_u64(adj
, 15625);
160 addendh
= (addend
& 0xffff0000) >> 16;
161 addendl
= addend
& 0xffff;
163 negative
= (negative
<< 15) & 0x8000;
165 mutex_lock(&hellcreek
->ptp_lock
);
167 /* Set drift register */
168 hellcreek_ptp_write(hellcreek
, negative
, PR_CLOCK_DRIFT_C
);
169 hellcreek_ptp_write(hellcreek
, 0x00, PR_CLOCK_DRIFT_C
);
170 hellcreek_ptp_write(hellcreek
, 0x00, PR_CLOCK_DRIFT_C
);
171 hellcreek_ptp_write(hellcreek
, addendh
, PR_CLOCK_DRIFT_C
);
172 hellcreek_ptp_write(hellcreek
, addendl
, PR_CLOCK_DRIFT_C
);
174 mutex_unlock(&hellcreek
->ptp_lock
);
179 static int hellcreek_ptp_adjtime(struct ptp_clock_info
*ptp
, s64 delta
)
181 struct hellcreek
*hellcreek
= ptp_to_hellcreek(ptp
);
182 u16 negative
= 0, counth
, countl
;
185 /* If the offset is larger than IP-Core slow offset resources. Don't
186 * consider slow adjustment. Rather, add the offset directly to the
189 if (abs(delta
) > MAX_SLOW_OFFSET_ADJ
) {
190 struct timespec64 now
, then
= ns_to_timespec64(delta
);
192 hellcreek_ptp_gettimex(ptp
, &now
, NULL
);
193 now
= timespec64_add(now
, then
);
194 hellcreek_ptp_settime(ptp
, &now
);
204 /* 'count_val' does not exceed the maximum register size (2^30) */
205 count_val
= div_s64(delta
, MAX_NS_PER_STEP
);
207 counth
= (count_val
& 0xffff0000) >> 16;
208 countl
= count_val
& 0xffff;
210 negative
= (negative
<< 15) & 0x8000;
212 mutex_lock(&hellcreek
->ptp_lock
);
214 /* Set offset write register */
215 hellcreek_ptp_write(hellcreek
, negative
, PR_CLOCK_OFFSET_C
);
216 hellcreek_ptp_write(hellcreek
, MAX_NS_PER_STEP
, PR_CLOCK_OFFSET_C
);
217 hellcreek_ptp_write(hellcreek
, MIN_CLK_CYCLES_BETWEEN_STEPS
,
219 hellcreek_ptp_write(hellcreek
, countl
, PR_CLOCK_OFFSET_C
);
220 hellcreek_ptp_write(hellcreek
, counth
, PR_CLOCK_OFFSET_C
);
222 mutex_unlock(&hellcreek
->ptp_lock
);
227 static int hellcreek_ptp_enable(struct ptp_clock_info
*ptp
,
228 struct ptp_clock_request
*rq
, int on
)
233 static void hellcreek_ptp_overflow_check(struct work_struct
*work
)
235 struct delayed_work
*dw
= to_delayed_work(work
);
236 struct hellcreek
*hellcreek
;
238 hellcreek
= dw_overflow_to_hellcreek(dw
);
240 mutex_lock(&hellcreek
->ptp_lock
);
241 __hellcreek_ptp_gettime(hellcreek
, NULL
);
242 mutex_unlock(&hellcreek
->ptp_lock
);
244 schedule_delayed_work(&hellcreek
->overflow_work
,
245 HELLCREEK_OVERFLOW_PERIOD
);
248 static enum led_brightness
hellcreek_get_brightness(struct hellcreek
*hellcreek
,
251 return (hellcreek
->status_out
& led
) ? 1 : 0;
254 static void hellcreek_set_brightness(struct hellcreek
*hellcreek
, int led
,
255 enum led_brightness b
)
257 mutex_lock(&hellcreek
->ptp_lock
);
260 hellcreek
->status_out
|= led
;
262 hellcreek
->status_out
&= ~led
;
264 hellcreek_ptp_write(hellcreek
, hellcreek
->status_out
, STATUS_OUT
);
266 mutex_unlock(&hellcreek
->ptp_lock
);
269 static void hellcreek_led_sync_good_set(struct led_classdev
*ldev
,
270 enum led_brightness b
)
272 struct hellcreek
*hellcreek
= led_to_hellcreek(ldev
, led_sync_good
);
274 hellcreek_set_brightness(hellcreek
, STATUS_OUT_SYNC_GOOD
, b
);
277 static enum led_brightness
hellcreek_led_sync_good_get(struct led_classdev
*ldev
)
279 struct hellcreek
*hellcreek
= led_to_hellcreek(ldev
, led_sync_good
);
281 return hellcreek_get_brightness(hellcreek
, STATUS_OUT_SYNC_GOOD
);
284 static void hellcreek_led_is_gm_set(struct led_classdev
*ldev
,
285 enum led_brightness b
)
287 struct hellcreek
*hellcreek
= led_to_hellcreek(ldev
, led_is_gm
);
289 hellcreek_set_brightness(hellcreek
, STATUS_OUT_IS_GM
, b
);
292 static enum led_brightness
hellcreek_led_is_gm_get(struct led_classdev
*ldev
)
294 struct hellcreek
*hellcreek
= led_to_hellcreek(ldev
, led_is_gm
);
296 return hellcreek_get_brightness(hellcreek
, STATUS_OUT_IS_GM
);
299 /* There two available LEDs internally called sync_good and is_gm. However, the
300 * user might want to use a different label and specify the default state. Take
301 * those properties from device tree.
303 static int hellcreek_led_setup(struct hellcreek
*hellcreek
)
305 struct device_node
*leds
, *led
= NULL
;
306 enum led_default_state state
;
310 of_node_get(hellcreek
->dev
->of_node
);
311 leds
= of_find_node_by_name(hellcreek
->dev
->of_node
, "leds");
313 dev_err(hellcreek
->dev
, "No LEDs specified in device tree!\n");
317 hellcreek
->status_out
= 0;
319 led
= of_get_next_available_child(leds
, led
);
321 dev_err(hellcreek
->dev
, "First LED not specified!\n");
325 ret
= of_property_read_string(led
, "label", &label
);
326 hellcreek
->led_sync_good
.name
= ret
? "sync_good" : label
;
328 state
= led_init_default_state_get(of_fwnode_handle(led
));
330 case LEDS_DEFSTATE_ON
:
331 hellcreek
->led_sync_good
.brightness
= 1;
333 case LEDS_DEFSTATE_KEEP
:
334 hellcreek
->led_sync_good
.brightness
=
335 hellcreek_get_brightness(hellcreek
, STATUS_OUT_SYNC_GOOD
);
338 hellcreek
->led_sync_good
.brightness
= 0;
341 hellcreek
->led_sync_good
.max_brightness
= 1;
342 hellcreek
->led_sync_good
.brightness_set
= hellcreek_led_sync_good_set
;
343 hellcreek
->led_sync_good
.brightness_get
= hellcreek_led_sync_good_get
;
345 led
= of_get_next_available_child(leds
, led
);
347 dev_err(hellcreek
->dev
, "Second LED not specified!\n");
352 ret
= of_property_read_string(led
, "label", &label
);
353 hellcreek
->led_is_gm
.name
= ret
? "is_gm" : label
;
355 state
= led_init_default_state_get(of_fwnode_handle(led
));
357 case LEDS_DEFSTATE_ON
:
358 hellcreek
->led_is_gm
.brightness
= 1;
360 case LEDS_DEFSTATE_KEEP
:
361 hellcreek
->led_is_gm
.brightness
=
362 hellcreek_get_brightness(hellcreek
, STATUS_OUT_IS_GM
);
365 hellcreek
->led_is_gm
.brightness
= 0;
368 hellcreek
->led_is_gm
.max_brightness
= 1;
369 hellcreek
->led_is_gm
.brightness_set
= hellcreek_led_is_gm_set
;
370 hellcreek
->led_is_gm
.brightness_get
= hellcreek_led_is_gm_get
;
372 /* Set initial state */
373 if (hellcreek
->led_sync_good
.brightness
== 1)
374 hellcreek_set_brightness(hellcreek
, STATUS_OUT_SYNC_GOOD
, 1);
375 if (hellcreek
->led_is_gm
.brightness
== 1)
376 hellcreek_set_brightness(hellcreek
, STATUS_OUT_IS_GM
, 1);
378 /* Register both leds */
379 led_classdev_register(hellcreek
->dev
, &hellcreek
->led_sync_good
);
380 led_classdev_register(hellcreek
->dev
, &hellcreek
->led_is_gm
);
390 int hellcreek_ptp_setup(struct hellcreek
*hellcreek
)
395 /* Set up the overflow work */
396 INIT_DELAYED_WORK(&hellcreek
->overflow_work
,
397 hellcreek_ptp_overflow_check
);
399 /* Setup PTP clock */
400 hellcreek
->ptp_clock_info
.owner
= THIS_MODULE
;
401 snprintf(hellcreek
->ptp_clock_info
.name
,
402 sizeof(hellcreek
->ptp_clock_info
.name
),
403 dev_name(hellcreek
->dev
));
405 /* IP-Core can add up to 0.5 ns per 8 ns cycle, which means
406 * accumulator_overflow_rate shall not exceed 62.5 MHz (which adjusts
407 * the nominal frequency by 6.25%)
409 hellcreek
->ptp_clock_info
.max_adj
= 62500000;
410 hellcreek
->ptp_clock_info
.n_alarm
= 0;
411 hellcreek
->ptp_clock_info
.n_pins
= 0;
412 hellcreek
->ptp_clock_info
.n_ext_ts
= 0;
413 hellcreek
->ptp_clock_info
.n_per_out
= 0;
414 hellcreek
->ptp_clock_info
.pps
= 0;
415 hellcreek
->ptp_clock_info
.adjfine
= hellcreek_ptp_adjfine
;
416 hellcreek
->ptp_clock_info
.adjtime
= hellcreek_ptp_adjtime
;
417 hellcreek
->ptp_clock_info
.gettimex64
= hellcreek_ptp_gettimex
;
418 hellcreek
->ptp_clock_info
.settime64
= hellcreek_ptp_settime
;
419 hellcreek
->ptp_clock_info
.enable
= hellcreek_ptp_enable
;
420 hellcreek
->ptp_clock_info
.do_aux_work
= hellcreek_hwtstamp_work
;
422 hellcreek
->ptp_clock
= ptp_clock_register(&hellcreek
->ptp_clock_info
,
424 if (IS_ERR(hellcreek
->ptp_clock
))
425 return PTR_ERR(hellcreek
->ptp_clock
);
427 /* Enable the offset correction process, if no offset correction is
428 * already taking place
430 status
= hellcreek_ptp_read(hellcreek
, PR_CLOCK_STATUS_C
);
431 if (!(status
& PR_CLOCK_STATUS_C_OFS_ACT
))
432 hellcreek_ptp_write(hellcreek
,
433 status
| PR_CLOCK_STATUS_C_ENA_OFS
,
436 /* Enable the drift correction process */
437 hellcreek_ptp_write(hellcreek
, status
| PR_CLOCK_STATUS_C_ENA_DRIFT
,
441 ret
= hellcreek_led_setup(hellcreek
);
443 if (hellcreek
->ptp_clock
)
444 ptp_clock_unregister(hellcreek
->ptp_clock
);
448 schedule_delayed_work(&hellcreek
->overflow_work
,
449 HELLCREEK_OVERFLOW_PERIOD
);
454 void hellcreek_ptp_free(struct hellcreek
*hellcreek
)
456 led_classdev_unregister(&hellcreek
->led_is_gm
);
457 led_classdev_unregister(&hellcreek
->led_sync_good
);
458 cancel_delayed_work_sync(&hellcreek
->overflow_work
);
459 if (hellcreek
->ptp_clock
)
460 ptp_clock_unregister(hellcreek
->ptp_clock
);
461 hellcreek
->ptp_clock
= NULL
;