Linux Audio

Check our new training course

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