Linux Audio

Check our new training course

Loading...
v6.8
  1// SPDX-License-Identifier: (GPL-2.0 OR MIT)
  2/*
  3 * DSA driver for:
  4 * Hirschmann Hellcreek TSN switch.
  5 *
  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>
 10 */
 11
 12#include <linux/of.h>
 13#include <linux/ptp_clock_kernel.h>
 14#include "hellcreek.h"
 15#include "hellcreek_ptp.h"
 16#include "hellcreek_hwtstamp.h"
 17
 18u16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset)
 19{
 20	return readw(hellcreek->ptp_base + offset);
 21}
 22
 23void hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data,
 24			 unsigned int offset)
 25{
 26	writew(data, hellcreek->ptp_base + offset);
 27}
 28
 29/* Get nanoseconds from PTP clock */
 30static u64 hellcreek_ptp_clock_read(struct hellcreek *hellcreek)
 
 31{
 32	u16 nsl, nsh;
 33
 34	/* Take a snapshot */
 35	hellcreek_ptp_write(hellcreek, PR_COMMAND_C_SS, PR_COMMAND_C);
 36
 37	/* The time of the day is saved as 96 bits. However, due to hardware
 38	 * limitations the seconds are not or only partly kept in the PTP
 39	 * core. Currently only three bits for the seconds are available. That's
 40	 * why only the nanoseconds are used and the seconds are tracked in
 41	 * software. Anyway due to internal locking all five registers should be
 42	 * read.
 43	 */
 44	nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
 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	nsl = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
 
 49
 50	return (u64)nsl | ((u64)nsh << 16);
 51}
 52
 53static u64 __hellcreek_ptp_gettime(struct hellcreek *hellcreek)
 
 54{
 55	u64 ns;
 56
 57	ns = hellcreek_ptp_clock_read(hellcreek);
 58	if (ns < hellcreek->last_ts)
 59		hellcreek->seconds++;
 60	hellcreek->last_ts = ns;
 61	ns += hellcreek->seconds * NSEC_PER_SEC;
 62
 63	return ns;
 64}
 65
 66/* Retrieve the seconds parts in nanoseconds for a packet timestamped with @ns.
 67 * There has to be a check whether an overflow occurred between the packet
 68 * arrival and now. If so use the correct seconds (-1) for calculating the
 69 * packet arrival time.
 70 */
 71u64 hellcreek_ptp_gettime_seconds(struct hellcreek *hellcreek, u64 ns)
 72{
 73	u64 s;
 74
 75	__hellcreek_ptp_gettime(hellcreek);
 76	if (hellcreek->last_ts > ns)
 77		s = hellcreek->seconds * NSEC_PER_SEC;
 78	else
 79		s = (hellcreek->seconds - 1) * NSEC_PER_SEC;
 80
 81	return s;
 82}
 83
 84static int hellcreek_ptp_gettime(struct ptp_clock_info *ptp,
 85				 struct timespec64 *ts)
 
 86{
 87	struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
 88	u64 ns;
 89
 90	mutex_lock(&hellcreek->ptp_lock);
 91	ns = __hellcreek_ptp_gettime(hellcreek);
 92	mutex_unlock(&hellcreek->ptp_lock);
 93
 94	*ts = ns_to_timespec64(ns);
 95
 96	return 0;
 97}
 98
 99static int hellcreek_ptp_settime(struct ptp_clock_info *ptp,
100				 const struct timespec64 *ts)
101{
102	struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
103	u16 secl, nsh, nsl;
104
105	secl = ts->tv_sec & 0xffff;
106	nsh  = ((u32)ts->tv_nsec & 0xffff0000) >> 16;
107	nsl  = ts->tv_nsec & 0xffff;
108
109	mutex_lock(&hellcreek->ptp_lock);
110
111	/* Update overflow data structure */
112	hellcreek->seconds = ts->tv_sec;
113	hellcreek->last_ts = ts->tv_nsec;
114
115	/* Set time in clock */
116	hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C);
117	hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C);
118	hellcreek_ptp_write(hellcreek, secl, PR_CLOCK_WRITE_C);
119	hellcreek_ptp_write(hellcreek, nsh,  PR_CLOCK_WRITE_C);
120	hellcreek_ptp_write(hellcreek, nsl,  PR_CLOCK_WRITE_C);
121
122	mutex_unlock(&hellcreek->ptp_lock);
123
124	return 0;
125}
126
127static int hellcreek_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
128{
129	struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
130	u16 negative = 0, addendh, addendl;
131	u32 addend;
132	u64 adj;
133
134	if (scaled_ppm < 0) {
135		negative = 1;
136		scaled_ppm = -scaled_ppm;
137	}
138
139	/* IP-Core adjusts the nominal frequency by adding or subtracting 1 ns
140	 * from the 8 ns (period of the oscillator) every time the accumulator
141	 * register overflows. The value stored in the addend register is added
142	 * to the accumulator register every 8 ns.
143	 *
144	 * addend value = (2^30 * accumulator_overflow_rate) /
145	 *                oscillator_frequency
146	 * where:
147	 *
148	 * oscillator_frequency = 125 MHz
149	 * accumulator_overflow_rate = 125 MHz * scaled_ppm * 2^-16 * 10^-6 * 8
150	 */
151	adj = scaled_ppm;
152	adj <<= 11;
153	addend = (u32)div_u64(adj, 15625);
154
155	addendh = (addend & 0xffff0000) >> 16;
156	addendl = addend & 0xffff;
157
158	negative = (negative << 15) & 0x8000;
159
160	mutex_lock(&hellcreek->ptp_lock);
161
162	/* Set drift register */
163	hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_DRIFT_C);
164	hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C);
165	hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C);
166	hellcreek_ptp_write(hellcreek, addendh,  PR_CLOCK_DRIFT_C);
167	hellcreek_ptp_write(hellcreek, addendl,  PR_CLOCK_DRIFT_C);
168
169	mutex_unlock(&hellcreek->ptp_lock);
170
171	return 0;
172}
173
174static int hellcreek_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
175{
176	struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
177	u16 negative = 0, counth, countl;
178	u32 count_val;
179
180	/* If the offset is larger than IP-Core slow offset resources. Don't
181	 * consider slow adjustment. Rather, add the offset directly to the
182	 * current time
183	 */
184	if (abs(delta) > MAX_SLOW_OFFSET_ADJ) {
185		struct timespec64 now, then = ns_to_timespec64(delta);
186
187		hellcreek_ptp_gettime(ptp, &now);
188		now = timespec64_add(now, then);
189		hellcreek_ptp_settime(ptp, &now);
190
191		return 0;
192	}
193
194	if (delta < 0) {
195		negative = 1;
196		delta = -delta;
197	}
198
199	/* 'count_val' does not exceed the maximum register size (2^30) */
200	count_val = div_s64(delta, MAX_NS_PER_STEP);
201
202	counth = (count_val & 0xffff0000) >> 16;
203	countl = count_val & 0xffff;
204
205	negative = (negative << 15) & 0x8000;
206
207	mutex_lock(&hellcreek->ptp_lock);
208
209	/* Set offset write register */
210	hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_OFFSET_C);
211	hellcreek_ptp_write(hellcreek, MAX_NS_PER_STEP, PR_CLOCK_OFFSET_C);
212	hellcreek_ptp_write(hellcreek, MIN_CLK_CYCLES_BETWEEN_STEPS,
213			    PR_CLOCK_OFFSET_C);
214	hellcreek_ptp_write(hellcreek, countl,  PR_CLOCK_OFFSET_C);
215	hellcreek_ptp_write(hellcreek, counth,  PR_CLOCK_OFFSET_C);
216
217	mutex_unlock(&hellcreek->ptp_lock);
218
219	return 0;
220}
221
222static int hellcreek_ptp_enable(struct ptp_clock_info *ptp,
223				struct ptp_clock_request *rq, int on)
224{
225	return -EOPNOTSUPP;
226}
227
228static void hellcreek_ptp_overflow_check(struct work_struct *work)
229{
230	struct delayed_work *dw = to_delayed_work(work);
231	struct hellcreek *hellcreek;
232
233	hellcreek = dw_overflow_to_hellcreek(dw);
234
235	mutex_lock(&hellcreek->ptp_lock);
236	__hellcreek_ptp_gettime(hellcreek);
237	mutex_unlock(&hellcreek->ptp_lock);
238
239	schedule_delayed_work(&hellcreek->overflow_work,
240			      HELLCREEK_OVERFLOW_PERIOD);
241}
242
243static enum led_brightness hellcreek_get_brightness(struct hellcreek *hellcreek,
244						    int led)
245{
246	return (hellcreek->status_out & led) ? 1 : 0;
247}
248
249static void hellcreek_set_brightness(struct hellcreek *hellcreek, int led,
250				     enum led_brightness b)
251{
252	mutex_lock(&hellcreek->ptp_lock);
253
254	if (b)
255		hellcreek->status_out |= led;
256	else
257		hellcreek->status_out &= ~led;
258
259	hellcreek_ptp_write(hellcreek, hellcreek->status_out, STATUS_OUT);
260
261	mutex_unlock(&hellcreek->ptp_lock);
262}
263
264static void hellcreek_led_sync_good_set(struct led_classdev *ldev,
265					enum led_brightness b)
266{
267	struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good);
268
269	hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, b);
270}
271
272static enum led_brightness hellcreek_led_sync_good_get(struct led_classdev *ldev)
273{
274	struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good);
275
276	return hellcreek_get_brightness(hellcreek, STATUS_OUT_SYNC_GOOD);
277}
278
279static void hellcreek_led_is_gm_set(struct led_classdev *ldev,
280				    enum led_brightness b)
281{
282	struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm);
283
284	hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, b);
285}
286
287static enum led_brightness hellcreek_led_is_gm_get(struct led_classdev *ldev)
288{
289	struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm);
290
291	return hellcreek_get_brightness(hellcreek, STATUS_OUT_IS_GM);
292}
293
294/* There two available LEDs internally called sync_good and is_gm. However, the
295 * user might want to use a different label and specify the default state. Take
296 * those properties from device tree.
297 */
298static int hellcreek_led_setup(struct hellcreek *hellcreek)
299{
300	struct device_node *leds, *led = NULL;
301	enum led_default_state state;
302	const char *label;
303	int ret = -EINVAL;
304
305	of_node_get(hellcreek->dev->of_node);
306	leds = of_find_node_by_name(hellcreek->dev->of_node, "leds");
307	if (!leds) {
308		dev_err(hellcreek->dev, "No LEDs specified in device tree!\n");
309		return ret;
310	}
311
312	hellcreek->status_out = 0;
313
314	led = of_get_next_available_child(leds, led);
315	if (!led) {
316		dev_err(hellcreek->dev, "First LED not specified!\n");
317		goto out;
318	}
319
320	ret = of_property_read_string(led, "label", &label);
321	hellcreek->led_sync_good.name = ret ? "sync_good" : label;
322
323	state = led_init_default_state_get(of_fwnode_handle(led));
324	switch (state) {
325	case LEDS_DEFSTATE_ON:
326		hellcreek->led_sync_good.brightness = 1;
327		break;
328	case LEDS_DEFSTATE_KEEP:
329		hellcreek->led_sync_good.brightness =
330			hellcreek_get_brightness(hellcreek, STATUS_OUT_SYNC_GOOD);
331		break;
332	default:
333		hellcreek->led_sync_good.brightness = 0;
334	}
335
336	hellcreek->led_sync_good.max_brightness = 1;
337	hellcreek->led_sync_good.brightness_set = hellcreek_led_sync_good_set;
338	hellcreek->led_sync_good.brightness_get = hellcreek_led_sync_good_get;
339
340	led = of_get_next_available_child(leds, led);
341	if (!led) {
342		dev_err(hellcreek->dev, "Second LED not specified!\n");
343		ret = -EINVAL;
344		goto out;
345	}
346
347	ret = of_property_read_string(led, "label", &label);
348	hellcreek->led_is_gm.name = ret ? "is_gm" : label;
349
350	state = led_init_default_state_get(of_fwnode_handle(led));
351	switch (state) {
352	case LEDS_DEFSTATE_ON:
353		hellcreek->led_is_gm.brightness = 1;
354		break;
355	case LEDS_DEFSTATE_KEEP:
356		hellcreek->led_is_gm.brightness =
357			hellcreek_get_brightness(hellcreek, STATUS_OUT_IS_GM);
358		break;
359	default:
360		hellcreek->led_is_gm.brightness = 0;
361	}
362
363	hellcreek->led_is_gm.max_brightness = 1;
364	hellcreek->led_is_gm.brightness_set = hellcreek_led_is_gm_set;
365	hellcreek->led_is_gm.brightness_get = hellcreek_led_is_gm_get;
366
367	/* Set initial state */
368	if (hellcreek->led_sync_good.brightness == 1)
369		hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, 1);
370	if (hellcreek->led_is_gm.brightness == 1)
371		hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, 1);
372
373	/* Register both leds */
374	led_classdev_register(hellcreek->dev, &hellcreek->led_sync_good);
375	led_classdev_register(hellcreek->dev, &hellcreek->led_is_gm);
376
377	ret = 0;
378
379out:
380	of_node_put(leds);
381
382	return ret;
383}
384
385int hellcreek_ptp_setup(struct hellcreek *hellcreek)
386{
387	u16 status;
388	int ret;
389
390	/* Set up the overflow work */
391	INIT_DELAYED_WORK(&hellcreek->overflow_work,
392			  hellcreek_ptp_overflow_check);
393
394	/* Setup PTP clock */
395	hellcreek->ptp_clock_info.owner = THIS_MODULE;
396	snprintf(hellcreek->ptp_clock_info.name,
397		 sizeof(hellcreek->ptp_clock_info.name),
398		 dev_name(hellcreek->dev));
399
400	/* IP-Core can add up to 0.5 ns per 8 ns cycle, which means
401	 * accumulator_overflow_rate shall not exceed 62.5 MHz (which adjusts
402	 * the nominal frequency by 6.25%)
403	 */
404	hellcreek->ptp_clock_info.max_adj     = 62500000;
405	hellcreek->ptp_clock_info.n_alarm     = 0;
406	hellcreek->ptp_clock_info.n_pins      = 0;
407	hellcreek->ptp_clock_info.n_ext_ts    = 0;
408	hellcreek->ptp_clock_info.n_per_out   = 0;
409	hellcreek->ptp_clock_info.pps	      = 0;
410	hellcreek->ptp_clock_info.adjfine     = hellcreek_ptp_adjfine;
411	hellcreek->ptp_clock_info.adjtime     = hellcreek_ptp_adjtime;
412	hellcreek->ptp_clock_info.gettime64   = hellcreek_ptp_gettime;
413	hellcreek->ptp_clock_info.settime64   = hellcreek_ptp_settime;
414	hellcreek->ptp_clock_info.enable      = hellcreek_ptp_enable;
415	hellcreek->ptp_clock_info.do_aux_work = hellcreek_hwtstamp_work;
416
417	hellcreek->ptp_clock = ptp_clock_register(&hellcreek->ptp_clock_info,
418						  hellcreek->dev);
419	if (IS_ERR(hellcreek->ptp_clock))
420		return PTR_ERR(hellcreek->ptp_clock);
421
422	/* Enable the offset correction process, if no offset correction is
423	 * already taking place
424	 */
425	status = hellcreek_ptp_read(hellcreek, PR_CLOCK_STATUS_C);
426	if (!(status & PR_CLOCK_STATUS_C_OFS_ACT))
427		hellcreek_ptp_write(hellcreek,
428				    status | PR_CLOCK_STATUS_C_ENA_OFS,
429				    PR_CLOCK_STATUS_C);
430
431	/* Enable the drift correction process */
432	hellcreek_ptp_write(hellcreek, status | PR_CLOCK_STATUS_C_ENA_DRIFT,
433			    PR_CLOCK_STATUS_C);
434
435	/* LED setup */
436	ret = hellcreek_led_setup(hellcreek);
437	if (ret) {
438		if (hellcreek->ptp_clock)
439			ptp_clock_unregister(hellcreek->ptp_clock);
440		return ret;
441	}
442
443	schedule_delayed_work(&hellcreek->overflow_work,
444			      HELLCREEK_OVERFLOW_PERIOD);
445
446	return 0;
447}
448
449void hellcreek_ptp_free(struct hellcreek *hellcreek)
450{
451	led_classdev_unregister(&hellcreek->led_is_gm);
452	led_classdev_unregister(&hellcreek->led_sync_good);
453	cancel_delayed_work_sync(&hellcreek->overflow_work);
454	if (hellcreek->ptp_clock)
455		ptp_clock_unregister(hellcreek->ptp_clock);
456	hellcreek->ptp_clock = NULL;
457}
v6.13.7
  1// SPDX-License-Identifier: (GPL-2.0 OR MIT)
  2/*
  3 * DSA driver for:
  4 * Hirschmann Hellcreek TSN switch.
  5 *
  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>
 10 */
 11
 12#include <linux/of.h>
 13#include <linux/ptp_clock_kernel.h>
 14#include "hellcreek.h"
 15#include "hellcreek_ptp.h"
 16#include "hellcreek_hwtstamp.h"
 17
 18u16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset)
 19{
 20	return readw(hellcreek->ptp_base + offset);
 21}
 22
 23void hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data,
 24			 unsigned int offset)
 25{
 26	writew(data, hellcreek->ptp_base + offset);
 27}
 28
 29/* Get nanoseconds from PTP clock */
 30static u64 hellcreek_ptp_clock_read(struct hellcreek *hellcreek,
 31				    struct ptp_system_timestamp *sts)
 32{
 33	u16 nsl, nsh;
 34
 35	/* Take a snapshot */
 36	hellcreek_ptp_write(hellcreek, PR_COMMAND_C_SS, PR_COMMAND_C);
 37
 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
 43	 * read.
 44	 */
 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);
 52
 53	return (u64)nsl | ((u64)nsh << 16);
 54}
 55
 56static u64 __hellcreek_ptp_gettime(struct hellcreek *hellcreek,
 57				   struct ptp_system_timestamp *sts)
 58{
 59	u64 ns;
 60
 61	ns = hellcreek_ptp_clock_read(hellcreek, sts);
 62	if (ns < hellcreek->last_ts)
 63		hellcreek->seconds++;
 64	hellcreek->last_ts = ns;
 65	ns += hellcreek->seconds * NSEC_PER_SEC;
 66
 67	return ns;
 68}
 69
 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.
 74 */
 75u64 hellcreek_ptp_gettime_seconds(struct hellcreek *hellcreek, u64 ns)
 76{
 77	u64 s;
 78
 79	__hellcreek_ptp_gettime(hellcreek, NULL);
 80	if (hellcreek->last_ts > ns)
 81		s = hellcreek->seconds * NSEC_PER_SEC;
 82	else
 83		s = (hellcreek->seconds - 1) * NSEC_PER_SEC;
 84
 85	return s;
 86}
 87
 88static int hellcreek_ptp_gettimex(struct ptp_clock_info *ptp,
 89				  struct timespec64 *ts,
 90				  struct ptp_system_timestamp *sts)
 91{
 92	struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
 93	u64 ns;
 94
 95	mutex_lock(&hellcreek->ptp_lock);
 96	ns = __hellcreek_ptp_gettime(hellcreek, sts);
 97	mutex_unlock(&hellcreek->ptp_lock);
 98
 99	*ts = ns_to_timespec64(ns);
100
101	return 0;
102}
103
104static int hellcreek_ptp_settime(struct ptp_clock_info *ptp,
105				 const struct timespec64 *ts)
106{
107	struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
108	u16 secl, nsh, nsl;
109
110	secl = ts->tv_sec & 0xffff;
111	nsh  = ((u32)ts->tv_nsec & 0xffff0000) >> 16;
112	nsl  = ts->tv_nsec & 0xffff;
113
114	mutex_lock(&hellcreek->ptp_lock);
115
116	/* Update overflow data structure */
117	hellcreek->seconds = ts->tv_sec;
118	hellcreek->last_ts = ts->tv_nsec;
119
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);
126
127	mutex_unlock(&hellcreek->ptp_lock);
128
129	return 0;
130}
131
132static int hellcreek_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
133{
134	struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
135	u16 negative = 0, addendh, addendl;
136	u32 addend;
137	u64 adj;
138
139	if (scaled_ppm < 0) {
140		negative = 1;
141		scaled_ppm = -scaled_ppm;
142	}
143
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.
148	 *
149	 * addend value = (2^30 * accumulator_overflow_rate) /
150	 *                oscillator_frequency
151	 * where:
152	 *
153	 * oscillator_frequency = 125 MHz
154	 * accumulator_overflow_rate = 125 MHz * scaled_ppm * 2^-16 * 10^-6 * 8
155	 */
156	adj = scaled_ppm;
157	adj <<= 11;
158	addend = (u32)div_u64(adj, 15625);
159
160	addendh = (addend & 0xffff0000) >> 16;
161	addendl = addend & 0xffff;
162
163	negative = (negative << 15) & 0x8000;
164
165	mutex_lock(&hellcreek->ptp_lock);
166
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);
173
174	mutex_unlock(&hellcreek->ptp_lock);
175
176	return 0;
177}
178
179static int hellcreek_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
180{
181	struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
182	u16 negative = 0, counth, countl;
183	u32 count_val;
184
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
187	 * current time
188	 */
189	if (abs(delta) > MAX_SLOW_OFFSET_ADJ) {
190		struct timespec64 now, then = ns_to_timespec64(delta);
191
192		hellcreek_ptp_gettimex(ptp, &now, NULL);
193		now = timespec64_add(now, then);
194		hellcreek_ptp_settime(ptp, &now);
195
196		return 0;
197	}
198
199	if (delta < 0) {
200		negative = 1;
201		delta = -delta;
202	}
203
204	/* 'count_val' does not exceed the maximum register size (2^30) */
205	count_val = div_s64(delta, MAX_NS_PER_STEP);
206
207	counth = (count_val & 0xffff0000) >> 16;
208	countl = count_val & 0xffff;
209
210	negative = (negative << 15) & 0x8000;
211
212	mutex_lock(&hellcreek->ptp_lock);
213
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,
218			    PR_CLOCK_OFFSET_C);
219	hellcreek_ptp_write(hellcreek, countl,  PR_CLOCK_OFFSET_C);
220	hellcreek_ptp_write(hellcreek, counth,  PR_CLOCK_OFFSET_C);
221
222	mutex_unlock(&hellcreek->ptp_lock);
223
224	return 0;
225}
226
227static int hellcreek_ptp_enable(struct ptp_clock_info *ptp,
228				struct ptp_clock_request *rq, int on)
229{
230	return -EOPNOTSUPP;
231}
232
233static void hellcreek_ptp_overflow_check(struct work_struct *work)
234{
235	struct delayed_work *dw = to_delayed_work(work);
236	struct hellcreek *hellcreek;
237
238	hellcreek = dw_overflow_to_hellcreek(dw);
239
240	mutex_lock(&hellcreek->ptp_lock);
241	__hellcreek_ptp_gettime(hellcreek, NULL);
242	mutex_unlock(&hellcreek->ptp_lock);
243
244	schedule_delayed_work(&hellcreek->overflow_work,
245			      HELLCREEK_OVERFLOW_PERIOD);
246}
247
248static enum led_brightness hellcreek_get_brightness(struct hellcreek *hellcreek,
249						    int led)
250{
251	return (hellcreek->status_out & led) ? 1 : 0;
252}
253
254static void hellcreek_set_brightness(struct hellcreek *hellcreek, int led,
255				     enum led_brightness b)
256{
257	mutex_lock(&hellcreek->ptp_lock);
258
259	if (b)
260		hellcreek->status_out |= led;
261	else
262		hellcreek->status_out &= ~led;
263
264	hellcreek_ptp_write(hellcreek, hellcreek->status_out, STATUS_OUT);
265
266	mutex_unlock(&hellcreek->ptp_lock);
267}
268
269static void hellcreek_led_sync_good_set(struct led_classdev *ldev,
270					enum led_brightness b)
271{
272	struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good);
273
274	hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, b);
275}
276
277static enum led_brightness hellcreek_led_sync_good_get(struct led_classdev *ldev)
278{
279	struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good);
280
281	return hellcreek_get_brightness(hellcreek, STATUS_OUT_SYNC_GOOD);
282}
283
284static void hellcreek_led_is_gm_set(struct led_classdev *ldev,
285				    enum led_brightness b)
286{
287	struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm);
288
289	hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, b);
290}
291
292static enum led_brightness hellcreek_led_is_gm_get(struct led_classdev *ldev)
293{
294	struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm);
295
296	return hellcreek_get_brightness(hellcreek, STATUS_OUT_IS_GM);
297}
298
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.
302 */
303static int hellcreek_led_setup(struct hellcreek *hellcreek)
304{
305	struct device_node *leds, *led = NULL;
306	enum led_default_state state;
307	const char *label;
308	int ret = -EINVAL;
309
310	of_node_get(hellcreek->dev->of_node);
311	leds = of_find_node_by_name(hellcreek->dev->of_node, "leds");
312	if (!leds) {
313		dev_err(hellcreek->dev, "No LEDs specified in device tree!\n");
314		return ret;
315	}
316
317	hellcreek->status_out = 0;
318
319	led = of_get_next_available_child(leds, led);
320	if (!led) {
321		dev_err(hellcreek->dev, "First LED not specified!\n");
322		goto out;
323	}
324
325	ret = of_property_read_string(led, "label", &label);
326	hellcreek->led_sync_good.name = ret ? "sync_good" : label;
327
328	state = led_init_default_state_get(of_fwnode_handle(led));
329	switch (state) {
330	case LEDS_DEFSTATE_ON:
331		hellcreek->led_sync_good.brightness = 1;
332		break;
333	case LEDS_DEFSTATE_KEEP:
334		hellcreek->led_sync_good.brightness =
335			hellcreek_get_brightness(hellcreek, STATUS_OUT_SYNC_GOOD);
336		break;
337	default:
338		hellcreek->led_sync_good.brightness = 0;
339	}
340
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;
344
345	led = of_get_next_available_child(leds, led);
346	if (!led) {
347		dev_err(hellcreek->dev, "Second LED not specified!\n");
348		ret = -EINVAL;
349		goto out;
350	}
351
352	ret = of_property_read_string(led, "label", &label);
353	hellcreek->led_is_gm.name = ret ? "is_gm" : label;
354
355	state = led_init_default_state_get(of_fwnode_handle(led));
356	switch (state) {
357	case LEDS_DEFSTATE_ON:
358		hellcreek->led_is_gm.brightness = 1;
359		break;
360	case LEDS_DEFSTATE_KEEP:
361		hellcreek->led_is_gm.brightness =
362			hellcreek_get_brightness(hellcreek, STATUS_OUT_IS_GM);
363		break;
364	default:
365		hellcreek->led_is_gm.brightness = 0;
366	}
367
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;
371
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);
377
378	/* Register both leds */
379	led_classdev_register(hellcreek->dev, &hellcreek->led_sync_good);
380	led_classdev_register(hellcreek->dev, &hellcreek->led_is_gm);
381
382	ret = 0;
383
384out:
385	of_node_put(leds);
386
387	return ret;
388}
389
390int hellcreek_ptp_setup(struct hellcreek *hellcreek)
391{
392	u16 status;
393	int ret;
394
395	/* Set up the overflow work */
396	INIT_DELAYED_WORK(&hellcreek->overflow_work,
397			  hellcreek_ptp_overflow_check);
398
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));
404
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%)
408	 */
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;
421
422	hellcreek->ptp_clock = ptp_clock_register(&hellcreek->ptp_clock_info,
423						  hellcreek->dev);
424	if (IS_ERR(hellcreek->ptp_clock))
425		return PTR_ERR(hellcreek->ptp_clock);
426
427	/* Enable the offset correction process, if no offset correction is
428	 * already taking place
429	 */
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,
434				    PR_CLOCK_STATUS_C);
435
436	/* Enable the drift correction process */
437	hellcreek_ptp_write(hellcreek, status | PR_CLOCK_STATUS_C_ENA_DRIFT,
438			    PR_CLOCK_STATUS_C);
439
440	/* LED setup */
441	ret = hellcreek_led_setup(hellcreek);
442	if (ret) {
443		if (hellcreek->ptp_clock)
444			ptp_clock_unregister(hellcreek->ptp_clock);
445		return ret;
446	}
447
448	schedule_delayed_work(&hellcreek->overflow_work,
449			      HELLCREEK_OVERFLOW_PERIOD);
450
451	return 0;
452}
453
454void hellcreek_ptp_free(struct hellcreek *hellcreek)
455{
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;
462}