Index: comedi/drivers/ni_mio_common.c =================================================================== RCS file: /var/cvs/comedi/comedi/drivers/ni_mio_common.c,v retrieving revision 1.43 diff -u -r1.43 ni_mio_common.c --- comedi/drivers/ni_mio_common.c 2001/02/05 02:58:40 1.43 +++ comedi/drivers/ni_mio_common.c 2001/02/05 02:59:48 @@ -161,6 +161,11 @@ static int ni_dio(comedi_device *dev,comedi_subdevice *s,comedi_trig *it); #endif +static int ni_serial_insn_config(comedi_device *dev,comedi_subdevice *s, + comedi_insn *insn,lsampl_t *data); +static int ni_serial_insn_bits(comedi_device *dev,comedi_subdevice *s, + comedi_insn *insn,lsampl_t *data); + #ifdef USE_TRIG static int ni_eeprom(comedi_device *dev,comedi_subdevice *s,comedi_trig *it); #endif @@ -203,6 +208,11 @@ #define AIMODE_SCAN 2 #define AIMODE_SAMPLE 3 +/* supported serial clock intervals */ +#define SERIAL_DISABLED 0 +#define SERIAL_600NS 600 +#define SERIAL_1_2US 1200 +#define SERIAL_10US 10000 static void ni_E_interrupt(int irq,void *d,struct pt_regs * regs) { @@ -1991,6 +2001,203 @@ return 2; } +static int ni_serial_insn_config(comedi_device *dev,comedi_subdevice *s, + comedi_insn *insn,lsampl_t *data) +{ +#ifdef DEBUG_DIO + printk("SPI serial I/O Config %d\n", data[0]); +#endif + + if(insn->n!=1)return -EINVAL; + devpriv->serial_interval_ns = data[0]; + devpriv->serial_hw_mode = 1; + devpriv->dio_control |= DIO_HW_Serial_Enable; + switch(data[0]) { + default: + case SERIAL_DISABLED: + /* Disable (or use software serial) */ + devpriv->serial_hw_mode = 0; + devpriv->dio_control &= ~(DIO_HW_Serial_Enable | + DIO_Software_Serial_Control); + break; + case SERIAL_600NS: + /* Warning: this clock speed is too fast to reliably + control SCXI. */ + devpriv->dio_control &= ~DIO_HW_Serial_Timebase; + devpriv->clock_and_fout |= Slow_Internal_Timebase; + devpriv->clock_and_fout &= ~DIO_Serial_Out_Divide_By_2; + break; + case SERIAL_1_2US: + devpriv->dio_control &= ~DIO_HW_Serial_Timebase; + devpriv->clock_and_fout |= Slow_Internal_Timebase | + DIO_Serial_Out_Divide_By_2; + break; + case SERIAL_10US: + devpriv->dio_control |= DIO_HW_Serial_Timebase; + devpriv->clock_and_fout |= Slow_Internal_Timebase | + DIO_Serial_Out_Divide_By_2; + /* Note: DIO_Serial_Out_Divide_By_2 only affects + 600ns/1.2us. If you turn divide_by_2 off with the + slow clock, you will still get 10us, except then + all your delays are wrong. */ + break; + } + win_out(devpriv->dio_control,DIO_Control_Register); + win_out(devpriv->clock_and_fout,Clock_and_FOUT_Register); + return 1; +} + +static int ni_serial_hw_readwrite8(comedi_device *dev,comedi_subdevice *s, + unsigned char data_out, + unsigned char *data_in) +{ + unsigned int status1; + int err = 0, count = 20; + +#ifdef DEBUG_DIO + printk("ni_serial_hw_readwrite8: outputting 0x%x\n", data_out); +#endif + + if(devpriv->serial_interval_ns == 0) { + err = -EINVAL; + goto Error; + } + + devpriv->dio_output &= ~DIO_Serial_Data_Mask; + devpriv->dio_output |= DIO_Serial_Data_Out(data_out); + win_out(devpriv->dio_output,DIO_Output_Register); + + status1 = win_in(Joint_Status_1_Register); + if(status1 & DIO_Serial_IO_In_Progress_St) { + err = -EBUSY; + goto Error; + } + + devpriv->dio_control |= DIO_HW_Serial_Start; + win_out(devpriv->dio_control,DIO_Control_Register); + devpriv->dio_control &= ~DIO_HW_Serial_Start; + + /* Wait until STC says we're done, but don't loop infinitely. Also, + we don't have to keep updating the window address for this. */ + ni_writew(Joint_Status_1_Register,Window_Address); + while((status1 = ni_readw(Window_Data)) & DIO_Serial_IO_In_Progress_St) { + /* Delay one bit per loop */ + nanodelay(devpriv->serial_interval_ns); + if(--count < 0) { + printk("ni_serial_hw_readwrite8: SPI serial I/O didn't finish in time!\n"); + err = -ETIME; + goto Error; + } + } + + /* Delay for last bit. This delay is absolutely necessary, because + DIO_Serial_IO_In_Progress_St goes high one bit too early. */ + nanodelay(devpriv->serial_interval_ns); + + if(data_in != NULL) { + *data_in = win_in(DIO_Serial_Input_Register); +#ifdef DEBUG_DIO + printk("ni_serial_hw_readwrite8: inputted 0x%x\n", *data_in); +#endif + } + + Error: + win_out(devpriv->dio_control,DIO_Control_Register); + + return err; +} + +static int ni_serial_sw_readwrite8(comedi_device *dev,comedi_subdevice *s, + unsigned char data_out, + unsigned char *data_in) +{ + unsigned char mask, input = 0; + +#ifdef DEBUG_DIO + printk("ni_serial_sw_readwrite8: outputting 0x%x\n", data_out); +#endif + + /* Wait for one bit before transfer */ + nanodelay(devpriv->serial_interval_ns); + + for(mask = 0x80; mask; mask >>= 1) { + /* Output current bit; note that we cannot touch s->state + because it is a per-subdevice field, and serial is + a separate subdevice from DIO. */ + devpriv->dio_output &= ~DIO_SDOUT; + if(data_out & mask) { + devpriv->dio_output |= DIO_SDOUT; + } + win_out(devpriv->dio_output,DIO_Output_Register); + + /* Assert SDCLK (active low, inverted), wait for half of + the delay, deassert SDCLK, and wait for the other half. */ + devpriv->dio_control |= DIO_Software_Serial_Control; + win_out(devpriv->dio_control,DIO_Control_Register); + + nanodelay(devpriv->serial_interval_ns / 2); + + devpriv->dio_control &= ~DIO_Software_Serial_Control; + win_out(devpriv->dio_control,DIO_Control_Register); + + nanodelay(devpriv->serial_interval_ns / 2); + + /* Input current bit */ + if(ni_readw(DIO_Parallel_Input) & DIO_SDIN) { + input |= mask; + } + } +#ifdef DEBUG_DIO + printk("ni_serial_sw_readwrite8: inputted 0x%x\n", input); +#endif + if(data_in) *data_in = input; + + return 0; +} + +static int ni_serial_insn_bits(comedi_device *dev,comedi_subdevice *s, + comedi_insn *insn,lsampl_t *data) +{ + int err = insn->n; + lsampl_t data_out, data_in, num_bits; + unsigned char byteOut, byteIn; + +#ifdef DEBUG_DIO + printk("ni_serial_insn_bits: num_bits=0x%x data_out=0x%x\n", data[0], + data[1]); +#endif + + if(insn->n!=2) return -EINVAL; + + num_bits = data[0]; + + if((num_bits % 8) != 0) return -EINVAL; + + data_out = data[1]; + data_in = 0; + while(num_bits > 0) { + /* Read from MSB to LSB */ + data_in <<= 8; + + byteOut = (data_out >> (num_bits - 8)) & 0xff; + if(devpriv->serial_hw_mode) { + err = ni_serial_hw_readwrite8(dev,s,byteOut,&byteIn); + } else if(devpriv->serial_interval_ns > 0) { + err = ni_serial_sw_readwrite8(dev,s,byteOut,&byteIn); + } else { + printk("ni_serial_insn_bits: serial disabled!\n"); + return -EINVAL; + } + if(err < 0) return err; + data_in |= byteIn; + + /* Write from MSB to LSB */ + num_bits -= 8; + } + data[1] = data_in; + return insn->n; +} + /* HACK! @@ -2031,7 +2238,7 @@ { comedi_subdevice *s; - dev->n_subdevices=7; + dev->n_subdevices=8; if(alloc_subdevices(dev)<0) return -ENOMEM; @@ -2144,11 +2351,24 @@ #ifdef USE_TRIG s->trig[0]=ni_eeprom; #endif - + + /* SPI serial I/O */ + s=dev->subdevices+7; + s->type=COMEDI_SUBD_SERIAL; + s->subdev_flags=SDF_READABLE|SDF_WRITEABLE|SDF_INTERNAL; + s->n_chan=1; + s->maxdata=0xff; + s->insn_bits=ni_serial_insn_bits; + s->insn_config=ni_serial_insn_config; + + /* serial configuration */ + devpriv->serial_interval_ns = 0; + devpriv->serial_hw_mode = 0; + /* ai configuration */ ni_ai_reset(dev,dev->subdevices+0); - win_out(0x1ba0,Clock_and_FOUT_Register); - + devpriv->clock_and_fout = 0x1ba0; + win_out(devpriv->clock_and_fout,Clock_and_FOUT_Register); /* analog output configuration */ Index: include/linux/comedi.h =================================================================== RCS file: /var/cvs/comedi/include/linux/comedi.h,v retrieving revision 1.5 diff -u -r1.5 comedi.h --- include/linux/comedi.h 2001/01/30 20:19:39 1.5 +++ include/linux/comedi.h 2001/02/05 02:59:50 @@ -148,7 +148,7 @@ #define COMEDI_SUBD_MEMORY 8 /* memory, EEPROM, DPRAM */ #define COMEDI_SUBD_CALIB 9 /* calibration DACs */ #define COMEDI_SUBD_PROC 10 /* processor, DSP */ - +#define COMEDI_SUBD_SERIAL 11 /* SPI serial I/O */ #define COMEDI_INPUT 0 #define COMEDI_OUTPUT 1 Index: include/linux/comedidev.h =================================================================== RCS file: /var/cvs/comedi/include/linux/comedidev.h,v retrieving revision 1.8 diff -u -r1.8 comedidev.h --- include/linux/comedidev.h 2001/02/05 02:09:19 1.8 +++ include/linux/comedidev.h 2001/02/05 02:59:51 @@ -276,6 +276,18 @@ return 0; } +static inline int nanodelay(unsigned long ns) +{ + /* We round up, so the result should always be longer than the + * specified time. It's probably not much more precise than + * using udelay(). Hopefully, one day Linux will have an + * nanodelay() function. */ + unsigned long loops_per_us = (loops_per_sec + 999999) / 1000000; + unsigned long loops = ((ns * loops_per_us) + 999) / 1000; + /* printk("nanodelay: ns=%ld loops=%ld\n", ns, loops); */ + __delay(loops); +} + #ifdef LINUX_V20 extern struct symbol_table comedi_syms;