1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * c6xdigio.c 4 * Hardware driver for Mechatronic Systems Inc. C6x_DIGIO DSP daughter card. 5 * http://web.archive.org/web/%2A/http://robot0.ge.uiuc.edu/~spong/mecha/ 6 * 7 * COMEDI - Linux Control and Measurement Device Interface 8 * Copyright (C) 1999 Dan Block 9 */ 10 11 /* 12 * Driver: c6xdigio 13 * Description: Mechatronic Systems Inc. C6x_DIGIO DSP daughter card 14 * Author: Dan Block 15 * Status: unknown 16 * Devices: [Mechatronic Systems Inc.] C6x_DIGIO DSP daughter card (c6xdigio) 17 * Updated: Sun Nov 20 20:18:34 EST 2005 18 * 19 * Configuration Options: 20 * [0] - base address 21 */ 22 23 #include <linux/kernel.h> 24 #include <linux/module.h> 25 #include <linux/sched.h> 26 #include <linux/mm.h> 27 #include <linux/errno.h> 28 #include <linux/interrupt.h> 29 #include <linux/timex.h> 30 #include <linux/timer.h> 31 #include <linux/io.h> 32 #include <linux/pnp.h> 33 34 #include "../comedidev.h" 35 36 /* 37 * Register I/O map 38 */ 39 #define C6XDIGIO_DATA_REG 0x00 40 #define C6XDIGIO_DATA_CHAN(x) (((x) + 1) << 4) 41 #define C6XDIGIO_DATA_PWM BIT(5) 42 #define C6XDIGIO_DATA_ENCODER BIT(6) 43 #define C6XDIGIO_STATUS_REG 0x01 44 #define C6XDIGIO_CTRL_REG 0x02 45 46 #define C6XDIGIO_TIME_OUT 20 47 48 static int c6xdigio_chk_status(struct comedi_device *dev, unsigned long context) 49 { 50 unsigned int status; 51 int timeout = 0; 52 53 do { 54 status = inb(dev->iobase + C6XDIGIO_STATUS_REG); 55 if ((status & 0x80) != context) 56 return 0; 57 timeout++; 58 } while (timeout < C6XDIGIO_TIME_OUT); 59 60 return -EBUSY; 61 } 62 63 static int c6xdigio_write_data(struct comedi_device *dev, 64 unsigned int val, unsigned int status) 65 { 66 outb_p(val, dev->iobase + C6XDIGIO_DATA_REG); 67 return c6xdigio_chk_status(dev, status); 68 } 69 70 static int c6xdigio_get_encoder_bits(struct comedi_device *dev, 71 unsigned int *bits, 72 unsigned int cmd, 73 unsigned int status) 74 { 75 unsigned int val; 76 77 val = inb(dev->iobase + C6XDIGIO_STATUS_REG); 78 val >>= 3; 79 val &= 0x07; 80 81 *bits = val; 82 83 return c6xdigio_write_data(dev, cmd, status); 84 } 85 86 static void c6xdigio_pwm_write(struct comedi_device *dev, 87 unsigned int chan, unsigned int val) 88 { 89 unsigned int cmd = C6XDIGIO_DATA_PWM | C6XDIGIO_DATA_CHAN(chan); 90 unsigned int bits; 91 92 if (val > 498) 93 val = 498; 94 if (val < 2) 95 val = 2; 96 97 bits = (val >> 0) & 0x03; 98 c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00); 99 bits = (val >> 2) & 0x03; 100 c6xdigio_write_data(dev, cmd | bits | (1 << 2), 0x80); 101 bits = (val >> 4) & 0x03; 102 c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00); 103 bits = (val >> 6) & 0x03; 104 c6xdigio_write_data(dev, cmd | bits | (1 << 2), 0x80); 105 bits = (val >> 8) & 0x03; 106 c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00); 107 108 c6xdigio_write_data(dev, 0x00, 0x80); 109 } 110 111 static int c6xdigio_encoder_read(struct comedi_device *dev, 112 unsigned int chan) 113 { 114 unsigned int cmd = C6XDIGIO_DATA_ENCODER | C6XDIGIO_DATA_CHAN(chan); 115 unsigned int val = 0; 116 unsigned int bits; 117 118 c6xdigio_write_data(dev, cmd, 0x00); 119 120 c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80); 121 val |= (bits << 0); 122 123 c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00); 124 val |= (bits << 3); 125 126 c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80); 127 val |= (bits << 6); 128 129 c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00); 130 val |= (bits << 9); 131 132 c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80); 133 val |= (bits << 12); 134 135 c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00); 136 val |= (bits << 15); 137 138 c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80); 139 val |= (bits << 18); 140 141 c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00); 142 val |= (bits << 21); 143 144 c6xdigio_write_data(dev, 0x00, 0x80); 145 146 return val; 147 } 148 149 static int c6xdigio_pwm_insn_write(struct comedi_device *dev, 150 struct comedi_subdevice *s, 151 struct comedi_insn *insn, 152 unsigned int *data) 153 { 154 unsigned int chan = CR_CHAN(insn->chanspec); 155 unsigned int val = (s->state >> (16 * chan)) & 0xffff; 156 int i; 157 158 for (i = 0; i < insn->n; i++) { 159 val = data[i]; 160 c6xdigio_pwm_write(dev, chan, val); 161 } 162 163 /* 164 * There are only 2 PWM channels and they have a maxdata of 500. 165 * Instead of allocating private data to save the values in for 166 * readback this driver just packs the values for the two channels 167 * in the s->state. 168 */ 169 s->state &= (0xffff << (16 * chan)); 170 s->state |= (val << (16 * chan)); 171 172 return insn->n; 173 } 174 175 static int c6xdigio_pwm_insn_read(struct comedi_device *dev, 176 struct comedi_subdevice *s, 177 struct comedi_insn *insn, 178 unsigned int *data) 179 { 180 unsigned int chan = CR_CHAN(insn->chanspec); 181 unsigned int val; 182 int i; 183 184 val = (s->state >> (16 * chan)) & 0xffff; 185 186 for (i = 0; i < insn->n; i++) 187 data[i] = val; 188 189 return insn->n; 190 } 191 192 static int c6xdigio_encoder_insn_read(struct comedi_device *dev, 193 struct comedi_subdevice *s, 194 struct comedi_insn *insn, 195 unsigned int *data) 196 { 197 unsigned int chan = CR_CHAN(insn->chanspec); 198 unsigned int val; 199 int i; 200 201 for (i = 0; i < insn->n; i++) { 202 val = c6xdigio_encoder_read(dev, chan); 203 204 /* munge two's complement value to offset binary */ 205 data[i] = comedi_offset_munge(s, val); 206 } 207 208 return insn->n; 209 } 210 211 static void c6xdigio_init(struct comedi_device *dev) 212 { 213 /* Initialize the PWM */ 214 c6xdigio_write_data(dev, 0x70, 0x00); 215 c6xdigio_write_data(dev, 0x74, 0x80); 216 c6xdigio_write_data(dev, 0x70, 0x00); 217 c6xdigio_write_data(dev, 0x00, 0x80); 218 219 /* Reset the encoders */ 220 c6xdigio_write_data(dev, 0x68, 0x00); 221 c6xdigio_write_data(dev, 0x6c, 0x80); 222 c6xdigio_write_data(dev, 0x68, 0x00); 223 c6xdigio_write_data(dev, 0x00, 0x80); 224 } 225 226 static const struct pnp_device_id c6xdigio_pnp_tbl[] = { 227 /* Standard LPT Printer Port */ 228 {.id = "PNP0400", .driver_data = 0}, 229 /* ECP Printer Port */ 230 {.id = "PNP0401", .driver_data = 0}, 231 {} 232 }; 233 234 static struct pnp_driver c6xdigio_pnp_driver = { 235 .name = "c6xdigio", 236 .id_table = c6xdigio_pnp_tbl, 237 }; 238 239 static int c6xdigio_attach(struct comedi_device *dev, 240 struct comedi_devconfig *it) 241 { 242 struct comedi_subdevice *s; 243 int ret; 244 245 ret = comedi_request_region(dev, it->options[0], 0x03); 246 if (ret) 247 return ret; 248 249 ret = comedi_alloc_subdevices(dev, 2); 250 if (ret) 251 return ret; 252 253 /* Make sure that PnP ports get activated */ 254 pnp_register_driver(&c6xdigio_pnp_driver); 255 256 s = &dev->subdevices[0]; 257 /* pwm output subdevice */ 258 s->type = COMEDI_SUBD_PWM; 259 s->subdev_flags = SDF_WRITABLE; 260 s->n_chan = 2; 261 s->maxdata = 500; 262 s->range_table = &range_unknown; 263 s->insn_write = c6xdigio_pwm_insn_write; 264 s->insn_read = c6xdigio_pwm_insn_read; 265 266 s = &dev->subdevices[1]; 267 /* encoder (counter) subdevice */ 268 s->type = COMEDI_SUBD_COUNTER; 269 s->subdev_flags = SDF_READABLE | SDF_LSAMPL; 270 s->n_chan = 2; 271 s->maxdata = 0xffffff; 272 s->range_table = &range_unknown; 273 s->insn_read = c6xdigio_encoder_insn_read; 274 275 /* I will call this init anyway but more than likely the DSP board */ 276 /* will not be connected when device driver is loaded. */ 277 c6xdigio_init(dev); 278 279 return 0; 280 } 281 282 static void c6xdigio_detach(struct comedi_device *dev) 283 { 284 comedi_legacy_detach(dev); 285 pnp_unregister_driver(&c6xdigio_pnp_driver); 286 } 287 288 static struct comedi_driver c6xdigio_driver = { 289 .driver_name = "c6xdigio", 290 .module = THIS_MODULE, 291 .attach = c6xdigio_attach, 292 .detach = c6xdigio_detach, 293 }; 294 module_comedi_driver(c6xdigio_driver); 295 296 MODULE_AUTHOR("Comedi https://www.comedi.org"); 297 MODULE_DESCRIPTION("Comedi driver for the C6x_DIGIO DSP daughter card"); 298 MODULE_LICENSE("GPL"); 299