1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * mpc624.c 4 * Hardware driver for a Micro/sys inc. MPC-624 PC/104 board 5 * 6 * COMEDI - Linux Control and Measurement Device Interface 7 * Copyright (C) 2000 David A. Schleef <ds@schleef.org> 8 */ 9 10 /* 11 * Driver: mpc624 12 * Description: Micro/sys MPC-624 PC/104 board 13 * Devices: [Micro/sys] MPC-624 (mpc624) 14 * Author: Stanislaw Raczynski <sraczynski@op.pl> 15 * Updated: Thu, 15 Sep 2005 12:01:18 +0200 16 * Status: working 17 * 18 * The Micro/sys MPC-624 board is based on the LTC2440 24-bit sigma-delta 19 * ADC chip. 20 * 21 * Subdevices supported by the driver: 22 * - Analog In: supported 23 * - Digital I/O: not supported 24 * - LEDs: not supported 25 * - EEPROM: not supported 26 * 27 * Configuration Options: 28 * [0] - I/O base address 29 * [1] - conversion rate 30 * Conversion rate RMS noise Effective Number Of Bits 31 * 0 3.52kHz 23uV 17 32 * 1 1.76kHz 3.5uV 20 33 * 2 880Hz 2uV 21.3 34 * 3 440Hz 1.4uV 21.8 35 * 4 220Hz 1uV 22.4 36 * 5 110Hz 750uV 22.9 37 * 6 55Hz 510nV 23.4 38 * 7 27.5Hz 375nV 24 39 * 8 13.75Hz 250nV 24.4 40 * 9 6.875Hz 200nV 24.6 41 * [2] - voltage range 42 * 0 -1.01V .. +1.01V 43 * 1 -10.1V .. +10.1V 44 */ 45 46 #include <linux/module.h> 47 #include "../comedidev.h" 48 49 #include <linux/delay.h> 50 51 /* Offsets of different ports */ 52 #define MPC624_MASTER_CONTROL 0 /* not used */ 53 #define MPC624_GNMUXCH 1 /* Gain, Mux, Channel of ADC */ 54 #define MPC624_ADC 2 /* read/write to/from ADC */ 55 #define MPC624_EE 3 /* read/write to/from serial EEPROM via I2C */ 56 #define MPC624_LEDS 4 /* write to LEDs */ 57 #define MPC624_DIO 5 /* read/write to/from digital I/O ports */ 58 #define MPC624_IRQ_MASK 6 /* IRQ masking enable/disable */ 59 60 /* Register bits' names */ 61 #define MPC624_ADBUSY BIT(5) 62 #define MPC624_ADSDO BIT(4) 63 #define MPC624_ADFO BIT(3) 64 #define MPC624_ADCS BIT(2) 65 #define MPC624_ADSCK BIT(1) 66 #define MPC624_ADSDI BIT(0) 67 68 /* 32-bit output value bits' names */ 69 #define MPC624_EOC_BIT BIT(31) 70 #define MPC624_DMY_BIT BIT(30) 71 #define MPC624_SGN_BIT BIT(29) 72 73 /* SDI Speed/Resolution Programming bits */ 74 #define MPC624_OSR(x) (((x) & 0x1f) << 27) 75 #define MPC624_SPEED_3_52_KHZ MPC624_OSR(0x11) 76 #define MPC624_SPEED_1_76_KHZ MPC624_OSR(0x12) 77 #define MPC624_SPEED_880_HZ MPC624_OSR(0x13) 78 #define MPC624_SPEED_440_HZ MPC624_OSR(0x14) 79 #define MPC624_SPEED_220_HZ MPC624_OSR(0x15) 80 #define MPC624_SPEED_110_HZ MPC624_OSR(0x16) 81 #define MPC624_SPEED_55_HZ MPC624_OSR(0x17) 82 #define MPC624_SPEED_27_5_HZ MPC624_OSR(0x18) 83 #define MPC624_SPEED_13_75_HZ MPC624_OSR(0x19) 84 #define MPC624_SPEED_6_875_HZ MPC624_OSR(0x1f) 85 86 struct mpc624_private { 87 unsigned int ai_speed; 88 }; 89 90 /* -------------------------------------------------------------------------- */ 91 static const struct comedi_lrange range_mpc624_bipolar1 = { 92 1, 93 { 94 /* BIP_RANGE(1.01) this is correct, */ 95 /* but my MPC-624 actually seems to have a range of 2.02 */ 96 BIP_RANGE(2.02) 97 } 98 }; 99 100 static const struct comedi_lrange range_mpc624_bipolar10 = { 101 1, 102 { 103 /* BIP_RANGE(10.1) this is correct, */ 104 /* but my MPC-624 actually seems to have a range of 20.2 */ 105 BIP_RANGE(20.2) 106 } 107 }; 108 109 static unsigned int mpc624_ai_get_sample(struct comedi_device *dev, 110 struct comedi_subdevice *s) 111 { 112 struct mpc624_private *devpriv = dev->private; 113 unsigned int data_out = devpriv->ai_speed; 114 unsigned int data_in = 0; 115 unsigned int bit; 116 int i; 117 118 /* Start reading data */ 119 udelay(1); 120 for (i = 0; i < 32; i++) { 121 /* Set the clock low */ 122 outb(0, dev->iobase + MPC624_ADC); 123 udelay(1); 124 125 /* Set the ADSDI line for the next bit (send to MPC624) */ 126 bit = (data_out & BIT(31)) ? MPC624_ADSDI : 0; 127 outb(bit, dev->iobase + MPC624_ADC); 128 udelay(1); 129 130 /* Set the clock high */ 131 outb(MPC624_ADSCK | bit, dev->iobase + MPC624_ADC); 132 udelay(1); 133 134 /* Read ADSDO on high clock (receive from MPC624) */ 135 data_in <<= 1; 136 data_in |= (inb(dev->iobase + MPC624_ADC) & MPC624_ADSDO) >> 4; 137 udelay(1); 138 139 data_out <<= 1; 140 } 141 142 /* 143 * Received 32-bit long value consist of: 144 * 31: EOC - (End Of Transmission) bit - should be 0 145 * 30: DMY - (Dummy) bit - should be 0 146 * 29: SIG - (Sign) bit - 1 if positive, 0 if negative 147 * 28: MSB - (Most Significant Bit) - the first bit of the 148 * conversion result 149 * .... 150 * 05: LSB - (Least Significant Bit)- the last bit of the 151 * conversion result 152 * 04-00: sub-LSB - sub-LSBs are basically noise, but when 153 * averaged properly, they can increase 154 * conversion precision up to 29 bits; 155 * they can be discarded without loss of 156 * resolution. 157 */ 158 if (data_in & MPC624_EOC_BIT) 159 dev_dbg(dev->class_dev, "EOC bit is set!"); 160 if (data_in & MPC624_DMY_BIT) 161 dev_dbg(dev->class_dev, "DMY bit is set!"); 162 163 if (data_in & MPC624_SGN_BIT) { 164 /* 165 * Voltage is positive 166 * 167 * comedi operates on unsigned numbers, so mask off EOC 168 * and DMY and don't clear the SGN bit 169 */ 170 data_in &= 0x3fffffff; 171 } else { 172 /* 173 * The voltage is negative 174 * 175 * data_in contains a number in 30-bit two's complement 176 * code and we must deal with it 177 */ 178 data_in |= MPC624_SGN_BIT; 179 data_in = ~data_in; 180 data_in += 1; 181 /* clear EOC and DMY bits */ 182 data_in &= ~(MPC624_EOC_BIT | MPC624_DMY_BIT); 183 data_in = 0x20000000 - data_in; 184 } 185 return data_in; 186 } 187 188 static int mpc624_ai_eoc(struct comedi_device *dev, 189 struct comedi_subdevice *s, 190 struct comedi_insn *insn, 191 unsigned long context) 192 { 193 unsigned char status; 194 195 status = inb(dev->iobase + MPC624_ADC); 196 if ((status & MPC624_ADBUSY) == 0) 197 return 0; 198 return -EBUSY; 199 } 200 201 static int mpc624_ai_insn_read(struct comedi_device *dev, 202 struct comedi_subdevice *s, 203 struct comedi_insn *insn, 204 unsigned int *data) 205 { 206 int ret; 207 int i; 208 209 /* 210 * WARNING: 211 * We always write 0 to GNSWA bit, so the channel range is +-/10.1Vdc 212 */ 213 outb(insn->chanspec, dev->iobase + MPC624_GNMUXCH); 214 215 for (i = 0; i < insn->n; i++) { 216 /* Trigger the conversion */ 217 outb(MPC624_ADSCK, dev->iobase + MPC624_ADC); 218 udelay(1); 219 outb(MPC624_ADCS | MPC624_ADSCK, dev->iobase + MPC624_ADC); 220 udelay(1); 221 outb(0, dev->iobase + MPC624_ADC); 222 udelay(1); 223 224 /* Wait for the conversion to end */ 225 ret = comedi_timeout(dev, s, insn, mpc624_ai_eoc, 0); 226 if (ret) 227 return ret; 228 229 data[i] = mpc624_ai_get_sample(dev, s); 230 } 231 232 return insn->n; 233 } 234 235 static int mpc624_attach(struct comedi_device *dev, struct comedi_devconfig *it) 236 { 237 struct mpc624_private *devpriv; 238 struct comedi_subdevice *s; 239 int ret; 240 241 ret = comedi_request_region(dev, it->options[0], 0x10); 242 if (ret) 243 return ret; 244 245 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 246 if (!devpriv) 247 return -ENOMEM; 248 249 switch (it->options[1]) { 250 case 0: 251 devpriv->ai_speed = MPC624_SPEED_3_52_KHZ; 252 break; 253 case 1: 254 devpriv->ai_speed = MPC624_SPEED_1_76_KHZ; 255 break; 256 case 2: 257 devpriv->ai_speed = MPC624_SPEED_880_HZ; 258 break; 259 case 3: 260 devpriv->ai_speed = MPC624_SPEED_440_HZ; 261 break; 262 case 4: 263 devpriv->ai_speed = MPC624_SPEED_220_HZ; 264 break; 265 case 5: 266 devpriv->ai_speed = MPC624_SPEED_110_HZ; 267 break; 268 case 6: 269 devpriv->ai_speed = MPC624_SPEED_55_HZ; 270 break; 271 case 7: 272 devpriv->ai_speed = MPC624_SPEED_27_5_HZ; 273 break; 274 case 8: 275 devpriv->ai_speed = MPC624_SPEED_13_75_HZ; 276 break; 277 case 9: 278 devpriv->ai_speed = MPC624_SPEED_6_875_HZ; 279 break; 280 default: 281 devpriv->ai_speed = MPC624_SPEED_3_52_KHZ; 282 } 283 284 ret = comedi_alloc_subdevices(dev, 1); 285 if (ret) 286 return ret; 287 288 /* Analog Input subdevice */ 289 s = &dev->subdevices[0]; 290 s->type = COMEDI_SUBD_AI; 291 s->subdev_flags = SDF_READABLE | SDF_DIFF; 292 s->n_chan = 4; 293 s->maxdata = 0x3fffffff; 294 s->range_table = (it->options[1] == 0) ? &range_mpc624_bipolar1 295 : &range_mpc624_bipolar10; 296 s->insn_read = mpc624_ai_insn_read; 297 298 return 0; 299 } 300 301 static struct comedi_driver mpc624_driver = { 302 .driver_name = "mpc624", 303 .module = THIS_MODULE, 304 .attach = mpc624_attach, 305 .detach = comedi_legacy_detach, 306 }; 307 module_comedi_driver(mpc624_driver); 308 309 MODULE_AUTHOR("Comedi https://www.comedi.org"); 310 MODULE_DESCRIPTION("Comedi driver for Micro/sys MPC-624 PC/104 board"); 311 MODULE_LICENSE("GPL"); 312