Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 28 Jan 2008 06:00:42 GMT
From:      Joe Landers <jlanders@vmware.com>
To:        freebsd-gnats-submit@FreeBSD.org
Subject:   kern/120073: [patch] need support for Meinberg PCI-based GPS reference clock
Message-ID:  <200801280600.m0S60gMK023182@www.freebsd.org>
Resent-Message-ID: <200801280610.m0S6A1ZS084415@freefall.freebsd.org>

next in thread | raw e-mail | index | archive | help

>Number:         120073
>Category:       kern
>Synopsis:       [patch] need support for Meinberg PCI-based GPS reference clock
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Mon Jan 28 06:10:01 UTC 2008
>Closed-Date:
>Last-Modified:
>Originator:     Joe Landers
>Release:        FreeBSD 7.0-RC1 i386
>Organization:
VMware, Inc.
>Environment:
FreeBSD gps170pci.eng.vmware.com 7.0-RC1 FreeBSD 7.0-RC1 #0: Sun Jan 27 12:33:41 PST 2008 root@gps170pci.eng.vmware.com:/usr
/src/sys/i386/compile/GPS170PCI i386
>Description:
FreeBSD 7.0 does not support Meinberg GPS reference clocks. The Meinberg GPS reference clock is a current production radio receiver capable of delivering precise time.

Although a simple driver exists for OpenBSD, the OpenBSD driver is only suitable for the OpenNTP implemenation. The most useful and popular NTP distribution from ntp.org, included with FreeBSD, can not use this driver.

>How-To-Repeat:
n/a
>Fix:
The attached patch provides support the Meinberg GPS170PCI radio clock. This driver has been built and tested on both FreeBSD 7.0 and 6.2 i386 systems. mbg allows the board to be used as a reference clock source for ntpd using the generic (parse) clock driver.

VMware, Inc. is providing the MBG Driver (source form) to you under the terms of a BSD license.  This Driver is provided as is, with no warranties or support of any kind.  VMware disclaims all liability in connection with your use or inability to use this Driver. Any use of the attached is considered acceptance of the above.

Thanks,

Joe Landers
jlanders@vmware.com


Patch attached with submission follows:

diff -rn -N -U 1 usr/src.old/share/man/man4/mbg.4 usr/src/share/man/man4/mbg.4
--- usr/src.old/share/man/man4/mbg.4	1969-12-31 16:00:00.000000000 -0800
+++ usr/src/share/man/man4/mbg.4	2008-01-27 12:06:27.000000000 -0800
@@ -0,0 +1,111 @@
+.\"
+.\" Copyright (c) 2007-2008 VMware, Inc.  All rights reserved.
+.\"
+.\" Joe Landers <jlanders@vmware.com>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of VMware, Inc. nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    with specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+.\" DAMAGE.
+.\"
+.Dd November 27, 2007
+.Dt MBG 4
+.Os
+.Sh NAME
+.Nm mbg
+.Nd "Meinberg Funkuhren radio clock"
+.Sh SYNOPSIS
+.Cd "device mbg"
+.Sh DESCRIPTION
+The
+.Nm
+driver supports Meinberg Funkuhren radio clocks.
+.Nm
+allows the device to be used as a reference clock
+source by
+.Xr ntpd 8
+via the generic (parse) clock driver.
+.Sh HARDWARE
+.Nm
+provides support for the
+GPS170PCI satellite radio plug-in board.
+.Sh IOCTLS
+The following
+.Xr ioctl 2
+calls are supported by the
+.Nm
+driver.
+They are defined in the header file
+.In sys/mbgio.h .
+.Bl -tag -width indent
+.It Dv MBG_GET_DEVICE_NAME    Fa "char *"
+Get the board's device name.
+.It Dv MBG_GET_FIRMWARE_ID    Fa "char *"
+Get the board's firmware id.
+.It Dv MBG_GET_TIME           Fa "struct mbg_tframe"
+Get the current time in standard format.
+.It Dv MBG_GET_HR_TIME        Fa "struct mbg_hr_time"
+Get the current time in high resolution format.
+.It Dv MBG_GET_LAST_SYNC_TIME Fa "struct mbg_tframe"
+Get the last synchronization time.
+.It Dv MBG_GET_RECV_INFO      Fa "struct mbg_gps_recv_info"
+Get information about the GPS receiver configuration.
+.It Dv MBG_GET_RECV_STAT      Fa "struct mbg_gps_stat_info"
+Get the GPS receiver status.
+.It Dv MBG_GET_GPS_POSITION   Fa "struct mbg_gps_position"
+Get the GPS receiver's position.
+.It Dv MBG_GET_GPS_PORT_PARM  Fa "struct mbg_gps_port_parm"
+Get the GPS receiver's port parameters.
+.It Dv MBG_GET_GPS_TZDL       Fa "struct mbg_gps_tzdl"
+Get the board's timezone and daylight savings time configuration.
+.It Dv MBG_GET_UCAPTURE_STATS Fa "struct mbg_ucapture_stats"
+Get the board's user capture statistics.
+.It Dv MBG_GET_UCAPTURE_EVENT Fa "struct mbg_hr_time"
+Get a user capture event from the FIFO.
+.It Dv MBG_SET_UCAPTURE_CLEAR Fa void
+Clear the user capture event FIFO.
+.It Dv MBG_SET_GPS_CMD        Fa "int"
+Send a command to the GPS receiver.
+.It Dv MBG_SET_GPS_TZDL       Fa "struct mbg_gps_tzdl"
+Set the timezone and daylight savings parameters.
+.It Dv MBG_SET_GPS_POS_LLA    Fa "double lla[3]"
+Set the latitude, longitude and altitude.
+Both latitude and longitude are in radians.
+Altitude is in meters.
+.It Dv MBG_SET_GPS_TIME       Fa "struct mbg_gps_dt"
+Set the board's date and time.
+.El
+.Sh SEE ALSO
+.Xr ioctl 2 ,
+.Xr ntpd 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 7.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Joe Landers Aq jlanders@vmware.com .
+
diff -rn -N -U 1 usr/src.old/sys/conf/files.MBG usr/src/sys/conf/files.MBG
--- usr/src.old/sys/conf/files.MBG	1969-12-31 16:00:00.000000000 -0800
+++ usr/src/sys/conf/files.MBG	2008-01-27 12:05:30.000000000 -0800
@@ -0,0 +1 @@
+dev/mbg/mbg.c	 optional mbg
diff -rn -N -U 1 usr/src.old/sys/dev/mbg/mbg.c usr/src/sys/dev/mbg/mbg.c
--- usr/src.old/sys/dev/mbg/mbg.c	1969-12-31 16:00:00.000000000 -0800
+++ usr/src/sys/dev/mbg/mbg.c	2008-01-27 12:19:11.000000000 -0800
@@ -0,0 +1,1938 @@
+/*
+ * Copyright (c) 2007-2008 VMware, Inc.  All rights reserved.
+ *
+ * Joe Landers <jlanders@vmware.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of VMware, Inc. nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    with specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+/*****************************************************************************/
+/* #includes                                                                 */
+/*****************************************************************************/
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>		/* cdevsw stuff */
+#include <sys/kernel.h>		/* SYSINIT stuff */
+#include <sys/uio.h>		/* SYSINIT stuff */
+#include <sys/malloc.h>		/* malloc region definitions */
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/proc.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/fcntl.h>
+#include <sys/poll.h>
+#include <sys/selinfo.h>
+#include <sys/ttycom.h>
+#include <sys/termios.h>
+#include <sys/mbgio.h>		/* mbg ioctl() definitions */
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <sys/rman.h>
+
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+
+/*****************************************************************************/
+/* #defines and enums                                                        */
+/*****************************************************************************/
+
+/*
+ * Macros for handling mbg_softc
+ */
+#define DEV2SOFTC(dev)           ((struct mbg_softc *) (dev)->si_drv1)
+#define DEVICE2SOFTC(dev)        ((struct mbg_softc *) device_get_softc(dev))
+
+/*
+ * Vendor and device IDs
+ */
+#define MBG_PCI_VENDOR_MEINBERG  0x1360
+#define MBG_PCI_DEVICE_GPS170PCI 0x0204
+
+/*
+ * Return status codes
+ *	Note: MBG_RET_STATUS_TIMEOUT may be returned if the board
+ *	      has a fault or the delay loop in mbg_pci_read()
+ *	      isn't properly calibrated. MBG_RET_STATUS_BUSY may
+ *	      returned if the board is still initializing and can
+ *	      not respond.
+ */
+#define MBG_RET_STATUS_OK        0x00 /* no error                            */
+#define MBG_RET_STATUS_TIMEOUT   0x01 /* operation timed out                 */
+#define MBG_RET_STATUS_BUSY      0x02 /* board reports busy                  */
+#define MBG_RET_STATUS_NBYTES    0x03 /* parameter byte count invalid        */
+#define MBG_RET_STATUS_INV_TYPE  0x04 /* invalid data type specified         */
+
+/*
+ * Possible board commands
+ */
+#define MBG_CMD_GET_GROUP        0x00 /* get time group                      */
+#define MBG_CMD_GET_TIME         0x00 /* current time                        */
+#define MBG_CMD_GET_TIME_NC      0x01 /* cur time, don't update last read    */
+#define MBG_CMD_GET_SYNC_TIME    0x02 /* last sync'd time                    */
+#define MBG_CMD_GET_HR_TIME      0x03 /* high resolution time                */
+
+#define MBG_CMD_SET_GROUP        0x10 /* set time group                      */
+#define MBG_CMD_SET_TIME         0x10 /* set board time                      */
+#define MBG_CMD_SET_EVENT_TIME   0x14 /* set event time (needs CERN firmware)*/
+
+#define MBG_CMD_IRQ_GROUP        0x20 /* IRQ group                           */
+#define MBG_CMD_IRQ_NONE         0x20 /* disable board's hardware IRQ        */
+#define MBG_CMD_IRQ_1_SEC        0x21 /* enable board's hardware IRQ 1/sec   */
+#define MBG_CMD_IRQ_1_MIN        0x22 /* enable board's hardware IRQ 1/min   */
+#define MBG_CMD_IRQ_10_MIN       0x24 /* enable board's IRQ every 10 min     */
+#define MBG_CMD_IRQ_30_MIN       0x28 /* enable board's IRQ every 30 min     */
+
+#define MBG_CMD_CFG_GROUP        0x30 /* configuration group                 */
+#define MBG_CMD_GET_SERIAL       0x30 /* get serial configuration            */
+#define MBG_CMD_SET_SERIAL       0x31 /* set serial configuration            */
+#define MBG_CMD_GET_TZCODE       0x32 /* get timezone code                   */
+#define MBG_CMD_SET_TZCODE       0x33 /* set timezone code                   */
+#define MBG_CMD_GET_TZDL         0x34 /* get timezone code and DST config    */
+#define MBG_CMD_SET_TZDL         0x35 /* set timezone code and DST config    */
+#define MBG_CMD_GET_REFOFF       0x36 /* get ref offset (for IRIG input)     */
+#define MBG_CMD_SET_REFOFF       0x37 /* set ref offset (for IRIG input)     */
+#define MBG_CMD_GET_OPT          0x38 /* get optional settings               */
+#define MBG_CMD_SET_OPT          0x39 /* set optional settings               */
+#define MBG_CMD_GET_IRIG_RX      0x3a /* get IRIG rx config                  */
+#define MBG_CMD_SET_IRIG_RX      0x3b /* set IRIG rx config                  */
+#define MBG_CMD_GET_IRIG_TX      0x3c /* get IRIG tx config                  */
+#define MBG_CMD_SET_IRIG_TX      0x3d /* set IRIG tx config                  */
+#define MBG_CMD_GET_SYNTH        0x3e /* get synthesizer config              */
+#define MBG_CMD_SET_SYNTH        0x3f /* set synthesizer config              */
+
+#define MBG_CMD_DATA_GROUP       0x40 /* data group                          */
+#define MBG_CMD_GET_FW_ID_1      0x40 /* get first block of firmware id      */
+#define MBG_CMD_GET_FW_ID_2      0x41 /* get second block of firmware id     */
+#define MBG_CMD_GET_SERNUM       0x42 /* get serial number                   */
+#define MBG_CMD_GENERIC_IO       0x43 /* generic i/o                         */
+#define MBG_CMD_GET_SYNTH_STATE  0x44 /* get synthesizer state               */
+#define MBG_CMD_GET_STATUS_PORT  0x4d /* get status port                     */
+#define MBG_CMD_GET_DEBUG_STATUS 0x4c /* get debug status                    */
+
+#define MBG_CMD_GPS_GROUP        0x50 /* GPS group                           */
+#define MBG_CMD_GET_GPS_DATA     0x50 /* read GPS data                       */
+#define MBG_CMD_SET_GPS_DATA     0x51 /* set GPS data                        */
+
+#define MBG_CMD_CONTROL_GROUP    0x60 /* control group                       */
+#define MBG_CMD_CLEAR_CAP_BUF    0x60 /* clear capture buffer                */
+#define MBG_CMD_GET_CAP_STATS    0x61 /* get capture buffer stats            */
+#define MBG_CMD_GET_CAP_EVENT    0x62 /* get event from capture buffer       */
+
+#define MBG_CMD_FORCE_RESET      0x80 /* reset microprocessor                */
+
+/*
+ * Feature list
+ */
+#define MBG_FEAT_SET_TIME        0x00001UL
+#define MBG_FEAT_SERIAL          0x00002UL
+#define MBG_FEAT_SYNC_TIME       0x00004UL
+#define MBG_FEAT_TZDL            0x00008UL
+#define MBG_FEAT_IDENT           0x00010UL
+#define MBG_FEAT_UTC_OFFS        0x00020UL
+#define MBG_FEAT_HR_TIME         0x00040UL
+#define MBG_FEAT_SERNUM          0x00080UL
+#define MBG_FEAT_TZCODE          0x00100UL
+#define MBG_FEAT_CABLE_LEN       0x00200UL
+#define MBG_FEAT_EVENT_TIME      0x00400UL
+#define MBG_FEAT_RECV_INFO       0x00800UL
+#define MBG_FEAT_CLR_UCAP_BUFF   0x01000UL
+#define MBG_FEAT_PCPS_TZDL       0x02000UL
+#define MBG_FEAT_UCAP            0x04000UL
+#define MBG_FEAT_IRIG_TX         0x08000UL
+#define MBG_FEAT_GPS_DATA_16     0x10000UL
+#define MBG_FEAT_SYNTH           0x20000UL
+#define MBG_FEAT_GENERIC_IO      0x40000UL
+
+/*
+ * GPS170PCI features.
+ *	Note: See also mbg_pci_attach() where, after a GPS
+ *	      engine query, MBG_FEAT_SYNTH may get set too
+ */
+#define MBG_FEAT_GPS170PCI	( MBG_FEAT_SET_TIME \
+				| MBG_FEAT_SERIAL \
+				| MBG_FEAT_SYNC_TIME \
+				| MBG_FEAT_TZDL  \
+				| MBG_FEAT_IDENT \
+				| MBG_FEAT_UTC_OFFS \
+				| MBG_FEAT_HR_TIME \
+				| MBG_FEAT_CABLE_LEN \
+				| MBG_FEAT_RECV_INFO \
+				| MBG_FEAT_CLR_UCAP_BUFF \
+				| MBG_FEAT_UCAP \
+				| MBG_FEAT_IRIG_TX \
+				| MBG_FEAT_GPS_DATA_16 \
+				| MBG_FEAT_GENERIC_IO )
+
+/*
+ * ASIC register offsets
+ */
+#define MBG_ASIC_REG_CFG         0x00 /* writable config                     */
+#define MBG_ASIC_REG_FEATURES    0x08 /* r/o, currently 0                    */
+#define MBG_ASIC_REG_STATUS      0x10 /* status                              */
+#define MBG_ASIC_REG_CTLSTATUS   0x14 /* control status                      */
+#define MBG_ASIC_REG_DATA        0x18 /* ASIC data (for write)               */
+#define MBG_ASIC_REG_RES1        0x1c /* reserved                            */
+#define MBG_ASIC_REG_FIFO        0x20 /* ASIC FIFO (for read)                */
+
+#define MBG_ASIC_PCI_IRQ_FLAG    0x00010000UL
+
+/*
+ * Supported IRQ operations
+ */
+enum MBG_IRQ_OP
+{
+	MBG_IRQ_OP_DISABLE = 0,       /* disable 1/sec IRQ                   */
+	MBG_IRQ_OP_ENABLE,            /* enable 1/sec IRQ                    */
+	MBG_IRQ_OP_ACK                /* ack an IRQ                          */
+};
+
+/*
+ * GPS receiver commands
+ */
+enum
+{
+	MBG_GPS_TZDL = 0,             /* Time zone / daylight saving         */
+	MBG_GPS_SW_REV,               /* Software revision                   */
+	MBG_GPS_BVAR_STAT,            /* Status of buffered variables        */
+	MBG_GPS_TIME,                 /* Current time                        */
+	MBG_GPS_POS_XYZ,              /* Current pos. in ECEF coords         */
+	MBG_GPS_POS_LLA,              /* Current pos. in geogr. coords       */
+	MBG_GPS_PORT_PARM,            /* Serial port params                  */
+	MBG_GPS_ANT_INFO,             /* Time diff after ant. disconnected   */
+	MBG_GPS_UCAP,                 /* User capture                        */
+	MBG_GPS_ENABLE_FLAGS,         /* Controls when to enable output      */
+	MBG_GPS_STAT_INFO,            /*                                     */
+	MBG_GPS_CMD,                  /* Commands                            */
+	MBG_GPS_IDENT,                /* Serial number                       */
+	MBG_GPS_POS,                  /* Position XYZ, LLA, and DMS          */
+	MBG_GPS_ANT_CABLE_LEN,        /* Used to compensate delay            */
+
+	/*
+	 * The codes below are supported by new GPS receiver boards
+	 */
+	MBG_GPS_RECV_INFO,            /* Receiver model info                 */
+	MBG_GPS_ALL_STR_TYPE_INFO,    /* All string types                    */
+	MBG_GPS_ALL_PORT_INFO,        /* All port info                       */
+	MBG_GPS_PORT_SETTINGS_IDX,    /* Port settings only                  */
+	MBG_GPS_ALL_POUT_INFO,        /* All pout info                       */
+	MBG_GPS_POUT_SETTINGS_IDX,    /* Pout settings only                  */
+
+	/*
+	 * GPS data 
+	 */
+	MBG_GPS_CFGH = 0x80,          /* SVs' config. and health codes       */
+	MBG_GPS_ALM,                  /* One SV's num and almanac            */
+	MBG_GPS_EPH,                  /* One SV's num and ephemeris          */
+	MBG_GPS_UTC,                  /* UTC corr. param.                    */
+	MBG_GPS_IONO,                 /* Ionospheric corr. param.            */
+	MBG_GPS_ASCII_MSG             /* GPS ASCII message                   */
+};
+
+/*
+ * Status port mask
+ */
+#define MBG_STATUS_BUSY          0x01 /* busy filling output FIFO            */
+#define MBG_STATUS_IRQF          0x02 /* generated an IRQ                    */
+
+/*
+ * Constants
+ */
+#define MBG_FIFO_LEN             16
+#define MBG_ID_LEN               (2 * MBG_FIFO_LEN + 1)
+#define MBG_TSTAMP_LEN           33
+
+/*****************************************************************************/
+/* data structures                                                           */
+/*****************************************************************************/
+
+/*
+ * mbg_softc
+ */
+struct mbg_softc {
+	bus_space_tag_t bt;           /* bus space tag                       */
+	bus_space_handle_t bh;        /* bus space handle                    */
+	int rid_ioport;               /* I/O port resource                   */
+	int rid_irq;                  /* IRQ resource                        */
+	uint32_t features;            /* card features mask                  */
+	struct mbg_tframe tframe;     /* time frame                          */
+	struct resource *res_ioport;  /* resource for port range             */
+	struct resource *res_irq;     /* resource for irq range              */
+	device_t device;              /* device handle                       */
+	void *intr_cookie;            /* interrupt cookie                    */
+	int (*mbg_irq_op)(struct mbg_softc *, enum MBG_IRQ_OP);
+	int (*mbg_read)(struct mbg_softc *scp, uint32_t cmd,
+			void *buf, size_t len);
+	int (*mbg_got_intr)(struct mbg_softc *scp);
+	struct mbgntp_struct {
+		struct cdev *ntp;     /* mbgntp cdev handle                  */
+		struct mtx spinLock;  /* driver spin lock                    */
+		struct mtx sema;      /* driver semaphore                    */
+		struct mtx lock;      /* lock protecting "opened" field      */
+		struct selinfo select;/* select() info                       */
+		int hasTimeData;      /* time frame field has data           */
+		int opened;           /* opened device count                 */
+	} mbgntp;
+	struct mbgclk_struct {
+		struct cdev *clk;     /* mbgclk cdev handle                  */
+		struct mtx lock;      /* lock protecting "opened" field      */
+		int opened;           /* opened device count                 */
+		int accessInProgress; /* set if access is in progress        */
+	} mbgclk;
+};
+
+/*****************************************************************************/
+/* function prototypes                                                       */
+/*****************************************************************************/
+
+/*
+ * Generic functions
+ */
+static int mbg_deallocate_resources(device_t device);
+static int mbg_allocate_resources(device_t device);
+static int mbg_attach(device_t device, struct mbg_softc *scp);
+static int mbg_detach(device_t device, struct mbg_softc *scp);
+
+/*
+ * PCI functions
+ */
+static int mbg_pci_probe(device_t);
+static int mbg_pci_attach(device_t);
+static int mbg_pci_detach(device_t);
+static int mbg_pci_shutdown(device_t);
+static int mbg_pci_suspend(device_t);
+static int mbg_pci_resume(device_t);
+
+/*
+ * mbgntp cdevsw functions
+ */
+static d_open_t  mbgntp_open;
+static d_close_t mbgntp_close;
+static d_read_t  mbgntp_read;
+static d_write_t mbgntp_write;
+static d_ioctl_t mbgntp_ioctl;
+static d_poll_t  mbgntp_poll;
+
+/*
+ * mbgclk cdevsw functions
+ */
+static d_open_t  mbgclk_open;
+static d_close_t mbgclk_close;
+static d_ioctl_t mbgclk_ioctl;
+
+/*
+ * interrupt handler
+ */
+static void mbg_intr(void *arg);
+
+/*****************************************************************************/
+/* statics and global variables                                              */
+/*****************************************************************************/
+
+/*
+ * mbgntp cdevsw
+ */
+static struct cdevsw mbgntp_cdevsw = {
+	.d_version = D_VERSION,
+	.d_open    = mbgntp_open,
+	.d_close   = mbgntp_close,
+	.d_read    = mbgntp_read,
+	.d_write   = mbgntp_write,
+	.d_ioctl   = mbgntp_ioctl,
+	.d_poll    = mbgntp_poll,
+	.d_name    = "mbgntp",
+};
+
+/*
+ * mbgclk cdevsw
+ */
+static struct cdevsw mbgclk_cdevsw = {
+	.d_version = D_VERSION,
+	.d_open    = mbgclk_open,
+	.d_close   = mbgclk_close,
+	.d_ioctl   = mbgclk_ioctl,
+	.d_name    = "mbgclk",
+};
+
+/*
+ * devclass
+ */
+static devclass_t mbg_devclass;
+
+/*
+ * PCI attachment
+ */
+static device_method_t mbg_pci_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe,    mbg_pci_probe),
+	DEVMETHOD(device_attach,   mbg_pci_attach),
+	DEVMETHOD(device_detach,   mbg_pci_detach),
+	DEVMETHOD(device_shutdown, mbg_pci_shutdown),
+	DEVMETHOD(device_suspend,  mbg_pci_suspend),
+	DEVMETHOD(device_resume,   mbg_pci_resume),
+	{ 0, 0 }
+};
+
+static driver_t mbg_pci_driver = {
+	"mbg",
+	mbg_pci_methods,
+	sizeof(struct mbg_softc),
+};
+
+static struct mbg_pci_ids
+{
+	uint16_t vendor_id;
+	uint16_t device_id;
+	const char *desc;
+} mbg_pci_ids[] = {
+	{ MBG_PCI_VENDOR_MEINBERG,
+	  MBG_PCI_DEVICE_GPS170PCI,
+	  "Meinberg GPS170PCI Radio Clock" },
+	{ 0x0000,
+	  0x0000,
+	  NULL }
+};
+
+/*
+ * Module glue
+ */
+DRIVER_MODULE(mbg, pci, mbg_pci_driver, mbg_devclass, 0, 0);
+MODULE_DEPEND(em, pci, 1, 1, 1);
+
+/*
+ * Driver version string
+ */
+char mbg_driver_version[] = "Version - 1.0.1";
+
+/*
+ * Onboard oscillator names
+ */
+static char *mbg_osc_feature_names[] = {
+	"unknown",
+	"TCXO LQ",
+	"TCXO HQ",
+	"OCXO LQ",
+	"OCXO MQ",
+	"OCXO HQ",
+	"OCXO XHQ",
+	"RUBIDIUM",
+	"TCXO MQ",
+	"OCXO DHQ"
+};
+
+/*
+ * GPS feature names
+ */
+static char *mbg_gps_feature_names[] = {
+	"Pulse Per Second",
+	"Pulse Per Minute",
+	"Programmable Synthesizer Output",
+	"DCF77 Time Marks",
+	"Onboard IRIG Out",
+	"Onboard IRIG In",
+	"IPv4 LAN Interface",
+	"Multiple Reference Sources",
+	"GPS Receive Timeout",
+	"Ignore Lock",
+	"5 MHz Output",
+	"External Multiple Ref. Src. Cfg.",
+	"Supports Optional Settings"
+};
+
+/*****************************************************************************/
+/* utilities                                                                 */
+/*****************************************************************************/
+
+/*
+ * mbg_swap32:
+ *	Swap uint16_t's in a uint32_t
+ *
+ *	bytes before:
+ *		+---+---+---+---+
+ *		| 0 | 1 | 2 | 3 |
+ *		+---+---+---+---+
+ *
+ *	bytes after:
+ *		+---+---+---+---+
+ *		| 2 | 3 | 0 | 1 |
+ *		+---+---+---+---+
+ *
+ * Returns swapped value
+ */
+static inline uint32_t
+mbg_swap32(const void *pp)
+{
+	unsigned char const *p = (unsigned char const *)pp;
+
+	return p[2] | (p[3] << 8) | (p[0] << 16) | (p[1] << 24);
+}
+
+/*
+ * mbg_swap64
+ *	Swap uint16_t's in a uint64_t (i.e. "swap doubles")
+ *
+ *	bytes before:
+ *		+---+---+---+---+---+---+---+---+
+ *		| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+ *		+---+---+---+---+---+---+---+---+
+ *
+ *	bytes after:
+ *		+---+---+---+---+---+---+---+---+
+ *		| 6 | 7 | 4 | 5 | 2 | 3 | 0 | 1 |
+ *		+---+---+---+---+---+---+---+---+
+ *
+ * Returns swapped value
+ */
+static inline uint64_t
+mbg_swap64(const void *pp)
+{
+ 	unsigned char const *p = (unsigned char const *)pp;
+
+	return (uint64_t)mbg_swap32(p + 4) |
+		((uint64_t)mbg_swap32(p) << 32);
+}
+
+/*
+ * mbg_pci_read:
+ *	Send a command and read back results
+ *
+ * Returns one of MBG_RET_STATUS_*
+ */
+static int
+mbg_pci_read(struct mbg_softc *scp, uint32_t cmd, void *buf, size_t len)
+{
+	size_t n;
+	uint32_t data;
+	uint16_t port;
+	uint8_t status;
+	char *p = buf;
+	long timer, tmax;
+
+	/*
+	 * Write the command
+	 */
+	bus_space_write_4(scp->bt, scp->bh, MBG_ASIC_REG_DATA, cmd);
+
+	/*
+	 * Wait for BUSY to go low
+	 */
+	tmax = cold ? 1000 : 200 * (hz / 1000);
+	timer = ticks + tmax;
+	do {
+		status = bus_space_read_1(scp->bt, scp->bh,
+					MBG_ASIC_REG_STATUS);
+	} while ((status & MBG_STATUS_BUSY) && (ticks <= timer));
+
+	/*
+	 * Error, "never" became available
+	 */
+	if ((status & MBG_STATUS_BUSY) || (ticks > timer))
+		return MBG_RET_STATUS_TIMEOUT;
+
+	/*
+	 * Read data from the FIFO
+	 */
+	port = MBG_ASIC_REG_FIFO;
+	for (n = 0; n < len / 4; n++) {
+		data = bus_space_read_4(scp->bt, scp->bh, port);
+		*(uint32_t *)p = data;
+		p += sizeof(data);
+		port += sizeof(data);
+	}
+
+	if (len % 4) {
+		data = bus_space_read_4(scp->bt, scp->bh, port);
+		for (n = 0; n < len % 4; n++) {
+			*p++ = data & 0xff;
+			data >>= 8;
+		}
+	}
+
+	return MBG_RET_STATUS_OK;
+}
+
+/*
+ * mbg_gps_read_block:
+ *	Get one block of data from the GPS receiver
+ *
+ * Returns one of MBG_RET_STATUS_*
+ */
+static int
+mbg_gps_read_block(struct mbg_softc *scp, uint8_t dataType,
+			void *buf, size_t bufLen,
+			uint32_t blockNum, size_t blockSize)
+{
+	int rc;
+	uint8_t dataSize, sizeNBytes;
+	uint16_t nBytes = 0;
+
+	/*
+	 * Write the command
+	 */
+	rc = scp->mbg_read(scp, MBG_CMD_GET_GPS_DATA,
+			 &dataSize, sizeof(dataSize));
+	if (rc != MBG_RET_STATUS_OK) {
+		device_printf(scp->device,
+			"mbg_gps_read_block(): MBG_CMD_GET_GPS_DATA failed!\n");
+		return rc;
+	}
+
+	if (dataSize != 1) {
+		if (dataSize == 0) {
+			device_printf(scp->device,
+				"mbg_gps_read_block(): dataSize == 0!\n");
+			return MBG_RET_STATUS_BUSY;
+		} else {
+			device_printf(scp->device,
+				"mbg_gps_read_block(): dataSize == %d!\n",
+				dataSize);
+			return MBG_RET_STATUS_NBYTES;
+		}
+	}
+
+	/*
+	 * Write the data type code
+	 */
+	if (scp->features & MBG_FEAT_GPS_DATA_16) 
+		sizeNBytes = sizeof(nBytes);
+	else {
+		if (bufLen > 255)
+			return MBG_RET_STATUS_NBYTES;
+		sizeNBytes = sizeof(uint8_t);
+	}
+
+	rc = scp->mbg_read(scp, dataType, &nBytes, sizeNBytes);
+	if (rc != MBG_RET_STATUS_OK) {
+		device_printf(scp->device,
+			"mbg_gps_read_block(): can't set data type\n");
+		return rc;
+	}
+
+	if (nBytes == 0) {
+		device_printf(scp->device,
+			"mbg_gps_read_block(): invalid data type\n");
+		return MBG_RET_STATUS_INV_TYPE;
+	}
+
+	if (nBytes != bufLen) {
+		device_printf(scp->device,
+			"mbg_gps_read_block(): nBytes (%d) != bufLen (%d)\n",
+			nBytes, bufLen);
+		return MBG_RET_STATUS_NBYTES;
+	}
+
+	/*
+	 * Write the block number, and read blockSize bytes
+	 */
+	rc = scp->mbg_read(scp, blockNum, buf, blockSize);
+	if (rc != MBG_RET_STATUS_OK)
+		device_printf(scp->device,
+			"mbg_gps_read_block(): block %d size %d failed\n",
+			blockNum, blockSize);
+
+	return rc;
+}
+
+/*
+ * mbg_gps_read:
+ *	Get data from the GPS receiver
+ *
+ * Returns one of MBG_RET_STATUS_*
+ */
+static int
+mbg_gps_read(struct mbg_softc *scp, uint8_t dataType,
+		void *buf, size_t bufLen)
+{
+	char *p = buf;
+	int i, rc = MBG_RET_STATUS_OK;
+	int nBlocks = bufLen / MBG_FIFO_LEN;
+	int nRemain = bufLen % MBG_FIFO_LEN;
+
+	/*
+	 * Get nBlocks of data
+	 */
+	for (i = 0; i < nBlocks; i++) {
+		rc = mbg_gps_read_block(scp, dataType, p,
+					bufLen, i, MBG_FIFO_LEN);
+		if (rc != MBG_RET_STATUS_OK) {
+			device_printf(scp->device,
+				"mbg_gps_read(): can't read block %d/%d\n",
+				i, nBlocks);
+			return rc;
+		}
+		p += MBG_FIFO_LEN;
+	}
+
+	/*
+	 * Get nRemain bytes of data
+	 */
+	if (nRemain) {
+		rc = mbg_gps_read_block(scp, dataType, p,
+					bufLen, i, nRemain);
+		if (rc != MBG_RET_STATUS_OK)
+			device_printf(scp->device,
+				"mbg_gps_read(): can't read remain block %d, size %d\n",
+				i, nRemain);
+	}
+
+	return rc;
+}
+
+/*
+ * mbg_gps_write
+ *	Write data to the GPS receiver
+ *
+ * Returns one of MBG_RET_STATUS_*
+ */
+static int
+mbg_gps_write(struct mbg_softc *scp, uint8_t dataType,
+		void *buf, size_t bufLen)
+{
+	int i, rc;
+	uint8_t *p = (uint8_t *)buf;
+	uint16_t nBytes = 0, retVal = 0;
+	uint8_t dataSize, sizeNBytes, sizeRetVal;
+
+	/*
+	 * Write the command
+	 */
+	rc = scp->mbg_read(scp, MBG_CMD_SET_GPS_DATA,
+			 &dataSize, sizeof(dataSize));
+	if (rc != MBG_RET_STATUS_OK) {
+		device_printf(scp->device,
+			"mbg_gps_write(): MBG_CMD_SET_GPS_DATA failed!\n");
+		return rc;
+	}
+
+	if (dataSize != 1) {
+		device_printf(scp->device,
+			"mbg_gps_write(): dataSize == %d!\n",
+			dataSize);
+		return MBG_RET_STATUS_NBYTES;
+	}
+
+	/*
+	 * Write the data type code
+	 */
+	if (scp->features & MBG_FEAT_GPS_DATA_16) {
+		sizeNBytes = sizeof(nBytes);
+		sizeRetVal = sizeof(rc);
+	} else {
+		if (bufLen > 255)
+			return MBG_RET_STATUS_NBYTES;
+		sizeRetVal = sizeNBytes = sizeof(uint8_t);
+	}
+
+	rc = scp->mbg_read(scp, dataType, &nBytes, sizeNBytes);
+	if (rc != MBG_RET_STATUS_OK) {
+		device_printf(scp->device,
+			"mbg_gps_write(): can't set datatype\n");
+		return rc;
+	}
+
+	if (nBytes != bufLen) {
+		device_printf(scp->device,
+			"mbg_gps_write(): dataType nBytes (%d) != bufLen (%d)\n",
+			nBytes, bufLen);
+		return MBG_RET_STATUS_NBYTES;
+	}
+
+	/*
+	 * Write bufLen - 1 bytes without reading
+	 */
+	for (i = 0; i < bufLen - 1; i++) {
+		rc = scp->mbg_read(scp, *p++, NULL, 0);
+		if (rc != MBG_RET_STATUS_OK) {
+			device_printf(scp->device,
+				"mbg_gps_write(): %d/%d failed\n", i, bufLen);
+			return rc;
+		}
+	}
+
+	/*
+	 * Write final byte from buffer and read result
+	 */
+	rc = scp->mbg_read(scp, *p, &retVal, sizeRetVal);
+	if (rc != MBG_RET_STATUS_OK) {
+		device_printf(scp->device,
+			"mbg_gps_write(): last byte write failed\n");
+	}
+
+	if (rc != MBG_RET_STATUS_OK) {
+		return rc;
+	} else {
+		return retVal;
+	}
+}
+
+/*
+ * mbg_pci_irq_op:
+ *	enable, disable or ack the interface IRQ
+ *
+ * Returns one of MBG_RET_STATUS_*
+ */
+static int
+mbg_pci_irq_op(struct mbg_softc *scp, enum MBG_IRQ_OP op)
+{
+	int32_t rc;
+	uint32_t ctlStatus;
+
+	switch (op) {
+	case MBG_IRQ_OP_DISABLE:
+		rc = scp->mbg_read(scp, MBG_CMD_IRQ_NONE, NULL, 0);
+		if (rc != MBG_RET_STATUS_OK) {
+			KASSERT(0, "mbg_pci_irq_op(): can't disable IRQ\n");
+			return rc;
+		}
+		ctlStatus = bus_space_read_4(scp->bt,
+					scp->bh,
+					MBG_ASIC_REG_CTLSTATUS);
+		bus_space_write_4(scp->bt,
+				scp->bh,
+				MBG_ASIC_REG_CTLSTATUS,
+				ctlStatus & ~(MBG_ASIC_PCI_IRQ_FLAG));
+		break;
+	case MBG_IRQ_OP_ENABLE:
+		rc = scp->mbg_read(scp, MBG_CMD_IRQ_1_SEC, NULL, 0);
+		if (rc != MBG_RET_STATUS_OK) {
+			KASSERT(0, "mbg_pci_irq_op(): can't enable IRQ\n");
+			return rc;
+		}
+		ctlStatus = bus_space_read_4(scp->bt,
+					scp->bh,
+					MBG_ASIC_REG_CTLSTATUS);
+		bus_space_write_4(scp->bt,
+				scp->bh,
+				MBG_ASIC_REG_CTLSTATUS,
+				ctlStatus | MBG_ASIC_PCI_IRQ_FLAG);
+		break;
+	case MBG_IRQ_OP_ACK:
+		bus_space_write_4(scp->bt,
+				scp->bh,
+				MBG_ASIC_REG_CTLSTATUS,
+				MBG_ASIC_PCI_IRQ_FLAG);
+		break;
+	default:
+		KASSERT(0, ("mbg_pci_irq_op unknown!"));
+		break;
+	}
+
+	return MBG_RET_STATUS_OK;
+}
+
+/*
+ * mbg_pci_got_intr:
+ *	see if the interface really generated an IRQ
+ *
+ * Returns 1 if yes, 0 otherwise
+ */
+static int
+mbg_pci_got_intr(struct mbg_softc *scp)
+{
+	if (bus_space_read_4(scp->bt,
+			scp->bh,
+			MBG_ASIC_REG_CTLSTATUS) &
+		MBG_ASIC_PCI_IRQ_FLAG)
+		return 1;
+
+	return 0;
+}
+
+/*
+ * mbg_tframe2str:
+ *	Convert a time frame to a string in Meinberg Standard format
+ *
+ * Returns nothing
+ */
+static void
+mbg_tframe2str(struct mbg_tframe *tframe, char *str)
+{
+	char *p = str;
+
+	if (tframe->status & MBG_STATUS_INVALID) {
+		tframe->status |= MBG_STATUS_FREERUN;
+		tframe->status &= ~MBG_STATUS_SYNC;
+	}
+
+	/*
+	 * STX
+	 */
+	*p++ = '\02';
+
+	/*
+	 * Meinberg standard time string
+	 *
+	 * Note: This is the time format expected by the
+	 *	 NTP parse clock driver.
+	 */
+	p += sprintf(p, "D:%02d.%02d.%02d;T:%1d;U:%02d.%02d.%02d;",
+		tframe->mday, tframe->mon, tframe->year, tframe->wday,
+		tframe->hour, tframe->min, tframe->sec);
+
+	/*
+	 * Has clock sync'd after reset?
+	 */
+	*p++ = (tframe->status & MBG_STATUS_SYNC) ? ' ' : '#';
+
+	/*
+	 * Has GPS receiver checked it position?
+	 */
+	*p++ = (tframe->status & MBG_STATUS_FREERUN) ? '*' : ' ';
+
+	/*
+	 * Are we using UTC, if not are we in MEZ summertime?
+	 */
+	*p++ = (tframe->status & MBG_STATUS_UTC) ? 'U' : /* UTC */
+		((tframe->status & MBG_STATUS_DST_ENA) ? 'S' : ' ' );
+
+	/*
+	 * Is there a DST announcement? A leap second announcement?
+	 */
+	*p++ = (tframe->status & MBG_STATUS_DST_CHG) ? '!' :
+		((tframe->status & MBG_STATUS_LEAP) ? 'A' : ' ' );
+
+	/*
+	 * ETX
+	 */
+	*p++ = '\03';
+
+	*p = 0;
+}
+
+/*****************************************************************************/
+/* PCI probe and attach                                                      */
+/*****************************************************************************/
+
+/*
+ * mbg_pci_probe:
+ *	See if the driver should be loaded based on the PCI
+ *	vendor and device ID of the adapter.
+ *
+ * Returns:
+ *	BUS_PROBE_DEFAULT, ENXIO on failure.
+ */
+static int
+mbg_pci_probe(device_t device)
+{
+	uint16_t pci_vendor_id;
+	uint16_t pci_device_id;
+	struct mbg_pci_ids *ent = mbg_pci_ids;
+	
+	pci_vendor_id = pci_get_vendor(device);
+	pci_device_id = pci_get_device(device);
+
+	while (ent->vendor_id != 0) {
+		if ((pci_vendor_id == ent->vendor_id) &&
+			(pci_device_id == ent->device_id)) {
+			device_set_desc(device, ent->desc);
+			return BUS_PROBE_DEFAULT;
+		}
+		++ent;
+	}
+
+	return ENXIO;
+}
+
+/*
+ * mbg_pci_attach:
+ *	Attach a PCI device and identify card features
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbg_pci_attach(device_t device)
+{
+	int error, rc;
+	char firmware[MBG_ID_LEN];
+	struct mbg_softc *scp = DEVICE2SOFTC(device);
+
+	KASSERT(scp != NULL, ("mbg_pci_attach: null software carrier!"));
+
+	bzero(scp, sizeof (*scp));
+	scp->device = device;
+
+	/*
+	 * Do generic attach
+	 */
+	error = mbg_attach(device, scp);
+	if (error) {
+		mbg_pci_detach(device);
+		return error;
+	}
+
+	/*
+	 * Set up feature mask and handlers for each device.
+	 * Obviously, only the GPS170PCI is supported here.
+	 */
+	switch (pci_get_device(device)) {
+	case MBG_PCI_DEVICE_GPS170PCI:
+		scp->features = MBG_FEAT_GPS170PCI;
+		scp->mbg_read = mbg_pci_read;
+		scp->mbg_irq_op = mbg_pci_irq_op;
+		scp->mbg_got_intr = mbg_pci_got_intr;
+		break;
+	default:
+		scp->features = 0x0;
+		break;
+	}
+
+	/*
+	 * Sanity check
+	 */
+	KASSERT(scp->mbg_read, "mbg_pci_attach(): no mbg_read() defined\n");
+	KASSERT(scp->mbg_irq_op, "mbg_pci_attach(): no mbg_irq_op() defined\n");
+	KASSERT(scp->mbg_got_intr, "mbg_pci_attach(): no mbg_got_intr() defined\n");
+
+	/*
+	 * Get the firmware version
+	 */
+	rc = scp->mbg_read(scp, MBG_CMD_GET_FW_ID_1,
+			 firmware, MBG_FIFO_LEN);
+	if (rc == MBG_RET_STATUS_OK) {
+		rc = scp->mbg_read(scp, MBG_CMD_GET_FW_ID_2,
+				 &firmware[MBG_FIFO_LEN], MBG_FIFO_LEN);
+		if (rc == MBG_RET_STATUS_OK) {
+			firmware[MBG_ID_LEN - 1] = '\0';
+			device_printf(device, "firmware: %s, ", firmware);
+		}
+	}
+
+	/*
+	 * Get the time from the board
+	 */
+	rc = scp->mbg_read(scp, MBG_CMD_GET_TIME,
+			&scp->tframe, sizeof(scp->tframe));
+	if (rc == MBG_RET_STATUS_OK) {
+		printf("date: %d/%d/%d, time: %02d:%02d:%02d.%02d, ",
+			scp->tframe.mon, scp->tframe.mday,
+			scp->tframe.year + 2000, scp->tframe.hour,
+			scp->tframe.min, scp->tframe.sec,
+			scp->tframe.hundreds);
+		if (scp->tframe.status & MBG_STATUS_FREERUN) {
+			printf("(free running on xtal)\n");
+		} else if (scp->tframe.status & MBG_STATUS_SYNC) {
+			printf("(synchronized)\n");
+		} else if (scp->tframe.status & MBG_STATUS_INVALID) {
+			printf("(invalid)\n");
+		}
+	}
+
+	/*
+	 * Get the GPS receiver info
+	 */
+	if (scp->features & MBG_FEAT_RECV_INFO) {
+		int i;
+		struct mbg_gps_recv_info ri;
+		rc = mbg_gps_read(scp, MBG_GPS_RECV_INFO, &ri, sizeof(ri));
+		if (rc == MBG_RET_STATUS_OK) {
+			device_printf(device, "serial number: %s, ",
+				ri.serial);
+			printf("GPS features: ");
+			for (i = 0; i < MBG_GPS_NFEATURES; i++) {
+				if ((ri.features & (1 << i)) &&
+					(i < sizeof(mbg_osc_feature_names)/sizeof(char *))) {
+					printf("%s, ", mbg_gps_feature_names[i]);
+				}
+			}
+			if (ri.features & MBG_GPS_FEAT_IRIG_TX)
+				scp->features |= MBG_FEAT_IRIG_TX;
+			if (ri.features & MBG_GPS_FEAT_SYNTH)
+				scp->features |= MBG_FEAT_SYNTH;
+			if (ri.osc_type >= sizeof(mbg_osc_feature_names)/sizeof(char *))
+				ri.osc_type = 0;
+			printf(" oscillator: %s\n",
+				mbg_osc_feature_names[ri.osc_type]);
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * mbg_pci_detach:
+ *	detach a PCI device
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbg_pci_detach(device_t device)
+{
+	int error;
+	struct mbg_softc *scp = DEVICE2SOFTC(device);
+
+	KASSERT(scp != NULL, ("mbg_pci_detach: null software carrier!"));
+
+	/*
+	 * Do generic detach
+	 */
+	error = mbg_detach(device, scp);
+
+	return error;
+}
+
+/*****************************************************************************/
+/* Common attachment functions                                               */
+/*****************************************************************************/
+
+/*
+ * mbg_attach:
+ *	Common attach function
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbg_attach(device_t device, struct mbg_softc *scp)
+{
+	int unit = device_get_unit(device);
+
+	/*
+	 * make mbgntp device
+	 */
+	scp->mbgntp.ntp = make_dev(&mbgntp_cdevsw, unit,
+					UID_ROOT, GID_OPERATOR,
+					S_IRUSR | S_IWUSR,
+					"mbgntp");
+	scp->mbgntp.ntp->si_drv1 = scp;
+
+	/*
+	 * make mbgclk device
+	 */
+	scp->mbgclk.clk = make_dev(&mbgclk_cdevsw, unit,
+					UID_ROOT, GID_OPERATOR,
+					S_IRUSR | S_IWUSR,
+					"mbgclk");
+	scp->mbgclk.clk->si_drv1 = scp;
+
+	if (mbg_allocate_resources(device))
+		goto errexit;
+
+	scp->bt = rman_get_bustag(scp->res_ioport);
+	scp->bh = rman_get_bushandle(scp->res_ioport);
+
+	/*
+	 * Setup interrupt
+	 */
+	if (scp->res_irq) {
+		if (!bus_setup_intr(device, scp->res_irq,
+				INTR_TYPE_TTY,
+#if __FreeBSD_version >= 700000
+				NULL,
+#endif
+				mbg_intr, scp, &scp->intr_cookie) == 0)
+			goto errexit;
+	}
+
+	mtx_init(&scp->mbgntp.lock, device_get_nameunit(device),
+			"mbgntp", MTX_DEF);
+	mtx_init(&scp->mbgclk.lock, device_get_nameunit(device),
+			"mbgclk", MTX_DEF);
+
+	return 0;
+
+errexit:
+	/*
+	 * Undo anything we may have done.
+	 */
+	mbg_detach(device, scp);
+	return ENXIO;
+}
+
+/*
+ * mbg_detach:
+ *	Common detach function
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbg_detach(device_t device, struct mbg_softc *scp)
+{
+	int retval;
+
+	/*
+	 * Remove interrupt handler
+	 */
+	if (scp->intr_cookie != NULL) {
+		if (bus_teardown_intr(device, scp->res_irq,
+				scp->intr_cookie) != 0)
+			device_printf(device,
+				"mbg_detach(): intr teardown failed\n");
+		scp->intr_cookie = NULL;
+	}
+
+	retval = mbg_deallocate_resources(device);
+	mtx_destroy(&scp->mbgclk.lock);
+	mtx_destroy(&scp->mbgntp.lock);
+
+	return retval;
+}
+
+/*
+ * mbg_allocate_resources:
+ *	Common resource allocation
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbg_allocate_resources(device_t device)
+{
+	int error;
+	struct mbg_softc *scp = DEVICE2SOFTC(device);
+
+	KASSERT(scp != NULL, ("mbg_alloc_resources: null software carrier!"));
+
+	scp->rid_ioport = PCIR_BAR(0);
+	scp->res_ioport = bus_alloc_resource_any(device, SYS_RES_IOPORT,
+						&scp->rid_ioport, RF_ACTIVE);
+	if (scp->res_ioport == NULL)
+		goto errexit;
+
+	scp->res_irq = bus_alloc_resource(device, SYS_RES_IRQ,
+			&scp->rid_irq, 0ul, ~0ul, 1, RF_SHAREABLE | RF_ACTIVE);
+	if (scp->res_irq == NULL)
+		goto errexit;
+
+	return 0;
+
+errexit:
+	error = ENXIO;
+
+	/*
+	 * Cleanup anything assigned
+	 */
+	mbg_deallocate_resources(device);
+
+	return ENXIO;
+}
+
+/*
+ * mbg_deallocate_resources:
+ *	Common resource deallocation
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbg_deallocate_resources(device_t device)
+{
+	struct mbg_softc *scp = DEVICE2SOFTC(device);
+
+	if (scp->res_irq != 0) {
+		bus_deactivate_resource(device, SYS_RES_IRQ,
+			scp->rid_irq, scp->res_irq);
+		bus_release_resource(device, SYS_RES_IRQ,
+			scp->rid_irq, scp->res_irq);
+		scp->res_irq = 0;
+	}
+
+	if (scp->res_ioport != 0) {
+		bus_deactivate_resource(device, SYS_RES_IOPORT,
+			scp->rid_ioport, scp->res_ioport);
+		bus_release_resource(device, SYS_RES_IOPORT,
+			scp->rid_ioport, scp->res_ioport);
+		scp->res_ioport = 0;
+	}
+
+	if (scp->mbgclk.clk) {
+		destroy_dev(scp->mbgclk.clk);
+		scp->mbgclk.clk = (struct cdev *)NULL;
+	}
+
+	if (scp->mbgntp.ntp) {
+		destroy_dev(scp->mbgntp.ntp);
+		scp->mbgntp.ntp = (struct cdev *)NULL;
+	}
+
+	return 0;
+}
+
+/*
+ * mbg_pci_shutdown:
+ *	Shutdown entry point
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbg_pci_shutdown(device_t dev)
+{
+	return 0;
+}
+
+/*
+ * mbg_pci_suspend:
+ *	Suspend device method
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbg_pci_suspend(device_t dev)
+{
+	return bus_generic_suspend(dev);
+}
+
+/*
+ * mbg_pci_resume:
+ *	Resume device method
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbg_pci_resume(device_t dev)
+{
+	return bus_generic_resume(dev);
+}
+
+/*****************************************************************************/
+/* interrupt processing functions                                            */
+/*****************************************************************************/
+
+/*
+ * mbg_intr:
+ *	Handle an interrupt
+ *
+ * Returns nothing
+ */
+static void
+mbg_intr(void *arg)
+{
+	int rc, accessInProgress;
+	struct mbg_softc *scp = (struct mbg_softc *) arg;
+
+	/*
+	 * Was this our interrupt?
+	 */
+	if (!scp->mbg_got_intr(scp))
+		return;
+
+	/*
+	 * Get the time
+	 */
+	mtx_lock_spin(&scp->mbgntp.spinLock);
+	accessInProgress = scp->mbgclk.accessInProgress;
+	if (!accessInProgress)
+		rc = scp->mbg_read(scp, MBG_CMD_GET_TIME,
+			&scp->tframe, sizeof(scp->tframe));
+	else
+		rc = MBG_RET_STATUS_BUSY;
+
+	/*
+	 * ACK the interrupt
+	 */
+	scp->mbg_irq_op(scp, MBG_IRQ_OP_ACK);
+
+	/*
+	 * Wakeup readers
+	 */
+	if (rc == MBG_RET_STATUS_OK) {
+		scp->mbgntp.hasTimeData = 1;
+		wakeup(&scp->mbgntp.hasTimeData);
+		selwakeup(&scp->mbgntp.select);
+	}
+	mtx_unlock_spin(&scp->mbgntp.spinLock);
+
+	return;
+}
+
+/*****************************************************************************/
+/* mbgntp cdevsw functions                                                   */
+/*****************************************************************************/
+
+/*
+ * mbgntp_open:
+ *	Handle an open request on /dev/mbgntp
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbgntp_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
+{
+	struct mbg_softc *scp = DEV2SOFTC(dev);
+
+	/*
+	 * Only 1 instance allowed
+	 */
+	mtx_lock(&scp->mbgntp.lock);
+	if (scp->mbgntp.opened) {
+		mtx_unlock(&scp->mbgntp.lock);
+		return EBUSY;
+	}
+	scp->mbgntp.opened++;
+	mtx_unlock(&scp->mbgntp.lock);
+
+	mtx_init(&scp->mbgntp.spinLock, "mbgSpin", NULL, MTX_SPIN);
+	mtx_init(&scp->mbgntp.sema, "mbgSema", NULL, MTX_DEF);
+	scp->mbgntp.hasTimeData = 0;
+	scp->mbg_irq_op(scp, MBG_IRQ_OP_ENABLE);
+
+	return 0;
+}
+
+/*
+ * mbgntp_close:
+ *	Handle a close request on /dev/mbgntp
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbgntp_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
+{
+	struct mbg_softc *scp = DEV2SOFTC(dev);
+
+	mtx_lock(&scp->mbgntp.lock);
+	scp->mbgntp.opened = 0;
+	mtx_unlock(&scp->mbgntp.lock);
+
+	scp->mbg_irq_op(scp, MBG_IRQ_OP_DISABLE);
+	
+	return 0;
+}
+
+/*
+ * mbgntp_read:
+ *	Handle a read request on /dev/mbgntp
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbgntp_read(struct cdev *dev, struct uio *uio, int ioflag)
+{
+	int ret = 0, nBytes;
+	struct mbg_tframe tframe;
+	char tstr[MBG_TSTAMP_LEN];
+	struct mbg_softc *scp = DEV2SOFTC(dev);
+
+	mtx_lock(&scp->mbgntp.lock);
+	if (!scp->mbgntp.opened) {
+		mtx_unlock(&scp->mbgntp.lock);
+		return EIO;
+	}
+	mtx_unlock(&scp->mbgntp.lock);
+
+	if (uio->uio_resid < sizeof(tstr))
+		return EINVAL;
+
+	if (ioflag & O_NONBLOCK) {
+		if (!mtx_trylock(&scp->mbgntp.sema))
+			return EAGAIN;
+	} else
+		mtx_lock(&scp->mbgntp.sema);
+
+	while (!scp->mbgntp.hasTimeData) {
+		mtx_unlock(&scp->mbgntp.sema);
+
+		if (ioflag & O_NONBLOCK)
+			return EAGAIN;
+
+		ret = tsleep(&scp->mbgntp.hasTimeData, PZERO | PCATCH, "mbg", 0);
+		if (ret == EINTR)
+			return EINTR;
+
+		mtx_lock(&scp->mbgntp.sema);
+	}
+
+	mtx_lock_spin(&scp->mbgntp.spinLock);
+	tframe = scp->tframe;
+	scp->mbgntp.hasTimeData = 0;
+	mtx_unlock_spin(&scp->mbgntp.spinLock);
+
+	mbg_tframe2str(&tframe, tstr);
+
+	nBytes = min(uio->uio_resid, sizeof(tstr) - 1);
+	ret = uiomove(tstr, nBytes, uio);
+
+	mtx_unlock(&scp->mbgntp.sema);
+
+	return ret;
+}
+
+/*
+ * mbgntp_write:
+ *	Handle a write request on /dev/mbgntp
+ *
+ * Always returns EPERM 
+ */
+static int
+mbgntp_write(struct cdev *dev, struct uio *uio, int ioflag)
+{
+	return EPERM;
+}
+
+/*
+ * mbgntp_ioctl:
+ *	Handle an ioctl request on /dev/mbgntp
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbgntp_ioctl(struct cdev *dev, u_long cmd, caddr_t data,
+		int flag, struct thread *td)
+{
+	int rc;
+
+	switch (cmd) {
+	/*
+	 * The TIOCxxx ioctl()'s are necessary because the
+	 * NTP parse clock driver thinks the Meinberg clock
+	 * is attached to a serial port.
+	 */
+	case TIOCGETA:
+	{
+		struct termios termios;
+
+		termios.c_iflag = IGNBRK | IGNPAR | ISTRIP;
+		termios.c_cflag = CS7 | PARENB | CREAD | HUPCL | CLOCAL;
+		termios.c_oflag = termios.c_lflag = 0;
+		termios.c_ispeed = termios.c_ospeed = B9600;
+		bcopy(&termios, data, sizeof(termios));
+		rc = MBG_RET_STATUS_OK;
+	}
+		break;
+	case TIOCGWINSZ:
+	{
+		struct winsize winsize = { 0, 0, 0, 0 };
+
+		bcopy(&winsize, data, sizeof(winsize));
+	}
+		rc = MBG_RET_STATUS_OK;
+		break;
+	case TIOCSETA:
+	case TIOCFLUSH:
+		rc = MBG_RET_STATUS_OK;
+		break;
+	default:
+		rc = EINVAL;
+	}
+
+	return rc;
+}
+
+/*
+ * mbgntp_poll:
+ *	Handle a poll request on /dev/mbgntp
+ *
+ * Returns number of events
+ */
+static int
+mbgntp_poll(struct cdev *dev, int poll_events, struct thread *td)
+{
+	int revents = 0;
+	struct mbg_softc *scp = DEV2SOFTC(dev);
+
+	if (scp->mbgntp.hasTimeData)
+		revents |= poll_events & (POLLIN | POLLRDNORM);
+	else
+		selrecord(td, &scp->mbgntp.select);
+
+	return revents;
+}
+
+/*****************************************************************************/
+/* mbgclk cdevsw functions                                                   */
+/*****************************************************************************/
+
+/*
+ * mbgclk_open:
+ *	Handle an open request on /dev/mbgclk
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbgclk_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
+{
+	struct mbg_softc *scp = DEV2SOFTC(dev);
+
+	/*
+	 * Only 1 instance allowed
+	 */
+	mtx_lock(&scp->mbgclk.lock);
+	if (scp->mbgclk.opened) {
+		mtx_unlock(&scp->mbgclk.lock);
+		return EBUSY;
+	}
+	scp->mbgclk.opened++;
+	mtx_unlock(&scp->mbgclk.lock);
+
+	return 0;
+}
+
+/*
+ * mbgclk_close:
+ *	Handle a close request on /dev/mbgclk
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbgclk_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
+{
+	struct mbg_softc *scp = DEV2SOFTC(dev);
+
+	mtx_lock(&scp->mbgclk.lock);
+	scp->mbgclk.opened = 0;
+	mtx_unlock(&scp->mbgclk.lock);
+
+	return 0;
+}
+
+/*
+ * mbgclk_access_inc:
+ *	Increment the mbgclk accessInProcess counter
+ *
+ * Returns nothing
+ */
+static void
+mbgclk_access_inc(struct mbgclk_struct *mbgclk)
+{
+	KASSERT(mbgclk->accessInProgress == 0,
+		("mbgclk_access_inc: accessInProcess != 0"));
+
+	mbgclk->accessInProgress++;
+}
+
+/*
+ * mbgclk_access_dec:
+ *	Decrement the mbgclk accessInProcess counter
+ *
+ * Returns nothing
+ */
+static void
+mbgclk_access_dec(struct mbgclk_struct *mbgclk)
+{
+	KASSERT(mbgclk->accessInProgress == 1,
+		("mbgclk_access_inc: accessInProcess != 1"));
+
+	mbgclk->accessInProgress--;
+}
+
+/*
+ * mbgclk_ioctl:
+ *	Handle an ioctl request on /dev/mbgclk
+ *
+ * Returns 0 on success, non-zero otherwise
+ */
+static int
+mbgclk_ioctl(struct cdev *dev, u_long cmd, caddr_t data,
+		int flag, struct thread *td)
+{
+	int rc = MBG_RET_STATUS_OK;
+	struct mbg_softc *scp = DEV2SOFTC(dev);
+
+	switch (cmd) {
+	case MBG_GET_DEVICE_NAME:
+	{
+		const char *p;
+
+		p = device_get_desc(scp->device);
+		rc = copyout(p, *(char **)data, strlen(p) + 1);
+	}
+		break;
+	case MBG_GET_FIRMWARE_ID:
+	{
+		char firmware[MBG_ID_LEN];
+
+		mbgclk_access_inc(&scp->mbgclk);
+		rc = scp->mbg_read(scp, MBG_CMD_GET_FW_ID_1,
+				 firmware, MBG_FIFO_LEN);
+		if (rc == MBG_RET_STATUS_OK) {
+			rc = scp->mbg_read(scp, MBG_CMD_GET_FW_ID_2,
+				 &firmware[MBG_FIFO_LEN], MBG_FIFO_LEN);
+			if (rc == MBG_RET_STATUS_OK) {
+				firmware[MBG_ID_LEN - 1] = '\0';
+				rc = copyout(firmware, *(char **)data,
+						MBG_ID_LEN);
+			} else
+				rc = EINVAL;
+		} else
+			rc = EINVAL;
+		mbgclk_access_dec(&scp->mbgclk);
+	}
+		break;
+	case MBG_GET_TIME:
+	{
+		struct mbg_tframe tf;
+
+		mbgclk_access_inc(&scp->mbgclk);
+		rc = scp->mbg_read(scp, MBG_CMD_GET_TIME,
+				&tf, sizeof(tf));
+		mbgclk_access_dec(&scp->mbgclk);
+		if (rc == MBG_RET_STATUS_OK)
+			bcopy(&tf, data, sizeof(tf));
+		else
+			rc = EINVAL;
+	}
+		break;
+	case MBG_GET_HR_TIME:
+	{
+		struct mbg_hr_time ht;
+
+		if (scp->features & MBG_FEAT_HR_TIME) {
+			mbgclk_access_inc(&scp->mbgclk);
+			rc = scp->mbg_read(scp, MBG_CMD_GET_HR_TIME,
+					&ht, sizeof(ht));
+			mbgclk_access_dec(&scp->mbgclk);
+			if (rc == MBG_RET_STATUS_OK)
+				bcopy(&ht, data, sizeof(ht));
+			else
+				rc = EINVAL;
+		} else
+			rc = ENXIO;
+	}
+		break;
+	case MBG_GET_LAST_SYNC_TIME:
+	{
+		struct mbg_tframe tf;
+
+		if (scp->features & MBG_FEAT_SYNC_TIME) {
+			mbgclk_access_inc(&scp->mbgclk);
+			rc = scp->mbg_read(scp, MBG_CMD_GET_SYNC_TIME,
+					&tf, sizeof(tf));
+			mbgclk_access_dec(&scp->mbgclk);
+			if (rc == MBG_RET_STATUS_OK)
+				bcopy(&tf, data, sizeof(tf));
+			else
+				rc = EINVAL;
+		} else
+			rc = ENXIO;
+	}
+		break;
+	case MBG_GET_RECV_INFO:
+	{
+		struct mbg_gps_recv_info ri;
+
+		if (scp->features & MBG_FEAT_RECV_INFO) {
+			mbgclk_access_inc(&scp->mbgclk);
+			rc = mbg_gps_read(scp, MBG_GPS_RECV_INFO,
+				&ri, sizeof(ri));
+			mbgclk_access_dec(&scp->mbgclk);
+			if (rc == MBG_RET_STATUS_OK)
+				bcopy(&ri, data, sizeof(ri));
+			else
+				rc = EINVAL;
+		} else
+			rc = ENXIO;
+	}
+		break;
+	case MBG_GET_RECV_STAT:
+	{
+		struct mbg_gps_stat_info si;
+
+		mbgclk_access_inc(&scp->mbgclk);
+		rc = mbg_gps_read(scp, MBG_GPS_STAT_INFO,
+				&si, sizeof(si));
+		mbgclk_access_dec(&scp->mbgclk);
+		if (rc == MBG_RET_STATUS_OK)
+			bcopy(&si, data, sizeof(si));
+		else
+			rc = EINVAL;
+	}
+		break;
+	case MBG_GET_GPS_POSITION:
+	{
+		struct mbg_gps_position gp;
+
+		mbgclk_access_inc(&scp->mbgclk);
+		rc = mbg_gps_read(scp, MBG_GPS_POS,
+				&gp, sizeof(gp));
+		mbgclk_access_dec(&scp->mbgclk);
+		if (rc == MBG_RET_STATUS_OK) {
+			/*
+			 * Note: Swap doubles as appropriate
+			 */
+			int i;
+			uint64_t u64;
+
+			for (i = 0; i < 3; i++) {
+				u64 = mbg_swap64(&gp.xyz[i]);
+				bcopy(&u64, &gp.xyz[i], sizeof(u64));
+				u64 = mbg_swap64(&gp.lla[i]);
+				bcopy(&u64, &gp.lla[i], sizeof(u64));
+			}
+			u64 = mbg_swap64(&gp.longitude.sec);
+			bcopy(&u64, &gp.longitude.sec, sizeof(u64));
+			u64 = mbg_swap64(&gp.latitude.sec);
+			bcopy(&u64, &gp.latitude.sec, sizeof(u64));
+			bcopy(&gp, data, sizeof(gp));
+		}  else
+			rc = EINVAL;
+	}
+		break;
+	case MBG_GET_GPS_PORT_PARM:
+	{
+		struct mbg_gps_port_parm pp;
+
+		mbgclk_access_inc(&scp->mbgclk);
+		rc = mbg_gps_read(scp, MBG_GPS_PORT_PARM,
+				 &pp, sizeof(pp));
+		mbgclk_access_dec(&scp->mbgclk);
+		if (rc == MBG_RET_STATUS_OK)
+			bcopy(&pp, data, sizeof(pp));
+		else
+			rc = EINVAL;
+	}
+		break;
+	case MBG_GET_GPS_TZDL:
+	{
+		struct mbg_gps_tzdl tz;
+
+		if (scp->features & MBG_FEAT_TZDL) {
+			mbgclk_access_inc(&scp->mbgclk);
+			rc = mbg_gps_read(scp, MBG_GPS_TZDL,
+					&tz, sizeof(tz));
+			mbgclk_access_dec(&scp->mbgclk);
+			if (rc == MBG_RET_STATUS_OK)
+				bcopy(&tz, data, sizeof(tz));
+			else
+				rc = EINVAL;
+		} else
+			rc = ENXIO;
+	}
+		break;
+	case MBG_GET_UCAPTURE_STATS:
+	{
+		struct mbg_ucapture_stats us;
+
+		/*
+		 * Note: Boards report one more max event than they
+		 *       can actually save. Adjust max accordingly.
+		 */
+		if (scp->features & MBG_FEAT_UCAP) {
+			mbgclk_access_inc(&scp->mbgclk);
+			rc = scp->mbg_read(scp, MBG_CMD_GET_CAP_STATS,
+					&us, sizeof(us));
+			mbgclk_access_dec(&scp->mbgclk);
+			if (rc == MBG_RET_STATUS_OK) {
+				us.max--;
+				bcopy(&us, data, sizeof(us));
+			} else
+				rc = EINVAL;
+		} else
+			rc = ENXIO;
+	}
+		break;
+	case MBG_GET_UCAPTURE_EVENT:
+	{
+		struct mbg_hr_time ht;
+
+		if (scp->features & MBG_FEAT_UCAP) {
+			mbgclk_access_inc(&scp->mbgclk);
+			rc = scp->mbg_read(scp, MBG_CMD_GET_CAP_EVENT,
+					&ht, sizeof(ht));
+			mbgclk_access_dec(&scp->mbgclk);
+			if (rc == MBG_RET_STATUS_OK)
+				bcopy(&ht, data, sizeof(ht));
+			else
+				rc = EINVAL;
+		} else
+			rc = ENXIO;
+	}
+		break;
+	case MBG_SET_UCAPTURE_CLEAR:
+		if (scp->features & MBG_FEAT_UCAP) {
+			mbgclk_access_inc(&scp->mbgclk);
+			rc = scp->mbg_read(scp, MBG_CMD_CLEAR_CAP_BUF,
+					NULL, 0);
+			mbgclk_access_dec(&scp->mbgclk);
+			if (rc != MBG_RET_STATUS_OK)
+				rc  = EINVAL;
+		} else 
+			rc = ENXIO;
+		break;
+	case MBG_SET_GPS_CMD:
+	{
+		uint16_t *cmd = (uint16_t *)data;
+
+		mbgclk_access_inc(&scp->mbgclk);
+		rc = mbg_gps_write(scp, MBG_GPS_CMD,
+				cmd, sizeof(*cmd));
+		mbgclk_access_dec(&scp->mbgclk);
+		if (rc != MBG_RET_STATUS_OK)
+			rc = EINVAL;
+	}
+		break;
+	case MBG_SET_GPS_TZDL:
+	{
+		struct mbg_gps_tzdl *tz = (struct mbg_gps_tzdl *)data;
+
+		if (scp->features & MBG_FEAT_TZDL) {
+			mbgclk_access_inc(&scp->mbgclk);
+			rc = mbg_gps_write(scp, MBG_GPS_TZDL,
+					tz, sizeof(*tz));
+			mbgclk_access_dec(&scp->mbgclk);
+			if (rc != MBG_RET_STATUS_OK)
+				rc = EINVAL;
+		} else
+			rc = ENXIO;
+	}
+		break;
+	case MBG_SET_GPS_POS_LLA:
+	{
+		int i;
+		uint64_t u64;
+		double lla[3];
+
+		/*
+		 * Note: Swap doubles as appropriate.
+		 *       Latitude and longitude are in radians.
+		 */
+		if (copyin(*(double **)data, &lla, sizeof(lla)))
+			rc = EFAULT;
+		else {
+			for (i = 0; i < 3; i++) {
+				u64 = mbg_swap64(&lla[i]);
+				bcopy(&u64, &lla[i], sizeof(u64));
+			}
+			mbgclk_access_inc(&scp->mbgclk);
+			rc = mbg_gps_write(scp, MBG_GPS_POS_LLA,
+					lla, sizeof(lla));
+			mbgclk_access_dec(&scp->mbgclk);
+			if (rc != MBG_RET_STATUS_OK)
+				rc = EINVAL;
+		} 
+	}
+		break;
+	case MBG_SET_GPS_TIME:
+	{
+		struct mbg_gps_dt *dt = (struct mbg_gps_dt *)data;
+
+		if (scp->features & MBG_FEAT_SET_TIME) {
+			mbgclk_access_inc(&scp->mbgclk);
+			rc = mbg_gps_write(scp, MBG_GPS_TIME,
+					dt, sizeof(*dt));
+			mbgclk_access_dec(&scp->mbgclk);
+			if (rc != MBG_RET_STATUS_OK)
+				rc = EINVAL;
+		} else
+			rc = ENXIO;
+	}
+		break;
+	default:
+		rc = EINVAL;
+	}
+
+	return rc;
+}
+
diff -rn -N -U 1 usr/src.old/sys/modules/mbg/Makefile usr/src/sys/modules/mbg/Makefile
--- usr/src.old/sys/modules/mbg/Makefile	1969-12-31 16:00:00.000000000 -0800
+++ usr/src/sys/modules/mbg/Makefile	2008-01-27 12:06:15.000000000 -0800
@@ -0,0 +1,10 @@
+#	MBG (Meinberg GPS170) Loadable Kernel Module
+#
+# $FreeBSD: $
+
+.PATH:  ${.CURDIR}/../../dev/mbg
+KMOD    = mbg
+SRCS    = mbg.c
+SRCS    += device_if.h bus_if.h pci_if.h
+
+.include <bsd.kmod.mk>
diff -rn -N -U 1 usr/src.old/sys/sys/mbgio.h usr/src/sys/sys/mbgio.h
--- usr/src.old/sys/sys/mbgio.h	1969-12-31 16:00:00.000000000 -0800
+++ usr/src/sys/sys/mbgio.h	2008-01-27 12:05:51.000000000 -0800
@@ -0,0 +1,369 @@
+/*
+ * Copyright (c) 2007-2008 VMware, Inc.  All rights reserved.
+ *
+ * Joe Landers <jlanders@vmware.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of VMware, Inc. nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    with specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef __SYS_MBGIO_H__
+#define __SYS_MBGIO_H__
+
+#ifndef KERNEL
+#include <sys/types.h>
+#endif
+#include <sys/ioccom.h>
+
+/*****************************************************************************/
+/* #defines and constants                                                    */
+/*****************************************************************************/
+
+/*
+ * Time frame status bits (status in mbg_tframe)
+ */
+#define MBG_STATUS_FREERUN         0x0001 /* free running on xtal            */
+#define MBG_STATUS_DST_ENA         0x0002 /* DST enabled                     */
+#define MBG_STATUS_SYNC            0x0004 /* clock synced at least once      */
+#define MBG_STATUS_DST_CHG         0x0008 /* DST announced                   */
+#define MBG_STATUS_UTC             0x0010 /* using UTC                       */
+#define MBG_STATUS_LEAP            0x0020 /* leap second announced           */
+#define MBG_STATUS_IFTM            0x0040 /* current time set from host      */
+#define MBG_STATUS_INVALID         0x0080 /* time is invalid                 */
+#define MBG_STATUS_LEAP_SEC        0x0100 /* current sec is leap sec         */
+#define MBG_STATUS_ANTENNA_FAIL    0x0200 /* antenna failure                 */
+#define MBG_STATUS_OVERRUN         0x2000 /* event interval too short        */
+#define MBG_STATUS_BUF_FULL        0x4000 /* event read too slow             */
+#define MBG_STATUS_IO_BLOCKED      0x8000 /* microprocessor busy             */
+
+/*
+ * GPS time status bits (used by status field of mbg_gps_time)
+ */
+#define MBG_GPS_STATUS_UTC         0x0001 /* UTC correction applied          */
+#define MBG_GPS_STATUS_LOCAL       0x0002 /* converted to local time         */
+#define MBG_GPS_STATUS_DL_ANN      0x0004 /* upcoming DST announcement       */
+#define MBG_GPS_STATUS_DL_ENB      0x0008 /* DST enabled                     */
+#define MBG_GPS_STATUS_LS_ANN      0x0010 /* leap second add announcement    */
+#define MBG_GPS_STATUS_LS_ENB      0x0020 /* current second is leap second   */
+#define MBG_GPS_STATUS_LS_ANN_NEG  0x0040 /* leap second del (set w/ LS_ANN) */
+#define MBG_GPS_STATUS_UNUSED      0x0080 /* reserved and not used           */
+#define MBG_GPS_STATUS_EXT_SYNC    0x0100 /* clock sync'd externally         */
+#define MBG_GPS_STATUS_HOLDOVER    0x0200 /* holdover mode from prev sync    */
+#define MBG_GPS_STATUS_ANT_SHORT   0x0400 /* antenna cable short circuited   */
+#define MBG_GPS_STATUS_NO_WARM     0x0800 /* OCXO has not warmed up          */
+#define MBG_GPS_STATUS_ANT_DISCONN 0x1000 /* antenna currently disconnected  */
+#define MBG_GPS_STATUS_SYN_FLAG    0x2000 /* TIME_SYN output is low          */
+#define MBG_GPS_STATUS_NO_SYNC     0x4000 /* time sync not verified          */
+#define MBG_GPS_STATUS_NO_POS      0x8000 /* position not verified, LOCK off */
+
+/*
+ * Signal level (signal in mbg_tframe)
+ */
+#define MBG_SIG_BIAS               55
+#define MBG_SIG_ERR                1
+#define MBG_SIG_MIN                20
+#define MBG_SIG_MAX                68
+
+/*
+ * GPS receiver modes (mode in mbg_gps_stat_info)
+ */
+#define MBG_GPS_STAT_REMOTE        0x10
+#define MBG_GPS_STAT_BOOT          0x20
+
+#define MBG_GPS_STAT_TRACK         (0x01)
+#define MBG_GPS_STAT_AUTO_166      (0x02)
+#define MBG_GPS_STAT_WARM_166      (0x03                    |MBG_GPS_STAT_BOOT)
+#define MBG_GPS_STAT_COLD_166      (0x04                    |MBG_GPS_STAT_BOOT)
+#define MBG_GPS_STAT_AUTO_BC       (0x05|MBG_GPS_STAT_REMOTE)
+#define MBG_GPS_STAT_WARM_BC       (0x06|MBG_GPS_STAT_REMOTE|MBG_GPS_STAT_BOOT)
+#define MBG_GPS_STAT_COLD_BC       (0x07|MBG_GPS_STAT_REMOTE|MBG_GPS_STAT_BOOT)
+#define MBG_GPS_STAT_UPDA_166      (0x08                    |MBG_GPS_STAT_BOOT)
+#define MBG_GPS_STAT_UPDA_BC       (0x09|MBG_GPS_STAT_REMOTE|MBG_GPS_STAT_BOOT)
+
+/*
+ * On-board oscillator DAC (bias adjustment in mbg_gps_stat_info)
+ */
+#define MBG_OSC_DAC_RANGE          4096UL
+#define MBG_OSC_DAC_BIAS           (MBG_OSC_DAC_RANGE / 2)
+
+/*
+ * GPS receiver info ID strings
+ */
+#define MBG_GPS_ID_SLEN            16
+#define MBG_GPS_ID_SSZ             (MBG_GPS_ID_SLEN + 1)
+
+/*
+ * GPS receiver EPLD string
+ */
+#define MBG_GPS_EPLD_SLEN          8
+#define MBG_GSP_EPLD_SSZ           (MBG_GPS_EPLD_SLEN + 1)
+
+/*
+ * GPS receivers have 2 serial ports
+ */
+#define MBG_NPORTS                 2
+
+/*
+ * Candidate GPS features (used as a mask in struct mbg_gps_recv_info)
+ */
+enum MBG_GPS_FEAT {
+	MBG_GPS_FEAT_PPS,          /* pulse per second output                */
+	MBG_GPS_FEAT_PPM,          /* pulse per minute output                */
+	MBG_GPS_FEAT_SYNTH,        /* programmable synthesizer output        */
+	MBG_GPS_FEAT_DCFMARKS,     /* DCF77 compatible time mark output      */
+	MBG_GPS_FEAT_IRIG_TX,      /* on-board IRIG output                   */
+	MBG_GPS_FEAT_IRIG_RX,      /* on-board IRIG input                    */
+	MBG_GPS_FEAT_LAN_IP4,      /* LAN IPv4 interface                     */
+	MBG_GPS_FEAT_MULTI_REF,    /* multiple input sources with priorities */
+	MBG_GPS_FEAT_RCV_TIMEOUT,  /* timeout after GPS reception stopped    */
+	MBG_GPS_FEAT_IGNORE_LOCK,  /* supports "ignore lock"                 */
+	MBG_GPS_FEAT_5_MHZ,        /* output 5 MHz rather than 100 kHz       */
+	MBG_GPS_FEAT_XMULTI_REF,   /* extended multiple input source config  */
+	MBG_GPS_FEAT_OPT_SETTINGS, /* supports MBG_OPT_SETTINGS              */
+	MBG_GPS_NFEATURES          /* the number of valid features           */
+};      
+
+/*
+ * Time string modes (used in mode field of mbg_gps_port_parm)
+ */
+enum MBG_TSMODE {
+	MBG_TSMODE_ON_REQ,         /* on request only                        */
+	MBG_TSMODE_PER_SEC,        /* automatically, when second changes     */
+	MBG_TSMODE_PER_MIN,        /* automatically, when minute changes     */
+	MBG_TSMODE_UCAP,           /* automatically, with user capture       */
+	MBG_TSMODE_UCAP_REQ        /* only if user capture requested         */
+};
+
+/*
+ * GPS commands (used by the MBG_SET_GPS_CMD ioctl)
+ */
+enum MBG_GPS_CMD {
+	MBG_GPS_CMD_BOOT = 1,      /* force the clock to boot mode           */
+	MBG_GPS_CMD_INIT_SYS,      /* clear all system variables             */
+	MBG_GPS_CMD_INIT_USER,     /* reset all user parameters to defaults  */
+	MBG_GPS_CMD_INIT_DAC       /* reset the on-board oscillator DAC      */
+};
+
+/*****************************************************************************/
+/* structs and data types                                                    */
+/*****************************************************************************/
+
+#pragma pack(1)
+
+/*
+ * Standard time frame
+ */
+struct mbg_tframe {
+	uint8_t hundreds;          /* hundredths of sec, 00..99              */
+	uint8_t sec;               /* seconds, 00..59, (or 60 if leap)       */
+	uint8_t min;               /* minutes, 00..59                        */
+	uint8_t hour;              /* hours, 00..23                          */
+	uint8_t mday;              /* day of month, 01..31                   */
+	uint8_t wday;              /* day of week, 1..7 (1 = Monday)         */
+	uint8_t mon;               /* month, 01..12                          */
+	uint8_t year;              /* year of century, 00..99                */
+	uint8_t status;            /* status bit mask                        */
+	uint8_t signal;            /* relative signal strength               */
+	int8_t  utc_off;           /* UTC offset in hours                    */
+};
+
+/*
+ * High resolution time
+ *	Note: frac may be converted via:
+ *		(uint32_t)((int64_t)frac * 10000000UL/(int64_t)0x100000000LL)
+ */
+struct mbg_hr_time {
+	struct mbg_time_stamp {  
+		uint32_t sec;      /* seconds since 1970 (UTC)               */
+		uint32_t frac;     /* frac of sec (0xFFFFFFFF == 0.99999...) */
+	} tstamp;                  /* high resolution time stamp             */
+	int32_t utc_offs;          /* UTC offset (sec)                       */
+	uint16_t status;           /* status bit mask                        */
+	uint8_t signal;            /* for time, the relative RF signal level */
+	                           /* for a capture event, channel number    */
+};
+
+/*
+ * GPS software version
+ */
+struct mbg_gps_srev {
+	uint16_t code;             /* e.g. 0x0120 means rev. 1.20            */
+	char name[MBG_GPS_ID_SSZ]; /* used to identify customized versions   */
+	uint8_t reserved;          /* pad struct size                        */
+};
+
+/*
+ * GPS fixed frequency data
+ */
+struct mbg_gps_finfo {
+	uint16_t khz_val;          /* base frequency [kHz]                   */
+	int16_t range;             /* optional exponent [base 10]            */
+};
+
+/*
+ * GPS receiver info
+ */
+struct mbg_gps_recv_info {
+	uint16_t model_code;       /* receiver model ID                      */
+	struct mbg_gps_srev srev;  /* software revision and ID               */
+	char model[MBG_GPS_ID_SSZ];/* receiver model                         */
+	char serial[MBG_GPS_ID_SSZ];/* serial number                         */
+	char epld[MBG_GSP_EPLD_SSZ];/* EPLD image name                       */
+	uint8_t n_channels;        /* number of receiver channels            */
+	uint32_t ticks_per_sec;    /* resolution of fractions of seconds     */
+	uint32_t features;         /* optional features                      */
+	struct mbg_gps_finfo finfo;/* opt non-standard fixed freq            */
+	uint8_t osc_type;          /* type of on-board oscillator            */
+	uint8_t osc_flags;         /* oscillator flags                       */
+	uint8_t n_ucaps;           /* number of user time capture inputs     */
+	uint8_t n_com_ports;       /* number of on-board serial ports        */
+	uint8_t n_str_type;        /* max num of string types supported      */
+	uint8_t n_prg_out;         /* number of programmable pulse outputs   */
+	uint16_t flags;            /* additional information                 */
+};
+
+/*
+ * GPS receiver status
+ */
+struct mbg_gps_stat_info { 
+	uint16_t mode;             /* operation mode                         */
+	uint16_t sats_used;        /* sats used                              */
+	uint16_t sats_in_view;     /* sats in view                           */
+	int16_t dac_val;           /* DAC cal (fine) - needs bias adjustment */
+	int16_t dac_cal;           /* DAC cal (coarse) - same as above       */
+};
+
+/*
+ * Geographic longitude or latitude in degrees, minutes, seconds
+ *	Note: Longitude East and latitude North are positive,
+ *            longitude West and latitude South are negative.
+ */
+struct mbg_dms {
+	uint16_t prefix;           /* 'N', 'E', 'S' or 'W'                   */
+	uint16_t deg;              /* [0...90 (lat) or 0...180 (lon)]        */
+	uint16_t min;              /* [0...59]                               */
+	double sec;                /* [0...59.999]                           */
+};
+
+/*
+ * GPS receiver position
+ *	Note: The ioctl() automatically handles word swapping in doubles.
+ */
+struct mbg_gps_position {
+	double xyz[3];             /* WGS84 ECEF coordinates                 */
+	double lla[3];             /* depends on reference ellipsoid         */
+	struct mbg_dms longitude;  /* longitude in degrees, minutes, seconds */
+	struct mbg_dms latitude;   /* latitude in degrees, minutes, seconds  */
+	int16_t ellipsoid;         /* reference ellipsoid                    */
+};
+
+/*
+ * GPS receiver serial port parameters
+ */
+struct mbg_gps_port_parm {
+	struct mbg_gps_com_parm {
+		int32_t speed;     /* baud rate (e.g. 19200)                 */
+		char framing[4];   /* character framing (e.g. "8N1")         */
+		int16_t handshake; /* no handshake currently supported       */
+	} com[MBG_NPORTS];         /* com0 and com1 settings                 */
+	uint8_t mode[MBG_NPORTS];  /* com0 and com1 output mode              */
+};
+
+/*
+ * User capture statistics
+ *	Note: Boards report one more max event than they can actually
+ *	      save. The ioctl() automatically adjusts for this behavior.
+ *	      Capture events are removed from the capture FIFO when read.
+ *	      Use MBG_GET_UCAPTURE_CLEAR to bulk clear the capture FIFO.
+ */
+struct mbg_ucapture_stats {
+	uint32_t used;             /* number of saved user capture events    */
+	uint32_t max;              /* maximum number of events (adjusted)    */
+};
+
+/*
+ * GPS receiver timezone and daylight savings time parameters
+ */
+struct mbg_gps_tzdl {
+	int32_t offs;              /* offset from UTC to local time [sec]    */
+	int32_t offs_dl;           /* addn'l offset if DST enabled [sec]     */
+	struct mbg_gps_time {      /* Local data and time from GPS time      */
+		int16_t year;      /*   0..9999                              */
+		int8_t month;      /*   1..12                                */
+		int8_t mday;       /*   1..31                                */
+		int16_t yday;      /*   1..366                               */
+		int8_t wday;       /*   0..6 == Sun..Sat                     */
+		int8_t hour;       /*   0..23                                */
+		int8_t min;        /*   0..59                                */
+		int8_t sec;        /*   0..59                                */
+		int32_t frac;      /*   frac sec; scale: 1/GPS_TICKS_PER_SEC */
+		int32_t utc_offs;  /*   local time offset from UTC           */
+		uint16_t status;   /*   flags                                */
+	} tm_on,                   /* date/time when daylight saving starts  */
+	  tm_off;                  /* date/time when daylight saving ends    */
+	char name[2][6];           /* names without and with DST enabled     */
+};
+
+/*
+ * GPS receiver date and time
+ */
+struct mbg_gps_dt {
+	int16_t channel;           /* -1 = current time; 0, 1: capture 0, 1  */
+	struct mbg_gps_fmt_time {
+		uint16_t wn;       /* GPS week number                        */
+		uint32_t sec;      /* second of the GPS week                 */
+		uint32_t tick;     /* frac sec; scale: 1/GPS_TICKS_PER_SEC   */
+	} gps_fmt_time;
+	struct mbg_gps_time time;  /* data and time computed from GPS time   */
+};
+
+#pragma pack()
+
+/*****************************************************************************/
+/* ioctl handling                                                            */
+/*****************************************************************************/
+
+/*
+ * Supported ioctls
+ */
+#define MBG_GET_DEVICE_NAME        _IO( 'M', 0x00)
+#define MBG_GET_FIRMWARE_ID        _IO( 'M', 0x01)
+#define MBG_GET_TIME               _IOR('M', 0x02, struct mbg_tframe)
+#define MBG_GET_HR_TIME            _IOR('M', 0x03, struct mbg_hr_time)
+#define MBG_GET_LAST_SYNC_TIME     _IOR('M', 0x04, struct mbg_tframe)
+#define MBG_GET_RECV_INFO          _IOR('M', 0x05, struct mbg_gps_recv_info)
+#define MBG_GET_RECV_STAT          _IOR('M', 0x06, struct mbg_gps_stat_info)
+#define MBG_GET_GPS_POSITION       _IOR('M', 0x07, struct mbg_gps_position)
+#define MBG_GET_GPS_PORT_PARM      _IOR('M', 0x08, struct mbg_gps_port_parm)
+#define MBG_GET_GPS_TZDL           _IOR('M', 0x09, struct mbg_gps_tzdl)
+#define MBG_GET_UCAPTURE_STATS     _IOR('M', 0x0a, struct mbg_ucapture_stats)
+#define MBG_GET_UCAPTURE_EVENT     _IOR('M', 0x0b, struct mbg_hr_time)
+
+#define MBG_SET_UCAPTURE_CLEAR     _IO( 'M', 0x0c)
+#define MBG_SET_GPS_CMD            _IOW('M', 0x0d, int)
+#define MBG_SET_GPS_TZDL           _IOW('M', 0x0e, struct mbg_gps_tzdl)
+#define MBG_SET_GPS_POS_LLA        _IO( 'M', 0x1f)
+#define MBG_SET_GPS_TIME           _IOW('M', 0x10, struct mbg_gps_dt)
+
+#endif


>Release-Note:
>Audit-Trail:
>Unformatted:



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200801280600.m0S60gMK023182>