1/*	$OpenBSD: umbg.c,v 1.29 2024/05/23 03:21:09 jsg Exp $ */
2
3/*
4 * Copyright (c) 2007 Marc Balmer <mbalmer@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/param.h>
20#include <sys/systm.h>
21#include <sys/device.h>
22#include <sys/time.h>
23#include <sys/sensors.h>
24#include <sys/timeout.h>
25
26#include <dev/usb/usb.h>
27#include <dev/usb/usbdi.h>
28#include <dev/usb/usbdevs.h>
29
30#ifdef UMBG_DEBUG
31#define DPRINTFN(n, x)	do { if (umbgdebug > (n)) printf x; } while (0)
32int umbgdebug = 0;
33#else
34#define DPRINTFN(n, x)
35#endif
36#define DPRINTF(x)	DPRINTFN(0, x)
37
38#ifdef UMBG_DEBUG
39#define TRUSTTIME	((long) 60)
40#else
41#define TRUSTTIME	((long) 12 * 60 * 60)	/* degrade OK > WARN > CRIT */
42#endif
43
44struct umbg_softc {
45	struct device		sc_dev;		/* base device */
46	struct usbd_device	*sc_udev;	/* USB device */
47	struct usbd_interface	*sc_iface;	/* data interface */
48
49	int			sc_bulkin_no;
50	struct usbd_pipe	*sc_bulkin_pipe;
51	int			sc_bulkout_no;
52	struct usbd_pipe	*sc_bulkout_pipe;
53
54	struct timeout		sc_to;		/* get time from device */
55	struct usb_task		sc_task;
56
57	struct timeout		sc_it_to;	/* invalidate sensor */
58
59	usb_device_request_t	sc_req;
60
61	struct ksensor		sc_timedelta;	/* timedelta */
62	struct ksensor		sc_signal;	/* signal quality and status */
63	struct ksensordev	sc_sensordev;
64};
65
66struct mbg_time {
67	u_int8_t		hundreds;
68	u_int8_t		sec;
69	u_int8_t		min;
70	u_int8_t		hour;
71	u_int8_t		mday;
72	u_int8_t		wday;	/* 1 (monday) - 7 (sunday) */
73	u_int8_t		mon;
74	u_int8_t		year;	/* 0 - 99 */
75	u_int8_t		status;
76	u_int8_t		signal;
77	int8_t			utc_off;
78};
79
80struct mbg_time_hr {
81	u_int32_t		sec;		/* always UTC */
82	u_int32_t		frac;		/* fractions of second */
83	int32_t			utc_off;	/* informal only, in seconds */
84	u_int16_t		status;
85	u_int8_t		signal;
86};
87
88/* mbg_time.status bits */
89#define MBG_FREERUN		0x01	/* clock running on xtal */
90#define MBG_DST_ENA		0x02	/* DST enabled */
91#define MBG_SYNC		0x04	/* clock synced at least once */
92#define MBG_DST_CHG		0x08	/* DST change announcement */
93#define MBG_UTC			0x10	/* special UTC firmware is installed */
94#define MBG_LEAP		0x20	/* announcement of a leap second */
95#define MBG_IFTM		0x40	/* current time was set from host */
96#define MBG_INVALID		0x80	/* time invalid, batt. was disconn. */
97
98/* commands */
99#define MBG_GET_TIME		0x00
100#define MBG_GET_SYNC_TIME	0x02
101#define MBG_GET_TIME_HR		0x03
102#define MBG_SET_TIME		0x10
103#define MBG_GET_TZCODE		0x32
104#define MBG_SET_TZCODE		0x33
105#define MBG_GET_FW_ID_1		0x40
106#define MBG_GET_FW_ID_2		0x41
107#define MBG_GET_SERNUM		0x42
108
109/* timezone codes (for MBG_{GET|SET}_TZCODE) */
110#define MBG_TZCODE_CET_CEST	0x00
111#define MBG_TZCODE_CET		0x01
112#define MBG_TZCODE_UTC		0x02
113#define MBG_TZCODE_EET_EEST	0x03
114
115/* misc. constants */
116#define MBG_FIFO_LEN		16
117#define MBG_ID_LEN		(2 * MBG_FIFO_LEN + 1)
118#define MBG_BUSY		0x01
119#define MBG_SIG_BIAS		55
120#define MBG_SIG_MAX		68
121#define NSECPERSEC		1000000000LL	/* nanoseconds per second */
122#define HRDIVISOR		0x100000000LL	/* for hi-res timestamp */
123
124static int t_wait, t_trust;
125
126void umbg_intr(void *);
127void umbg_it_intr(void *);
128
129int umbg_match(struct device *, void *, void *);
130void umbg_attach(struct device *, struct device *, void *);
131int umbg_detach(struct device *, int);
132
133void umbg_task(void *);
134
135int umbg_read(struct umbg_softc *, u_int8_t cmd, char *buf, size_t len,
136    struct timespec *tstamp);
137
138struct cfdriver umbg_cd = {
139	NULL, "umbg", DV_DULL
140};
141
142const struct cfattach umbg_ca = {
143	sizeof(struct umbg_softc), umbg_match, umbg_attach, umbg_detach
144};
145
146int
147umbg_match(struct device *parent, void *match, void *aux)
148{
149	struct usb_attach_arg *uaa = aux;
150
151	if (uaa->iface == NULL)
152		return UMATCH_NONE;
153
154	return uaa->vendor == USB_VENDOR_MEINBERG && (
155	    uaa->product == USB_PRODUCT_MEINBERG_USB5131 ||
156	    uaa->product == USB_PRODUCT_MEINBERG_DCF600USB) ?
157	    UMATCH_VENDOR_PRODUCT : UMATCH_NONE;
158}
159
160void
161umbg_attach(struct device *parent, struct device *self, void *aux)
162{
163	struct umbg_softc *sc = (struct umbg_softc *)self;
164	struct usb_attach_arg *uaa = aux;
165	struct usbd_device *dev = uaa->device;
166	struct usbd_interface *iface = uaa->iface;
167	struct mbg_time tframe;
168	usb_endpoint_descriptor_t *ed;
169	usbd_status err;
170	int signal;
171	const char *desc;
172#ifdef UMBG_DEBUG
173	char fw_id[MBG_ID_LEN];
174#endif
175	sc->sc_udev = dev;
176
177	strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
178	    sizeof(sc->sc_sensordev.xname));
179
180	sc->sc_timedelta.type = SENSOR_TIMEDELTA;
181	sc->sc_timedelta.status = SENSOR_S_UNKNOWN;
182
183	switch (uaa->product) {
184	case USB_PRODUCT_MEINBERG_DCF600USB:
185		desc = "DCF600USB";
186		break;
187	case USB_PRODUCT_MEINBERG_USB5131:
188		desc = "USB5131";
189		break;
190	default:
191		desc = "Unspecified Radio clock";
192	}
193	strlcpy(sc->sc_timedelta.desc, desc,
194	    sizeof(sc->sc_timedelta.desc));
195	sensor_attach(&sc->sc_sensordev, &sc->sc_timedelta);
196
197	sc->sc_signal.type = SENSOR_PERCENT;
198	strlcpy(sc->sc_signal.desc, "Signal", sizeof(sc->sc_signal.desc));
199	sensor_attach(&sc->sc_sensordev, &sc->sc_signal);
200	sensordev_install(&sc->sc_sensordev);
201
202	usb_init_task(&sc->sc_task, umbg_task, sc, USB_TASK_TYPE_GENERIC);
203	timeout_set(&sc->sc_to, umbg_intr, sc);
204	timeout_set(&sc->sc_it_to, umbg_it_intr, sc);
205
206	if ((err = usbd_device2interface_handle(dev, 0, &iface))) {
207		printf("%s: failed to get interface, err=%s\n",
208		    sc->sc_dev.dv_xname, usbd_errstr(err));
209		goto fishy;
210	}
211
212	ed = usbd_interface2endpoint_descriptor(iface, 0);
213	sc->sc_bulkin_no = ed->bEndpointAddress;
214	ed = usbd_interface2endpoint_descriptor(iface, 1);
215	sc->sc_bulkout_no = ed->bEndpointAddress;
216
217	sc->sc_iface = iface;
218
219	err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkin_no,
220	    USBD_EXCLUSIVE_USE, &sc->sc_bulkin_pipe);
221	if (err) {
222		printf("%s: open rx pipe failed: %s\n", sc->sc_dev.dv_xname,
223		    usbd_errstr(err));
224		goto fishy;
225	}
226
227	err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkout_no,
228	    USBD_EXCLUSIVE_USE, &sc->sc_bulkout_pipe);
229	if (err) {
230		printf("%s: open tx pipe failed: %s\n", sc->sc_dev.dv_xname,
231		    usbd_errstr(err));
232		goto fishy;
233	}
234
235	printf("%s: ", sc->sc_dev.dv_xname);
236	if (umbg_read(sc, MBG_GET_TIME, (char *)&tframe,
237	    sizeof(struct mbg_time), NULL)) {
238		sc->sc_signal.status = SENSOR_S_CRIT;
239		printf("unknown status");
240	} else {
241		sc->sc_signal.status = SENSOR_S_OK;
242		signal = tframe.signal - MBG_SIG_BIAS;
243		if (signal < 0)
244			signal = 0;
245		else if (signal > MBG_SIG_MAX)
246			signal = MBG_SIG_MAX;
247		sc->sc_signal.value = signal;
248
249		if (tframe.status & MBG_SYNC)
250			printf("synchronized");
251		else
252			printf("not synchronized");
253		if (tframe.status & MBG_FREERUN) {
254			sc->sc_signal.status = SENSOR_S_WARN;
255			printf(", freerun");
256		}
257		if (tframe.status & MBG_IFTM)
258			printf(", time set from host");
259	}
260#ifdef UMBG_DEBUG
261	if (umbg_read(sc, MBG_GET_FW_ID_1, fw_id, MBG_FIFO_LEN, NULL) ||
262	    umbg_read(sc, MBG_GET_FW_ID_2, &fw_id[MBG_FIFO_LEN], MBG_FIFO_LEN,
263	    NULL))
264		printf(", firmware unknown");
265	else {
266		fw_id[MBG_ID_LEN - 1] = '\0';
267		printf(", firmware %s", fw_id);
268	}
269#endif
270	printf("\n");
271
272	t_wait = 5;
273
274	t_trust = TRUSTTIME;
275
276	usb_add_task(sc->sc_udev, &sc->sc_task);
277	return;
278
279fishy:
280	usbd_deactivate(sc->sc_udev);
281}
282
283int
284umbg_detach(struct device *self, int flags)
285{
286	struct umbg_softc *sc = (struct umbg_softc *)self;
287	usbd_status err;
288
289	if (timeout_initialized(&sc->sc_to))
290		timeout_del(&sc->sc_to);
291	if (timeout_initialized(&sc->sc_it_to))
292		timeout_del(&sc->sc_it_to);
293
294	usb_rem_task(sc->sc_udev, &sc->sc_task);
295
296	if (sc->sc_bulkin_pipe != NULL) {
297		err = usbd_close_pipe(sc->sc_bulkin_pipe);
298		if (err)
299			printf("%s: close rx pipe failed: %s\n",
300			    sc->sc_dev.dv_xname, usbd_errstr(err));
301		sc->sc_bulkin_pipe = NULL;
302	}
303	if (sc->sc_bulkout_pipe != NULL) {
304		err = usbd_close_pipe(sc->sc_bulkout_pipe);
305		if (err)
306			printf("%s: close tx pipe failed: %s\n",
307			    sc->sc_dev.dv_xname, usbd_errstr(err));
308		sc->sc_bulkout_pipe = NULL;
309	}
310
311	/* Unregister the clock with the kernel */
312	sensordev_deinstall(&sc->sc_sensordev);
313
314	return 0;
315}
316
317void
318umbg_intr(void *xsc)
319{
320	struct umbg_softc *sc = xsc;
321	usb_add_task(sc->sc_udev, &sc->sc_task);
322}
323
324/* umbg_task_hr() read a high resolution timestamp from the device. */
325void
326umbg_task(void *arg)
327{
328	struct umbg_softc *sc = (struct umbg_softc *)arg;
329	struct mbg_time_hr tframe;
330	struct timespec tstamp;
331	int64_t tlocal, trecv;
332	int signal;
333
334	if (usbd_is_dying(sc->sc_udev))
335		return;
336
337	if (umbg_read(sc, MBG_GET_TIME_HR, (char *)&tframe, sizeof(tframe),
338	    &tstamp)) {
339		sc->sc_signal.status = SENSOR_S_CRIT;
340		goto bail_out;
341	}
342	if (tframe.status & MBG_INVALID) {
343		sc->sc_signal.status = SENSOR_S_CRIT;
344		goto bail_out;
345	}
346
347	tlocal = tstamp.tv_sec * NSECPERSEC + tstamp.tv_nsec;
348	trecv = letoh32(tframe.sec) * NSECPERSEC +
349	    (letoh32(tframe.frac) * NSECPERSEC >> 32);
350
351	sc->sc_timedelta.value = tlocal - trecv;
352	if (sc->sc_timedelta.status == SENSOR_S_UNKNOWN ||
353		!(letoh16(tframe.status) & MBG_FREERUN)) {
354		sc->sc_timedelta.status = SENSOR_S_OK;
355		timeout_add_sec(&sc->sc_it_to, t_trust);
356	}
357
358	sc->sc_timedelta.tv.tv_sec = tstamp.tv_sec;
359	sc->sc_timedelta.tv.tv_usec = tstamp.tv_nsec / 1000;
360
361	signal = tframe.signal - MBG_SIG_BIAS;
362	if (signal < 0)
363		signal = 0;
364	else if (signal > MBG_SIG_MAX)
365		signal = MBG_SIG_MAX;
366
367	sc->sc_signal.value = signal * 100000 / MBG_SIG_MAX;
368	sc->sc_signal.status = letoh16(tframe.status) & MBG_FREERUN ?
369	    SENSOR_S_WARN : SENSOR_S_OK;
370	sc->sc_signal.tv.tv_sec = sc->sc_timedelta.tv.tv_sec;
371	sc->sc_signal.tv.tv_usec = sc->sc_timedelta.tv.tv_usec;
372
373bail_out:
374	timeout_add_sec(&sc->sc_to, t_wait);
375
376}
377
378/* send a command and read back results */
379int
380umbg_read(struct umbg_softc *sc, u_int8_t cmd, char *buf, size_t len,
381    struct timespec *tstamp)
382{
383	usbd_status err;
384	struct usbd_xfer *xfer;
385
386	xfer = usbd_alloc_xfer(sc->sc_udev);
387	if (xfer == NULL) {
388		DPRINTF(("%s: alloc xfer failed\n", sc->sc_dev.dv_xname));
389		return -1;
390	}
391
392	usbd_setup_xfer(xfer, sc->sc_bulkout_pipe, NULL, &cmd, sizeof(cmd),
393	    USBD_SHORT_XFER_OK | USBD_SYNCHRONOUS, USBD_DEFAULT_TIMEOUT, NULL);
394	if (tstamp)
395		nanotime(tstamp);
396	err = usbd_transfer(xfer);
397	if (err) {
398		DPRINTF(("%s: sending of command failed: %s\n",
399		    sc->sc_dev.dv_xname, usbd_errstr(err)));
400		usbd_free_xfer(xfer);
401		return -1;
402	}
403
404	usbd_setup_xfer(xfer, sc->sc_bulkin_pipe, NULL, buf, len,
405	    USBD_SHORT_XFER_OK | USBD_SYNCHRONOUS, USBD_DEFAULT_TIMEOUT, NULL);
406
407	err = usbd_transfer(xfer);
408	usbd_free_xfer(xfer);
409	if (err) {
410		DPRINTF(("%s: reading data failed: %s\n",
411		    sc->sc_dev.dv_xname, usbd_errstr(err)));
412		return -1;
413	}
414	return 0;
415}
416
417void
418umbg_it_intr(void *xsc)
419{
420	struct umbg_softc *sc = xsc;
421
422	if (usbd_is_dying(sc->sc_udev))
423		return;
424
425	if (sc->sc_timedelta.status == SENSOR_S_OK) {
426		sc->sc_timedelta.status = SENSOR_S_WARN;
427		/*
428		 * further degrade in TRUSTTIME seconds if the clocks remains
429		 * free running.
430		 */
431		timeout_add_sec(&sc->sc_it_to, t_trust);
432	} else
433		sc->sc_timedelta.status = SENSOR_S_CRIT;
434}
435