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