| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * ii_pci20kc.c |
| * Driver for Intelligent Instruments PCI-20001C carrier board and modules. |
| * |
| * Copyright (C) 2000 Markus Kempf <kempf@matsci.uni-sb.de> |
| * with suggestions from David Schleef 16.06.2000 |
| */ |
| |
| /* |
| * Driver: ii_pci20kc |
| * Description: Intelligent Instruments PCI-20001C carrier board |
| * Devices: [Intelligent Instrumentation] PCI-20001C (ii_pci20kc) |
| * Author: Markus Kempf <kempf@matsci.uni-sb.de> |
| * Status: works |
| * |
| * Supports the PCI-20001C-1a and PCI-20001C-2a carrier boards. The |
| * -2a version has 32 on-board DIO channels. Three add-on modules |
| * can be added to the carrier board for additional functionality. |
| * |
| * Supported add-on modules: |
| * PCI-20006M-1 1 channel, 16-bit analog output module |
| * PCI-20006M-2 2 channel, 16-bit analog output module |
| * PCI-20341M-1A 4 channel, 16-bit analog input module |
| * |
| * Options: |
| * 0 Board base address |
| * 1 IRQ (not-used) |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/io.h> |
| #include "../comedidev.h" |
| |
| /* |
| * Register I/O map |
| */ |
| #define II20K_SIZE 0x400 |
| #define II20K_MOD_OFFSET 0x100 |
| #define II20K_ID_REG 0x00 |
| #define II20K_ID_MOD1_EMPTY BIT(7) |
| #define II20K_ID_MOD2_EMPTY BIT(6) |
| #define II20K_ID_MOD3_EMPTY BIT(5) |
| #define II20K_ID_MASK 0x1f |
| #define II20K_ID_PCI20001C_1A 0x1b /* no on-board DIO */ |
| #define II20K_ID_PCI20001C_2A 0x1d /* on-board DIO */ |
| #define II20K_MOD_STATUS_REG 0x40 |
| #define II20K_MOD_STATUS_IRQ_MOD1 BIT(7) |
| #define II20K_MOD_STATUS_IRQ_MOD2 BIT(6) |
| #define II20K_MOD_STATUS_IRQ_MOD3 BIT(5) |
| #define II20K_DIO0_REG 0x80 |
| #define II20K_DIO1_REG 0x81 |
| #define II20K_DIR_ENA_REG 0x82 |
| #define II20K_DIR_DIO3_OUT BIT(7) |
| #define II20K_DIR_DIO2_OUT BIT(6) |
| #define II20K_BUF_DISAB_DIO3 BIT(5) |
| #define II20K_BUF_DISAB_DIO2 BIT(4) |
| #define II20K_DIR_DIO1_OUT BIT(3) |
| #define II20K_DIR_DIO0_OUT BIT(2) |
| #define II20K_BUF_DISAB_DIO1 BIT(1) |
| #define II20K_BUF_DISAB_DIO0 BIT(0) |
| #define II20K_CTRL01_REG 0x83 |
| #define II20K_CTRL01_SET BIT(7) |
| #define II20K_CTRL01_DIO0_IN BIT(4) |
| #define II20K_CTRL01_DIO1_IN BIT(1) |
| #define II20K_DIO2_REG 0xc0 |
| #define II20K_DIO3_REG 0xc1 |
| #define II20K_CTRL23_REG 0xc3 |
| #define II20K_CTRL23_SET BIT(7) |
| #define II20K_CTRL23_DIO2_IN BIT(4) |
| #define II20K_CTRL23_DIO3_IN BIT(1) |
| |
| #define II20K_ID_PCI20006M_1 0xe2 /* 1 AO channels */ |
| #define II20K_ID_PCI20006M_2 0xe3 /* 2 AO channels */ |
| #define II20K_AO_STRB_REG(x) (0x0b + ((x) * 0x08)) |
| #define II20K_AO_LSB_REG(x) (0x0d + ((x) * 0x08)) |
| #define II20K_AO_MSB_REG(x) (0x0e + ((x) * 0x08)) |
| #define II20K_AO_STRB_BOTH_REG 0x1b |
| |
| #define II20K_ID_PCI20341M_1 0x77 /* 4 AI channels */ |
| #define II20K_AI_STATUS_CMD_REG 0x01 |
| #define II20K_AI_STATUS_CMD_BUSY BIT(7) |
| #define II20K_AI_STATUS_CMD_HW_ENA BIT(1) |
| #define II20K_AI_STATUS_CMD_EXT_START BIT(0) |
| #define II20K_AI_LSB_REG 0x02 |
| #define II20K_AI_MSB_REG 0x03 |
| #define II20K_AI_PACER_RESET_REG 0x04 |
| #define II20K_AI_16BIT_DATA_REG 0x06 |
| #define II20K_AI_CONF_REG 0x10 |
| #define II20K_AI_CONF_ENA BIT(2) |
| #define II20K_AI_OPT_REG 0x11 |
| #define II20K_AI_OPT_TRIG_ENA BIT(5) |
| #define II20K_AI_OPT_TRIG_INV BIT(4) |
| #define II20K_AI_OPT_TIMEBASE(x) (((x) & 0x3) << 1) |
| #define II20K_AI_OPT_BURST_MODE BIT(0) |
| #define II20K_AI_STATUS_REG 0x12 |
| #define II20K_AI_STATUS_INT BIT(7) |
| #define II20K_AI_STATUS_TRIG BIT(6) |
| #define II20K_AI_STATUS_TRIG_ENA BIT(5) |
| #define II20K_AI_STATUS_PACER_ERR BIT(2) |
| #define II20K_AI_STATUS_DATA_ERR BIT(1) |
| #define II20K_AI_STATUS_SET_TIME_ERR BIT(0) |
| #define II20K_AI_LAST_CHAN_ADDR_REG 0x13 |
| #define II20K_AI_CUR_ADDR_REG 0x14 |
| #define II20K_AI_SET_TIME_REG 0x15 |
| #define II20K_AI_DELAY_LSB_REG 0x16 |
| #define II20K_AI_DELAY_MSB_REG 0x17 |
| #define II20K_AI_CHAN_ADV_REG 0x18 |
| #define II20K_AI_CHAN_RESET_REG 0x19 |
| #define II20K_AI_START_TRIG_REG 0x1a |
| #define II20K_AI_COUNT_RESET_REG 0x1b |
| #define II20K_AI_CHANLIST_REG 0x80 |
| #define II20K_AI_CHANLIST_ONBOARD_ONLY BIT(5) |
| #define II20K_AI_CHANLIST_GAIN(x) (((x) & 0x3) << 3) |
| #define II20K_AI_CHANLIST_MUX_ENA BIT(2) |
| #define II20K_AI_CHANLIST_CHAN(x) (((x) & 0x3) << 0) |
| #define II20K_AI_CHANLIST_LEN 0x80 |
| |
| /* the AO range is set by jumpers on the 20006M module */ |
| static const struct comedi_lrange ii20k_ao_ranges = { |
| 3, { |
| BIP_RANGE(5), /* Chan 0 - W1/W3 in Chan 1 - W2/W4 in */ |
| UNI_RANGE(10), /* Chan 0 - W1/W3 out Chan 1 - W2/W4 in */ |
| BIP_RANGE(10) /* Chan 0 - W1/W3 in Chan 1 - W2/W4 out */ |
| } |
| }; |
| |
| static const struct comedi_lrange ii20k_ai_ranges = { |
| 4, { |
| BIP_RANGE(5), /* gain 1 */ |
| BIP_RANGE(0.5), /* gain 10 */ |
| BIP_RANGE(0.05), /* gain 100 */ |
| BIP_RANGE(0.025) /* gain 200 */ |
| }, |
| }; |
| |
| static void __iomem *ii20k_module_iobase(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| return dev->mmio + (s->index + 1) * II20K_MOD_OFFSET; |
| } |
| |
| static int ii20k_ao_insn_write(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| void __iomem *iobase = ii20k_module_iobase(dev, s); |
| unsigned int chan = CR_CHAN(insn->chanspec); |
| int i; |
| |
| for (i = 0; i < insn->n; i++) { |
| unsigned int val = data[i]; |
| |
| s->readback[chan] = val; |
| |
| /* munge the offset binary data to 2's complement */ |
| val = comedi_offset_munge(s, val); |
| |
| writeb(val & 0xff, iobase + II20K_AO_LSB_REG(chan)); |
| writeb((val >> 8) & 0xff, iobase + II20K_AO_MSB_REG(chan)); |
| writeb(0x00, iobase + II20K_AO_STRB_REG(chan)); |
| } |
| |
| return insn->n; |
| } |
| |
| static int ii20k_ai_eoc(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned long context) |
| { |
| void __iomem *iobase = ii20k_module_iobase(dev, s); |
| unsigned char status; |
| |
| status = readb(iobase + II20K_AI_STATUS_REG); |
| if ((status & II20K_AI_STATUS_INT) == 0) |
| return 0; |
| return -EBUSY; |
| } |
| |
| static void ii20k_ai_setup(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| unsigned int chanspec) |
| { |
| void __iomem *iobase = ii20k_module_iobase(dev, s); |
| unsigned int chan = CR_CHAN(chanspec); |
| unsigned int range = CR_RANGE(chanspec); |
| unsigned char val; |
| |
| /* initialize module */ |
| writeb(II20K_AI_CONF_ENA, iobase + II20K_AI_CONF_REG); |
| |
| /* software conversion */ |
| writeb(0, iobase + II20K_AI_STATUS_CMD_REG); |
| |
| /* set the time base for the settling time counter based on the gain */ |
| val = (range < 3) ? II20K_AI_OPT_TIMEBASE(0) : II20K_AI_OPT_TIMEBASE(2); |
| writeb(val, iobase + II20K_AI_OPT_REG); |
| |
| /* set the settling time counter based on the gain */ |
| val = (range < 2) ? 0x58 : (range < 3) ? 0x93 : 0x99; |
| writeb(val, iobase + II20K_AI_SET_TIME_REG); |
| |
| /* set number of input channels */ |
| writeb(1, iobase + II20K_AI_LAST_CHAN_ADDR_REG); |
| |
| /* set the channel list byte */ |
| val = II20K_AI_CHANLIST_ONBOARD_ONLY | |
| II20K_AI_CHANLIST_MUX_ENA | |
| II20K_AI_CHANLIST_GAIN(range) | |
| II20K_AI_CHANLIST_CHAN(chan); |
| writeb(val, iobase + II20K_AI_CHANLIST_REG); |
| |
| /* reset settling time counter and trigger delay counter */ |
| writeb(0, iobase + II20K_AI_COUNT_RESET_REG); |
| |
| /* reset channel scanner */ |
| writeb(0, iobase + II20K_AI_CHAN_RESET_REG); |
| } |
| |
| static int ii20k_ai_insn_read(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| void __iomem *iobase = ii20k_module_iobase(dev, s); |
| int ret; |
| int i; |
| |
| ii20k_ai_setup(dev, s, insn->chanspec); |
| |
| for (i = 0; i < insn->n; i++) { |
| unsigned int val; |
| |
| /* generate a software start convert signal */ |
| readb(iobase + II20K_AI_PACER_RESET_REG); |
| |
| ret = comedi_timeout(dev, s, insn, ii20k_ai_eoc, 0); |
| if (ret) |
| return ret; |
| |
| val = readb(iobase + II20K_AI_LSB_REG); |
| val |= (readb(iobase + II20K_AI_MSB_REG) << 8); |
| |
| /* munge the 2's complement data to offset binary */ |
| data[i] = comedi_offset_munge(s, val); |
| } |
| |
| return insn->n; |
| } |
| |
| static void ii20k_dio_config(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| unsigned char ctrl01 = 0; |
| unsigned char ctrl23 = 0; |
| unsigned char dir_ena = 0; |
| |
| /* port 0 - channels 0-7 */ |
| if (s->io_bits & 0x000000ff) { |
| /* output port */ |
| ctrl01 &= ~II20K_CTRL01_DIO0_IN; |
| dir_ena &= ~II20K_BUF_DISAB_DIO0; |
| dir_ena |= II20K_DIR_DIO0_OUT; |
| } else { |
| /* input port */ |
| ctrl01 |= II20K_CTRL01_DIO0_IN; |
| dir_ena &= ~II20K_DIR_DIO0_OUT; |
| } |
| |
| /* port 1 - channels 8-15 */ |
| if (s->io_bits & 0x0000ff00) { |
| /* output port */ |
| ctrl01 &= ~II20K_CTRL01_DIO1_IN; |
| dir_ena &= ~II20K_BUF_DISAB_DIO1; |
| dir_ena |= II20K_DIR_DIO1_OUT; |
| } else { |
| /* input port */ |
| ctrl01 |= II20K_CTRL01_DIO1_IN; |
| dir_ena &= ~II20K_DIR_DIO1_OUT; |
| } |
| |
| /* port 2 - channels 16-23 */ |
| if (s->io_bits & 0x00ff0000) { |
| /* output port */ |
| ctrl23 &= ~II20K_CTRL23_DIO2_IN; |
| dir_ena &= ~II20K_BUF_DISAB_DIO2; |
| dir_ena |= II20K_DIR_DIO2_OUT; |
| } else { |
| /* input port */ |
| ctrl23 |= II20K_CTRL23_DIO2_IN; |
| dir_ena &= ~II20K_DIR_DIO2_OUT; |
| } |
| |
| /* port 3 - channels 24-31 */ |
| if (s->io_bits & 0xff000000) { |
| /* output port */ |
| ctrl23 &= ~II20K_CTRL23_DIO3_IN; |
| dir_ena &= ~II20K_BUF_DISAB_DIO3; |
| dir_ena |= II20K_DIR_DIO3_OUT; |
| } else { |
| /* input port */ |
| ctrl23 |= II20K_CTRL23_DIO3_IN; |
| dir_ena &= ~II20K_DIR_DIO3_OUT; |
| } |
| |
| ctrl23 |= II20K_CTRL01_SET; |
| ctrl23 |= II20K_CTRL23_SET; |
| |
| /* order is important */ |
| writeb(ctrl01, dev->mmio + II20K_CTRL01_REG); |
| writeb(ctrl23, dev->mmio + II20K_CTRL23_REG); |
| writeb(dir_ena, dev->mmio + II20K_DIR_ENA_REG); |
| } |
| |
| static int ii20k_dio_insn_config(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| unsigned int chan = CR_CHAN(insn->chanspec); |
| unsigned int mask; |
| int ret; |
| |
| if (chan < 8) |
| mask = 0x000000ff; |
| else if (chan < 16) |
| mask = 0x0000ff00; |
| else if (chan < 24) |
| mask = 0x00ff0000; |
| else |
| mask = 0xff000000; |
| |
| ret = comedi_dio_insn_config(dev, s, insn, data, mask); |
| if (ret) |
| return ret; |
| |
| ii20k_dio_config(dev, s); |
| |
| return insn->n; |
| } |
| |
| static int ii20k_dio_insn_bits(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| unsigned int mask; |
| |
| mask = comedi_dio_update_state(s, data); |
| if (mask) { |
| if (mask & 0x000000ff) |
| writeb((s->state >> 0) & 0xff, |
| dev->mmio + II20K_DIO0_REG); |
| if (mask & 0x0000ff00) |
| writeb((s->state >> 8) & 0xff, |
| dev->mmio + II20K_DIO1_REG); |
| if (mask & 0x00ff0000) |
| writeb((s->state >> 16) & 0xff, |
| dev->mmio + II20K_DIO2_REG); |
| if (mask & 0xff000000) |
| writeb((s->state >> 24) & 0xff, |
| dev->mmio + II20K_DIO3_REG); |
| } |
| |
| data[1] = readb(dev->mmio + II20K_DIO0_REG); |
| data[1] |= readb(dev->mmio + II20K_DIO1_REG) << 8; |
| data[1] |= readb(dev->mmio + II20K_DIO2_REG) << 16; |
| data[1] |= readb(dev->mmio + II20K_DIO3_REG) << 24; |
| |
| return insn->n; |
| } |
| |
| static int ii20k_init_module(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| void __iomem *iobase = ii20k_module_iobase(dev, s); |
| unsigned char id; |
| int ret; |
| |
| id = readb(iobase + II20K_ID_REG); |
| switch (id) { |
| case II20K_ID_PCI20006M_1: |
| case II20K_ID_PCI20006M_2: |
| /* Analog Output subdevice */ |
| s->type = COMEDI_SUBD_AO; |
| s->subdev_flags = SDF_WRITABLE; |
| s->n_chan = (id == II20K_ID_PCI20006M_2) ? 2 : 1; |
| s->maxdata = 0xffff; |
| s->range_table = &ii20k_ao_ranges; |
| s->insn_write = ii20k_ao_insn_write; |
| |
| ret = comedi_alloc_subdev_readback(s); |
| if (ret) |
| return ret; |
| break; |
| case II20K_ID_PCI20341M_1: |
| /* Analog Input subdevice */ |
| s->type = COMEDI_SUBD_AI; |
| s->subdev_flags = SDF_READABLE | SDF_DIFF; |
| s->n_chan = 4; |
| s->maxdata = 0xffff; |
| s->range_table = &ii20k_ai_ranges; |
| s->insn_read = ii20k_ai_insn_read; |
| break; |
| default: |
| s->type = COMEDI_SUBD_UNUSED; |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int ii20k_attach(struct comedi_device *dev, |
| struct comedi_devconfig *it) |
| { |
| struct comedi_subdevice *s; |
| unsigned int membase; |
| unsigned char id; |
| bool has_dio; |
| int ret; |
| |
| membase = it->options[0]; |
| if (!membase || (membase & ~(0x100000 - II20K_SIZE))) { |
| dev_warn(dev->class_dev, |
| "%s: invalid memory address specified\n", |
| dev->board_name); |
| return -EINVAL; |
| } |
| |
| if (!request_mem_region(membase, II20K_SIZE, dev->board_name)) { |
| dev_warn(dev->class_dev, "%s: I/O mem conflict (%#x,%u)\n", |
| dev->board_name, membase, II20K_SIZE); |
| return -EIO; |
| } |
| dev->iobase = membase; /* actually, a memory address */ |
| |
| dev->mmio = ioremap(membase, II20K_SIZE); |
| if (!dev->mmio) |
| return -ENOMEM; |
| |
| id = readb(dev->mmio + II20K_ID_REG); |
| switch (id & II20K_ID_MASK) { |
| case II20K_ID_PCI20001C_1A: |
| has_dio = false; |
| break; |
| case II20K_ID_PCI20001C_2A: |
| has_dio = true; |
| break; |
| default: |
| return -ENODEV; |
| } |
| |
| ret = comedi_alloc_subdevices(dev, 4); |
| if (ret) |
| return ret; |
| |
| s = &dev->subdevices[0]; |
| if (id & II20K_ID_MOD1_EMPTY) { |
| s->type = COMEDI_SUBD_UNUSED; |
| } else { |
| ret = ii20k_init_module(dev, s); |
| if (ret) |
| return ret; |
| } |
| |
| s = &dev->subdevices[1]; |
| if (id & II20K_ID_MOD2_EMPTY) { |
| s->type = COMEDI_SUBD_UNUSED; |
| } else { |
| ret = ii20k_init_module(dev, s); |
| if (ret) |
| return ret; |
| } |
| |
| s = &dev->subdevices[2]; |
| if (id & II20K_ID_MOD3_EMPTY) { |
| s->type = COMEDI_SUBD_UNUSED; |
| } else { |
| ret = ii20k_init_module(dev, s); |
| if (ret) |
| return ret; |
| } |
| |
| /* Digital I/O subdevice */ |
| s = &dev->subdevices[3]; |
| if (has_dio) { |
| s->type = COMEDI_SUBD_DIO; |
| s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
| s->n_chan = 32; |
| s->maxdata = 1; |
| s->range_table = &range_digital; |
| s->insn_bits = ii20k_dio_insn_bits; |
| s->insn_config = ii20k_dio_insn_config; |
| |
| /* default all channels to input */ |
| ii20k_dio_config(dev, s); |
| } else { |
| s->type = COMEDI_SUBD_UNUSED; |
| } |
| |
| return 0; |
| } |
| |
| static void ii20k_detach(struct comedi_device *dev) |
| { |
| if (dev->mmio) |
| iounmap(dev->mmio); |
| if (dev->iobase) /* actually, a memory address */ |
| release_mem_region(dev->iobase, II20K_SIZE); |
| } |
| |
| static struct comedi_driver ii20k_driver = { |
| .driver_name = "ii_pci20kc", |
| .module = THIS_MODULE, |
| .attach = ii20k_attach, |
| .detach = ii20k_detach, |
| }; |
| module_comedi_driver(ii20k_driver); |
| |
| MODULE_AUTHOR("Comedi https://www.comedi.org"); |
| MODULE_DESCRIPTION("Comedi driver for Intelligent Instruments PCI-20001C"); |
| MODULE_LICENSE("GPL"); |