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