diff options
Diffstat (limited to 'mbglib/common/pcpsdrvr.c')
-rwxr-xr-x | mbglib/common/pcpsdrvr.c | 3395 |
1 files changed, 3395 insertions, 0 deletions
diff --git a/mbglib/common/pcpsdrvr.c b/mbglib/common/pcpsdrvr.c new file mode 100755 index 0000000..ae179e6 --- /dev/null +++ b/mbglib/common/pcpsdrvr.c @@ -0,0 +1,3395 @@ + +/************************************************************************** + * + * $Id: pcpsdrvr.c 1.46.2.24 2011/02/04 14:44:45 martin TEST $ + * + * Copyright (c) Meinberg Funkuhren, Bad Pyrmont, Germany + * + * Description: + * Driver functions that detect Meinberg PC plug-in devices and set up + * the software environment (port base address, clock features, etc.). + * + * These functions should be used with programs which have direct + * access to the hardware (e.g. device drivers). + * + * Programs which access the devices via device drivers should + * use the functions provided by the mbgdevio module. + * + * There are several preprocessor symbols defined at the top of + * pcpsdrvr.h which control the default support of some features + * under the different operating systems. If required, each of + * those symbols can be overridden by compiler arguments. + * + * Basically the following radio clocks are supported: + * USB: USB5131, TCR51USB, MSF51USB, WWVB51USB, DCF600USB + * PCI express: PEX511, TCR511PEX, GPS170PEX, PTP270PEX, + * FRC511PEX, TCR170PEX, GPS180PEX, TCR180PEX + * PCI bus 5V/3.3V: PCI510, PCI511, GPS169PCI, GPS170PCI, + * TCR510PCI, TCR167PCI, TCR511PCI + * PCI bus 5V: PCI32, GPS167PCI, PCI509, GPS168PCI + * MCA bus: PS31 + * ISA bus: PC31, PC32, GPS167PC + * + * USB is not supported for some target environments, mainly because + * those operating systems don't provide full USB support. + * + * PCI support is possible in two different ways. The preferred + * functions are compiled in if one of the symbols _PCPS_USE_PCI_PNP + * or _PCPS_USE_PCI_BIOS is defined != 0. + * + * If _PCPS_USE_PCI_PNP is != 0 it is assumed that the operating + * system's PCI layer detects a new PCI device and calls a driver's + * add_device()/start_device() function to initialize the device. + * This new technique is supported with PNP operating systems + * (e.g. Win98, Win2K, newer Linux versions). + * + * If _PCPS_USE_PCI_BIOS is != 0 the program scans the PCI bus + * during startup to detect and initialize supported PCI devices. + * This techique is used with non-PNP operating systems. + * + * The symbol _PCPS_USE_RSRCMGR must be defined != 0 to include + * support of resource managers, if necessary. + * + * If the symbol _PCPS_USE_MCA is defined != 0 then Micro Channel + * detection (and therefore auto-detection of a MCA clock) is + * supported. + * + * MCA clocks are accessed using the same low level functions as + * ISA clocks, so if autodetection of MCA clocks is not supported + * then a MCA clock's known port number can be passed to + * pcps_detect_clocks() to let it be treated like an ISA clock. + * + * ----------------------------------------------------------------------- + * $Log: pcpsdrvr.c $ + * Revision 1.46.2.24 2011/02/04 14:44:45 martin + * Revision 1.46.2.23 2011/02/01 17:12:04 martin + * Revision 1.46.2.22 2011/02/01 15:08:34 martin + * Revision 1.46.2.21 2011/02/01 12:12:18 martin + * Revision 1.46.2.20 2011/01/31 17:30:28 martin + * Preliminary virt addr under *BSD. + * Revision 1.46.2.19 2011/01/28 10:34:06 martin + * Moved MBG_TGT_SUPP_MEM_ACC definition to pcpsdev.h. + * Revision 1.46.2.18 2011/01/27 15:13:08 martin + * Added some debug messages in pcps_start_device(). + * Revision 1.46.2.17 2011/01/27 13:39:33 martin + * Revision 1.46.2.16 2011/01/27 11:01:48 martin + * Support static device list (no malloc) and use it under FreeBSD. + * Revision 1.46.2.15 2011/01/26 16:40:14 martin + * Fixed build under FreeBSD. + * Revision 1.46.2.14 2011/01/26 11:30:29 martin + * Fixed PTP270PEX boot delay. + * Revision 1.46.2.13 2011/01/07 14:00:58 daniel + * Re-enabled wait period for PTP270PEX + * Revision 1.46.2.12 2010/11/23 11:07:56Z martin + * Support memory mapped access under DOS. + * Revision 1.46.2.11 2010/11/22 14:19:27Z martin + * Support DCF600USB. + * Cleanup. + * Revision 1.46.2.10 2010/10/06 09:24:02 martin + * Revision 1.46.2.9 2010/09/27 13:09:22Z martin + * Revision 1.46.2.8 2010/09/21 13:10:15 daniel + * Check for raw IRIG data support in reciver info. + * Revision 1.46.2.7 2010/09/02 12:19:24Z martin + * Introduced and use new function check_ri_feature(). + * Also detect support for raw IRIG data from RECEIVER_INFO. + * Added debug code. + * Sleeping for PTP270PEX if uptime is too low needs to be fixed. + * Revision 1.46.2.6 2010/08/16 15:41:28 martin + * Revision 1.46.2.5 2010/08/13 11:56:49 martin + * Fixed build on WIN32_NON_PNP. + * Revision 1.46.2.4 2010/08/13 11:20:23Z martin + * If required, wait until PTP270PEX has finished booting. + * Revision 1.46.2.3 2010/08/11 13:41:47Z martin + * Code cleanup. + * Revision 1.46.2.2 2010/07/14 14:51:56 martin + * Use direct pointer to memory maopped timestamp. + * Revision 1.46.2.1 2010/06/30 15:02:12 martin + * Support GPS180PEX and TCR180PEX. + * Revision 1.46 2009/12/15 14:45:33 daniel + * Account for feature to read the raw IRIG bits. + * Revision 1.45 2009/09/29 07:24:50Z martin + * Use standard feature flag to check if fast HR time is supported. + * Revision 1.44 2009/06/23 07:10:47 martin + * Fixed/modified some debug messages. + * Revision 1.43 2009/06/19 12:13:59 martin + * Check if TCR cards support raw IRIG time. + * Revision 1.42 2009/06/09 10:15:33 daniel + * Check if card has LAN interface and supports PTP. + * Revision 1.41 2009/04/08 08:33:20 daniel + * Check whether the TCR511PCI or devices with + * RECEIVER_INFO support IRIG control function bits. + * Revision 1.40 2009/03/27 09:55:13Z martin + * Added some debug messages. + * Account for renamed library symbols. + * Revision 1.39 2009/03/19 12:04:31Z martin + * Adjust endianess of ASIC version and ASIC features after having read. + * Revision 1.38 2009/03/17 15:33:53 martin + * Support reading IRIG control function bits. + * Revision 1.37 2009/03/13 09:17:00Z martin + * Bug fix: Hadn't checked whether TCR170PEX card provides the + * programmable synthesizer. + * As a fix moved the code from the body of check_opt_features() + * into pcps_start_device() so that the check is done for every + * type of card. + * Swap receiver_info to make this work on non-x86 architectures. + * Support configurable time scales, and reading/writing GPS UTC + * parameters via the PC bus. + * Use mbg_get_pc_cycles() instead of _pcps_get_cycles(). + * Revision 1.36 2009/01/13 12:03:57Z martin + * Generate a separate warning message if the firmware could not + * be read from an ISA card. + * Care about "long long" in debug msg. + * Revision 1.35 2008/12/16 14:38:49Z martin + * Account for new devices PTP270PEX, FRC270PEX, TCR170PEX, and WWVB51USB. + * Check the firmware / ASIC version of PEX cards and flag the device + * unsafe for IRQs if the versions are older than required. + * Check whether PEX511 and PCI511 support HR time. + * Moved initialization of common spinlocks and mutexes to pcps_start_device(). + * Take access cycles count in the low level routines, with interrupts disabled. + * Cleanup for pcps_read_usb() which is now possible since access cycles count + * is now taken inside the low evel routines. + * Support mapped I/O resources, unaligned access and endianess conversion. + * Account for ASIC_FEATURES being coded as flags, and account for + * new symbol PCI_ASIC_HAS_MM_IO. + * Account for new MBG_PC_CYCLES type. + * Account for signed irq_num. + * Renamed MBG_VIRT_ADDR to MBG_MEM_ADDR. + * Use MBG_MEM_ADDR type for memory rather than split high/low types. + * Distinguish device port variables for IRQ handling. + * Preliminarily support USB latency compensation under Win32 PNP targets + * and account for USB EHCI microframe timing which requires a different + * latency compensation approach. This is useful if a USB 2.0 hub is connected + * between device and host. + * Also read the ASIC version at device initialization. + * pcps_alloc_ddev() does not take a parameter anymore. + * Cleaned up comments. + * Revision 1.34 2008/02/27 10:03:02 martin + * Support TCR51USB and MSF51USB. + * Preliminary support for mapped memory access under Windows and Linux. + * Enabled PCPS_IRQ_1_SEC for USB within WIN32 targets + * in pcps_start_device(). + * Fixed a bug in pcps_write() where the error code + * that was returned from a USB device was misinterpreted + * due to a signed/unsigned mismatch (added typecast). + * Removed obsolete function pcps_cleanup_all_devices(). + * Code cleanup. + * Revision 1.33 2008/01/31 08:51:30Z martin + * Picked up changes from 1.31.2.1: + * Changed default definition of PCI_DWORD to uint32_t. + * Removed erraneous brace from debug code. + * Revision 1.32 2007/09/26 11:05:57Z martin + * Added support for USB in general and new USB device USB5131. + * Renamed ..._USE_PCIMGR symbols to ..._USE_PCI_PNP. + * Renamed ..._USE_PCIBIOS symbols to ..._USE_PCI_BIOS. + * Added new symbol _USE_ISA_PNP to exclude non-PNP stuff. + * from build if ISA devices are also handled by the PNP manager. + * Use new MBG_... codes defined in mbgerror.h. + * Unified timeout handling in low level functions by using an inline function. + * Renamed pcps_pnp_start_device() to pcps_start_device(). + * Renamed pcps_setup_pci_dev() to pcps_setup_and_startpci_dev(). + * Merged code from init_ddev_cfg() and finish_ddev_cfg() into pcps_start_device(). + * Improved and unified handling of ISA devices. + * Removed calling register_pnp_devices() from pcps_detect_clocks(), + * this is now called directly. + * Added missing IRIG support to pcps_rsrc_register_device(). + * Revision 1.31 2007/07/17 08:22:47Z martin + * Added support for TCR511PEX and GPS170PEX. + * Revision 1.30 2007/07/16 12:56:01Z martin + * Added support for PEX511. + * Rewrote common resource handling code in order to simplify + * OS specific code. + * Revision 1.29 2007/03/02 09:40:33Z martin + * Use generic port I/O macros. + * Pass PCPS_DDEV structure to the low level read functions. + * Use new _pcps_..._timeout_clk() macros. + * Added init code qualifier. + * Preliminary support for *BSD. + * Preliminary support for USB. + * Revision 1.28 2006/07/11 10:24:20 martin + * Use _fmemcpy() in pcps_generic_io() to support environments which + * require far data pointers. + * Revision 1.27 2006/07/07 09:41:15 martin + * Renamed pci_..() function calls to _mbg_pci_..() calls which are defined according to the + * OS requirements, in order to avoid naming conflicts. + * Revision 1.26 2006/06/19 15:28:52 martin + * Added support for TCR511PCI. + * Modified parameters required to detect ISA cards. + * The array of port addresses does no more require a 0 address + * as last value. + * Revision 1.25 2006/03/10 11:01:27 martin + * Added support for PCI511. + * Revision 1.24 2005/11/03 15:50:45Z martin + * Added support for GPS170PCI. + * Revision 1.23 2005/09/16 08:21:08Z martin + * Also flag PCI cards which have base_addr set to 0 as uninitialized. + * Revision 1.22 2005/06/02 10:32:07Z martin + * Changed more types to C99 fixed size types. + * New function pcps_generic_io(). + * Revision 1.21 2004/12/13 14:19:38Z martin + * Support configuration of on-board frequency synthesizer. + * Revision 1.20 2004/11/09 13:02:48Z martin + * Redefined fixed width data types using standard C99 types. + * Fixed warnings about lvalue casts. + * Revision 1.19 2004/10/14 15:01:24 martin + * Added support for TCR167PCI. + * Revision 1.18 2004/09/06 15:16:57Z martin + * Support a GPS_DATA interface where sizes are specified + * by 16 instead of the original 8 bit quantities, thus allowing + * to transfer data blocks which exceed 255 bytes. + * Conditionally skip assertions under Linux. + * Revision 1.17 2004/04/22 14:47:54 martin + * Fixed conversion of firmware rev. number. + * Revision 1.16 2004/04/07 09:45:04Z martin + * Support new feature PCPS_HAS_IRIG_TX for GPS169PCI. + * Revision 1.15 2003/12/22 16:15:21Z martin + * Support PCPS_HR_TIME for TCR510PCI. + * Revision 1.14 2003/07/30 07:28:23Z martin + * Moved prototype for register_pci_devices() outside to top of file. + * Revision 1.13 2003/07/08 15:11:55 martin + * Support PCI PNP interface under Linux. + * New function pcps_rsrc_release(). + * Made some functions public. + * Renamed some public functions to start with pcps_... + * Revision 1.12 2003/06/19 10:08:43 MARTIN + * Renamed some functions to follow common naming conventions. + * Made a function's parameter pointer const. + * Changes due to renamed symbols in pcpsdev.h. + * Check devices for _pcps_has_ucap() support. + * Revision 1.11 2003/05/16 09:28:06 MARTIN + * Moved inclusion of some headers to pcpsdrvr.h. + * Revision 1.10 2003/04/09 16:35:57 martin + * Supports PCI510, GPS169PCI, and TCR510PCI, + * and new PCI_ASIC used by those devices. + * Revision 1.9 2003/03/20 11:42:37 martin + * Fixed syntax for QNX. + * Revision 1.8 2002/08/09 08:25:50 MARTIN + * Support feature PCPS_CAN_CLR_CAP_BUFF. + * Fixed a bug resulting in an unterminated string + * if SERNUM was being read. + * Revision 1.7 2002/02/26 09:31:57 MARTIN + * New function pcps_read_sernum(). + * Revision 1.6 2002/02/19 09:46:26 MARTIN + * Use new header mbg_tgt.h to check the target environment. + * Removed function pcps_sn_str_from_ident(), use new + * function mbg_gps_ident_decode() from identdec.c now. + * If a PCI clock's interface is not properly configured don't + * enable the device and set the read function to the new + * dummy function pcps_read_null() to prevent driver from + * accessing random ports. + * Revision 1.5 2002/02/01 12:06:12 MARTIN + * Added support for GPS168PCI. + * Removed obsolete code. + * Revision 1.4 2001/11/30 09:52:48 martin + * Added support for event_time which, however, requires + * a custom GPS firmware. + * Revision 1.3 2001/09/18 06:59:18 MARTIN + * Account for new preprocessor symbols in the header file. + * Added some type casts to avoid compiler warnings under Win32. + * Added some debug messages to clock detection functions. + * Revision 1.2 2001/03/16 14:45:33 MARTIN + * New functions and definitions to support PNP drivers. + * Revision 1.1 2001/03/01 16:26:41 MARTIN + * Initial revision for the new library. + * + **************************************************************************/ + +#define _PCPSDRVR + #include <pcpsdrvr.h> +#undef _PCPSDRVR + +#include <identdec.h> +#include <mbgddmsg.h> +#include <plxdefs.h> +#include <pci_asic.h> +#include <amccdefs.h> + +#if defined( MBG_TGT_WIN32_PNP ) + #include <usbdrv.h> + #include <pcpsdefs.h> + #include <ntddk.h> + #include <stdio.h> +#elif defined( MBG_TGT_WIN32 ) + #include <pcps_ioc.h> + #include <stdio.h> +#endif + +#if !defined( MBG_TGT_LINUX ) && !defined( MBG_TGT_BSD ) + #include <assert.h> +#endif + +#if defined( MBG_TGT_BSD ) + #include <sys/rman.h> + #include <sys/libkern.h> +#endif + +#if _PCPS_USE_MCA + #include <mca.h> +#endif + +#if _PCPS_USE_PCI + #include <pci.h> +#endif + +#if _PCPS_USE_USB + #define MBGUSB_MIN_ENDPOINTS_REQUIRED 3 +#endif + + +// time required for PTP270PEX to be ready after booting +#define MAX_BOOT_TIME_PTP270PEX 27 // [s] + + +#if !defined( DEBUG_IO ) + #define DEBUG_IO ( defined( DEBUG ) && ( DEBUG >= DEBUG_LVL_IO ) ) +#endif + +#if !defined( DEBUG_PORTS ) + #define DEBUG_PORTS ( defined( DEBUG ) && ( DEBUG >= DEBUG_LVL_PORTS ) ) +#endif + +#if !defined( DEBUG_SERNUM ) + #define DEBUG_SERNUM ( defined( DEBUG ) && ( DEBUG >= DEBUG_LVL_SERNUM ) ) +#endif + +extern const char *pcps_driver_name; + + +// In some environments special far functions are are neither +// required nor supported, so redefine calls to those functions +// to appropriate standard function calls. +#if defined( MBG_TGT_NETWARE ) || defined( MBG_TGT_WIN32 ) || \ + defined( MBG_TGT_LINUX ) || defined( MBG_TGT_BSD ) || \ + defined( MBG_TGT_QNX ) + #define _fmemcpy( _d, _s, _n ) memcpy( _d, _s, _n ) + #define _fstrlen( _s ) strlen( _s ) + #define _fstrncmp( _s1, _s2, _n ) strncmp( (_s1), (_s2), (_n) ) +#elif defined( MBG_TGT_OS2 ) + #define _fstrncmp( _s1, _s2, _n ) _fmemcmp( (_s1), (_s2), (_n) ) +#endif + +#if defined( MBG_TGT_OS2 ) + // Watcom C Compiler options for the OS/2 device driver result in + // warnings if automatic stack addresses are passed to functions. + #define static_wc static + #define FMT_03X "%X" + #define FMT_08X "%X" +#else + #define static_wc + #define FMT_03X "%03X" + #define FMT_08X "%08lX" +#endif + +#if defined( MBG_TGT_LINUX ) + typedef unsigned int PCI_DWORD; +#else + typedef uint32_t PCI_DWORD; +#endif + + +#if defined( MBG_TGT_LINUX ) + + #define _pcps_irq_flags \ + unsigned long irq_flags; + + #define _pcps_disb_local_irq_save() \ + local_irq_save( irq_flags ) + + #define _pcps_local_irq_restore() \ + local_irq_restore( irq_flags ) + +#elif defined( MBG_TGT_WIN32 ) + + #define _pcps_irq_flags \ + KIRQL old_irq_lvl; + + #define _pcps_disb_local_irq_save() \ + KeRaiseIrql( HIGH_LEVEL, &old_irq_lvl ) + + #define _pcps_local_irq_restore() \ + KeLowerIrql( old_irq_lvl ) + +#else + + // Nothing to define here. + +#endif + +#if !defined( _pcps_irq_flags ) && \ + !defined( _pcps_disb_local_irq_save ) && \ + !defined( _pcps_local_irq_restore) + #define _pcps_irq_flags + #define _pcps_disb_local_irq_save(); + #define _pcps_local_irq_restore(); +#endif + + +#if defined( MBG_TGT_LINUX ) && defined( time_after ) + #define _pcps_time_after( _curr, _tmo ) \ + time_after( (unsigned long) _curr, (unsigned long) _tmo ) +#else + #define _pcps_time_after( _curr, _tmo ) ( _curr >= _tmo ) +#endif + + + +static /*HDR*/ +long mbg_delta_sys_time_ms( const MBG_SYS_TIME *t2, const MBG_SYS_TIME *t1 ) +{ + #if defined( MBG_TGT_LINUX ) || defined( MBG_TGT_BSD ) + long dt = ( t2->tv_sec - t1->tv_sec ) * 1000; + dt += ( t2->tv_usec - t1->tv_usec ) / 1000; + return dt; + #elif defined( MBG_TGT_WIN32 ) + return (long) ( ( t2->QuadPart - t1->QuadPart ) / HNS_PER_MS ); + #else + #error mbg_delta_sys_time_ms not implemented for this target + #endif + +} // mbg_delta_sys_time_ms + + + +static /*HDR*/ +void report_uptime( const MBG_SYS_UPTIME *p_uptime ) +{ + #if defined( MBG_TGT_LINUX ) + printk( KERN_INFO "%s: uptime %Lu jiffies -> %Lu s, required %u s\n", + pcps_driver_name, (uint64_t) get_jiffies_64() - INITIAL_JIFFIES, + (uint64_t) *p_uptime, MAX_BOOT_TIME_PTP270PEX ); + #elif defined( MBG_TGT_BSD ) + printf( "%s: uptime %lli s, required %u s\n", + pcps_driver_name, (long long) *p_uptime, MAX_BOOT_TIME_PTP270PEX ); + #elif defined( MBG_TGT_WIN32 ) + WCHAR wcs_msg[120]; + + swprintf( wcs_msg, L"uptime: %I64u s, required %u s", + (uint64_t) *p_uptime, MAX_BOOT_TIME_PTP270PEX ); + _evt_msg( GlbDriverObject, wcs_msg ); + #endif + +} // report_uptime + + + +static /*HDR*/ +int pcps_check_pex_irq_unsafe( PCPS_DDEV *pddev, uint16_t req_fw_ver, + uint8_t req_asic_ver_major, uint8_t req_asic_ver_minor ) +{ + int rc = !_pcps_pex_irq_is_safe( _pcps_ddev_fw_rev_num( pddev ), req_fw_ver, + _pcps_ddev_asic_version( pddev ), + req_asic_ver_major, req_asic_ver_minor ); + + if ( rc ) + { + pddev->irq_stat_info |= PCPS_IRQ_STAT_UNSAFE; + + // Prevent the driver from writing IRQ ACK to the card even if IRQs + // should be unintentionally enabled. + pddev->irq_ack_port = 0; + pddev->irq_ack_mask = 0; + } + + return rc; + +} // pcps_check_pex_irq_unsafe + + + +#if MBG_TGT_SUPP_MEM_ACC + +static __mbg_inline /*HDR*/ +int map_sys_virtual_address( PCPS_DDEV *pddev ) +{ + pddev->mm_tstamp_addr = NULL; // unless configured below + + #if defined ( MBG_TGT_WIN32 ) + { + PHYSICAL_ADDRESS pAD; + + pAD.QuadPart = pddev->rsrc_info.mem[0].start; + pddev->mm_addr = MmMapIoSpace( pAD, sizeof( *pddev->mm_addr ), MmNonCached ); + } + #elif defined ( MBG_TGT_LINUX ) + + pddev->mm_addr = ioremap( ( (ulong) pddev->rsrc_info.mem[0].start ), sizeof( *pddev->mm_addr ) ); + + #elif defined ( MBG_TGT_BSD ) + + pddev->mm_addr = rman_get_virtual( pddev->rsrc_info.mem[0].bsd.res ); + + #else // DOS, ... + + pddev->mm_addr = (PCPS_MM_LAYOUT FAR *) pddev->rsrc_info.mem[0].start; + + #endif // target specific code + + if ( pddev->mm_addr == NULL ) + return -1; + + if ( _pcps_ddev_is_pci_mbgpex( pddev ) ) + pddev->mm_tstamp_addr = &pddev->mm_addr->mbgpex.tstamp; + else + if ( _pcps_ddev_is_pci_pex8311( pddev ) ) + pddev->mm_tstamp_addr = &pddev->mm_addr->pex8311.tstamp; + + _mbgddmsg_3( MBG_DBG_INIT_DEV, "MM addr: base: 0x%p, tstamp: 0x%p, offs: 0x%02lX", + pddev->mm_addr, pddev->mm_tstamp_addr, + (ulong) pddev->mm_tstamp_addr - (ulong) pddev->mm_addr ); + + return 0; + +} // map_sys_virtual_address + + + +static __mbg_inline /*HDR*/ +void unmap_sys_virtual_address( PCPS_DDEV *pddev ) +{ + if ( pddev->mm_addr ) + { + #if defined ( MBG_TGT_WIN32 ) + MmUnmapIoSpace( pddev->mm_addr, sizeof( *pddev->mm_addr ) ); + #elif defined ( MBG_TGT_LINUX ) + iounmap( pddev->mm_addr ); + #else // DOS, ... + // nothing to do + #endif + + pddev->mm_addr = NULL; + pddev->mm_tstamp_addr = NULL; + } + +} // unmap_sys_virtual_address + +#endif // MBG_TGT_SUPP_MEM_ACC + + + +#if defined( __GNUC__ ) +// Avoid "no previous prototype" with some gcc versions. +__mbg_inline +int pcps_wait_busy( PCPS_DDEV *pddev ) __attribute__((always_inline)); +#endif + +__mbg_inline /*HDR*/ +int pcps_wait_busy( PCPS_DDEV *pddev ) +{ + if ( _pcps_ddev_status_busy( pddev ) ) + { + #if defined( MBG_TGT_BSD ) + struct timeval tv_start; + + getmicrouptime( &tv_start ); + + while ( _pcps_ddev_status_busy( pddev ) ) + { + struct timeval tv_now; + long long delta_ms; + + getmicrouptime( &tv_now ); + delta_ms = ( ( tv_now.tv_sec - tv_start.tv_sec ) * 1000 ) + + ( ( tv_now.tv_usec - tv_start.tv_usec ) / 1000 ); + if ( delta_ms > _pcps_ddev_timeout_clk( pddev ) ) + return MBG_ERR_TIMEOUT; + } + #elif _PCPS_USE_CLOCK_TICK + clock_t timeout_val = clock() + _pcps_ddev_timeout_clk( pddev ); + + while ( _pcps_ddev_status_busy( pddev ) ) + if ( _pcps_time_after( clock(), timeout_val ) ) + return MBG_ERR_TIMEOUT; + #else + long cnt = _pcps_ddev_timeout_clk( pddev ); + + for ( ; _pcps_ddev_status_busy( pddev ); cnt-- ) + if ( cnt == 0 ) + return MBG_ERR_TIMEOUT; + #endif + } + + return 0; + +} // pcps_wait_busy + + + +/*-------------------------------------------------------------- + * Name: pcps_read_null() + * pcps_read_std() + * pcps_read_amcc_s5933() + * pcps_read_amcc_s5920() + * pcps_read_asic() + * pcps_read_usb() + * + * Purpose: These functions are used for low level access + * to Meinberg plug-in radio clocks. The function + * to be used depends on the clock's bus type and + * interface chip. + * + * Input: pcfg pointer to the clock's configuration + * cmd the command code for the board + * count the number of bytes to be read + * + * Output: buffer the bytes that could be read + * + * Ret value: MBG_SUCCESS no error + * MBG_ERR_TIMEOUT board is busy for too long + *-------------------------------------------------------------*/ + +// The dummy read function below is used if a clock is +// not properly initialized, in order to avoid I/O access +// on unspecified ports. + +static /*HDR*/ /* type: PCPS_READ_FNC */ +short pcps_read_null( PCPS_DDEV *pddev, uint8_t cmd, + uint8_t FAR *buffer, uint8_t count ) +{ + + return MBG_ERR_TIMEOUT; + +} // pcps_read_null + + + +// The function below must be used to access a clock with +// standard ISA or micro channel bus. + +static /*HDR*/ /* type: PCPS_READ_FNC */ +short pcps_read_std( PCPS_DDEV *pddev, uint8_t cmd, + uint8_t FAR *buffer, uint8_t count ) +{ + PCPS_IO_ADDR_MAPPED port = _pcps_ddev_io_base_mapped( pddev, 0 ); + int i; + _pcps_irq_flags + + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_INIT_DEV, "pcps_read_std: cmd %02X", cmd ); + #endif + + _pcps_disb_local_irq_save(); + mbg_get_pc_cycles( &pddev->acc_cycles ); + // write the command byte + _mbg_outp8( pddev, port, cmd ); + _pcps_local_irq_restore(); + + // wait until BUSY flag goes low or timeout + if ( pcps_wait_busy( pddev ) < 0 ) + return MBG_ERR_TIMEOUT; + + + // no timeout: read bytes from the board's FIFO + for ( i = 0; i < count; i++ ) + { + *buffer++ = _mbg_inp8( pddev, port ); + + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_DETAIL, "pcps_read_std: %02X", buffer[i] ); + #endif + } + + return MBG_SUCCESS; + +} // pcps_read_std + + + +#if _PCPS_USE_PCI + +// The function below must be used to access a clock with +// PCI bus and AMCC S5933 interface chip. + +static /*HDR*/ /* type: PCPS_READ_FNC */ +short pcps_read_amcc_s5933( PCPS_DDEV *pddev, uint8_t cmd, + uint8_t FAR *buffer, uint8_t count ) +{ + PCPS_IO_ADDR_MAPPED port = _pcps_ddev_io_base_mapped( pddev, 0 ); + int i; + _pcps_irq_flags + + + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_INIT_DEV, "pcps_read_amcc_s5933: cmd %02X", cmd ); + #endif + + // reset inbound mailbox and FIFO status + _mbg_outp8( pddev, port + AMCC_OP_REG_MCSR + 3, 0x0C ); + + // set FIFO + _mbg_outp8( pddev, port + AMCC_OP_REG_INTCSR + 3, 0x3C ); + + _pcps_disb_local_irq_save(); + mbg_get_pc_cycles( &pddev->acc_cycles ); + // write the command byte + _mbg_outp8( pddev, port + AMCC_OP_REG_OMB1, cmd ); + _pcps_local_irq_restore(); + + // wait until BUSY flag goes low or timeout + if ( pcps_wait_busy( pddev ) < 0 ) + return MBG_ERR_TIMEOUT; + + + // no timeout: read bytes from the board's FIFO + for ( i = 0; i < count; i++ ) + { + if ( _mbg_inp16_to_cpu( pddev, port + AMCC_OP_REG_MCSR ) & 0x20 ) + return MBG_ERR_FIFO; + + buffer[i] = _mbg_inp8( pddev, port + AMCC_OP_REG_FIFO + ( i % sizeof( uint32_t) ) ); + + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_DETAIL, "pcps_read_amcc_s5933: %02X", buffer[i] ); + #endif + } + + return MBG_SUCCESS; + +} /* pcps_read_amcc_s5933 */ + +#endif /* _PCPS_USE_PCI */ + + + +#if _PCPS_USE_PCI + +// The function below must be used to access a clock with +// PCI bus and AMCC S5920 interface chip. + +static /*HDR*/ /* type: PCPS_READ_FNC */ +short pcps_read_amcc_s5920( PCPS_DDEV *pddev, uint8_t cmd, + uint8_t FAR *buffer, uint8_t count ) +{ + uint8_t FAR *p = buffer; + PCPS_IO_ADDR_MAPPED data_port = _pcps_ddev_io_base_mapped( pddev, 1 ); + int i; + int dt_quot; + int dt_rem; + _pcps_irq_flags + + + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_INIT_DEV, "pcps_read_amcc_s5920: cmd %02X", cmd ); + #endif + + _pcps_disb_local_irq_save(); + mbg_get_pc_cycles( &pddev->acc_cycles ); + // write the command byte + _mbg_outp8( pddev, _pcps_ddev_io_base_mapped( pddev, 0 ) + AMCC_OP_REG_OMB, cmd ); + _pcps_local_irq_restore(); + + dt_quot = count / 4; + dt_rem = count % 4; + + // wait until BUSY flag goes low or timeout + if ( pcps_wait_busy( pddev ) < 0 ) + return MBG_ERR_TIMEOUT; + + + if ( count ) + { + // do this only if we must read data + + uint32_t ul; + + // first read full 32 bit words + for ( i = 0; i < dt_quot; i++ ) + { + ul = _mbg_inp32_to_cpu( pddev, data_port ); + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_INIT_DEV, "pcps_read_amcc_s5920: %08X", ul ); + #endif + _pcps_put_unaligned( ul, (uint32_t FAR *) p ); + p += sizeof( ul ); + } + + // then read the remaining bytes, if required + if ( dt_rem ) + { + ul = _mbg_inp32_to_cpu( pddev, data_port ); + + for ( i = 0; i < dt_rem; i++ ) + { + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_INIT_DEV, "pcps_read_amcc_s5920: %02X", BYTE_OF( ul, i ) ); + #endif + + *p++ = BYTE_OF( ul, i ); + } + } + } + else + _mbg_inp32( pddev, data_port ); // do a dummy read + + return MBG_SUCCESS; + +} // pcps_read_amcc_s5920 + +#endif /* _PCPS_USE_PCI */ + + + +#if _PCPS_USE_PCI + +// The function below must be used to access a clock with +// PCI bus and Meinberg PCI interface ASIC. + +static /*HDR*/ /* type: PCPS_READ_FNC */ +short pcps_read_asic( PCPS_DDEV *pddev, uint8_t cmd, + uint8_t FAR *buffer, uint8_t count ) +{ + uint8_t FAR *p = buffer; + PCPS_IO_ADDR_MAPPED data_port; + PCI_ASIC_REG ar; + int i; + int dt_quot; + int dt_rem; + _pcps_irq_flags + + + #if DEBUG_IO + _mbgddmsg_3( MBG_DBG_INIT_DEV, "pcps_read_asic: cmd: 0x%02X (0x%08X), cnt: %u", + cmd, _cpu_to_mbg32( cmd ), count ); + #endif + + _pcps_disb_local_irq_save(); + mbg_get_pc_cycles( &pddev->acc_cycles ); + // write the command byte + _mbg_outp32( pddev, _pcps_ddev_io_base_mapped( pddev, 0 ) + + offsetof( PCI_ASIC, pci_data ), cmd ); + _pcps_local_irq_restore(); + + data_port = _pcps_ddev_io_base_mapped( pddev, 0 ) + + offsetof( PCI_ASIC, addon_data ); + dt_quot = count / 4; + dt_rem = count % 4; + + // wait until BUSY flag goes low or timeout + if ( pcps_wait_busy( pddev ) < 0 ) + return MBG_ERR_TIMEOUT; + + + // no timeout: read bytes from the board's FIFO + + // first read full 32 bit words + for ( i = 0; i < dt_quot; i++ ) + { + ar.ul = _mbg_inp32_to_cpu( pddev, data_port ); + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_INIT_DEV, "pcps_read_asic: %08X", ar.ul ); + #endif + _pcps_put_unaligned( ar.ul, (uint32_t FAR *) p ); + p += sizeof( ar.ul ); + data_port += sizeof( ar.ul ); + } + + // then read the remaining bytes, if required + if ( dt_rem ) + { + ar.ul = _mbg_inp32_to_cpu( pddev, data_port ); + + for ( i = 0; i < dt_rem; i++ ) + { + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_INIT_DEV, "pcps_read_asic: %02X", ar.b[i] ); + #endif + + *p++ = ar.b[i]; + } + } + + return MBG_SUCCESS; + +} // pcps_read_asic + +#endif /* _PCPS_USE_PCI */ + + + +#if _PCPS_USE_USB + +// The function below must be used to access a clock +// connected via USB bus. + +static /*HDR*/ /* type: PCPS_READ_FNC */ +short pcps_read_usb( PCPS_DDEV *pddev, uint8_t cmd, + uint8_t FAR *buffer, uint8_t count ) +{ + int actual_count = 0; + short rc; + + mbg_get_pc_cycles( &pddev->acc_cycles ); + + rc = _pcps_usb_write_var( pddev, &cmd ); + + if ( ( rc == MBG_SUCCESS ) && ( count && buffer ) ) + { + #if defined( MBG_TGT_WIN32_PNP ) + int temp_fn1 = frame_number_1; + int temp_fn2 = frame_number_2; + LARGE_INTEGER UsbPreCount = Count1; + LARGE_INTEGER UsbPostCount = Count2; + #endif + + rc = _pcps_usb_read( pddev, buffer, count ); + + #if defined( MBG_TGT_WIN32_PNP ) + if ( cmd == PCPS_GIVE_HR_TIME && rc == PCPS_SUCCESS ) + { + ULONGLONG usb_latency_cycles; + ULONGLONG cycles_diff; + ULONGLONG time_diff; + ULONGLONG frame_length_cycles; + int FrameNumberDiff; + + if ( pddev->usb_20_mode ) + { + // USB 2.0 microframe timing. + // Just add an offset to compensate constant latency. + // This value has been determined experimentally on different hardware platforms + usb_latency_cycles = ( (ULONGLONG) PerfFreq.QuadPart ) / 20000UL; // represents 50 us + } + else + { + // USB 1.1 mode with millisecond timing. + // Compensate latency to millisecond frame boundaries. + + if ( (temp_fn2 - temp_fn1) < 0 ) + FrameNumberDiff = 2; + else + FrameNumberDiff = temp_fn2 - temp_fn1; + + cycles_diff = (ULONGLONG) ( UsbPostCount.QuadPart - UsbPreCount.QuadPart ); + frame_length_cycles = (ULONGLONG) ( (ULONGLONG) PerfFreq.QuadPart ) / 1000UL; + + if ( ( temp_fn1 == 0 ) && ( temp_fn2 == 0 ) ) + { + if ( cycles_diff > frame_length_cycles ) + usb_latency_cycles = cycles_diff - frame_length_cycles; + else + usb_latency_cycles = frame_length_cycles - cycles_diff; + } + else + usb_latency_cycles = cycles_diff - ( ( FrameNumberDiff - 1 ) * frame_length_cycles ); + + #if DBG + swprintf( pddev->wcs_msg, L"FD %d CD %I64u l %I64u fl %I64u", FrameNumberDiff, cycles_diff, usb_latency_cycles, frame_length_cycles ); + _dbg_evt_msg( GlbDriverObject, pddev->wcs_msg ); + #endif + } + + pddev->acc_cycles += usb_latency_cycles; + } + #endif + } + + return rc; + +} // pcps_read_usb + +#endif + + + +/*-------------------------------------------------------------- + * Name: pcps_write() + * + * Purpose: Write data to the radio clock. + * + * Input: pddev pointer to the device information + * cmd the address of buffer holding the + * date/time/status information + * read_fnc function to access the board + * + * Output: -- + * + * Ret value: MBG_SUCCESS no error + * MBG_ERR_TIMEOUT board is busy for too long + * MBG_ERR_NBYTES the number of parameter bytes + * did not match the number of + * data bytes expected + * MBG_ERR_STIME the date, time or status + * has been invalid + *-------------------------------------------------------------*/ + +/*HDR*/ +short pcps_write( PCPS_DDEV *pddev, uint8_t cmd, + const void FAR *buffer, uint8_t count ) +{ + short rc; + +#if _PCPS_USE_USB + if ( _pcps_ddev_is_usb( pddev ) ) + { + int actual_count = 0; // required by macro + int n = sizeof( cmd ) + count; + uint8_t *p = _pcps_kmalloc( n ); + + if ( p == NULL ) + return MBG_ERR_NO_MEM; + + p[0] = cmd; + memcpy( &p[1], buffer, count ); + + rc = _pcps_usb_write( pddev, p, n ); + + if ( rc == MBG_SUCCESS ) + { + rc = _pcps_usb_read( pddev, p, 1 ); + + if ( rc == MBG_SUCCESS ) + rc = (int8_t) p[0]; // return the rc from the board + } + + _pcps_kfree( p ); + } + else +#endif // _PCPS_USE_USB + { + const uint8_t FAR *p = (const uint8_t FAR *) buffer; + int i; + uint8_t bytes_expected; + int8_t write_rc; + + // Write the command and read one byte which will contain + // the number of data bytes that must follow. + rc = _pcps_read_var( pddev, cmd, bytes_expected ); + + #if DEBUG_IO + _mbgddmsg_4( MBG_DBG_DETAIL, "pcps_write: cmd %02X, %u bytes, expects %u, rc: %i", + cmd, count, bytes_expected, rc ); + #endif + + if ( rc < 0 ) + goto done; + + + // Check if the number of data bytes to be written is correct. + if ( bytes_expected != count ) + { + rc = MBG_ERR_NBYTES; + goto done; + } + + + // Write all bytes but the last one without reading anything. + bytes_expected--; + + for ( i = 0; i < bytes_expected; i++ ) + { + #if DEBUG_IO + _mbgddmsg_2( MBG_DBG_DETAIL, "pcps_write: byte %i: 0x%02X", i, *p ); + #endif + + rc = _pcps_write_byte( pddev, *p++ ); + + if ( rc < 0 ) + goto done; + } + + // Write the last byte and read the completion code. + #if DEBUG_IO + _mbgddmsg_2( MBG_DBG_DETAIL, "pcps_write: last byte %i: 0x%02X", i, *p ); + #endif + + rc = _pcps_read_var( pddev, *p++, write_rc ); + + // If an error code has been returned by the I/O function, + // return that code, otherwise return the completion code + // read from the board. + if ( !( rc < 0 ) ) + rc = write_rc; + } + +done: + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_DETAIL, "pcps_write: return %i", rc ); + #endif + + return rc; + +} // pcps_write + + + +/*-------------------------------------------------------------- + * Name: pcps_generic_io() + * + * Purpose: Write data to the radio clock. + * + * Input: pddev pointer to the device information + * cmd the address of buffer holding the + * date/time/status information + * read_fnc function to access the board + * + * Output: -- + * + * Ret value: MBG_SUCCESS no error + * MBG_ERR_TIMEOUT board is busy for too long + * MBG_ERR_NBYTES the number of parameter bytes + * did not match the number of + * data bytes expected + * MBG_ERR_STIME the date, time or status + * has been invalid + *-------------------------------------------------------------*/ + +/*HDR*/ +short pcps_generic_io( PCPS_DDEV *pddev, uint8_t type, + const void FAR *in_buff, uint8_t in_cnt, + void FAR *out_buff, uint8_t out_cnt ) +{ + const uint8_t FAR *p; + int i; + short rc; + uint8_t tmp_byte; + int8_t data_read[PCPS_FIFO_SIZE]; + uint8_t bytes_to_read; + + // Write the command and read one byte which will contain + // the number of data bytes that must follow. + rc = _pcps_read_var( pddev, PCPS_GENERIC_IO, tmp_byte ); + + if ( rc < 0 ) + return rc; + + + // Check if the number of data bytes to be written is correct. + if ( tmp_byte != 3 ) + return MBG_ERR_NBYTES; + + + // Write the 3 bytes which are expected: + rc = _pcps_write_byte( pddev, type ); + + if ( rc != MBG_SUCCESS ) + goto done; + + + rc = _pcps_write_byte( pddev, in_cnt ); + + if ( rc != MBG_SUCCESS ) + goto done; + + + if ( in_cnt == 0 ) + tmp_byte = out_cnt; + else + { + rc = _pcps_write_byte( pddev, out_cnt ); + + if ( rc != MBG_SUCCESS ) + goto done; + + + // Write the input parameters + p = (const uint8_t FAR *) in_buff; + tmp_byte = in_cnt - 1; + + for ( i = 0; i < tmp_byte; i++ ) + { + rc = _pcps_write_byte( pddev, *p++ ); + + if ( rc < 0 ) + goto done; + } + + tmp_byte = *p; + } + + + bytes_to_read = 2 + out_cnt; + + if ( bytes_to_read > sizeof( data_read ) ) + bytes_to_read = sizeof( data_read ); + + + // Write the last byte and read the completion code. + rc = _pcps_read( pddev, tmp_byte, data_read, bytes_to_read ); + + if ( out_cnt ) //##++ should do some more plausibility checks + if ( rc == MBG_SUCCESS ) + { + _fmemcpy( out_buff, &data_read[2], out_cnt ); + } + +done: + // If an error code has been returned by the I/O function, + // return that code, otherwise return the completion code + // read from the board. + return ( rc < 0 ) ? rc : data_read[0]; + +} // pcps_generic_io + + + +/*-------------------------------------------------------------- + * Name: pcps_read_gps_block() + * + * Purpose: Get a block of data from GPS clock device. + * This is a local function which is called + * by pcps_read_gps(). + * + * Input: pddev pointer to the device information + * data_type the code assigned to the data type + * buffer_size the size of the buffer + * block_num the number of the block to read + * block_size the size of the block to read + * + * Output: buffer filled with data + * + * Ret value: MBG_SUCCESS + * MBG_ERR_TIMEOUT + * MBG_ERR_NBYTES + *-------------------------------------------------------------*/ + +static /*HDR*/ +short pcps_read_gps_block( PCPS_DDEV *pddev, + uint8_t data_type, + void FAR *buffer, + uint16_t buffer_size, + uint8_t block_num, + uint8_t block_size ) +{ + short rc; + uint16_t n_bytes; + uint8_t size_n_bytes; + uint8_t uc; + + + /* Determine which interface buffer size is supported + and use the appropriate size specification */ + if ( _pcps_ddev_has_gps_data_16( pddev ) ) + size_n_bytes = 2; + else + { + if ( buffer_size > 255 ) + return MBG_ERR_NBYTES; // Error ... + + size_n_bytes = 1; + } + + #if DEBUG_IO + _mbgddmsg_4( MBG_DBG_DETAIL, + "pcps_read_gps_block: cmd 0x%02X, block %u (%u), size_n_bytes = %u", + data_type, block_num, block_size, size_n_bytes ); + #endif + + // Write the command, expect to read one byte. + rc = _pcps_read_var( pddev, PCPS_READ_GPS_DATA, uc ); + + if ( rc != MBG_SUCCESS ) // Error ... + return rc; + + if ( uc != 1 ) // The board doesn't expect exactly one more byte + { + if ( uc == 0 ) + { + // The board can't respond now. This may occur if a + // GPS receiver is still initializing after power-up. + #if DEBUG_IO + _mbgddmsg_0( MBG_DBG_DETAIL, "pcps_read_gps_block: board not yet initialized" ); + #endif + + return MBG_ERR_NOT_READY; + } + else + { + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_DETAIL, "pcps_read_gps_block: board expects %u bytes rather than 1", uc ); + #endif + + return MBG_ERR_NBYTES; // Error ... + } + } + + // Write the code corresponding to the type of data we + // want to read, expect to read the expected size. + n_bytes = 0; + rc = _pcps_read( pddev, data_type, &n_bytes, size_n_bytes ); + + if ( rc != MBG_SUCCESS ) // Error ... + return rc; + + #if defined( MBG_ARCH_BIG_ENDIAN ) + // Swap n_bytes regardless of whether we have actuall read 1 or 2 bytes. + // If we have read only 1 byte then the other one is 0. + n_bytes = _mbg16_to_cpu( n_bytes ); + #endif + + #if DEBUG_IO + _mbgddmsg_2( MBG_DBG_DETAIL, "pcps_read_gps_block: board expects data size %u, buffer size %u", + n_bytes, buffer_size ); + #endif + + if ( n_bytes == 0 ) + return MBG_ERR_INV_TYPE; + + if ( n_bytes != buffer_size ) // Size of data structure does not match. + return MBG_ERR_NBYTES; + + + // Write the block number and read n bytes of data. + rc = _pcps_read( pddev, block_num, buffer, block_size ); + + return rc; + +} // pcps_read_gps_block + + + +/*-------------------------------------------------------------- + * Name: pcps_read_gps() + * + * Purpose: Get a data structure from a GPS clock. + * + * Input: pddev pointer to the device information + * data_type the code assigned to the data type + * buffer_size the size of the buffer + * + * Output: buffer filled with data + * + * Ret value: MBG_SUCCESS + * MBG_ERR_TIMEOUT + * MBG_ERR_NBYTES + *-------------------------------------------------------------*/ + +/*HDR*/ +short pcps_read_gps( PCPS_DDEV *pddev, + uint8_t data_type, + void FAR *buffer, + uint16_t buffer_size ) +{ + uint8_t FAR *p = (uint8_t FAR *) buffer; + short rc = 0; + int dt_quot; + int dt_rem; + int block_num; + + + #if DEBUG_IO + _mbgddmsg_3( MBG_DBG_DETAIL, "Going to read GPS data, type: %02X, addr: %p, size: %u", + data_type, buffer, buffer_size ); + #endif + + // Split buffer size to a number of blocks of PCPS_FIFO_SIZE + // and a number of remaining bytes (less than PCPS_FIFO_SIZE). + dt_quot = buffer_size / PCPS_FIFO_SIZE; + dt_rem = buffer_size % PCPS_FIFO_SIZE; + + // Read dt_quot full blocks of data. + for ( block_num = 0; block_num < dt_quot; block_num++ ) + { + rc = pcps_read_gps_block( pddev, data_type, p, buffer_size, + (uint8_t) block_num, PCPS_FIFO_SIZE ); + + if ( rc != MBG_SUCCESS ) // Error ... + goto done; + + // Move the destination pointer to the next free byte. + p += PCPS_FIFO_SIZE; + } + + + // Read dt_rem additional bytes of data. + if ( dt_rem ) + rc = pcps_read_gps_block( pddev, data_type, p, buffer_size, + (uint8_t) block_num, (uint8_t) dt_rem ); + +done: + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_DETAIL, "Done reading GPS data, rc: %i", rc ); + #endif + + return rc; + +} // pcps_read_gps + + + +/*-------------------------------------------------------------- + * Name: pcps_write_gps() + * + * Purpose: Write a data structure to a GPS clock. + * + * Input: pddev pointer to the device information + * data_type the code assigned to the data type + * buffer the data to write + * buffer_size the size of the buffer + * read_fnc function to access the board + * + * Output: -- + * + * Ret value: MBG_SUCCESS + * MBG_ERR_TIMEOUT + * MBG_ERR_NBYTES + *-------------------------------------------------------------*/ + +/*HDR*/ +short pcps_write_gps( PCPS_DDEV *pddev, + uint8_t data_type, + const void FAR *buffer, + uint16_t buffer_size ) +{ + const uint8_t FAR *p = (const uint8_t FAR *) buffer; + short rc; + short i; + uint16_t n_bytes; + uint8_t size_n_bytes; + uint8_t uc; + + + /* Determine which interface buffer size is supported + and use the appropriate size specification */ + if ( _pcps_ddev_has_gps_data_16( pddev ) ) + size_n_bytes = 2; + else + { + if ( buffer_size > 255 ) + return MBG_ERR_NBYTES; // Error ... + + size_n_bytes = 1; + } + + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_DETAIL, "pcps_write_gps: size_n_bytes = %u", size_n_bytes ); + #endif + + // Write the command, expect to read one byte. + rc = _pcps_read_var( pddev, PCPS_WRITE_GPS_DATA, uc ); + + if ( rc != MBG_SUCCESS ) // Error ... + return rc; + + if ( uc != 1 ) // The board doesn't expect exactly one more byte + { + if ( uc == 0 ) + { + // The board can't respond now. This may occur if a + // GPS receiver is still initializing after power-up. + #if DEBUG_IO + _mbgddmsg_0( MBG_DBG_DETAIL, "pcps_write_gps: board not yet initialized" ); + #endif + + return MBG_ERR_NOT_READY; + } + else + { + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_DETAIL, "pcps_write_gps: board expects %u bytes rather than 1", uc ); + #endif + + return MBG_ERR_NBYTES; // Error ... + } + } + + // Write the code corresponding to the type of data we + // want to write, expect to read the expected size. + n_bytes = 0; + rc = _pcps_read( pddev, data_type, &n_bytes, size_n_bytes ); + + if ( rc != MBG_SUCCESS ) // Error ... + return rc; + + #if defined( MBG_ARCH_BIG_ENDIAN ) + // Swap n_bytes regardless of whether we have actuall read 1 or 2 bytes. + // If we have read only 1 byte then the other one is 0. + n_bytes = _mbg16_to_cpu( n_bytes ); + #endif + + #if DEBUG_IO + _mbgddmsg_2( MBG_DBG_DETAIL, "pcps_write_gps: board expects data size %u, buffer size %u", + n_bytes, buffer_size ); + #endif + + if ( n_bytes != buffer_size ) // The board doesn't expect the number + return MBG_ERR_NBYTES; // of bytes we were going to write. + + + // Write all bytes but the last one without reading. + buffer_size--; + + for ( i = 0; i < buffer_size; i++ ) + { + rc = _pcps_write_byte( pddev, *p++ ); + + if ( rc != MBG_SUCCESS ) // Error ... + return rc; + } + + + // Write the last byte and read the completion code. + rc = _pcps_read_var( pddev, *p, n_bytes ); + + // If an error code has been returned by read_fnc, return that + // code, otherwise return the completion code read from the board. + return rc ? rc : n_bytes; + +} // pcps_write_gps + + + +/*-------------------------------------------------------------- + * Name: pcps_get_fw_id() + * + * Purpose: This function tries to read the firmware ID + * from the board. It should be used to check + * if the board is properly installed and can + * be accessed without problems. + * + * Input: pddev pointer to the device information + * + * Output: fw_id buffer filled with ASCIIZ string + * + * Ret value: MBG_SUCCESS no error + * MBG_ERR_TIMEOUT the board is busy for too long + *-------------------------------------------------------------*/ + +/*HDR*/ +short pcps_get_fw_id( PCPS_DDEV *pddev, PCPS_ID_STR FAR fw_id ) +{ + short rc; + + + // read first part of the firmware ID + rc = _pcps_read( pddev, PCPS_GIVE_FW_ID_1, &fw_id[0], PCPS_FIFO_SIZE ); + + if ( rc != MBG_SUCCESS ) + return rc; // may be timeout + + + // read second part of the firmware ID + rc = _pcps_read( pddev, PCPS_GIVE_FW_ID_2, &fw_id[PCPS_FIFO_SIZE], PCPS_FIFO_SIZE ); + + if ( rc != MBG_SUCCESS ) + return rc; // may be timeout + + + // terminate the string with 0 + + fw_id[PCPS_ID_SIZE - 1] = 0; + + + return MBG_SUCCESS; + +} // pcps_get_fw_id + + + +/*-------------------------------------------------------------- + * Name: pcps_check_id() + * + * Purpose: Check an ASCIIZ string for a valid signature. + * + * Input: pddev pointer to the device information + * ref the reference signature + * + * Output: -- + * + * Ret value: MBG_SUCCESS no error + * MBG_ERR_FW_ID the firmware ID is not valid + *-------------------------------------------------------------*/ + +/*HDR*/ +short pcps_check_id( PCPS_DDEV *pddev, const char FAR *ref ) +{ + // check if the first characters of the string match the reference + + if ( ref ) + if ( _fstrncmp( _pcps_ddev_fw_id( pddev ), ref, _fstrlen( ref ) ) ) + return MBG_ERR_FW_ID; + + return MBG_SUCCESS; + +} // pcps_check_id + + + +/*-------------------------------------------------------------- + * Name: pcps_get_rev_num() + * + * Purpose: Get a version number from an ID string. + * + * Input: idstr the ID string + * + * Output: -- + * + * Ret value: on success: the version number in hex + * (e.g. 0x0270 for version 2.7) + * on error: 0 + *-------------------------------------------------------------*/ + +/*HDR*/ +short pcps_get_rev_num( char FAR *idstr ) +{ + int i; + int len = _fstrlen( idstr ) - 2; + char c; + + uchar rev_num_hi; + uchar rev_num_lo; + + for ( i = 0; i < len; i++ ) + { + if ( idstr[i + 1] == '.' ) + { + rev_num_hi = idstr[i] & 0x0F; + rev_num_lo = ( idstr[i + 2] & 0x0F ) << 4; + + c = idstr[i + 3]; + + if ( c >= '0' && c <= '9' ) + rev_num_lo |= c & 0x0F; + + return ( rev_num_hi << 8 ) | rev_num_lo; + } + } + + return 0; + +} // pcps_get_rev_num + + + +/*-------------------------------------------------------------- + * Name: pcps_read_sernum() + * + * Purpose: This function tries to read the clock's S/N + * from the board, if supported by the clock. + * + * Input: pddev pointer to the device information + * + * Output: pddev sernum field filled with ASCIIZ + * + * Ret value: MBG_SUCCESS no error + * other error + *-------------------------------------------------------------*/ + +/*HDR*/ +int pcps_read_sernum( PCPS_DDEV *pddev ) +{ + char *cp; + int i; + int rc = MBG_SUCCESS; + + + memset( pddev->dev.cfg.sernum, 0, sizeof( pddev->dev.cfg.sernum ) ); + + // There are different ways to read the clock's S/N. + // Check which way is supported by the clock, and + // read the S/N, + + // Read directly. This is supported by newer DCF77 clocks. + if ( _pcps_ddev_has_sernum( pddev ) ) + { + #if DEBUG_SERNUM + _mbgddmsg_0( MBG_DBG_DETAIL, "getting S/N via PCPS_GIVE_SERNUM cmd" ); + #endif + + rc = _pcps_read( pddev, PCPS_GIVE_SERNUM, pddev->dev.cfg.sernum, PCPS_FIFO_SIZE ); + + if ( rc != MBG_SUCCESS ) + { + _mbgddmsg_2( MBG_DBG_INIT_DEV, "PCPS read SERNUM %X: rc = %i", + _pcps_ddev_dev_id( pddev ), rc ); + goto fail; + } + + goto check; + } + + + // The S/N is part of the RECEIVER_INFO structure, + // so use that one, if supported. + if ( _pcps_ddev_has_receiver_info( pddev ) ) + { + static_wc RECEIVER_INFO ri; + + #if DEBUG_SERNUM + _mbgddmsg_0( MBG_DBG_DETAIL, "getting S/N from receiver info" ); + #endif + + rc = _pcps_read_gps_var( pddev, PC_GPS_RECEIVER_INFO, ri ); + + if ( rc != MBG_SUCCESS ) + { + _mbgddmsg_2( MBG_DBG_INIT_DEV, "PCPS read GPS receiver info %X: rc = %i", + _pcps_ddev_dev_id( pddev ), rc ); + goto fail; + } + + _mbg_swab_receiver_info( &ri ); + + #if DEBUG_IO + _mbgddmsg_1( MBG_DBG_DETAIL, "ri.sw_rev.code: %04X", ri.sw_rev.code ); + _mbgddmsg_1( MBG_DBG_DETAIL, "ri.model_code: %04X", ri.model_code ); + _mbgddmsg_3( MBG_DBG_DETAIL, "ri.model_name: %-*.*s", (int) sizeof( ri.model_name ), (int) sizeof( ri.model_name ), ri.model_name ); + _mbgddmsg_1( MBG_DBG_DETAIL, "ri.features: %08X", ri.features ); + #endif + + strncpy( pddev->dev.cfg.sernum, ri.sernum, + sizeof( pddev->dev.cfg.sernum ) ); + goto check; + } + + + // Older GPS clocks store the S/N in an IDENT structure + // which must be decoded to get the S/N. + if ( _pcps_ddev_has_ident( pddev ) ) + { + static_wc IDENT ident = { { 0 } }; + + #if DEBUG_SERNUM + _mbgddmsg_0( MBG_DBG_DETAIL, "getting S/N from ident" ); + #endif + + rc = _pcps_read_gps_var( pddev, PC_GPS_IDENT, ident ); + + #if !defined( MBG_TGT_LINUX ) && !defined( MBG_TGT_BSD ) + assert( sizeof( ident ) < sizeof( pddev->dev.cfg.sernum ) ); + #endif + + if ( rc != MBG_SUCCESS ) + { + _mbgddmsg_2( MBG_DBG_INIT_DEV, "PCPS read GPS ident %X: rc = %i", + _pcps_ddev_dev_id( pddev ), rc ); + goto fail; + } + + // The ident union must never be swapped due to endianess since we are + // using it only as an array of characters. + + #if DEBUG_SERNUM + for ( i = 0; i < sizeof( ident ); i += 4 ) + _mbgddmsg_5( MBG_DBG_DETAIL, "ident[%02i]: %02X %02X %02X %02X", + i, ident.c[i], ident.c[i+1], ident.c[i+2], ident.c[i+3] ); + #endif + + mbg_gps_ident_decode( _pcps_ddev_sernum( pddev ), &ident ); + goto check; + } + + // The clock doesn't support a S/N. + // Assume the rc is still set to MBG_SUCCESS. + strcpy( _pcps_ddev_sernum( pddev ), "N/A" ); + goto done; + + +check: + // Remove unprintable characters. + for ( i = 0, cp = pddev->dev.cfg.sernum; + i < sizeof( pddev->dev.cfg.sernum ); i++, cp++ ) + if ( ( *cp < 0x20 ) || ( *cp >= 0x7F ) ) + { + #if DEBUG_SERNUM + *cp = '#'; + #else + *cp = 0; + #endif + } + +#if DEBUG_SERNUM + +fail: + goto done; + +#else + // Remove trailing spaces which may unfortunately + // be returned by some devices. + for ( i = strlen( pddev->dev.cfg.sernum ); ; ) + { + if ( i == 0 ) + break; + + --i; + cp = &pddev->dev.cfg.sernum[i]; + if ( *cp > ' ' ) // not a trailing space, done + break; + + *cp = 0; + } + + if ( strlen( pddev->dev.cfg.sernum ) ) + goto done; + + +fail: + // No valid serial number has been found, though the device + // should have one. In order to distinguish from devices which + // don't even support a serial number we return a number of '?' + // rather than "N/A". + memset( pddev->dev.cfg.sernum, '?', 8 ); + pddev->dev.cfg.sernum[8] = 0; +#endif + +done: + // Make sure the S/N is terminated by 0. + pddev->dev.cfg.sernum[sizeof( pddev->dev.cfg.sernum ) - 1] = 0; + + return rc; + +} // pcps_read_sernum + + + +#if _PCPS_USE_RSRCMGR + +/*HDR*/ +int pcps_rsrc_claim( PCPS_DDEV *pddev ) +{ + ushort decode_width; + int i; + + if ( _pcps_ddev_is_pci( pddev ) ) + decode_width = PCPS_DECODE_WIDTH_PCI; + else + if ( _pcps_ddev_is_mca( pddev ) ) + decode_width = PCPS_DECODE_WIDTH_MCA; + else + decode_width = PCPS_DECODE_WIDTH_ISA; + + for ( i = 0; i < pddev->rsrc_info.num_rsrc_io; i++ ) + { + PCPS_IO_RSRC *p = &_pcps_ddev_io_rsrc( pddev, i ); + ushort rc; + + rc = _rsrc_alloc_ports( &pddev->rsrc, p->base_raw, p->num, decode_width ); //##++ + + // If the resource manager was unable to alloc the resources + // then the selected range of ports is probably in use + // by another hardware device and/or driver + if ( rc ) + { + _pcps_ddev_set_err_flags( pddev, PCPS_EF_IO_RSRC ); + return MBG_ERR_CLAIM_RSRC; + } + } + + return 0; + +} // pcps_rsrc_claim + + + +/*HDR*/ +void pcps_rsrc_release( PCPS_DDEV *pddev ) +{ + int i; + + for ( i = 0; i < N_PCPS_PORT_RSRC; i++ ) + { + PCPS_PORT_RSRC *p = &_pcps_ddev_port_rsrc( pddev, i ); + + if ( _pcps_port_rsrc_unused( p ) ) + continue; + + // clean up if clock not found + _rsrc_dealloc_ports( &pddev->rsrc.hResource[i], p->base, p->num ); + } + +} // pcps_rsrc_release + + + +#if defined( MBG_TGT_OS2 ) + +static /*HDR*/ +void pcps_rsrc_register_device( PCPS_DDEV *pddev ) +{ + #define RSRC_BASE_NAME "RADIOCLK_# Meinberg Radio Clock " + static const char rsrc_type_dcf77[] = RSRC_BASE_NAME "(DCF77)"; + static const char rsrc_type_gps[] = RSRC_BASE_NAME "(GPS)"; + static const char rsrc_type_irig[] = RSRC_BASE_NAME "(IRIG)"; + + uchar bus_type; + ushort rc; + const char *cp; + + #if _PCPS_USE_USB + #error USB not supported for this target environment! + #endif + + if ( _pcps_ddev_is_pci( pddev ) ) + bus_type = RSRC_BUS_PCI; + else + if ( _pcps_ddev_is_mca( pddev ) ) + bus_type = RSRC_BUS_MCA; + else + bus_type = RSRC_BUS_ISA; + + if ( _pcps_ddev_is_irig_rx( pddev ) ) + cp = rsrc_type_irig; + else + if ( _pcps_ddev_is_gps( pddev ) ) + cp = rsrc_type_gps; + else + cp = rsrc_type_dcf77; + + rc = rsrc_register_device( &pddev->hDev, &pddev->rsrc, n_ddevs - 1, cp, bus_type ); + +} // pcps_rsrc_register_device + +#endif // defined( MBG_TGT_OS2 ) + +#endif // _PCPS_USE_RSRCMGR + + + +#if _PCPS_USE_MCA + +/*-------------------------------------------------------------- + * PS31 only: + * + * The scheme below shows the way the bits of the POS 103 + * configuration byte are mapped to the PS31's programmable + * address decoder: + * + * MSB LSB + * | || | + * 0bbbbbb1000bxxxx <-- 16 bit port base address (binary) + * |||||| | + * \\\\\\ | b: configurable bit + * \\\\\\ | x: don't care bit + * ||||||| + * 1bbbbbbb <-- 8 bit configuration byte (POS 103) + * | + * | + * decoder enable bit, always 1 if adapter enabled + * + *-------------------------------------------------------------*/ + +/*-------------------------------------------------------------- + * Convert the code read from PS/2 POS to the port base address. + *-------------------------------------------------------------*/ + +/*HDR*/ +ushort pcps_port_from_pos( ushort pos ) +{ + ushort us = ( ( pos & 0x007E ) << 8 ) | 0x0100; + + if ( pos & 0x0001 ) + us |= 0x0010; + + return us; + +} // pcps_port_from_pos + + + +/*-------------------------------------------------------------- + * Convert the port base address to a PS/2 POS code. + *-------------------------------------------------------------*/ + +/*HDR*/ +uchar pcps_pos_from_port( ushort port ) +{ + uchar uc; + + + uc = *( ( (uchar *) (&port) ) + 1 ) & 0x7E; + + if ( port & 0x0010 ) + uc |= 1; + + return uc; + +} // pcps_pos_from_port + + + +static /*HDR*/ +void pcps_detect_mca_clocks( PCPS_DDEV_ALLOC_FNC alloc_fnc, void *alloc_arg ) +{ + short rc; + ushort type_idx; + + rc = mca_fnc_init(); + + if ( rc != MCA_SUCCESS ) + return; + + + // MCA is installed, now try to find a MCA clock with + // known ID. + for ( type_idx = 0; type_idx < N_PCPS_DEV_TYPE; type_idx++ ) + { + static_wc MCA_POS_DATA pos_data; + PCPS_DDEV *pddev; + PCPS_DEV_TYPE *p = &pcps_dev_type[type_idx]; + + static_wc uchar slot_num; // the slot in which the board is installed + + + if ( !( p->bus_flags & PCPS_BUS_MCA ) ) + continue; + + + rc = mca_find_adapter( p->dev_id, &slot_num, &pos_data ); + + if ( rc != MCA_SUCCESS ) + continue; + + + // New device found, try to add to list. + pddev = alloc_fnc( alloc_arg ); + + if ( pddev ) // Setup only if successful. + { + pddev->dev.type = *p; + pcps_add_rsrc_io( pddev, pcps_port_from_pos( pos_data.pos_103 ), + PCPS_NUM_PORTS_MCA ); + + //##++ Should try to read the interrupt line assigned to the clock. + // The standard functions, however, don't use any interrupt. + + pcps_start_device( pddev, 0, slot_num ); + } + } + + mca_fnc_deinit(); + +} // pcps_detect_mca_clocks + +#endif /* _PCPS_USE_MCA */ + + + +// The function below takes a bus flag and device ID to search +// the table of known devices for a device which matches the +// given criteria. + +/*HDR*/ +PCPS_DEV_TYPE *pcps_get_dev_type( int bus_mask, ushort dev_id ) +{ + int i; + + for ( i = 0; i < N_PCPS_DEV_TYPE; i++ ) + { + PCPS_DEV_TYPE *p = &pcps_dev_type[i]; + + if ( !( p->bus_flags & bus_mask ) ) + continue; + + if ( p->dev_id == dev_id ) + return p; + } + + return NULL; + +} // pcps_get_dev_type + + + +/*HDR*/ +PCPS_DDEV *pcps_alloc_ddev( void ) +{ + PCPS_DDEV *pddev; + + #if !_PCPS_STATIC_DEV_LIST + pddev = _pcps_kmalloc( sizeof( *pddev ) ); + #else + if ( n_ddevs >= PCPS_MAX_DDEVS ) + { + _mbgddmsg_0( MBG_DBG_INIT_DEV, + "Unable to add new device: max count reached" ); + + return NULL; + } + + pddev = &pcps_ddev[n_ddevs]; + n_ddevs++; + #endif + + if ( pddev ) + memset( pddev, 0, sizeof( *pddev ) ); + + return pddev; + +} // pcps_alloc_ddev + + + +/*HDR*/ +void pcps_free_ddev( PCPS_DDEV *pddev ) +{ + #if !_PCPS_STATIC_DEV_LIST + if ( pddev ) + _pcps_kfree( pddev ); + #else + memset( pddev, 0, sizeof( *pddev ) ); + + if ( n_ddevs ) + n_ddevs--; + #endif + +} // pcps_free_ddev + + + +static /*HDR*/ +void rsrc_port_to_cfg_port( PCPS_PORT_RSRC *p_port_rsrc, const PCPS_IO_RSRC *p_io_rsrc ) +{ + p_port_rsrc->base = (PCPS_PORT_ADDR) p_io_rsrc->base_raw; + p_port_rsrc->num = p_io_rsrc->num; + +} // rsrc_port_to_cfg_port + + + +/*HDR*/ +int pcps_add_rsrc_io( PCPS_DDEV *pddev, ulong base, ulong num ) +{ + PCPS_RSRC_INFO *prsrci = &pddev->rsrc_info; + + if ( prsrci->num_rsrc_io < N_PCPS_PORT_RSRC ) + { + PCPS_IO_RSRC *p = &prsrci->port[prsrci->num_rsrc_io]; + + p->base_mapped = (PCPS_IO_ADDR_MAPPED) _pcps_ioremap( base, num ); + p->base_raw = (PCPS_IO_ADDR_RAW) base; + p->num = (uint16_t) num; + + prsrci->num_rsrc_io++; + + _mbgddmsg_3( MBG_DBG_INIT_DEV, "Adding I/O rsrc #%i: %03lX(%lu)", + prsrci->num_rsrc_io, (ulong) base, (ulong) num ); + + return MBG_SUCCESS; + } + + return MBG_ERR_GENERIC; + +} // pcps_add_rsrc_io + + + +/*HDR*/ +int pcps_add_rsrc_mem( PCPS_DDEV *pddev, MBG_MEM_ADDR start, ulong len ) +{ + PCPS_RSRC_INFO *prsrci = &pddev->rsrc_info; + + if ( prsrci->num_rsrc_mem < N_PCPS_MEM_RSRC ) + { + PCPS_MEM_RSRC *p = &prsrci->mem[prsrci->num_rsrc_mem]; + p->start = start; + p->len = len; + prsrci->num_rsrc_mem++; + + #if defined( MBG_TGT_UNIX ) + _mbgddmsg_3( MBG_DBG_INIT_DEV, "Adding mem rsrc #%i: %08llX(%lu)", + prsrci->num_rsrc_mem, (unsigned long long) start, len ); + #else + _mbgddmsg_3( MBG_DBG_INIT_DEV, "Adding mem rsrc #%i: %08lX(%lu)", + prsrci->num_rsrc_mem, (unsigned long) start, len ); + #endif + + return MBG_SUCCESS; + } + + return MBG_ERR_GENERIC; + +} // pcps_add_rsrc_mem + + + +/*HDR*/ +int pcps_add_rsrc_irq( PCPS_DDEV *pddev, int16_t irq_num ) +{ + PCPS_RSRC_INFO *prsrci = &pddev->rsrc_info; + + if ( prsrci->num_rsrc_irq == 0 ) + { + prsrci->irq.num = irq_num; + prsrci->num_rsrc_irq++; + + _mbgddmsg_2( MBG_DBG_INIT_DEV, "Adding IRQ rsrc #%i: %i", + prsrci->num_rsrc_irq, irq_num ); + + return MBG_SUCCESS; + } + + return MBG_ERR_GENERIC; + +} // pcps_add_rsrc_irq + + + +#if _PCPS_USE_PNP + +/*HDR*/ +int pcps_init_ddev( PCPS_DDEV *pddev, int bus_flags, ushort dev_id ) +{ + // First check if we really support the device to be added. + PCPS_DEV_TYPE *pdt = pcps_get_dev_type( bus_flags, dev_id ); + + if ( pdt == NULL ) + { + _mbgddmsg_1( MBG_DBG_INIT_DEV, "PCPS add PNP device %X: unknown type", + dev_id ); + + return MBG_ERR_DEV_NOT_SUPP; + } + + + pddev->dev.type = *pdt; + + _mbgddmsg_2( MBG_DBG_INIT_DEV, "PCPS add PNP device %X: found %s", + dev_id, pdt->name ); + + return MBG_SUCCESS; + +} // pcps_init_ddev + +#endif // _PCPS_USE_PNP + + + +#if defined( DEBUG ) +static /*HDR*/ +const char *get_feature_name( PCPS_FEATURES flag ) +{ + static const char *pcps_feature_names[N_PCPS_FEATURE] = PCPS_FEATURE_NAMES; + + int i; + + for ( i = 0; i < N_PCPS_FEATURE; i++ ) + if ( ( 1UL << i ) == flag ) + return pcps_feature_names[i]; + + return "unknown"; + +} // get_feature_name + +#endif + + + +static /*HDR*/ +void check_feature( PCPS_DDEV *pddev, ushort req_rev_num, + PCPS_FEATURES flag ) +{ + int supported = _pcps_ddev_fw_rev_num( pddev ) >= req_rev_num; + + if ( supported ) + pddev->dev.cfg.features |= flag; + + #if defined( DEBUG ) + _mbgddmsg_5( MBG_DBG_INIT_DEV, "%s v%03X: feature 0x%08lX (%s) %ssupported", + _pcps_ddev_type_name( pddev ), + _pcps_ddev_fw_rev_num( pddev ), + (ulong) flag, get_feature_name( flag ), + supported ? "" : "not " ); + #endif + +} // check_feature + + + +static /*HDR*/ +void check_ri_feature( PCPS_DDEV *pddev, const RECEIVER_INFO *p_ri, + RI_FEATURES ri_flag, PCPS_FEATURES flag ) +{ + int supported = ( p_ri->features & ri_flag ) != 0; + + if ( supported ) + pddev->dev.cfg.features |= flag; + + #if defined( DEBUG ) + _mbgddmsg_5( MBG_DBG_INIT_DEV, "%s v%03X: feature 0x%08lX (%s) %ssupported according to RECEIVER_INFO", + _pcps_ddev_type_name( pddev ), + _pcps_ddev_fw_rev_num( pddev ), + (ulong) flag, get_feature_name( flag ), + supported ? "" : "not " ); + #endif + +} // check_ri_feature + + + +/*HDR*/ +int pcps_start_device( PCPS_DDEV *pddev, + PCPS_BUS_NUM bus_num, + PCPS_SLOT_NUM dev_fnc_num ) +{ + ushort port_rsrc_len[N_PCPS_PORT_RSRC] = { 0 }; + int port_ranges_required = 0; + ushort status_port_offs = 0; + int i; + int rc; + + _mbgddmsg_1( MBG_DBG_INIT_DEV, "PCPS start device %X", + _pcps_ddev_dev_id( pddev ) ); + + pddev->read = pcps_read_null; + pddev->dev.cfg.bus_num = bus_num; + pddev->dev.cfg.slot_num = dev_fnc_num; + + if ( _pcps_ddev_chk_err_flags( pddev, PCPS_EF_IO_INIT | PCPS_EF_IO_ENB ) ) + { + _mbgddmsg_1( MBG_DBG_INIT_DEV, "PCPS start device %X: failing due to error flags.", + _pcps_ddev_dev_id( pddev ) ); + goto fail; + } + + _pcps_mutex_init( &pddev->dev_mutex ); + _pcps_spin_lock_init( &pddev->mm_lock ); + _pcps_spin_lock_init( &pddev->irq_lock ); + + switch ( _pcps_ddev_bus_flags( pddev ) ) + { + #if _PCPS_USE_USB + case PCPS_BUS_USB: + // No direct port I/O required. + pddev->read = pcps_read_usb; + + // In case of an USB device, do some additional + // USB initializiation + #if defined( MBG_TGT_WIN32_PNP ) + rc = pcps_usb_init( pddev ); + #elif defined( MBG_TGT_LINUX ) + rc = usb_set_interface( pddev->udev, 0, 0 ); + + if ( rc ) + _mbgddmsg_1( MBG_DBG_INIT_DEV, "usb_set_interface returned %d", rc ); + else + { + struct usb_host_interface *iface_desc = pddev->intf->cur_altsetting; + int i; + + pddev->n_usb_ep = 0; + + for ( i = 0; i < iface_desc->desc.bNumEndpoints; i++ ) + { + struct usb_endpoint_descriptor *endpoint = &iface_desc->endpoint[i].desc; + PCPS_USB_EP *pep = &pddev->ep[i]; + pep->addr = endpoint->bEndpointAddress; + pep->max_packet_size = le16_to_cpu( endpoint->wMaxPacketSize ); + _mbgddmsg_3( MBG_DBG_INIT_DEV, "endpoint %d: addr %02X, size: %d", + i, pep->addr, pep->max_packet_size ); + pddev->n_usb_ep++; + } + } + #else + #error USB endpoint configuration can not be determined for this target. + #endif + + if ( rc == MBG_SUCCESS ) + if ( pddev->n_usb_ep < MBGUSB_MIN_ENDPOINTS_REQUIRED ) + { + #if defined( MBG_TGT_LINUX ) + printk( KERN_INFO "device supports only %d endpoints while %d are required\n", + pddev->n_usb_ep, MBGUSB_MIN_ENDPOINTS_REQUIRED ); + #elif defined( MBG_TGT_WIN32_PNP ) + swprintf( pddev->wcs_msg, L"device supports only %d endpoints while %d are required", + pddev->n_usb_ep, MBGUSB_MIN_ENDPOINTS_REQUIRED ); + _evt_msg( GlbDriverObject, pddev->wcs_msg ); + #else + //##++ + #endif + + rc = MBG_ERR_GENERIC; + } + #if defined( MBG_TGT_WIN32_PNP ) + else + { + LARGE_INTEGER Count1, Count2, PerfFreq; + PCPS_HR_TIME t; + + uint8_t irq_cmd = PCPS_IRQ_1_SEC; + rc = _pcps_usb_write_var( pddev, &irq_cmd ); + + // get access time to determine latency compensation mode + Count1 = KeQueryPerformanceCounter( &PerfFreq ); + rc = _pcps_read_var( pddev, PCPS_GIVE_HR_TIME, t ); + Count2 = KeQueryPerformanceCounter( NULL ); + + // If access time is below 1 ms then there might be a V2.0 Hub between device and host. + // In this case, you can expect that there is the 125 us microframe timing of USB 2.0. + if ( ( (ULONGLONG) ( Count2.QuadPart - Count1.QuadPart ) ) < ( (ULONGLONG) PerfFreq.QuadPart ) / 1000UL ) + pddev->usb_20_mode = 1; + else + pddev->usb_20_mode = 0; + } + #endif + + if ( rc != MBG_SUCCESS ) + { + _pcps_ddev_set_err_flags( pddev, PCPS_EF_IO_INIT ); + goto fail; + } + break; + #endif + + case PCPS_BUS_PCI_PEX8311: + port_rsrc_len[0] = 0; + port_rsrc_len[1] = sizeof( PCI_ASIC ); // same as ASIC + port_ranges_required = 2; // will be swapped later + status_port_offs = offsetof( PCI_ASIC, status_port ); // same as ASIC + pddev->read = pcps_read_asic; + break; + + case PCPS_BUS_PCI_ASIC: + case PCPS_BUS_PCI_MBGPEX: + port_rsrc_len[0] = sizeof( PCI_ASIC ); + port_ranges_required = 1; + status_port_offs = offsetof( PCI_ASIC, status_port ); + pddev->read = pcps_read_asic; + break; + + case PCPS_BUS_PCI_S5920: + port_rsrc_len[0] = AMCC_OP_REG_RANGE_S5920; + port_rsrc_len[1] = 16; //##++ + port_ranges_required = 2; + status_port_offs = AMCC_OP_REG_IMB4 + 3; + pddev->read = pcps_read_amcc_s5920; + break; + + case PCPS_BUS_PCI_S5933: + port_rsrc_len[0] = AMCC_OP_REG_RANGE_S5933; + port_ranges_required = 1; + status_port_offs = AMCC_OP_REG_IMB4 + 3; + pddev->read = pcps_read_amcc_s5933; + break; + + case PCPS_BUS_MCA: + case PCPS_BUS_ISA: + // resource lengths have always been set + port_ranges_required = 1; + status_port_offs = 1; + pddev->read = pcps_read_std; + break; + + } // switch ( _pcps_ddev_bus_flags( pddev ) ) + + + // check if all required resources have been assigned + if ( pddev->rsrc_info.num_rsrc_io < port_ranges_required ) + { + _mbgddmsg_3( MBG_DBG_INIT_DEV, "PCPS start device %X fails: port ranges (%u) less than required (%u)", + _pcps_ddev_dev_id( pddev ), pddev->rsrc_info.num_rsrc_io, port_ranges_required ); + _pcps_ddev_set_err_flags( pddev, PCPS_EF_IO_INIT ); + goto fail; + } + + if ( _pcps_ddev_is_pci_mbgpex( pddev ) ) + { + pddev->irq_enb_disb_port = _pcps_ddev_io_base_mapped( pddev, 0 ) + + offsetof( PCI_ASIC, control_status ); + pddev->irq_flag_port = pddev->irq_enb_disb_port; + pddev->irq_flag_mask = PCI_ASIC_PCI_IRQF; + + pddev->irq_ack_port = pddev->irq_enb_disb_port; + pddev->irq_ack_mask = PCI_ASIC_PCI_IRQF; + goto chip_setup_done; + } + + + // setup additional properties depending on the + // type of bus interface chip + if ( _pcps_ddev_is_pci_pex8311( pddev ) ) + { + // I/O and memory ranges must be swapped for the + // low level functions because otherwise the first + // range addressed the chip configuration registers + // while the second range addressed data registers. + + PCPS_MEM_RSRC tmp_mem_rsrc; + PCPS_IO_RSRC tmp_io_rsrc; + + tmp_mem_rsrc = pddev->rsrc_info.mem[0]; + pddev->rsrc_info.mem[0] = pddev->rsrc_info.mem[1]; + pddev->rsrc_info.mem[1] = tmp_mem_rsrc; + + #if DEBUG_IO + _mbgddmsg_4( MBG_DBG_DETAIL, "Ports before swapping: %04lX (%08lX), %04lX (%08lX)", + (ulong) pddev->rsrc_info.port[0].base_raw, (ulong) pddev->rsrc_info.port[0].base_mapped, + (ulong) pddev->rsrc_info.port[1].base_raw, (ulong) pddev->rsrc_info.port[1].base_mapped ); + #endif + + tmp_io_rsrc = pddev->rsrc_info.port[0]; + pddev->rsrc_info.port[0] = pddev->rsrc_info.port[1]; + pddev->rsrc_info.port[1] = tmp_io_rsrc; + + #if DEBUG_IO + _mbgddmsg_4( MBG_DBG_DETAIL, "Ports after swapping: %04lX (%08lX), %04lX (%08lX)", + (ulong) pddev->rsrc_info.port[0].base_raw, (ulong) pddev->rsrc_info.port[0].base_mapped, + (ulong) pddev->rsrc_info.port[1].base_raw, (ulong) pddev->rsrc_info.port[1].base_mapped ); + #endif + + // Attention: the interrupt control/status register is located in + // the PLX configuration space which is addressed by a different + // port address range than the normal data ports !! + pddev->irq_enb_disb_port = _pcps_ddev_io_base_mapped( pddev, 1 ) + PLX8311_REG_INTCSR; + pddev->irq_enb_mask = PLX8311_INT_ENB; + pddev->irq_disb_mask = PLX8311_INT_ENB; + + pddev->irq_flag_port = _pcps_ddev_io_base_mapped( pddev, 1 ) + PLX8311_REG_INTCSR; + pddev->irq_flag_mask = PLX8311_INT_FLAG; + + pddev->irq_ack_port = _pcps_ddev_io_base_mapped( pddev, 0 ) + offsetof( PCI_ASIC, control_status ); + pddev->irq_ack_mask = PCI_ASIC_PCI_IRQF; + goto chip_setup_done; + } + + if ( _pcps_ddev_is_pci_asic( pddev ) ) + { + pddev->irq_enb_disb_port = _pcps_ddev_io_base_mapped( pddev, 0 ) + + offsetof( PCI_ASIC, control_status ); + pddev->irq_flag_port = pddev->irq_enb_disb_port; + pddev->irq_flag_mask = PCI_ASIC_PCI_IRQF; + + pddev->irq_ack_port = pddev->irq_enb_disb_port; + pddev->irq_ack_mask = PCI_ASIC_PCI_IRQF; + goto chip_setup_done; + } + + if ( _pcps_ddev_is_pci_amcc( pddev ) ) + { + pddev->irq_enb_disb_port = _pcps_ddev_io_base_mapped( pddev, 0 ) + + AMCC_OP_REG_INTCSR; + pddev->irq_enb_mask = AMCC_INT_ENB; + pddev->irq_disb_mask = AMCC_INT_MASK; + + pddev->irq_flag_port = pddev->irq_enb_disb_port; + pddev->irq_flag_mask = AMCC_INT_FLAG; + + pddev->irq_ack_port = pddev->irq_enb_disb_port; + pddev->irq_ack_mask = AMCC_INT_ACK; + goto chip_setup_done; + } + +chip_setup_done: + + pddev->status_port = _pcps_ddev_io_base_mapped( pddev, 0 ) + status_port_offs; + + // Set up the resource list in pddev->dev.cfg which + // isn't real required anymore, but just informational: + + for ( i = 0; i < N_PCPS_PORT_RSRC; i++ ) + { + PCPS_IO_RSRC *prsrc; + + if ( i >= port_ranges_required ) + break; + + prsrc = &pddev->rsrc_info.port[i]; + + // if the resource len has not yet been set + // then use the default resource len + if ( prsrc->num == 0 ) + prsrc->num = port_rsrc_len[i]; + + rsrc_port_to_cfg_port( &pddev->dev.cfg.port[i], &pddev->rsrc_info.port[i] ); + } + + pddev->dev.cfg.irq_num = pddev->rsrc_info.num_rsrc_irq ? + pddev->rsrc_info.irq.num : -1; + pddev->dev.cfg.status_port = _pcps_ddev_port_base( pddev, 0 ) + status_port_offs; + + pddev->dev.cfg.timeout_clk = PCPS_TIMEOUT_CNT; + + #if DEBUG_PORTS + _mbgddmsg_3( MBG_DBG_DETAIL, "IRQ enb/disb port: %04lX, enb: %08lX, disb: %08lX", + (ulong) pddev->irq_enb_disb_port, + (ulong) pddev->irq_enb_mask, + (ulong) pddev->irq_disb_mask + ); + _mbgddmsg_2( MBG_DBG_DETAIL, "IRQ flag port: %04lX, mask: %08lX", + (ulong) pddev->irq_flag_port, + (ulong) pddev->irq_flag_mask + ); + _mbgddmsg_2( MBG_DBG_DETAIL, "IRQ ack port: %04lX, mask: %08lX", + (ulong) pddev->irq_ack_port, + (ulong) pddev->irq_ack_mask + ); + _mbgddmsg_1( MBG_DBG_DETAIL, "status port: %04lX", (ulong) pddev->status_port ); + #endif + + #if _PCPS_USE_RSRCMGR + rc = pcps_rsrc_claim( pddev ); + + if ( rc < 0 ) + { + _mbgddmsg_1( MBG_DBG_INIT_DEV, "PCPS start device %X: failed to alloc resources", + _pcps_ddev_dev_id( pddev ) ); + + goto fail_with_cleanup; + } + #endif + + // Make sure a PTP270PEX card has finished booting. + if ( _pcps_ddev_dev_id( pddev ) == PCI_DEV_PTP270PEX ) + { + MBG_SYS_TIME t1; + MBG_SYS_TIME t2; + MBG_SYS_UPTIME uptime; + int delayed = 0; + + mbg_get_sys_time( &t1 ); + + for (;;) + { + mbg_get_sys_uptime( &uptime ); + + #if !defined( DEBUG ) + if ( !delayed ) + #endif + report_uptime( &uptime ); + + if ( uptime == 0 ) + break; // assume uptime not supported + + if ( uptime >= MAX_BOOT_TIME_PTP270PEX ) + break; + + mbg_sleep_sec( 1 ); + delayed = 1; + } + + if ( delayed ) + { + long dt; + + mbg_get_sys_time( &t2 ); + + dt = mbg_delta_sys_time_ms( &t2, &t1 ); + + #if defined( MBG_TGT_LINUX ) + printk( KERN_INFO "PTP270PEX startup delay: %li.%03li s\n", + dt / 1000, ( ( dt < 0 ) ? -dt : dt ) % 1000 ); + #elif defined( MBG_TGT_BSD ) + printf( "PTP270PEX startup delay: %li.%03li s\n", + dt / 1000, ( ( dt < 0 ) ? -dt : dt ) % 1000 ); + #elif defined( MBG_TGT_WIN32 ) + swprintf( pddev->wcs_msg, L"PTP270PEX startup delay: %li.%03li s", + dt / 1000, ( ( dt < 0 ) ? -dt : dt ) % 1000 ); + _evt_msg( GlbDriverObject, pddev->wcs_msg ); + #endif + } + } + + + // try to read EPROM ID + rc = pcps_get_fw_id( pddev, pddev->dev.cfg.fw_id ); + + if ( rc < 0 ) + { + if ( _pcps_ddev_is_isa( pddev ) ) + { + // ISA devices are detected by trying to read a firmware ID via + // a given port, so if the firmware ID could not be read then this + // just means there is no device using the given port address. + #if defined( MBG_TGT_WIN32 ) + swprintf( pddev->wcs_msg, L"No ISA card found at port %03lXh.", + (ulong) _pcps_ddev_port_base( pddev, 0 ) ); + _evt_msg( GlbDriverObject, pddev->wcs_msg ); + #else + _mbgddmsg_1( MBG_DBG_INIT_DEV, "No ISA card found at port %03lXh.", + (ulong) _pcps_ddev_port_base( pddev, 0 ) ); + #endif + } + else + { + // Non-ISA devices are detected by some other means, so if the firmware + // ID could not be read this is a serious error. + #if defined( MBG_TGT_WIN32 ) //##+++ debug or not debug ... ;-) + _evt_msg( GlbDriverObject, L"StartDevice: failed to read firmware ID" ); + #else + _mbgddmsg_1( MBG_DBG_INIT_DEV, "PCPS start device %X: failed to read firmware ID", + _pcps_ddev_dev_id( pddev ) ); + #endif + } + + _pcps_ddev_set_err_flags( pddev, PCPS_EF_TIMEOUT ); + goto fail_with_cleanup; + } + + if ( _pcps_ddev_bus_flags( pddev ) == PCPS_BUS_ISA ) + { + ushort dev_type; + + // Still need to find out which type of ISA clock we have found. + // Check EPROM ID to find out which kind of clock is installed. + if ( pcps_check_id( pddev, fw_id_ref_gps ) == MBG_SUCCESS ) + dev_type = PCPS_TYPE_GPS167PC; + else + { + if ( pcps_check_id( pddev, fw_id_ref_pcps ) == MBG_SUCCESS ) + { + // Device is a PC31, or a PC32 if it has signature code. + // If no support for MCA has been compiled in, it may even + // be a PS31 which is software compatible with a PC31. + dev_type = + ( _mbg_inp16_to_cpu( pddev, _pcps_ddev_io_base_mapped( pddev, 0 ) + 2 ) + == pcps_dev_type[PCPS_TYPE_PC32].dev_id ) ? + PCPS_TYPE_PC32 : PCPS_TYPE_PC31; + } + else + { + _pcps_ddev_set_err_flags( pddev, PCPS_EF_INV_EPROM_ID ); + goto fail_with_cleanup; + } + } + + pddev->dev.type = pcps_dev_type[dev_type]; + } + + #if defined( MBG_TGT_OS2 ) + pcps_rsrc_register_device( pddev ); + #endif + + // Extract the firmware revision number from the ID string. + pddev->dev.cfg.fw_rev_num = pcps_get_rev_num( _pcps_ddev_fw_id( pddev ) ); + + // If the device has an ASIC or EPLD read the ASIC version number + if ( _pcps_ddev_has_asic_version( pddev ) ) + { + pddev->raw_asic_version = _mbg_inp32_to_cpu( pddev, _pcps_ddev_io_base_mapped( pddev, 0 ) + + offsetof( PCI_ASIC, raw_version ) ); + + _mbg_swab_asic_version( &pddev->raw_asic_version ); + + pddev->asic_version = _convert_asic_version_number( pddev->raw_asic_version ); + } + + // Setup some feature flags which depend on the device type + // and firmware version. + switch( _pcps_ddev_type_num( pddev ) ) + { + case PCPS_TYPE_PC31: + case PCPS_TYPE_PS31_OLD: + case PCPS_TYPE_PS31: + pddev->dev.cfg.features = PCPS_FEAT_PC31PS31; + check_feature( pddev, REV_CAN_SET_TIME_PC31PS31, PCPS_CAN_SET_TIME ); + check_feature( pddev, REV_HAS_SERIAL_PC31PS31, PCPS_HAS_SERIAL ); + check_feature( pddev, REV_HAS_SYNC_TIME_PC31PS31, PCPS_HAS_SYNC_TIME ); + check_feature( pddev, REV_HAS_UTC_OFFS_PC31PS31, PCPS_HAS_UTC_OFFS ); + break; + + case PCPS_TYPE_PC32: + pddev->dev.cfg.features = PCPS_FEAT_PC32; + break; + + case PCPS_TYPE_PCI32: + pddev->dev.cfg.features = PCPS_FEAT_PCI32; + break; + + case PCPS_TYPE_GPS167PC: + pddev->dev.cfg.features = PCPS_FEAT_GPS167PC; + check_feature( pddev, REV_HAS_HR_TIME_GPS167PC, PCPS_HAS_HR_TIME ); + check_feature( pddev, REV_HAS_CABLE_LEN_GPS167PC, PCPS_HAS_CABLE_LEN ); + break; + + case PCPS_TYPE_GPS167PCI: + pddev->dev.cfg.features = PCPS_FEAT_GPS167PCI; + check_feature( pddev, REV_HAS_CABLE_LEN_GPS167PCI, PCPS_HAS_CABLE_LEN ); + check_feature( pddev, REV_CAN_CLR_UCAP_BUFF_GPS167PCI, PCPS_CAN_CLR_UCAP_BUFF ); + check_feature( pddev, REV_HAS_UCAP_GPS167PCI, PCPS_HAS_UCAP ); + break; + + case PCPS_TYPE_PCI509: + pddev->dev.cfg.features = PCPS_FEAT_PCI509; + break; + + case PCPS_TYPE_GPS168PCI: + pddev->dev.cfg.features = PCPS_FEAT_GPS168PCI; + check_feature( pddev, REV_CAN_CLR_UCAP_BUFF_GPS168PCI, PCPS_CAN_CLR_UCAP_BUFF ); + check_feature( pddev, REV_HAS_UCAP_GPS168PCI, PCPS_HAS_UCAP ); + break; + + case PCPS_TYPE_PCI510: + pddev->dev.cfg.features = PCPS_FEAT_PCI510; + break; + + case PCPS_TYPE_GPS169PCI: + pddev->dev.cfg.features = PCPS_FEAT_GPS169PCI; + check_feature( pddev, REV_HAS_GPS_DATA_16_GPS169PCI, PCPS_HAS_GPS_DATA_16 ); + break; + + case PCPS_TYPE_TCR510PCI: + pddev->dev.cfg.features = PCPS_FEAT_TCR510PCI; + check_feature( pddev, REV_HAS_HR_TIME_TCR510PCI, PCPS_HAS_HR_TIME ); + break; + + case PCPS_TYPE_TCR167PCI: + pddev->dev.cfg.features = PCPS_FEAT_TCR167PCI; + break; + + case PCPS_TYPE_GPS170PCI: + pddev->dev.cfg.features = PCPS_FEAT_GPS170PCI; + break; + + case PCPS_TYPE_PCI511: + pddev->dev.cfg.features = PCPS_FEAT_PCI511; + check_feature( pddev, REV_HAS_HR_TIME_PCI511, PCPS_HAS_HR_TIME ); + break; + + case PCPS_TYPE_TCR511PCI: + pddev->dev.cfg.features = PCPS_FEAT_TCR511PCI; + check_feature( pddev, REV_HAS_IRIG_CTRL_BITS_TCR511PCI, PCPS_HAS_IRIG_CTRL_BITS ); + check_feature( pddev, REV_HAS_IRIG_TIME_TCR511PCI, PCPS_HAS_IRIG_TIME ); + check_feature( pddev, REV_HAS_RAW_IRIG_DATA_TCR511PCI, PCPS_HAS_RAW_IRIG_DATA ); + break; + + case PCPS_TYPE_PEX511: + pddev->dev.cfg.features = PCPS_FEAT_PEX511; + // HR time support for the PEX511 requires both a certain ASIC + // version plus a certain firmware version. + if ( _pcps_asic_version_greater_equal( _pcps_ddev_asic_version( pddev ), + PCI_ASIC_MAJOR_PEX511, PCI_ASIC_HR_TIME_MINOR_PEX511 ) ) + check_feature( pddev, REV_HAS_HR_TIME_PEX511, PCPS_HAS_HR_TIME ); + + pcps_check_pex_irq_unsafe( pddev, REV_HAS_IRQ_FIX_MINOR_PEX511, + PCI_ASIC_MAJOR_PEX511, PCI_ASIC_FIX_IRQ_MINOR_PEX511 ); + break; + + case PCPS_TYPE_TCR511PEX: + pddev->dev.cfg.features = PCPS_FEAT_TCR511PEX; + check_feature( pddev, REV_HAS_IRIG_CTRL_BITS_TCR511PEX, PCPS_HAS_IRIG_CTRL_BITS ); + check_feature( pddev, REV_HAS_IRIG_TIME_TCR511PEX, PCPS_HAS_IRIG_TIME ); + check_feature( pddev, REV_HAS_RAW_IRIG_DATA_TCR511PEX, PCPS_HAS_RAW_IRIG_DATA ); + pcps_check_pex_irq_unsafe( pddev, REV_HAS_IRQ_FIX_MINOR_TCR511PEX, + PCI_ASIC_MAJOR_TCR511PEX, PCI_ASIC_FIX_IRQ_MINOR_TCR511PEX ); + break; + + case PCPS_TYPE_GPS170PEX: + pddev->dev.cfg.features = PCPS_FEAT_GPS170PEX; + pcps_check_pex_irq_unsafe( pddev, REV_HAS_IRQ_FIX_MINOR_GPS170PEX, + PCI_ASIC_MAJOR_GPS170PEX, PCI_ASIC_FIX_IRQ_MINOR_GPS170PEX ); + break; + + case PCPS_TYPE_USB5131: + pddev->dev.cfg.features = PCPS_FEAT_USB5131; + break; + + case PCPS_TYPE_TCR51USB: + pddev->dev.cfg.features = PCPS_FEAT_TCR51USB; + check_feature( pddev, REV_HAS_IRIG_CTRL_BITS_TCR51USB, PCPS_HAS_IRIG_CTRL_BITS ); + check_feature( pddev, REV_HAS_IRIG_TIME_TCR51USB, PCPS_HAS_IRIG_TIME ); + check_feature( pddev, REV_HAS_RAW_IRIG_DATA_TCR51USB, PCPS_HAS_RAW_IRIG_DATA ); + break; + + case PCPS_TYPE_MSF51USB: + pddev->dev.cfg.features = PCPS_FEAT_MSF51USB; + break; + + case PCPS_TYPE_PTP270PEX: + pddev->dev.cfg.features = PCPS_FEAT_PTP270PEX; + break; + + case PCPS_TYPE_FRC511PEX: + pddev->dev.cfg.features = PCPS_FEAT_FRC511PEX; + break; + + case PCPS_TYPE_TCR170PEX: + pddev->dev.cfg.features = PCPS_FEAT_TCR170PEX; + break; + + case PCPS_TYPE_WWVB51USB: + pddev->dev.cfg.features = PCPS_FEAT_WWVB51USB; + break; + + case PCPS_TYPE_GPS180PEX: + pddev->dev.cfg.features = PCPS_FEAT_GPS180PEX; + break; + + case PCPS_TYPE_TCR180PEX: + pddev->dev.cfg.features = PCPS_FEAT_TCR180PEX; + break; + + case PCPS_TYPE_DCF600USB: + pddev->dev.cfg.features = PCPS_FEAT_DCF600USB; + break; + + } // switch + + + if ( _pcps_ddev_has_receiver_info( pddev ) ) + { + // detect the presence of some optional features at run time + RECEIVER_INFO rcvr_info; + int rc; + + rc = _pcps_read_gps_var( pddev, PC_GPS_RECEIVER_INFO, rcvr_info ); + + if ( rc == MBG_SUCCESS ) + { + _mbg_swab_receiver_info( &rcvr_info ); + + _mbgddmsg_3( MBG_DBG_INIT_DEV, "%s v%03X RECEIVER_INFO features: 0x%08lX", + _pcps_ddev_type_name( pddev ), _pcps_ddev_fw_rev_num( pddev ), + (ulong) rcvr_info.features ); + + check_ri_feature( pddev, &rcvr_info, GPS_HAS_IRIG_TX, PCPS_HAS_IRIG_TX ); + check_ri_feature( pddev, &rcvr_info, GPS_HAS_IRIG_CTRL_BITS, PCPS_HAS_IRIG_CTRL_BITS ); + check_ri_feature( pddev, &rcvr_info, GPS_HAS_SYNTH, PCPS_HAS_SYNTH ); + check_ri_feature( pddev, &rcvr_info, GPS_HAS_TIME_SCALE, PCPS_HAS_TIME_SCALE ); + + // Devices which support a configurable time scale do also + // support reading/writing the GPS UTC parameters via the PC bus. + // This is not explicitely coded in the rcvr_info structure + // since the the rcvr_info structure can also be read via + // the serial port, and reading/writing the GPS UTC parameters + // via the serial port is supported by all GPS devices anyway. + check_ri_feature( pddev, &rcvr_info, GPS_HAS_TIME_SCALE, PCPS_HAS_UTC_PARM ); + + // Devices which support reading raw IRIG data via the PC interface also support + // reading the raw IRIG time. However, there is no receiver info feature flag + // since this call is not supported via the serial interface, so we use the + // GPS_HAS_RAW_IRIG_DATA flag to check both features. + check_ri_feature( pddev, &rcvr_info, GPS_HAS_RAW_IRIG_DATA, PCPS_HAS_IRIG_TIME ); + check_ri_feature( pddev, &rcvr_info, GPS_HAS_RAW_IRIG_DATA, PCPS_HAS_RAW_IRIG_DATA ); + + check_ri_feature( pddev, &rcvr_info, GPS_HAS_LAN_IP4, PCPS_HAS_LAN_INTF ); + check_ri_feature( pddev, &rcvr_info, GPS_HAS_PTP, PCPS_HAS_PTP ); + } + } + + + #if !defined( MBG_TGT_OS2 ) && !defined( MBG_TGT_BSD ) + // Function strstr may not be supported at kernel level, + // but this is not required, in most cases, either. + if ( strstr( _pcps_ddev_fw_id( pddev ), "CERN" ) != NULL ) + pddev->dev.cfg.features |= PCPS_HAS_EVENT_TIME; + #endif + + #if DEBUG_IO && defined( MBG_TGT_LINUX ) + { + PCPS_TIME t = { 0 }; + rc = _pcps_read( pddev, PCPS_GIVE_TIME, &t, sizeof( t ) ); + printk( KERN_INFO "read time, sz: %lu, returned %i\n", (ulong) sizeof( t ), rc ); + printk( KERN_INFO " sec100 %02X, sec %02X, min %02X hour %02X\n", + t.sec100, t.sec, t.min, t.hour ); + printk( KERN_INFO " mday %02X, wday %02X, month %02X year %02X\n", + t.mday, t.wday, t.month, t.year ); + printk( KERN_INFO " status %02X, sig %02X, offs_utc %02X\n", + t.status, t.signal, t.offs_utc ); + } + #endif + + if ( _pcps_ddev_has_asic_features( pddev ) ) + { + pddev->asic_features = _mbg_inp32_to_cpu( pddev, _pcps_ddev_io_base_mapped( pddev, 0 ) + + offsetof( PCI_ASIC, features ) ); + + _mbg_swab_asic_features( &pddev->asic_features ); + + #if MBG_TGT_SUPP_MEM_ACC + if ( pddev->asic_features & PCI_ASIC_HAS_MM_IO ) + pddev->dev.cfg.features |= PCPS_HAS_FAST_HR_TSTAMP; + else + if ( pddev->dev.cfg.features & PCPS_HAS_FAST_HR_TSTAMP ) + { + // The device supports memory mapped time stamps by default. + // However, this is not reflected by the ASIC features. + _mbgddmsg_0( MBG_DBG_INIT_DEV, + "Warning: ASIC features don't reflect memory mapped time stamp support." ); + } + + if ( pddev->dev.cfg.features & PCPS_HAS_FAST_HR_TSTAMP ) + if ( map_sys_virtual_address( pddev ) < 0 ) + goto fail_with_cleanup; + + #endif + } + + pcps_read_sernum( pddev ); + + _mbgddmsg_3( MBG_DBG_INIT_DEV, "%s v%03X actual features: 0x%08lX", + _pcps_ddev_type_name( pddev ), _pcps_ddev_fw_rev_num( pddev ), + (ulong) _pcps_ddev_features( pddev ) ); + + return MBG_SUCCESS; + + +fail_with_cleanup: + pcps_cleanup_device( pddev ); + +fail: + return MBG_ERR_GENERIC; + +} // pcps_start_device + + + +/*HDR*/ +void pcps_cleanup_device( PCPS_DDEV *pddev ) +{ + pddev->read = pcps_read_null; + + #if MBG_TGT_SUPP_MEM_ACC + unmap_sys_virtual_address( pddev ); + #endif + + #if _PCPS_USE_RSRCMGR + pcps_rsrc_release( pddev ); + #endif + +} // pcps_cleanup_device + + + +/*-------------------------------------------------------------- + * PCI functions + *-------------------------------------------------------------*/ + +#if _PCPS_USE_PCI_BIOS + +static /*HDR*/ +PCPS_ERR_FLAGS pcps_read_pci_rsrc( PCPS_BUS_NUM bus_num, + PCPS_SLOT_NUM dev_fnc_num, + PCPS_DDEV *pddev, + PCPS_BUS_FLAGS bus_flags ) +{ + PCPS_ERR_FLAGS err_flags = 0; + uchar irq; + short rc; + PCI_DWORD dw; + int i; + + // Clear resources + memset( &pddev->rsrc_info, 0, sizeof( pddev->rsrc_info ) ); + + for ( i = 0; i < MAX_PCPS_RSRC; i++ ) + { + rc = _mbg_pci_read_cfg_dword( bus_num, dev_fnc_num, + PCI_CS_BASE_ADDRESS_0 + i * sizeof( uint32_t ), &dw ); + + if ( rc != PCI_SUCCESS ) + break; + + if ( dw == 0 ) // base address register not used + continue; + + if ( dw & 0x0001 ) // is an I/O resource + { + if ( dw & 0xFFFF0000UL ) + { + // The PCI interface chip is not initialized. This + // should occur ONLY at the first-time installation + // at the factory. + err_flags |= PCPS_EF_IO_INIT; + goto done; + } + + pcps_add_rsrc_io( pddev, (uint16_t) ( dw & ~0x0001 ), 0 ); + } + else + pcps_add_rsrc_mem( pddev, dw, 0 ); //##++ range length? + } + + // Read the interrupt line assigned to the clock. + // The standard functions, however, don't use any + // interrupt. + rc = _mbg_pci_read_cfg_byte( bus_num, dev_fnc_num, + PCI_CS_INTERRUPT_LINE, &irq ); + + if ( rc == PCI_SUCCESS ) + pcps_add_rsrc_irq( pddev, irq ); + +done: + return err_flags; + +} // pcps_read_pci_rsrc + + + +static /*HDR*/ +PCPS_ERR_FLAGS pcps_enable_pci_dev( PCPS_BUS_NUM bus_num, + PCPS_SLOT_NUM dev_fnc_num, + int num_rsrc_mem ) +{ + PCPS_ERR_FLAGS err_flags = 0; + uint16_t pci_command; + uint16_t new_pci_command; + int rc; + + + // If the option "PNP OS installed" is set to "YES" in the + // PC's BIOS setup, then I/O access to the board may still + // be disabled, so check if the clock is enabled and enable + // access to the board, if nessessary. + rc = _mbg_pci_read_cfg_word( bus_num, dev_fnc_num, + PCI_CS_COMMAND, &pci_command ); + new_pci_command = pci_command | PCI_CMD_ENB_IO_ACC; + + if ( num_rsrc_mem ) + new_pci_command |= PCI_CMD_ENB_MEM_ACC; + + if ( new_pci_command != pci_command ) + { + rc = _mbg_pci_write_cfg_word( bus_num, dev_fnc_num, + PCI_CS_COMMAND, pci_command ); + if ( rc != PCI_SUCCESS ) + { + err_flags |= PCPS_EF_IO_ENB; + + _mbgddmsg_1( MBG_DBG_INIT_DEV, + "PCI enable device returned %d", rc ); + } + } + + return err_flags; + +} // pcps_enable_pci_dev + + + +/*HDR*/ +void pcps_setup_and_start_pci_dev( PCPS_DDEV *pddev, + PCPS_BUS_NUM bus_num, PCPS_SLOT_NUM dev_fnc_num ) +{ + PCPS_ERR_FLAGS err_flags; + + err_flags = pcps_read_pci_rsrc( bus_num, dev_fnc_num, + pddev, _pcps_ddev_bus_flags( pddev ) ); + _pcps_ddev_set_err_flags( pddev, err_flags ); + + if ( !( err_flags & PCPS_EF_IO_INIT ) ) + { + err_flags = pcps_enable_pci_dev( bus_num, dev_fnc_num, + pddev->rsrc_info.num_rsrc_mem ); + _pcps_ddev_set_err_flags( pddev, err_flags ); + } + + pcps_start_device( pddev, bus_num, dev_fnc_num ); + +} // pcps_setup_and_start_pci_dev + + + +/*HDR*/ +void pcps_detect_pci_clocks( PCPS_DDEV_ALLOC_FNC alloc_fnc, void *alloc_arg, + PCPS_DDEV_CLEANUP_FNC cleanup_fnc, + ushort vendor_id, PCPS_DEV_TYPE dev_type[], + int n_dev_types ) +{ + #if defined( MBG_TGT_QNX ) + #if defined( MBG_TGT_QNX_NTO ) + unsigned int pci_handle; // specific to QNX Neutrino + #endif + unsigned int pci_hardware_mechanism; + unsigned int pci_last_bus_number; + unsigned int pci_interface_level_version; + #elif defined( MBG_TGT_LINUX ) + // not yet supported/used + #else + uchar pci_hardware_mechanism; + uchar pci_last_bus_number; + ushort pci_interface_level_version; + #endif + ushort type_idx; + int rc; + + + #ifdef _mbg_pci_fnc_init + rc = _mbg_pci_fnc_init(); + + if ( rc != PCI_SUCCESS ) + return; + #endif + + + // See if PCI BIOS is installed on the machine. + rc = _mbg_pci_find_bios( &pci_hardware_mechanism, + &pci_interface_level_version, + &pci_last_bus_number + ); + + if ( rc == PCI_SUCCESS ) + { + // PCI BIOS is installed, now try to find a PCI clock with + // known ID (the list is terminated with a ID of 0). + for ( type_idx = 0; type_idx < n_dev_types; type_idx++ ) + { + ushort dev_idx; + PCPS_DEV_TYPE *p = &dev_type[type_idx]; + + if ( !( p->bus_flags & PCPS_BUS_PCI ) ) + continue; + + + for ( dev_idx = 0; ; dev_idx++ ) + { + PCPS_DDEV *pddev; + #if defined( MBG_TGT_QNX ) + unsigned bus_num; + unsigned dev_fnc_num; + #else + uchar bus_num; + uchar dev_fnc_num; + #endif + + rc = _mbg_pci_find_device( p->dev_id, vendor_id, + dev_idx, &bus_num, &dev_fnc_num ); + + if ( rc != PCI_SUCCESS ) + break; // go to try next device ID + + + // New device found, try to add to list. + pddev = alloc_fnc(); + + if ( pddev ) // Setup only if successful. + { + #if _PCPS_USE_PCI_PNP //##++ + // This can be used to test the PNP functions in a + // non-PNP environment. + pcps_init_ddev( pddev, PCPS_BUS_PCI, p->dev_id ); + #else + pddev->dev.type = *p; + #endif + + pcps_setup_and_start_pci_dev( pddev, bus_num, dev_fnc_num ); + + #if !_ACCEPT_UNINITD_CLOCKS + if ( pddev->dev.cfg.err_flags ) + { + _mbgddmsg_1( MBG_DBG_INIT_DEV, + "Remove PCI device: err_flags " FMT_08X "h", + (ulong) pddev->dev.cfg.err_flags ); + + if ( cleanup_fnc ) + cleanup_fnc( pddev ); + } + #endif + } + } + } + } + + #ifdef _mbg_pci_fnc_deinit + _mbg_pci_fnc_deinit(); + #endif + +} // pcps_detect_pci_clocks + +#endif // _PCPS_USE_PCI_BIOS + + + +/*-------------------------------------------------------------- + * Try to detect ISA clocks + *-------------------------------------------------------------*/ + +#if !_PCPS_USE_ISA_PNP + +/*HDR*/ +void pcps_detect_isa_clocks( PCPS_DDEV_ALLOC_FNC alloc_fnc, + PCPS_DDEV_CLEANUP_FNC cleanup_fnc, + PCPS_DDEV_REGISTER_FNC register_fnc, + int isa_ports[PCPS_MAX_ISA_CARDS], + int isa_irqs[PCPS_MAX_ISA_CARDS] ) +{ + int *p_port = isa_ports; + int *p_irq = isa_irqs; + PCPS_DDEV *pddev; + int i; + + if ( p_port == NULL ) // No list has been passed + return; // so don't try to detect ISA clocks. + + + for( i = 0; i < PCPS_MAX_ISA_CARDS; + i++, p_port++, p_irq ? ( p_irq++ ) : p_irq ) + { + int irq_num; + + if ( *p_port == 0 ) + continue; + + irq_num = p_irq ? *p_irq : -1; + + _mbgddmsg_2( MBG_DBG_INIT_DEV, + "Check ISA device at port " FMT_03X "h, irq %d", + *p_port, irq_num ); + + // Assume ISA device is available, + // but clock type is unknown, yet. + pddev = alloc_fnc(); + + if ( pddev ) // Setup only if successfull. + { + pddev->dev.type.bus_flags = PCPS_BUS_ISA; + + // Set up basic cfg for ISA devices. + pcps_add_rsrc_io( pddev, (uint16_t) *p_port, PCPS_NUM_PORTS_ISA ); + + if ( irq_num != -1 ) + pcps_add_rsrc_irq( pddev, (uint16_t) *p_irq ); + + // Init the device structure. This includes registration + // of I/O ports with the OS's resource manager (if supported), + // and reading the firmware ID. + pcps_start_device( pddev, 0, 0 ); + + // If an error has occurred, then remove the last + // device from the list and try next. + if ( pddev->dev.cfg.err_flags ) + { + _mbgddmsg_1( MBG_DBG_INIT_DEV, + "ISA device not found: err_flags " FMT_08X "h", + (ulong) pddev->dev.cfg.err_flags ); + if ( cleanup_fnc ) + cleanup_fnc( pddev ); + + continue; + } + + // Register the device with the OS, if required. + if ( register_fnc ) + register_fnc( pddev ); //##++ + } + } + +} // pcps_detect_isa_clocks + +#endif //!_PCPS_USE_ISA_PNP + + + +#if !_PCPS_USE_PNP + +/*-------------------------------------------------------------- + * Try to detect any plug-in radio clock. If a DOS TSR is + * installed, be sure it is disabled (BUSY flag set) when + * this function is called. + *-------------------------------------------------------------*/ + +/*HDR*/ +void _MBG_INIT_CODE_ATTR pcps_detect_clocks_alloc( PCPS_DDEV_ALLOC_FNC alloc_fnc, + void *alloc_arg, + PCPS_DDEV_CLEANUP_FNC cleanup_fnc, + int isa_ports[PCPS_MAX_ISA_CARDS], + int isa_irqs[PCPS_MAX_ISA_CARDS] ) +{ + #if defined( MBG_TGT_OS2 ) + rsrc_register_driver(); // register driver and init resource manager + #endif + + #if _PCPS_USE_PCI_BIOS + pcps_detect_pci_clocks( alloc_fnc, alloc_arg, cleanup_fnc, + PCI_VENDOR_MEINBERG, pcps_dev_type, N_PCPS_DEV_TYPE ); + #endif + + #if _PCPS_USE_MCA + pcps_detect_mca_clocks( alloc_fnc, alloc_arg ); + #endif + + #if !_PCPS_USE_ISA_PNP + pcps_detect_isa_clocks( alloc_fnc, cleanup_fnc, NULL, isa_ports, isa_irqs ); + #endif + +} // pcps_detect_clocks_alloc + + + +/*HDR*/ +void _MBG_INIT_CODE_ATTR pcps_detect_clocks( int isa_ports[PCPS_MAX_ISA_CARDS], + int isa_irqs[PCPS_MAX_ISA_CARDS] ) +{ + pcps_detect_clocks_alloc( pcps_alloc_ddev, NULL, pcps_free_ddev, + isa_ports, isa_irqs ); + +} // pcps_detect_clocks + +#endif // !_PCPS_USE_PNP + + |