The Apex Embedded Systems STX104 features 16 channels of single-ended (8
channels of true differential) 16-bit analog input. Differential input
configuration may be selected via a physical jumper on the device.
Similarly, input polarity (unipolar/bipolar) is configured via a
physical jumper on the device.

Input gain selection is available to the user via software, thus
allowing eight possible input ranges: +-10V, +-5V, +-2.5V, +-1.25V,
0 to 10V, 0 to 5V, 0 to 2.5V, and 0 to 1.25V. Four input gain
configurations are supported: x1, x2, x4, and x8.

This ADC resolution is 16-bits (1/65536 of full scale). Analog input
samples are taken on software trigger; neither FIFO sampling nor
interrupt triggering is supported by this driver.

Signed-off-by: William Breathitt Gray <vilhelm.g...@gmail.com>
---
 drivers/iio/dac/Kconfig  |   9 ++-
 drivers/iio/dac/stx104.c | 154 +++++++++++++++++++++++++++++++++++++++++------
 2 files changed, 139 insertions(+), 24 deletions(-)

diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index ca81447..f7f896e 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -246,14 +246,13 @@ config MCP4922
          will be called mcp4922.
 
 config STX104
-       tristate "Apex Embedded Systems STX104 DAC driver"
+       tristate "Apex Embedded Systems STX104 driver"
        depends on X86 && ISA_BUS_API
        select GPIOLIB
        help
-         Say yes here to build support for the 2-channel DAC and GPIO on the
-         Apex Embedded Systems STX104 integrated analog PC/104 card. The base
-         port addresses for the devices may be configured via the base array
-         module parameter.
+         Say yes here to build support for the Apex Embedded Systems STX104
+         integrated analog PC/104 card. The base port addresses for the devices
+         may be configured via the base array module parameter.
 
 config VF610_DAC
        tristate "Vybrid vf610 DAC driver"
diff --git a/drivers/iio/dac/stx104.c b/drivers/iio/dac/stx104.c
index 792a971..4986e9a 100644
--- a/drivers/iio/dac/stx104.c
+++ b/drivers/iio/dac/stx104.c
@@ -1,5 +1,5 @@
 /*
- * DAC driver for the Apex Embedded Systems STX104
+ * IIO driver for the Apex Embedded Systems STX104
  * Copyright (C) 2016 William Breathitt Gray
  *
  * This program is free software; you can redistribute it and/or modify
@@ -24,17 +24,32 @@
 #include <linux/moduleparam.h>
 #include <linux/spinlock.h>
 
-#define STX104_NUM_CHAN 2
+#define STX104_EXTENT 16
 
-#define STX104_CHAN(chan) {                            \
+#define STX104_OUT_CHAN(chan) {                                \
        .type = IIO_VOLTAGE,                            \
        .channel = chan,                                \
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),   \
        .indexed = 1,                                   \
        .output = 1                                     \
 }
+#define STX104_GAIN_CHAN {                                     \
+       .type = IIO_VOLTAGE,                                    \
+       .info_mask_separate = BIT(IIO_CHAN_INFO_HARDWAREGAIN),  \
+       .output = 1                                             \
+}
+#define STX104_IN_CHAN(chan) {                         \
+       .type = IIO_VOLTAGE,                            \
+       .channel = chan,                                \
+       .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE), \
+       .indexed = 1                                    \
+}
 
-#define STX104_EXTENT 16
+#define STX104_NUM_OUT_CHAN 2
+#define STX104_NUM_GAIN_CHAN 1
+#define STX104_NUM_IN_CHAN 16
+#define IN_CHAN_OFFSET (STX104_NUM_OUT_CHAN + STX104_NUM_GAIN_CHAN)
+#define STX104_MAX_NUM_CHAN (IN_CHAN_OFFSET + STX104_NUM_IN_CHAN)
 
 static unsigned int base[max_num_isa_dev(STX104_EXTENT)];
 static unsigned int num_stx104;
@@ -47,7 +62,7 @@ MODULE_PARM_DESC(base, "Apex Embedded Systems STX104 base 
addresses");
  * @base:              base port address of the IIO device
  */
 struct stx104_iio {
-       unsigned chan_out_states[STX104_NUM_CHAN];
+       unsigned int chan_out_states[STX104_NUM_OUT_CHAN];
        unsigned base;
 };
 
@@ -69,26 +84,103 @@ static int stx104_read_raw(struct iio_dev *indio_dev,
        struct iio_chan_spec const *chan, int *val, int *val2, long mask)
 {
        struct stx104_iio *const priv = iio_priv(indio_dev);
+       long adc_sample;
+       unsigned int adc_config;
+       long adbu;
+       unsigned int gain;
+
+       /* handle output channels */
+       if (chan->output) {
+               switch (mask) {
+               case IIO_CHAN_INFO_RAW:
+                       *val = priv->chan_out_states[chan->channel];
+                       return IIO_VAL_INT;
+               case IIO_CHAN_INFO_HARDWAREGAIN:
+                       *val = 1 << (inb(priv->base + 11) & 0x3);
+                       return IIO_VAL_INT;
+               default:
+                       return -EINVAL;
+               }
+       }
 
-       if (mask != IIO_CHAN_INFO_RAW)
+       if (mask != IIO_CHAN_INFO_SCALE)
                return -EINVAL;
 
-       *val = priv->chan_out_states[chan->channel];
-
-       return IIO_VAL_INT;
+       /* select ADC channel */
+       outb(chan->channel | (chan->channel << 4), priv->base + 2);
+
+       /* trigger ADC sample capture and wait for completion*/
+       outb(0, priv->base);
+       while (inb(priv->base + 8) & BIT(7));
+
+       adc_sample = inw(priv->base);
+
+       /* get ADC bipolar/unipolar and gain configuration */
+       adc_config = inb(priv->base + 11);
+       adbu = !(adc_config & BIT(2));
+       gain = adc_config & 0x3;
+
+       /* Value conversion math:
+        * ----------------------
+        * scale = adc_sample / 65536
+        * range = 10 / (1 << gain)
+        * voltage = scale * (range + adbu * range) - adbu * range
+        *
+        * Simplified:
+        * -----------
+        * voltage = 5 * (adc_sample * (1 + adbu) - adbu * 65536) /
+        *      (1 << (15 + gain))
+        *
+        * Portability Caution:
+        * --------------------
+        * *val will be set to a value between -327680 and 327675; in order to
+        * prevent integer underflow/overflow, the int data type of the
+        * implementation should be capable of representing this value range.
+        */
+       *val = 5 * (adc_sample * (1 + adbu) - adbu * 65536);
+       *val2 = 15 + gain;
+
+       return IIO_VAL_FRACTIONAL_LOG2;
 }
 
 static int stx104_write_raw(struct iio_dev *indio_dev,
        struct iio_chan_spec const *chan, int val, int val2, long mask)
 {
        struct stx104_iio *const priv = iio_priv(indio_dev);
-       const unsigned chan_addr_offset = 2 * chan->channel;
 
-       if (mask != IIO_CHAN_INFO_RAW)
+       switch (mask) {
+       case IIO_CHAN_INFO_RAW:
+               /* DAC can only accept up to a 16-bit value */
+               if ((unsigned int)val > 65535)
+                       return -EINVAL;
+
+               priv->chan_out_states[chan->channel] = val;
+               outw(val, priv->base + 4 + 2 * chan->channel);
+
+               break;
+       case IIO_CHAN_INFO_HARDWAREGAIN:
+               /* Only four gain states (x1, x2, x4, x8) */
+               switch (val) {
+               case 1:
+                       outb(0, priv->base + 11);
+                       break;
+               case 2:
+                       outb(1, priv->base + 11);
+                       break;
+               case 4:
+                       outb(2, priv->base + 11);
+                       break;
+               case 8:
+                       outb(3, priv->base + 11);
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               break;
+       default:
                return -EINVAL;
-
-       priv->chan_out_states[chan->channel] = val;
-       outw(val, priv->base + 4 + chan_addr_offset);
+       }
 
        return 0;
 }
@@ -99,9 +191,15 @@ static const struct iio_info stx104_info = {
        .write_raw = stx104_write_raw
 };
 
-static const struct iio_chan_spec stx104_channels[STX104_NUM_CHAN] = {
-       STX104_CHAN(0),
-       STX104_CHAN(1)
+static struct iio_chan_spec stx104_channels[STX104_MAX_NUM_CHAN] = {
+       STX104_OUT_CHAN(0), STX104_OUT_CHAN(1),
+       STX104_GAIN_CHAN,
+       STX104_IN_CHAN(0), STX104_IN_CHAN(1), STX104_IN_CHAN(2),
+       STX104_IN_CHAN(3), STX104_IN_CHAN(4), STX104_IN_CHAN(5),
+       STX104_IN_CHAN(6), STX104_IN_CHAN(7), STX104_IN_CHAN(8),
+       STX104_IN_CHAN(9), STX104_IN_CHAN(10), STX104_IN_CHAN(11),
+       STX104_IN_CHAN(12), STX104_IN_CHAN(13), STX104_IN_CHAN(14),
+       STX104_IN_CHAN(15)
 };
 
 static int stx104_gpio_get_direction(struct gpio_chip *chip,
@@ -169,6 +267,7 @@ static int stx104_probe(struct device *dev, unsigned int id)
        struct iio_dev *indio_dev;
        struct stx104_iio *priv;
        struct stx104_gpio *stx104gpio;
+       int i;
        int err;
 
        indio_dev = devm_iio_device_alloc(dev, sizeof(*priv));
@@ -188,13 +287,30 @@ static int stx104_probe(struct device *dev, unsigned int 
id)
 
        indio_dev->info = &stx104_info;
        indio_dev->modes = INDIO_DIRECT_MODE;
+       indio_dev->num_channels = IN_CHAN_OFFSET + STX104_NUM_IN_CHAN;
+
+       /* determine if differential inputs */
+       if (inb(base[id] + 8) & BIT(5)) {
+               indio_dev->num_channels -= STX104_NUM_IN_CHAN / 2;
+
+               for (i = 0; i < STX104_NUM_IN_CHAN / 2; i++) {
+                       stx104_channels[i + IN_CHAN_OFFSET].differential = 1;
+                       stx104_channels[i + IN_CHAN_OFFSET].channel2 = i;
+               }
+       }
+
        indio_dev->channels = stx104_channels;
-       indio_dev->num_channels = STX104_NUM_CHAN;
        indio_dev->name = dev_name(dev);
 
        priv = iio_priv(indio_dev);
        priv->base = base[id];
 
+       /* configure device for software trigger operation */
+       outb(0, base[id] + 9);
+
+       /* initialize gain setting to x1 */
+       outb(0, base[id] + 11);
+
        /* initialize DAC output to 0V */
        outw(0, base[id] + 4);
        outw(0, base[id] + 6);
@@ -251,5 +367,5 @@ static struct isa_driver stx104_driver = {
 module_isa_driver(stx104_driver, num_stx104);
 
 MODULE_AUTHOR("William Breathitt Gray <vilhelm.g...@gmail.com>");
-MODULE_DESCRIPTION("Apex Embedded Systems STX104 DAC driver");
+MODULE_DESCRIPTION("Apex Embedded Systems STX104 IIO driver");
 MODULE_LICENSE("GPL v2");
-- 
2.7.3

Reply via email to