| 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * ssv_dnp.c |
| 4 | * generic comedi driver for SSV Embedded Systems' DIL/Net-PCs |
| 5 | * Copyright (C) 2001 Robert Schwebel <robert@schwebel.de> |
| 6 | * |
| 7 | * COMEDI - Linux Control and Measurement Device Interface |
| 8 | * Copyright (C) 2000 David A. Schleef <ds@schleef.org> |
| 9 | */ |
| 10 | |
| 11 | /* |
| 12 | * Driver: ssv_dnp |
| 13 | * Description: SSV Embedded Systems DIL/Net-PC |
| 14 | * Author: Robert Schwebel <robert@schwebel.de> |
| 15 | * Devices: [SSV Embedded Systems] DIL/Net-PC 1486 (dnp-1486) |
| 16 | * Status: unknown |
| 17 | */ |
| 18 | |
| 19 | /* include files ----------------------------------------------------------- */ |
| 20 | |
| 21 | #include <linux/module.h> |
| 22 | #include <linux/comedi/comedidev.h> |
| 23 | |
| 24 | /* Some global definitions: the registers of the DNP ----------------------- */ |
| 25 | /* */ |
| 26 | /* For port A and B the mode register has bits corresponding to the output */ |
| 27 | /* pins, where Bit-N = 0 -> input, Bit-N = 1 -> output. Note that bits */ |
| 28 | /* 4 to 7 correspond to pin 0..3 for port C data register. Ensure that bits */ |
| 29 | /* 0..3 remain unchanged! For details about Port C Mode Register see */ |
| 30 | /* the remarks in dnp_insn_config() below. */ |
| 31 | |
| 32 | #define CSCIR 0x22 /* Chip Setup and Control Index Register */ |
| 33 | #define CSCDR 0x23 /* Chip Setup and Control Data Register */ |
| 34 | #define PAMR 0xa5 /* Port A Mode Register */ |
| 35 | #define PADR 0xa9 /* Port A Data Register */ |
| 36 | #define PBMR 0xa4 /* Port B Mode Register */ |
| 37 | #define PBDR 0xa8 /* Port B Data Register */ |
| 38 | #define PCMR 0xa3 /* Port C Mode Register */ |
| 39 | #define PCDR 0xa7 /* Port C Data Register */ |
| 40 | |
| 41 | static int dnp_dio_insn_bits(struct comedi_device *dev, |
| 42 | struct comedi_subdevice *s, |
| 43 | struct comedi_insn *insn, |
| 44 | unsigned int *data) |
| 45 | { |
| 46 | unsigned int mask; |
| 47 | unsigned int val; |
| 48 | |
| 49 | /* |
| 50 | * Ports A and B are straight forward: each bit corresponds to an |
| 51 | * output pin with the same order. Port C is different: bits 0...3 |
| 52 | * correspond to bits 4...7 of the output register (PCDR). |
| 53 | */ |
| 54 | |
| 55 | mask = comedi_dio_update_state(s, data); |
| 56 | if (mask) { |
| 57 | outb(PADR, CSCIR); |
| 58 | outb(value: s->state & 0xff, CSCDR); |
| 59 | |
| 60 | outb(PBDR, CSCIR); |
| 61 | outb(value: (s->state >> 8) & 0xff, CSCDR); |
| 62 | |
| 63 | outb(PCDR, CSCIR); |
| 64 | val = inb(CSCDR) & 0x0f; |
| 65 | outb(value: ((s->state >> 12) & 0xf0) | val, CSCDR); |
| 66 | } |
| 67 | |
| 68 | outb(PADR, CSCIR); |
| 69 | val = inb(CSCDR); |
| 70 | outb(PBDR, CSCIR); |
| 71 | val |= (inb(CSCDR) << 8); |
| 72 | outb(PCDR, CSCIR); |
| 73 | val |= ((inb(CSCDR) & 0xf0) << 12); |
| 74 | |
| 75 | data[1] = val; |
| 76 | |
| 77 | return insn->n; |
| 78 | } |
| 79 | |
| 80 | static int dnp_dio_insn_config(struct comedi_device *dev, |
| 81 | struct comedi_subdevice *s, |
| 82 | struct comedi_insn *insn, |
| 83 | unsigned int *data) |
| 84 | { |
| 85 | unsigned int chan = CR_CHAN(insn->chanspec); |
| 86 | unsigned int mask; |
| 87 | unsigned int val; |
| 88 | int ret; |
| 89 | |
| 90 | ret = comedi_dio_insn_config(dev, s, insn, data, mask: 0); |
| 91 | if (ret) |
| 92 | return ret; |
| 93 | |
| 94 | if (chan < 8) { /* Port A */ |
| 95 | mask = 1 << chan; |
| 96 | outb(PAMR, CSCIR); |
| 97 | } else if (chan < 16) { /* Port B */ |
| 98 | mask = 1 << (chan - 8); |
| 99 | outb(PBMR, CSCIR); |
| 100 | } else { /* Port C */ |
| 101 | /* |
| 102 | * We have to pay attention with port C. |
| 103 | * This is the meaning of PCMR: |
| 104 | * Bit in PCMR: 7 6 5 4 3 2 1 0 |
| 105 | * Corresponding port C pin: d 3 d 2 d 1 d 0 d= don't touch |
| 106 | * |
| 107 | * Multiplication by 2 brings bits into correct position |
| 108 | * for PCMR! |
| 109 | */ |
| 110 | mask = 1 << ((chan - 16) * 2); |
| 111 | outb(PCMR, CSCIR); |
| 112 | } |
| 113 | |
| 114 | val = inb(CSCDR); |
| 115 | if (data[0] == COMEDI_OUTPUT) |
| 116 | val |= mask; |
| 117 | else |
| 118 | val &= ~mask; |
| 119 | outb(value: val, CSCDR); |
| 120 | |
| 121 | return insn->n; |
| 122 | } |
| 123 | |
| 124 | static int dnp_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
| 125 | { |
| 126 | struct comedi_subdevice *s; |
| 127 | int ret; |
| 128 | |
| 129 | /* |
| 130 | * We use I/O ports 0x22, 0x23 and 0xa3-0xa9, which are always |
| 131 | * allocated for the primary 8259, so we don't need to allocate |
| 132 | * them ourselves. |
| 133 | */ |
| 134 | |
| 135 | ret = comedi_alloc_subdevices(dev, num_subdevices: 1); |
| 136 | if (ret) |
| 137 | return ret; |
| 138 | |
| 139 | s = &dev->subdevices[0]; |
| 140 | /* digital i/o subdevice */ |
| 141 | s->type = COMEDI_SUBD_DIO; |
| 142 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
| 143 | s->n_chan = 20; |
| 144 | s->maxdata = 1; |
| 145 | s->range_table = &range_digital; |
| 146 | s->insn_bits = dnp_dio_insn_bits; |
| 147 | s->insn_config = dnp_dio_insn_config; |
| 148 | |
| 149 | /* configure all ports as input (default) */ |
| 150 | outb(PAMR, CSCIR); |
| 151 | outb(value: 0x00, CSCDR); |
| 152 | outb(PBMR, CSCIR); |
| 153 | outb(value: 0x00, CSCDR); |
| 154 | outb(PCMR, CSCIR); |
| 155 | outb(value: (inb(CSCDR) & 0xAA), CSCDR); |
| 156 | |
| 157 | return 0; |
| 158 | } |
| 159 | |
| 160 | static void dnp_detach(struct comedi_device *dev) |
| 161 | { |
| 162 | outb(PAMR, CSCIR); |
| 163 | outb(value: 0x00, CSCDR); |
| 164 | outb(PBMR, CSCIR); |
| 165 | outb(value: 0x00, CSCDR); |
| 166 | outb(PCMR, CSCIR); |
| 167 | outb(value: (inb(CSCDR) & 0xAA), CSCDR); |
| 168 | } |
| 169 | |
| 170 | static struct comedi_driver dnp_driver = { |
| 171 | .driver_name = "dnp-1486" , |
| 172 | .module = THIS_MODULE, |
| 173 | .attach = dnp_attach, |
| 174 | .detach = dnp_detach, |
| 175 | }; |
| 176 | module_comedi_driver(dnp_driver); |
| 177 | |
| 178 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
| 179 | MODULE_DESCRIPTION("Comedi low-level driver" ); |
| 180 | MODULE_LICENSE("GPL" ); |
| 181 | |