1// SPDX-License-Identifier: GPL-2.0 2/* 3 * pcm3724.c 4 * Comedi driver for Advantech PCM-3724 Digital I/O board 5 * 6 * Drew Csillag <drew_csillag@yahoo.com> 7 */ 8 9/* 10 * Driver: pcm3724 11 * Description: Advantech PCM-3724 12 * Devices: [Advantech] PCM-3724 (pcm3724) 13 * Author: Drew Csillag <drew_csillag@yahoo.com> 14 * Status: tested 15 * 16 * This is driver for digital I/O boards PCM-3724 with 48 DIO. 17 * It needs 8255.o for operations and only immediate mode is supported. 18 * See the source for configuration details. 19 * 20 * Copy/pasted/hacked from pcm724.c 21 * 22 * Configuration Options: 23 * [0] - I/O port base address 24 */ 25 26#include <linux/module.h> 27#include <linux/comedi/comedidev.h> 28#include <linux/comedi/comedi_8255.h> 29 30/* 31 * Register I/O Map 32 * 33 * This board has two standard 8255 devices that provide six 8-bit DIO ports 34 * (48 channels total). Six 74HCT245 chips (one for each port) buffer the 35 * I/O lines to increase driving capability. Because the 74HCT245 is a 36 * bidirectional, tri-state line buffer, two additional I/O ports are used 37 * to control the direction of data and the enable of each port. 38 */ 39#define PCM3724_8255_0_BASE 0x00 40#define PCM3724_8255_1_BASE 0x04 41#define PCM3724_DIO_DIR_REG 0x08 42#define PCM3724_DIO_DIR_C0_OUT BIT(0) 43#define PCM3724_DIO_DIR_B0_OUT BIT(1) 44#define PCM3724_DIO_DIR_A0_OUT BIT(2) 45#define PCM3724_DIO_DIR_C1_OUT BIT(3) 46#define PCM3724_DIO_DIR_B1_OUT BIT(4) 47#define PCM3724_DIO_DIR_A1_OUT BIT(5) 48#define PCM3724_GATE_CTRL_REG 0x09 49#define PCM3724_GATE_CTRL_C0_ENA BIT(0) 50#define PCM3724_GATE_CTRL_B0_ENA BIT(1) 51#define PCM3724_GATE_CTRL_A0_ENA BIT(2) 52#define PCM3724_GATE_CTRL_C1_ENA BIT(3) 53#define PCM3724_GATE_CTRL_B1_ENA BIT(4) 54#define PCM3724_GATE_CTRL_A1_ENA BIT(5) 55 56/* used to track configured dios */ 57struct priv_pcm3724 { 58 int dio_1; 59 int dio_2; 60}; 61 62static int compute_buffer(int config, int devno, struct comedi_subdevice *s) 63{ 64 /* 1 in io_bits indicates output */ 65 if (s->io_bits & 0x0000ff) { 66 if (devno == 0) 67 config |= PCM3724_DIO_DIR_A0_OUT; 68 else 69 config |= PCM3724_DIO_DIR_A1_OUT; 70 } 71 if (s->io_bits & 0x00ff00) { 72 if (devno == 0) 73 config |= PCM3724_DIO_DIR_B0_OUT; 74 else 75 config |= PCM3724_DIO_DIR_B1_OUT; 76 } 77 if (s->io_bits & 0xff0000) { 78 if (devno == 0) 79 config |= PCM3724_DIO_DIR_C0_OUT; 80 else 81 config |= PCM3724_DIO_DIR_C1_OUT; 82 } 83 return config; 84} 85 86static void do_3724_config(struct comedi_device *dev, 87 struct comedi_subdevice *s, int chanspec) 88{ 89 struct comedi_subdevice *s_dio1 = &dev->subdevices[0]; 90 struct comedi_subdevice *s_dio2 = &dev->subdevices[1]; 91 int config; 92 int buffer_config; 93 unsigned long port_8255_cfg; 94 95 config = I8255_CTRL_CW; 96 97 /* 1 in io_bits indicates output, 1 in config indicates input */ 98 if (!(s->io_bits & 0x0000ff)) 99 config |= I8255_CTRL_A_IO; 100 101 if (!(s->io_bits & 0x00ff00)) 102 config |= I8255_CTRL_B_IO; 103 104 if (!(s->io_bits & 0xff0000)) 105 config |= I8255_CTRL_C_HI_IO | I8255_CTRL_C_LO_IO; 106 107 buffer_config = compute_buffer(0, 0, s_dio1); 108 buffer_config = compute_buffer(buffer_config, 1, s_dio2); 109 110 if (s == s_dio1) 111 port_8255_cfg = dev->iobase + I8255_CTRL_REG; 112 else 113 port_8255_cfg = dev->iobase + I8255_SIZE + I8255_CTRL_REG; 114 115 outb(buffer_config, dev->iobase + PCM3724_DIO_DIR_REG); 116 117 outb(config, port_8255_cfg); 118} 119 120static void enable_chan(struct comedi_device *dev, struct comedi_subdevice *s, 121 int chanspec) 122{ 123 struct priv_pcm3724 *priv = dev->private; 124 struct comedi_subdevice *s_dio1 = &dev->subdevices[0]; 125 unsigned int mask; 126 int gatecfg; 127 128 gatecfg = 0; 129 130 mask = 1 << CR_CHAN(chanspec); 131 if (s == s_dio1) 132 priv->dio_1 |= mask; 133 else 134 priv->dio_2 |= mask; 135 136 if (priv->dio_1 & 0xff0000) 137 gatecfg |= PCM3724_GATE_CTRL_C0_ENA; 138 139 if (priv->dio_1 & 0xff00) 140 gatecfg |= PCM3724_GATE_CTRL_B0_ENA; 141 142 if (priv->dio_1 & 0xff) 143 gatecfg |= PCM3724_GATE_CTRL_A0_ENA; 144 145 if (priv->dio_2 & 0xff0000) 146 gatecfg |= PCM3724_GATE_CTRL_C1_ENA; 147 148 if (priv->dio_2 & 0xff00) 149 gatecfg |= PCM3724_GATE_CTRL_B1_ENA; 150 151 if (priv->dio_2 & 0xff) 152 gatecfg |= PCM3724_GATE_CTRL_A1_ENA; 153 154 outb(gatecfg, dev->iobase + PCM3724_GATE_CTRL_REG); 155} 156 157/* overriding the 8255 insn config */ 158static int subdev_3724_insn_config(struct comedi_device *dev, 159 struct comedi_subdevice *s, 160 struct comedi_insn *insn, 161 unsigned int *data) 162{ 163 unsigned int chan = CR_CHAN(insn->chanspec); 164 unsigned int mask; 165 int ret; 166 167 if (chan < 8) 168 mask = 0x0000ff; 169 else if (chan < 16) 170 mask = 0x00ff00; 171 else if (chan < 20) 172 mask = 0x0f0000; 173 else 174 mask = 0xf00000; 175 176 ret = comedi_dio_insn_config(dev, s, insn, data, mask); 177 if (ret) 178 return ret; 179 180 do_3724_config(dev, s, insn->chanspec); 181 enable_chan(dev, s, insn->chanspec); 182 183 return insn->n; 184} 185 186static int pcm3724_attach(struct comedi_device *dev, 187 struct comedi_devconfig *it) 188{ 189 struct priv_pcm3724 *priv; 190 struct comedi_subdevice *s; 191 int ret, i; 192 193 priv = comedi_alloc_devpriv(dev, sizeof(*priv)); 194 if (!priv) 195 return -ENOMEM; 196 197 ret = comedi_request_region(dev, it->options[0], 0x10); 198 if (ret) 199 return ret; 200 201 ret = comedi_alloc_subdevices(dev, 2); 202 if (ret) 203 return ret; 204 205 for (i = 0; i < dev->n_subdevices; i++) { 206 s = &dev->subdevices[i]; 207 ret = subdev_8255_io_init(dev, s, i * I8255_SIZE); 208 if (ret) 209 return ret; 210 s->insn_config = subdev_3724_insn_config; 211 } 212 return 0; 213} 214 215static struct comedi_driver pcm3724_driver = { 216 .driver_name = "pcm3724", 217 .module = THIS_MODULE, 218 .attach = pcm3724_attach, 219 .detach = comedi_legacy_detach, 220}; 221module_comedi_driver(pcm3724_driver); 222 223MODULE_AUTHOR("Comedi https://www.comedi.org"); 224MODULE_DESCRIPTION("Comedi driver for Advantech PCM-3724 Digital I/O board"); 225MODULE_LICENSE("GPL"); 226