diff options
-rw-r--r-- | etc/MAKEDEV | 54 | ||||
-rw-r--r-- | etc/etc.i386/MAKEDEV | 54 | ||||
-rw-r--r-- | share/man/man4/man4.i386/labpc.4 | 151 | ||||
-rw-r--r-- | sys/conf/NOTES | 5 | ||||
-rw-r--r-- | sys/conf/files.i386 | 3 | ||||
-rw-r--r-- | sys/i386/conf/LINT | 5 | ||||
-rw-r--r-- | sys/i386/conf/NOTES | 5 | ||||
-rw-r--r-- | sys/i386/conf/files.i386 | 3 | ||||
-rw-r--r-- | sys/i386/i386/conf.c | 18 | ||||
-rw-r--r-- | sys/i386/isa/labpc.c | 1051 |
10 files changed, 1341 insertions, 8 deletions
diff --git a/etc/MAKEDEV b/etc/MAKEDEV index 10970eb..224d697 100644 --- a/etc/MAKEDEV +++ b/etc/MAKEDEV @@ -63,6 +63,11 @@ # # Call units: # +# SCSI devices (other than CD-ROM, tape and disk) +# uk* "unknown" device (supports ioctl calls only) +# worm* WORM driver +# pt* Processor Type (HP scanner, as one example) +# # Special purpose devices: # bpf* packet filter # speaker pc speaker @@ -76,8 +81,9 @@ # tun Tunneling IP device # spigot Video Spigot video aquisition card # isdn* ISDN devices +# labpc* National Instrument's Lab-PC and LAB-PC+ # -# $Id: MAKEDEV,v 1.83 1995/04/23 12:02:21 ache Exp $ +# $Id: MAKEDEV,v 1.84 1995/04/26 08:07:30 bde Exp $ # PATH=/sbin:/bin/:/usr/bin:/usr/sbin: @@ -858,6 +864,52 @@ isdn*) esac ;; +# dufault@hda.com: If I do much more work on other A-D boards +# then eventually we'll have a "ad" and "dio" interface and some of these +# "labpcaio" ones will be gone. +# labpcaio: D-A and A-D. +# labpcdio: Digital in and Digital out. +# +labpc*) + umask 7 + case $i in + labpcaio*) + name=labpcaio + unit=`expr $i : 'labpcaio\(.*\)'` + all="0 1 2 3 4 5 6 7" + offset=0 + ;; + labpcdio*) + name=labpcdio + unit=`expr $i : 'labpcdio\(.*\)'` + all="0 1 2 3" + offset=128 + ;; + *) + echo "Don't understand that labpc name" + exit + ;; + esac + if [ "X${unit}" = "X" ]; then + unit=all + fi + case $unit in + 0|1|2|3|4|5|6|7) + mknod $name$unit c 66 `expr $offset + $unit ` + ;; + all) + for i in $all + do + mknod $name$i c 66 `expr $offset + $i ` + done + ;; + *) + echo "No such LabPC unit: $unit" + ;; + esac + umask 77 + ;; + local) umask 0 # XXX should be elsewhere sh MAKEDEV.local diff --git a/etc/etc.i386/MAKEDEV b/etc/etc.i386/MAKEDEV index 10970eb..224d697 100644 --- a/etc/etc.i386/MAKEDEV +++ b/etc/etc.i386/MAKEDEV @@ -63,6 +63,11 @@ # # Call units: # +# SCSI devices (other than CD-ROM, tape and disk) +# uk* "unknown" device (supports ioctl calls only) +# worm* WORM driver +# pt* Processor Type (HP scanner, as one example) +# # Special purpose devices: # bpf* packet filter # speaker pc speaker @@ -76,8 +81,9 @@ # tun Tunneling IP device # spigot Video Spigot video aquisition card # isdn* ISDN devices +# labpc* National Instrument's Lab-PC and LAB-PC+ # -# $Id: MAKEDEV,v 1.83 1995/04/23 12:02:21 ache Exp $ +# $Id: MAKEDEV,v 1.84 1995/04/26 08:07:30 bde Exp $ # PATH=/sbin:/bin/:/usr/bin:/usr/sbin: @@ -858,6 +864,52 @@ isdn*) esac ;; +# dufault@hda.com: If I do much more work on other A-D boards +# then eventually we'll have a "ad" and "dio" interface and some of these +# "labpcaio" ones will be gone. +# labpcaio: D-A and A-D. +# labpcdio: Digital in and Digital out. +# +labpc*) + umask 7 + case $i in + labpcaio*) + name=labpcaio + unit=`expr $i : 'labpcaio\(.*\)'` + all="0 1 2 3 4 5 6 7" + offset=0 + ;; + labpcdio*) + name=labpcdio + unit=`expr $i : 'labpcdio\(.*\)'` + all="0 1 2 3" + offset=128 + ;; + *) + echo "Don't understand that labpc name" + exit + ;; + esac + if [ "X${unit}" = "X" ]; then + unit=all + fi + case $unit in + 0|1|2|3|4|5|6|7) + mknod $name$unit c 66 `expr $offset + $unit ` + ;; + all) + for i in $all + do + mknod $name$i c 66 `expr $offset + $i ` + done + ;; + *) + echo "No such LabPC unit: $unit" + ;; + esac + umask 77 + ;; + local) umask 0 # XXX should be elsewhere sh MAKEDEV.local diff --git a/share/man/man4/man4.i386/labpc.4 b/share/man/man4/man4.i386/labpc.4 new file mode 100644 index 0000000..42e3eff --- /dev/null +++ b/share/man/man4/man4.i386/labpc.4 @@ -0,0 +1,151 @@ +.\" +.\" Copyright (c) 1995 HD Associates, Inc. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by Christopher G. Demetriou. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software withough specific prior written permission +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" +.rm ES +.rm EE +.de ES +.Pp +.nf +.in +0.5i +.. +.de EE +.in -0.5i +.fi +.. +.Dd April 26, 1995 +.Dt LABPC 4 i386 +.Os FreeBSD +.Sh NAME +.Nm labpc +.Nd +National Instruments LABPC and LABPC+ driver +.Sh SYNOPSIS +.Cd "device labpc0 at isa? port 260 tty irq 5 vector labpcintr +.Sh DESCRIPTION +This supports the National Instruments LABPC and LABPC+ \fILow-Cost +Multifunction I/O Board\fP. +.sp +This board provides 8 12 bit A-D input channels, 2 12 bit D-A output +channels, and 3 8 bit digital I/O ports. It also supports setting +up the National Instruments SCXI bus off the digital I/O ports, +although that software is currently present in an external program +and not as part of the driver. +.Pp +The selection of the input or output device, mode, and channel is +through the minor number: +.ES +The 8 bit minor number format is UUSIDCCC, with + UU: Board unit. + S: SCAN bit for scan enable. + I: INTERVAL for interval support + D: 1: Digital I/O, 0: Analog I/O +CCC: The channel selector: + ANALOG: + input: channel must be 0 to 7 for AD0 ... AD7 + output: channel must be 0 to 2 + 0: D-A 0 + 1: D-A 1 + 2: Alternate channel 0 then 1 + + DIGITAL: + input: Channel must be 0 to 2. + output: Channel must be 0 to 2. +.EE +.Pp +The /dev/MAKEDEV script will make the basic analog and digital +devices if you do "sh MAKEDEV labpcaio" and "sh MAKEDEV labpcdio", +however, it won't make device entries for INTERVAL and SCAN devices. +SCAN devices automatically scan over all channels from the channel +number down to 0 (for example, a SCAN for /dev/ad7 will read channels +AD7 ... AD0) while the INTERVAL device will wait for an external +signal and then read all the channels from the selected channel +number down to 0 as fast as it can. This is done by the hardware +and not the driver (other than turning it on); See the \fILabPC+ +User's Manual\fP and the source code if this doesn't make sense to +you. +.Pp +The driver supports several Analog I/O ioctl calls. These are defined +in sys/aio.h and are intended to be compatable with other AIO board +device drivers. +.Pp +AD_MICRO_PERIOD_SET takes a pointer to a long argument specifying +the number of microseconds between samples. +.Pp +AD_MICRO_PERIOD_GET takes a pointer to a long argument and returns +the current number of microseconds between samples. +.Pp +AD_NGAINS_GET takes a pointer to an integer and returns the number +of different gain settings the board supports. This is 8 for the +LabPC board. +.Pp +AD_NCHANS_GET takes a pointer to an integer and returns the number +of channels the board supports. This is 8 for the LabPC board. +.Pp +AD_SUPPORTED_GAINS returns an array of NGAINS (the number obtained +by AD_NGAINS_GET) doubles. These are the gains the board supports. +.Pp +AD_GAINS_SET takes an array of NCHANS (the number obtained by +AD_NCHANS_GET) integers and sets the board gains. These are indices +into the supported gain array, with one gain for each channel. +.Pp +AD_GAINS_GET takes an array of NCHANS (returned by AD_NCHANS_GET) +integers and returns the cuurrent board gains. +.Sh SEE ALSO +.Xr aio 1 +.Sh BUGS +In general, only those capabilities that I needed are present. In +particular the following notable restrictions are present. +.sp +The analog input is only supported in a clocked conversion mode. +.Pp +You can only set the sample clock down to 15 Hz. Anything slower +needs a modification to the driver to support TCINTEN in order to +chain together the counters to get a lower clock rate. +.Pp +There is no support for clocked D-A output. +.Pp +The 8255 providing the digital I/O ports is supported only in mode +0 (three 8 bit ports). +.Pp +There is support for the SCXI bus off the LABPC board. It is not +in the driver but is a separate user process that accesses the +digital I/O devices. Ask dufault@hda.com for it. +.Pp +MAKEDEV only makes the "basic" devices. You must make the INTERVAL +or SCAN devices by hand. +.Pp +The INTERVAL device sets the clock rate as high as possible so that +it samples all the channels as quickly as it can after the external +signal. This is a silly restriction; it should simply use the +clock setting. +.Sh CAVEATS +National Instrument's (like most other laboratory grade board +vendors) definition of Low-Cost differs dramatically from many on +the net. diff --git a/sys/conf/NOTES b/sys/conf/NOTES index 671bdfd..c30911e 100644 --- a/sys/conf/NOTES +++ b/sys/conf/NOTES @@ -2,7 +2,7 @@ # LINT -- config file for checking all the sources, tries to pull in # as much of the source tree as it can. # -# $Id: LINT,v 1.174 1995/04/24 05:33:59 phk Exp $ +# $Id: LINT,v 1.175 1995/04/25 03:44:04 phk Exp $ # # NB: You probably don't want to try running a kernel built from this # file. Instead, you should start from GENERIC, and add options from @@ -638,6 +638,8 @@ device pca0 at isa? port IO_TIMER1 tty # gp: National Instruments AT-GPIB and AT-GPIB/TNT board # gsc: Genius GS-4500 hand scanner. # joy: joystick +# labpc: National Instrument's Lab-PC and Lab-PC+ + # # Notes on the spigot: # The video spigot is at 0xad6. This port address can not be changed. @@ -659,6 +661,7 @@ device gp0 at isa? port 0x2c0 tty device gsc0 at isa? port "IO_GSC1" tty drq 3 device joy0 at isa? port "IO_GAME" device cy0 at isa? tty irq 10 iomem 0xd4000 vector cyintr +device labpc0 at isa? port 0x260 tty irq 5 vector labpcintr # # PCI devices: diff --git a/sys/conf/files.i386 b/sys/conf/files.i386 index fefdea5..30adf58 100644 --- a/sys/conf/files.i386 +++ b/sys/conf/files.i386 @@ -1,7 +1,7 @@ # This file tells config what files go into building a kernel, # files marked standard are always included. # -# $Id: files.i386,v 1.96 1995/04/23 09:12:00 julian Exp $ +# $Id: files.i386,v 1.97 1995/04/23 18:30:27 wollman Exp $ # aic7xxx_asm optional ahc device-driver \ dependency "$S/dev/aic7xxx/aic7xxx_asm.c" \ @@ -89,6 +89,7 @@ i386/isa/if_zp.c optional zp device-driver i386/isa/isa.c optional isa device-driver i386/isa/joy.c optional joy device-driver i386/isa/lpt.c optional lpt device-driver +i386/isa/labpc.c optional labpc device-driver i386/isa/mcd.c optional mcd device-driver i386/isa/mse.c optional mse device-driver i386/isa/ncr5380.c optional nca device-driver diff --git a/sys/i386/conf/LINT b/sys/i386/conf/LINT index 671bdfd..c30911e 100644 --- a/sys/i386/conf/LINT +++ b/sys/i386/conf/LINT @@ -2,7 +2,7 @@ # LINT -- config file for checking all the sources, tries to pull in # as much of the source tree as it can. # -# $Id: LINT,v 1.174 1995/04/24 05:33:59 phk Exp $ +# $Id: LINT,v 1.175 1995/04/25 03:44:04 phk Exp $ # # NB: You probably don't want to try running a kernel built from this # file. Instead, you should start from GENERIC, and add options from @@ -638,6 +638,8 @@ device pca0 at isa? port IO_TIMER1 tty # gp: National Instruments AT-GPIB and AT-GPIB/TNT board # gsc: Genius GS-4500 hand scanner. # joy: joystick +# labpc: National Instrument's Lab-PC and Lab-PC+ + # # Notes on the spigot: # The video spigot is at 0xad6. This port address can not be changed. @@ -659,6 +661,7 @@ device gp0 at isa? port 0x2c0 tty device gsc0 at isa? port "IO_GSC1" tty drq 3 device joy0 at isa? port "IO_GAME" device cy0 at isa? tty irq 10 iomem 0xd4000 vector cyintr +device labpc0 at isa? port 0x260 tty irq 5 vector labpcintr # # PCI devices: diff --git a/sys/i386/conf/NOTES b/sys/i386/conf/NOTES index 671bdfd..c30911e 100644 --- a/sys/i386/conf/NOTES +++ b/sys/i386/conf/NOTES @@ -2,7 +2,7 @@ # LINT -- config file for checking all the sources, tries to pull in # as much of the source tree as it can. # -# $Id: LINT,v 1.174 1995/04/24 05:33:59 phk Exp $ +# $Id: LINT,v 1.175 1995/04/25 03:44:04 phk Exp $ # # NB: You probably don't want to try running a kernel built from this # file. Instead, you should start from GENERIC, and add options from @@ -638,6 +638,8 @@ device pca0 at isa? port IO_TIMER1 tty # gp: National Instruments AT-GPIB and AT-GPIB/TNT board # gsc: Genius GS-4500 hand scanner. # joy: joystick +# labpc: National Instrument's Lab-PC and Lab-PC+ + # # Notes on the spigot: # The video spigot is at 0xad6. This port address can not be changed. @@ -659,6 +661,7 @@ device gp0 at isa? port 0x2c0 tty device gsc0 at isa? port "IO_GSC1" tty drq 3 device joy0 at isa? port "IO_GAME" device cy0 at isa? tty irq 10 iomem 0xd4000 vector cyintr +device labpc0 at isa? port 0x260 tty irq 5 vector labpcintr # # PCI devices: diff --git a/sys/i386/conf/files.i386 b/sys/i386/conf/files.i386 index fefdea5..30adf58 100644 --- a/sys/i386/conf/files.i386 +++ b/sys/i386/conf/files.i386 @@ -1,7 +1,7 @@ # This file tells config what files go into building a kernel, # files marked standard are always included. # -# $Id: files.i386,v 1.96 1995/04/23 09:12:00 julian Exp $ +# $Id: files.i386,v 1.97 1995/04/23 18:30:27 wollman Exp $ # aic7xxx_asm optional ahc device-driver \ dependency "$S/dev/aic7xxx/aic7xxx_asm.c" \ @@ -89,6 +89,7 @@ i386/isa/if_zp.c optional zp device-driver i386/isa/isa.c optional isa device-driver i386/isa/joy.c optional joy device-driver i386/isa/lpt.c optional lpt device-driver +i386/isa/labpc.c optional labpc device-driver i386/isa/mcd.c optional mcd device-driver i386/isa/mse.c optional mse device-driver i386/isa/ncr5380.c optional nca device-driver diff --git a/sys/i386/i386/conf.c b/sys/i386/i386/conf.c index bf02cf5..60acb7d 100644 --- a/sys/i386/i386/conf.c +++ b/sys/i386/i386/conf.c @@ -42,7 +42,7 @@ * SUCH DAMAGE. * * from: @(#)conf.c 5.8 (Berkeley) 5/12/91 - * $Id: conf.c,v 1.82 1995/04/10 20:40:11 wollman Exp $ + * $Id: conf.c,v 1.83 1995/04/14 15:13:26 dufault Exp $ */ #include <sys/param.h> @@ -1003,6 +1003,19 @@ d_ttycv_t rcdevtotty; #define rcdevtotty nxdevtotty #endif +#include "labpc.h" +#if NLABPC > 0 +d_open_t labpcopen; +d_close_t labpcclose; +d_strategy_t labpcstrategy; +d_ioctl_t labpcioctl; +#else +#define labpcopen nxopen +#define labpcclose nxclose +#define labpcstrategy nxstrategy +#define labpcioctl nxioctl +#endif + /* open, close, read, write, ioctl, stop, reset, ttys, select, mmap, strat */ struct cdevsw cdevsw[] = { @@ -1214,6 +1227,9 @@ struct cdevsw cdevsw[] = { sctargopen, sctargclose, rawread, rawwrite, /*65*/ sctargioctl, nostop, nullreset, nodevtotty,/* sctarg */ seltrue, nommap, sctargstrategy }, + { labpcopen, labpcclose, rawread, rawwrite, /*66*/ + labpcioctl, nostop, nullreset, nodevtotty,/* labpc */ + seltrue, nommap, labpcstrategy }, }; int nchrdev = sizeof (cdevsw) / sizeof (cdevsw[0]); diff --git a/sys/i386/isa/labpc.c b/sys/i386/isa/labpc.c new file mode 100644 index 0000000..e5ddeac --- /dev/null +++ b/sys/i386/isa/labpc.c @@ -0,0 +1,1051 @@ +/* + * Copyright (c) 1995 HD Associates, Inc. + * All rights reserved. + * + * HD Associates, Inc. + * PO Box 276 + * Pepperell, MA 01463-0276 + * dufault@hda.com + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by HD Associates, Inc. + * 4. The name of HD Associates, Inc. + * may not be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY HD ASSOCIATES ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Written by: + * Peter Dufault + * dufault@hda.com + */ + +#include "labpc.h" +#include "aio.h" + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/time.h> + +#include <sys/systm.h> +#include <sys/devconf.h> + +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/errno.h> +#include <sys/buf.h> +#include <machine/devconf.h> +#include <machine/clock.h> + + +#ifndef LABPC_DEFAULT_HERZ +#define LABPC_DEFAULT_HERZ 500 +#endif + +/* Minor number: + * UUSIDCCC + * UU: Board unit. + * S: SCAN bit for scan enable. + * I: INTERVAL for interval support + * D: 1: Digital I/O, 0: Analog I/O + * CCC: Channel. + * Analog (D==0): + * input: channel must be 0 to 7. + * output: channel must be 0 to 2 + * 0: D-A 0 + * 1: D-A 1 + * 2: Alternate channel 0 then 1 + * + * Digital (D==1): + * input: Channel must be 0 to 2. + * output: Channel must be 0 to 2. + */ + +/* Up to four boards: + */ +#define MAX_UNITS 4 +#define UNIT(dev) (((minor(dev) & 0xB0) >> 6) & 0x3) + +#define SCAN(dev) ((minor(dev) & 0x20) >> 5) +#define INTERVAL(dev) ((minor(dev) & 0x10) >> 4) +#define DIGITAL(dev) ((minor(dev) & 0x08) >> 3) + +/* Eight channels: + */ + +#define CHAN(dev) (minor(dev) & 0x7) + +/* History: Derived from "dt2811.c" March 1995 + */ + +struct ctlr +{ + int err; +#define DROPPED_INPUT 0x100 + int base; + int unit; + unsigned long flags; +#define BUSY 0x00000001 + + u_char cr_image[4]; + + u_short sample_us; + + struct buf start_queue; /* Start queue */ + struct buf *last; /* End of start queue */ + u_char *data; + u_char *data_end; + long tmo; /* Timeout in Herz */ + + int gains[8]; + + dev_t dev; /* Copy of device */ + + void (*starter)(struct ctlr *ctlr, long count); + void (*stop)(struct ctlr *ctlr); + void (*intr)(struct ctlr *ctlr); + + /* Digital I/O support. Copy of Data Control Register for 8255: + */ + u_char dcr_val, dcr_is; + + /* Device configuration structure: + */ + struct kern_devconf kdc; +}; + +#ifdef LOUTB +/* loutb is a slow outb for debugging. The overrun test may fail + * with this for some slower processors. + */ +static inline void loutb(int port, u_char val) +{ + outb(port, val); + DELAY(1); +} +#else +#define loutb(port, val) outb(port, val) +#endif + +struct ctlr **labpcs; /* XXX: Should be dynamic */ + +/* CR_EXPR: A macro that sets the shadow register in addition to + * sending out the data. + */ +#define CR_EXPR(LABPC, CR, EXPR) do { \ + (LABPC)->cr_image[CR - 1] EXPR ; \ + loutb((LABPC)->base + (CR == 4 ? 0x0F : CR - 1), (LABPC)->cr_image[(CR - 1)]); \ +} while (0) + +#define CR_CLR(LABPC, CR) CR_EXPR(LABPC, CR, &=0) +#define CR_REFRESH(LABPC, CR) CR_EXPR(LABPC, CR, &=0xff) +#define CR_SET(LABPC, CR, EXPR) CR_EXPR(LABPC, CR, = EXPR) + +/* Configuration and Status Register Group. + */ +#define CR1(LABPC) ((LABPC)->base + 0x00) /* Page 4-5 */ + #define SCANEN 0x80 + #define GAINMASK 0x70 + #define GAIN(LABPC, SEL) do { \ + (LABPC)->cr_image[1 - 1] &= ~GAINMASK; \ + (LABPC)->cr_image[1 - 1] |= (SEL << 4); \ + loutb((LABPC)->base + (1 - 1), (LABPC)->cr_image[(1 - 1)]); \ + } while (0) + + #define TWOSCMP 0x08 + #define MAMASK 0x07 + #define MA(LABPC, SEL) do { \ + (LABPC)->cr_image[1 - 1] &= ~MAMASK; \ + (LABPC)->cr_image[1 - 1] |= SEL; \ + loutb((LABPC)->base + (1 - 1), (LABPC)->cr_image[(1 - 1)]); \ + } while (0) + +#define STATUS(LABPC) ((LABPC)->base + 0x00) /* Page 4-7 */ + #define LABPCPLUS 0x80 + #define EXTGATA0 0x40 + #define GATA0 0x20 + #define DMATC 0x10 + #define CNTINT 0x08 + #define OVERFLOW 0x04 + #define OVERRUN 0x02 + #define DAVAIL 0x01 + +#define CR2(LABPC) ((LABPC)->base + 0x01) /* Page 4-9 */ + #define LDAC1 0x80 + #define LDAC0 0x40 + #define _2SDAC1 0x20 + #define _2SDAC0 0x10 + #define TBSEL 0x08 + #define SWTRIG 0x04 + #define HWTRIG 0x02 + #define PRETRIG 0x01 + #define SWTRIGGERRED(LABPC) ((LABPC->cr_image[1]) & SWTRIG) + +#define CR3(LABPC) ((LABPC)->base + 0x02) /* Page 4-11 */ + #define FIFOINTEN 0x20 + #define ERRINTEN 0x10 + #define CNTINTEN 0x08 + #define TCINTEN 0x04 + #define DIOINTEN 0x02 + #define DMAEN 0x01 + + #define ALLINTEN 0x3E + #define FIFOINTENABLED(LABPC) ((LABPC->cr_image[2]) & FIFOINTEN) + +#define CR4(LABPC) ((LABPC)->base + 0x0F) /* Page 4-13 */ + #define ECLKRCV 0x10 + #define SE_D 0x08 + #define ECKDRV 0x04 + #define EOIRCV 0x02 + #define INTSCAN 0x01 + +/* Analog Input Register Group + */ +#define ADFIFO(LABPC) ((LABPC)->base + 0x0A) /* Page 4-16 */ +#define ADCLEAR(LABPC) ((LABPC)->base + 0x08) /* Page 4-18 */ +#define ADSTART(LABPC) ((LABPC)->base + 0x03) /* Page 4-19 */ +#define DMATCICLR(LABPC) ((LABPC)->base + 0x0A) /* Page 4-20 */ + +/* Analog Output Register Group + */ +#define DAC0L(LABPC) ((LABPC)->base + 0x04) /* Page 4-22 */ +#define DAC0H(LABPC) ((LABPC)->base + 0x05) /* Page 4-22 */ +#define DAC1L(LABPC) ((LABPC)->base + 0x06) /* Page 4-22 */ +#define DAC1H(LABPC) ((LABPC)->base + 0x07) /* Page 4-22 */ + +/* 8253 registers: + */ +#define A0DATA(LABPC) ((LABPC)->base + 0x14) +#define A1DATA(LABPC) ((LABPC)->base + 0x15) +#define A2DATA(LABPC) ((LABPC)->base + 0x16) +#define AMODE(LABPC) ((LABPC)->base + 0x17) + +#define TICR(LABPC) ((LABPC)->base + 0x0c) + +#define B0DATA(LABPC) ((LABPC)->base + 0x18) +#define B1DATA(LABPC) ((LABPC)->base + 0x19) +#define B2DATA(LABPC) ((LABPC)->base + 0x1A) +#define BMODE(LABPC) ((LABPC)->base + 0x1B) + +/* 8255 registers: + */ + +#define PORTX(LABPC, X) ((LABPC)->base + 0x10 + X) + +#define PORTA(LABPC) PORTX(LABPC, 0) +#define PORTB(LABPC) PORTX(LABPC, 1) +#define PORTC(LABPC) PORTX(LABPC, 2) + +#define DCR(LABPC) ((LABPC)->base + 0x13) + +extern int labpcattach(struct isa_device *dev); +extern int labpcdetach(struct isa_device *dev); +extern int labpcprobe(struct isa_device *dev); +struct isa_driver labpcdriver = + { labpcprobe, labpcattach, "labpc", 0 /* , labpcdetach */ }; + +static void +bp_done(struct buf *bp, int err) +{ + if (err) + { + bp->b_error = err; + bp->b_flags |= B_ERROR; + } + + biodone(bp); +} + +static void tmo_stop(void *p); + +static void +done_and_dequeu(struct ctlr *ctlr, struct buf *bp, int err) +{ + static void start(struct ctlr *ctlr); + + bp->b_resid = ctlr->data_end - ctlr->data; + + ctlr->data = 0; + + ctlr->start_queue.b_actf = bp->b_actf; + bp_done(bp, err); + + untimeout(tmo_stop, ctlr); + + start(ctlr); +} + +static inline void +ad_clear(struct ctlr *ctlr) +{ + int i; + loutb(ADCLEAR(ctlr), 0); + for (i = 0; i < 10000 && (inb(STATUS(ctlr)) & GATA0); i++) + ; + (void)inb(ADFIFO(ctlr)); + (void)inb(ADFIFO(ctlr)); +} + +/* reset: Reset the board following the sequence on page 5-1 + */ +static inline void +reset(struct ctlr *ctlr) +{ + int s = splhigh(); + + CR_CLR(ctlr, 3); /* Turn off interrupts first */ + splx(s); + + CR_CLR(ctlr, 1); + CR_CLR(ctlr, 2); + CR_CLR(ctlr, 4); + + loutb(AMODE(ctlr), 0x34); + loutb(A0DATA(ctlr),0x0A); + loutb(A0DATA(ctlr),0x00); + + loutb(DMATCICLR(ctlr), 0x00); + loutb(TICR(ctlr), 0x00); + + ad_clear(ctlr); + + loutb(DAC0L(ctlr), 0); + loutb(DAC0H(ctlr), 0); + loutb(DAC1L(ctlr), 0); + loutb(DAC1H(ctlr), 0); + + ad_clear(ctlr); +} + +static int +labpc_goaway(struct kern_devconf *kdc, int force) +{ + if(force) { + dev_detach(kdc); + return 0; + } else { + return EBUSY; /* XXX fix */ + } +} + +static struct kern_devconf kdc_template = { + 0, 0, 0, /* filled in by dev_attach */ + "labpc", 0, { MDDT_ISA, 0, "tty" }, + isa_generic_externalize, 0, labpc_goaway, ISA_EXTERNALLEN, + &kdc_isa0, /* parent */ + 0, /* parentdata */ + DC_UNKNOWN, + "?" /* Description (filled in later ) */ +}; + +static inline void +labpc_registerdev(struct isa_device *id) +{ + struct kern_devconf *kdc = &labpcs[id->id_unit]->kdc; + kdc->kdc_unit = id->id_unit; + kdc->kdc_parentdata = id; + dev_attach(kdc); +} + +/* overrun: slam the start convert register and OVERRUN should get set: + */ +static u_char +overrun(struct ctlr *ctlr) +{ + int i; + + u_char status = inb(STATUS(ctlr)); + for (i = 0; ((status & OVERRUN) == 0) && i < 100; i++) + { + loutb(ADSTART(ctlr), 1); + status = inb(STATUS(ctlr)); + } + + return status; +} + +static int +labpcinit(void) +{ + if (NLABPC > MAX_UNITS) + return 0; + + labpcs = malloc(NLABPC * sizeof(struct ctlr *), M_DEVBUF, M_NOWAIT); + if (labpcs) + { + bzero(labpcs, NLABPC * sizeof(struct cltr *)); + return 1; + } + return 0; +} + +int labpcprobe(struct isa_device *dev) +{ + static unit; + struct ctlr scratch, *ctlr; + u_char status; + + if (!labpcs) + { + if (labpcinit() == 0) + { + printf("labpcprobe: init failed\n"); + return 0; + } + } + + if (unit > NLABPC) + { + printf("Too many LAB-PCs. Reconfigure O/S.\n"); + return 0; + } + ctlr = &scratch; /* Need somebody with the right base for the macros */ + ctlr->base = dev->id_iobase; + + /* XXX: There really isn't a perfect way to probe this board. + * Here is my best attempt: + */ + reset(ctlr); + + /* After reset none of these bits should be set: + */ + status = inb(STATUS(ctlr)); + if (status & (GATA0 | OVERFLOW | DAVAIL | OVERRUN)) + return 0; + + /* Now try to overrun the board FIFO and get the overrun bit set: + */ + status = overrun(ctlr); + + if ((status & OVERRUN) == 0) /* No overrun bit set? */ + return 0; + + /* Assume we have a board. + */ + reset(ctlr); + + if ( (labpcs[unit] = malloc(sizeof(struct ctlr), M_DEVBUF, M_NOWAIT)) ) + { + struct ctlr *l = labpcs[unit]; + + bzero(l, sizeof(struct ctlr)); + l->base = ctlr->base; + dev->id_unit = l->unit = unit; + + l->kdc = kdc_template; + l->kdc.kdc_state = DC_IDLE; + + if ((status & LABPCPLUS) == 0) + l->kdc.kdc_description = "National Instrument's LabPC+"; + else + l->kdc.kdc_description = "National Instrument's LabPC"; + + unit++; + return 1; + } + else + { + printf("labpc%d: Can't malloc.\n", unit); + return 0; + } +} + +/* attach: Set things in a normal state. + */ +int labpcattach(struct isa_device *dev) +{ + struct ctlr *ctlr = labpcs[dev->id_unit]; + ctlr->sample_us = (1000000.0 / (double)LABPC_DEFAULT_HERZ) + .50; + reset(ctlr); + labpc_registerdev(dev); + + ctlr->dcr_val = 0x80; + ctlr->dcr_is = 0x80; + loutb(DCR(ctlr), ctlr->dcr_val); + + return 1; +} + +int +labpcdetach(struct isa_device *id) +{ + struct ctlr *ctlr = labpcs[id->id_unit]; + CR_CLR(ctlr, 3); + reset(ctlr); + dev_detach(&ctlr->kdc); + return 0; +} + +/* Null handlers: + */ +static void null_intr (struct ctlr *ctlr) { } +static void null_start(struct ctlr *ctlr, long count) { } +static void null_stop (struct ctlr *ctlr) { } + +static inline void +trigger(struct ctlr *ctlr) +{ + CR_EXPR(ctlr, 2, |= SWTRIG); +} + +static void +ad_start(struct ctlr *ctlr, long count) +{ + if (!SWTRIGGERRED(ctlr)) { + int chan = CHAN(ctlr->dev); + CR_EXPR(ctlr, 1, &= ~SCANEN); + CR_EXPR(ctlr, 2, &= ~TBSEL); + + MA(ctlr, chan); + GAIN(ctlr, ctlr->gains[chan]); + + if (SCAN(ctlr->dev)) + CR_EXPR(ctlr, 1, |= SCANEN); + + loutb(AMODE(ctlr), 0x34); + loutb(A0DATA(ctlr), (u_char)((ctlr->sample_us & 0xff))); + loutb(A0DATA(ctlr), (u_char)((ctlr->sample_us >> 8)&0xff)); + loutb(AMODE(ctlr), 0x70); + + ad_clear(ctlr); + trigger(ctlr); + } + + ctlr->tmo = ((count + 16) * (long)ctlr->sample_us * hz) / 1000000 + 1; +} + +static void +ad_interval_start(struct ctlr *ctlr, long count) +{ + int chan = CHAN(ctlr->dev); + int n_frames = count / (chan + 1); + + if (!SWTRIGGERRED(ctlr)) { + CR_EXPR(ctlr, 1, &= ~SCANEN); + CR_EXPR(ctlr, 2, &= ~TBSEL); + + MA(ctlr, chan); + GAIN(ctlr, ctlr->gains[chan]); + + /* XXX: Is it really possible that you clear INTSCAN as + * the documentation says? That seems pretty unlikely. + */ + CR_EXPR(ctlr, 4, &= ~INTSCAN); /* XXX: Is this possible? */ + + /* Program the sample interval counter to run as fast as + * possible. + */ + loutb(AMODE(ctlr), 0x34); + loutb(A0DATA(ctlr), (u_char)(0x02)); + loutb(A0DATA(ctlr), (u_char)(0x00)); + loutb(AMODE(ctlr), 0x70); + + /* Program the interval scanning counter to run at the sample + * frequency. + */ + loutb(BMODE(ctlr), 0x74); + loutb(B1DATA(ctlr), (u_char)((ctlr->sample_us & 0xff))); + loutb(B1DATA(ctlr), (u_char)((ctlr->sample_us >> 8)&0xff)); + CR_EXPR(ctlr, 1, |= SCANEN); + + ad_clear(ctlr); + trigger(ctlr); + } + + /* Each frame time takes two microseconds per channel times + * the number of channels being sampled plus the sample period. + */ + ctlr->tmo = ((n_frames + 16) * + ((long)ctlr->sample_us + (chan + 1 ) * 2 ) * hz) / 1000000 + 1; +} + +static void +all_stop(struct ctlr *ctlr) +{ + reset(ctlr); +} + +static void +tmo_stop(void *p) +{ + struct ctlr *ctlr = (struct ctlr *)p; + struct buf *bp; + int s = spltty(); + + if (ctlr == 0) + { + printf("labpc?: Null ctlr struct?\n"); + return; + } + + printf("labpc%d: timeout\n", ctlr->unit); + + (*ctlr->stop)(ctlr); + + bp = ctlr->start_queue.b_actf; + + if (bp == 0) { + printf("labpc%d timeout: Null bp.\n", ctlr->unit); + + /* No more data being transferred. + */ + return; + } + + done_and_dequeu(ctlr, bp, ETIMEDOUT); + splx(s); +} + +static void ad_intr(struct ctlr *ctlr) +{ + u_char status; + + while ( (status = (inb(STATUS(ctlr)) & (DAVAIL|OVERRUN|OVERFLOW)) ) ) + { + if ((status & (OVERRUN|OVERFLOW))) + { + struct buf *bp = ctlr->start_queue.b_actf; + + printf("ad_intr: error: data %p, status %x", + ctlr->data, status); + + if (status & OVERRUN) + printf(" OVERRUN"); + + if (status & OVERFLOW) + printf(" OVERFLOW"); + + printf("\n"); + + (*ctlr->stop)(ctlr); + + /* There may not be a bp if the interrupt went off between + * frames, that is, when no process was ready to receive and + * we are using a mode that is driven by the sample clock. + */ + if (bp) + { + done_and_dequeu(ctlr, bp, EIO); + return; + } + else + ctlr->err = status; /* Set overrun condition */ + } + else /* FIFO interrupt */ + { + if (ctlr->data) + { + *ctlr->data++ = inb(ADFIFO(ctlr)); + if (ctlr->data == ctlr->data_end) /* Normal completion */ + { + struct buf *bp = ctlr->start_queue.b_actf; + + done_and_dequeu(ctlr, bp, 0); + return; + } + } + else /* Interrupt with no where to put the data. */ + { + (void)inb(ADFIFO(ctlr)); + ctlr->err = DROPPED_INPUT; + return; + } + } + } +} + +void labpcintr(int unit) +{ + struct ctlr *ctlr = labpcs[unit]; + (*ctlr->intr)(ctlr); +} + +/* mode_change_needed: Return whether or not we can open again, or + * if the new mode is inconsistent with an already opened mode. + * We only permit multiple opens for digital I/O now. + */ + +static int +mode_change_needed(dev_t current, dev_t next) +{ + return ! (DIGITAL(current) && DIGITAL(next)); +} + +int +labpcopen(dev_t dev, int flags, int fmt, struct proc *p) +{ + u_short unit = UNIT(dev); + + struct ctlr *ctlr; + + if (unit >= MAX_UNITS) + return ENXIO; + + ctlr = labpcs[unit]; + + if (ctlr == 0) + return ENXIO; + + /* Don't allow another open if we have to change modes. + */ + + if ( (ctlr->flags & BUSY) == 0) + { + ctlr->flags |= BUSY; + ctlr->kdc.kdc_state = DC_BUSY; + + reset(ctlr); + + ctlr->err = 0; + ctlr->dev = dev; + + ctlr->intr = null_intr; + ctlr->starter = null_start; + ctlr->stop = null_stop; + + CR_EXPR(ctlr, 3, |= ERRINTEN); + } + else if (mode_change_needed(ctlr->dev, dev)) + return EBUSY; + + return 0; +} + +int +labpcclose(dev_t dev, int flags, int fmt, struct proc *p) +{ + struct ctlr *ctlr = labpcs[UNIT(dev)]; + + (*ctlr->stop)(ctlr); + + ctlr->kdc.kdc_state = DC_IDLE; + ctlr->flags &= ~BUSY; + + return 0; +} + +/* Start: Start a frame going in or out. + */ +static void +start(struct ctlr *ctlr) +{ + struct buf *bp; + + if ((bp = ctlr->start_queue.b_actf) == 0) + { + /* We must turn off FIFO interrupts when there is no + * place to put the data. We have to get back to + * reading before the FIFO overflows. + */ + CR_EXPR(ctlr, 3, &= ~FIFOINTEN); + ctlr->start_queue.b_active = 0; + return; + } + + ctlr->data = (u_char *)bp->b_un.b_addr; + ctlr->data_end = ctlr->data + bp->b_bcount; + + if (!FIFOINTENABLED(ctlr)) /* We can store the data again */ + { + if (ctlr->err) /* Dropped input between records */ + { + done_and_dequeu(ctlr, bp, ENOSPC); + return; + } + CR_EXPR(ctlr, 3, |= FIFOINTEN); + } + ctlr->err = 0; + + (*ctlr->starter)(ctlr, bp->b_bcount); + + timeout(tmo_stop, ctlr, ctlr->tmo); +} + +static void +ad_strategy(struct buf *bp, struct ctlr *ctlr) +{ + int s; + + s = spltty(); + bp->b_actf = NULL; + + if (ctlr->start_queue.b_active) + { + ctlr->last->b_actf = bp; + ctlr->last = bp; + } + else + { + ctlr->start_queue.b_active = 1; + ctlr->start_queue.b_actf = bp; + ctlr->last = bp; + start(ctlr); + } + splx(s); +} + +/* da_strategy: Send data to the D-A. The CHAN field should be + * 0: D-A port 0 + * 1: D-A port 1 + * 2: Alternate port 0 then port 1 + * + * XXX: + * + * 1. There is no state for CHAN field 2: + * the first sample in each buffer goes to channel 0. + * + * 2. No interrupt support yet. + */ +void da_strategy(struct buf *bp, struct ctlr *ctlr) +{ + int len; + u_char *data; + int port; + int i; + + switch(CHAN(bp->b_dev)) + { + case 0: + port = DAC0L(ctlr); + break; + + case 1: + port = DAC1L(ctlr); + break; + + case 2: /* Device 2 handles both ports interleaved. */ + if (bp->b_bcount <= 2) + { + port = DAC0L(ctlr); + break; + } + + len = bp->b_bcount / 2; + data = (u_char *)bp->b_un.b_addr; + + for (i = 0; i < len; i++) + { + loutb(DAC0H(ctlr), *data++); + loutb(DAC0L(ctlr), *data++); + loutb(DAC1H(ctlr), *data++); + loutb(DAC1L(ctlr), *data++); + } + + bp->b_resid = bp->b_bcount & 3; + bp_done(bp, 0); + return; + + default: + bp_done(bp, ENXIO); + return; + } + + /* Port 0 or 1 falls through to here. + */ + if (bp->b_bcount & 1) /* Odd transfers are illegal */ + bp_done(bp, EIO); + + len = bp->b_bcount; + data = (u_char *)bp->b_un.b_addr; + + for (i = 0; i < len; i++) + { + loutb(port + 1, *data++); + loutb(port, *data++); + } + + bp->b_resid = 0; + + bp_done(bp, 0); +} + +/* Input masks for MODE 0 of the ports treating PC as a single + * 8 bit port. Set these bits to set the port to input. + */ + /* A B lowc highc combined */ +static u_char set_input[] = { 0x10, 0x02, 0x01, 0x08, 0x09 }; + +static void flush_dcr(struct ctlr *ctlr) +{ + if (ctlr->dcr_is != ctlr->dcr_val) + { + loutb(DCR(ctlr), ctlr->dcr_val); + ctlr->dcr_is = ctlr->dcr_val; + } +} + +/* do: Digital output + */ +static void +digital_out_strategy(struct buf *bp, struct ctlr *ctlr) +{ + int len; + u_char *data; + int port; + int i; + int chan = CHAN(bp->b_dev); + + ctlr->dcr_val &= ~set_input[chan]; /* Digital out: Clear bit */ + flush_dcr(ctlr); + + port = PORTX(ctlr, chan); + + len = bp->b_bcount; + data = (u_char *)bp->b_un.b_addr; + + for (i = 0; i < len; i++) + { + loutb(port, *data++); + } + + bp->b_resid = 0; + + bp_done(bp, 0); +} + +/* digital_in_strategy: Digital input + */ +static void +digital_in_strategy(struct buf *bp, struct ctlr *ctlr) +{ + int len; + u_char *data; + int port; + int i; + int chan = CHAN(bp->b_dev); + + ctlr->dcr_val |= set_input[chan]; /* Digital in: Set bit */ + flush_dcr(ctlr); + port = PORTX(ctlr, chan); + + len = bp->b_bcount; + data = (u_char *)bp->b_un.b_addr; + + for (i = 0; i < len; i++) + { + *data++ = inb(port); + } + + bp->b_resid = 0; + + bp_done(bp, 0); +} + + +void +labpcstrategy(struct buf *bp) +{ + struct ctlr *ctlr = labpcs[UNIT(bp->b_dev)]; + + if (DIGITAL(bp->b_dev)) { + if (bp->b_flags & B_READ) { + ctlr->starter = null_start; + ctlr->stop = all_stop; + ctlr->intr = null_intr; + digital_in_strategy(bp, ctlr); + } + else + { + ctlr->starter = null_start; + ctlr->stop = all_stop; + ctlr->intr = null_intr; + digital_out_strategy(bp, ctlr); + } + } + else { + if (bp->b_flags & B_READ) { + + ctlr->starter = INTERVAL(ctlr->dev) ? ad_interval_start : ad_start; + ctlr->stop = all_stop; + ctlr->intr = ad_intr; + ad_strategy(bp, ctlr); + } + else + { + ctlr->starter = null_start; + ctlr->stop = all_stop; + ctlr->intr = null_intr; + da_strategy(bp, ctlr); + } + } +} + +int +labpcioctl(dev_t dev, int cmd, caddr_t arg, int mode, struct proc *p) +{ + struct ctlr *ctlr = labpcs[UNIT(dev)]; + + switch(cmd) + { + case AD_MICRO_PERIOD_SET: + { + /* XXX I'm only supporting what I have to, which is + * no slow periods. You can't get any slower than 15 Hz + * with the current setup. To go slower you'll need to + * support TCINTEN in CR3. + */ + + long sample_us = *(long *)arg; + + if (sample_us > 65535) + return EIO; + + ctlr->sample_us = sample_us; + return 0; + } + + case AD_MICRO_PERIOD_GET: + *(long *)arg = ctlr->sample_us; + return 0; + + case AD_NGAINS_GET: + *(int *)arg = 8; + return 0; + + case AD_NCHANS_GET: + *(int *)arg = 8; + return 0; + + case AD_SUPPORTED_GAINS: + { + static double gains[] = {1., 1.25, 2., 5., 10., 20., 50., 100.}; + copyout(gains, *(caddr_t *)arg, sizeof(gains)); + + return 0; + } + + case AD_GAINS_SET: + { + copyin(*(caddr_t *)arg, ctlr->gains, sizeof(ctlr->gains)); + return 0; + } + + case AD_GAINS_GET: + { + copyout(ctlr->gains, *(caddr_t *)arg, sizeof(ctlr->gains)); + return 0; + } + + default: + return ENOTTY; + } +} |