linux/drivers/staging/comedi/drivers/addi_apci_3501.c
<<
>>
Prefs
   1/*
   2 * addi_apci_3501.c
   3 * Copyright (C) 2004,2005  ADDI-DATA GmbH for the source code of this module.
   4 * Project manager: Eric Stolz
   5 *
   6 *      ADDI-DATA GmbH
   7 *      Dieselstrasse 3
   8 *      D-77833 Ottersweier
   9 *      Tel: +19(0)7223/9493-0
  10 *      Fax: +49(0)7223/9493-92
  11 *      http://www.addi-data.com
  12 *      info@addi-data.com
  13 *
  14 * This program is free software; you can redistribute it and/or modify it
  15 * under the terms of the GNU General Public License as published by the
  16 * Free Software Foundation; either version 2 of the License, or (at your
  17 * option) any later version.
  18 *
  19 * This program is distributed in the hope that it will be useful, but WITHOUT
  20 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  21 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  22 * more details.
  23 */
  24
  25#include <linux/module.h>
  26#include <linux/interrupt.h>
  27#include <linux/sched.h>
  28
  29#include "../comedi_pci.h"
  30#include "addi_tcw.h"
  31#include "amcc_s5933.h"
  32
  33/*
  34 * PCI bar 1 register I/O map
  35 */
  36#define APCI3501_AO_CTRL_STATUS_REG             0x00
  37#define APCI3501_AO_CTRL_BIPOLAR                BIT(0)
  38#define APCI3501_AO_STATUS_READY                BIT(8)
  39#define APCI3501_AO_DATA_REG                    0x04
  40#define APCI3501_AO_DATA_CHAN(x)                ((x) << 0)
  41#define APCI3501_AO_DATA_VAL(x)                 ((x) << 8)
  42#define APCI3501_AO_DATA_BIPOLAR                BIT(31)
  43#define APCI3501_AO_TRIG_SCS_REG                0x08
  44#define APCI3501_TIMER_BASE                     0x20
  45#define APCI3501_DO_REG                         0x40
  46#define APCI3501_DI_REG                         0x50
  47
  48/*
  49 * AMCC S5933 NVRAM
  50 */
  51#define NVRAM_USER_DATA_START   0x100
  52
  53#define NVCMD_BEGIN_READ        (0x7 << 5)
  54#define NVCMD_LOAD_LOW          (0x4 << 5)
  55#define NVCMD_LOAD_HIGH         (0x5 << 5)
  56
  57/*
  58 * Function types stored in the eeprom
  59 */
  60#define EEPROM_DIGITALINPUT             0
  61#define EEPROM_DIGITALOUTPUT            1
  62#define EEPROM_ANALOGINPUT              2
  63#define EEPROM_ANALOGOUTPUT             3
  64#define EEPROM_TIMER                    4
  65#define EEPROM_WATCHDOG                 5
  66#define EEPROM_TIMER_WATCHDOG_COUNTER   10
  67
  68struct apci3501_private {
  69        unsigned long amcc;
  70        unsigned long tcw;
  71        struct task_struct *tsk_Current;
  72        unsigned char timer_mode;
  73};
  74
  75static struct comedi_lrange apci3501_ao_range = {
  76        2, {
  77                BIP_RANGE(10),
  78                UNI_RANGE(10)
  79        }
  80};
  81
  82static int apci3501_wait_for_dac(struct comedi_device *dev)
  83{
  84        unsigned int status;
  85
  86        do {
  87                status = inl(dev->iobase + APCI3501_AO_CTRL_STATUS_REG);
  88        } while (!(status & APCI3501_AO_STATUS_READY));
  89
  90        return 0;
  91}
  92
  93static int apci3501_ao_insn_write(struct comedi_device *dev,
  94                                  struct comedi_subdevice *s,
  95                                  struct comedi_insn *insn,
  96                                  unsigned int *data)
  97{
  98        unsigned int chan = CR_CHAN(insn->chanspec);
  99        unsigned int range = CR_RANGE(insn->chanspec);
 100        unsigned int cfg = APCI3501_AO_DATA_CHAN(chan);
 101        int ret;
 102        int i;
 103
 104        /*
 105         * All analog output channels have the same output range.
 106         *      14-bit bipolar: 0-10V
 107         *      13-bit unipolar: +/-10V
 108         * Changing the range of one channel changes all of them!
 109         */
 110        if (range) {
 111                outl(0, dev->iobase + APCI3501_AO_CTRL_STATUS_REG);
 112        } else {
 113                cfg |= APCI3501_AO_DATA_BIPOLAR;
 114                outl(APCI3501_AO_CTRL_BIPOLAR,
 115                     dev->iobase + APCI3501_AO_CTRL_STATUS_REG);
 116        }
 117
 118        for (i = 0; i < insn->n; i++) {
 119                unsigned int val = data[i];
 120
 121                if (range == 1) {
 122                        if (data[i] > 0x1fff) {
 123                                dev_err(dev->class_dev,
 124                                        "Unipolar resolution is only 13-bits\n");
 125                                return -EINVAL;
 126                        }
 127                }
 128
 129                ret = apci3501_wait_for_dac(dev);
 130                if (ret)
 131                        return ret;
 132
 133                outl(cfg | APCI3501_AO_DATA_VAL(val),
 134                     dev->iobase + APCI3501_AO_DATA_REG);
 135
 136                s->readback[chan] = val;
 137        }
 138
 139        return insn->n;
 140}
 141
 142#include "addi-data/hwdrv_apci3501.c"
 143
 144static int apci3501_di_insn_bits(struct comedi_device *dev,
 145                                 struct comedi_subdevice *s,
 146                                 struct comedi_insn *insn,
 147                                 unsigned int *data)
 148{
 149        data[1] = inl(dev->iobase + APCI3501_DI_REG) & 0x3;
 150
 151        return insn->n;
 152}
 153
 154static int apci3501_do_insn_bits(struct comedi_device *dev,
 155                                 struct comedi_subdevice *s,
 156                                 struct comedi_insn *insn,
 157                                 unsigned int *data)
 158{
 159        s->state = inl(dev->iobase + APCI3501_DO_REG);
 160
 161        if (comedi_dio_update_state(s, data))
 162                outl(s->state, dev->iobase + APCI3501_DO_REG);
 163
 164        data[1] = s->state;
 165
 166        return insn->n;
 167}
 168
 169static void apci3501_eeprom_wait(unsigned long iobase)
 170{
 171        unsigned char val;
 172
 173        do {
 174                val = inb(iobase + AMCC_OP_REG_MCSR_NVCMD);
 175        } while (val & 0x80);
 176}
 177
 178static unsigned short apci3501_eeprom_readw(unsigned long iobase,
 179                                            unsigned short addr)
 180{
 181        unsigned short val = 0;
 182        unsigned char tmp;
 183        unsigned char i;
 184
 185        /* Add the offset to the start of the user data */
 186        addr += NVRAM_USER_DATA_START;
 187
 188        for (i = 0; i < 2; i++) {
 189                /* Load the low 8 bit address */
 190                outb(NVCMD_LOAD_LOW, iobase + AMCC_OP_REG_MCSR_NVCMD);
 191                apci3501_eeprom_wait(iobase);
 192                outb((addr + i) & 0xff, iobase + AMCC_OP_REG_MCSR_NVDATA);
 193                apci3501_eeprom_wait(iobase);
 194
 195                /* Load the high 8 bit address */
 196                outb(NVCMD_LOAD_HIGH, iobase + AMCC_OP_REG_MCSR_NVCMD);
 197                apci3501_eeprom_wait(iobase);
 198                outb(((addr + i) >> 8) & 0xff,
 199                     iobase + AMCC_OP_REG_MCSR_NVDATA);
 200                apci3501_eeprom_wait(iobase);
 201
 202                /* Read the eeprom data byte */
 203                outb(NVCMD_BEGIN_READ, iobase + AMCC_OP_REG_MCSR_NVCMD);
 204                apci3501_eeprom_wait(iobase);
 205                tmp = inb(iobase + AMCC_OP_REG_MCSR_NVDATA);
 206                apci3501_eeprom_wait(iobase);
 207
 208                if (i == 0)
 209                        val |= tmp;
 210                else
 211                        val |= (tmp << 8);
 212        }
 213
 214        return val;
 215}
 216
 217static int apci3501_eeprom_get_ao_n_chan(struct comedi_device *dev)
 218{
 219        struct apci3501_private *devpriv = dev->private;
 220        unsigned char nfuncs;
 221        int i;
 222
 223        nfuncs = apci3501_eeprom_readw(devpriv->amcc, 10) & 0xff;
 224
 225        /* Read functionality details */
 226        for (i = 0; i < nfuncs; i++) {
 227                unsigned short offset = i * 4;
 228                unsigned short addr;
 229                unsigned char func;
 230                unsigned short val;
 231
 232                func = apci3501_eeprom_readw(devpriv->amcc, 12 + offset) & 0x3f;
 233                addr = apci3501_eeprom_readw(devpriv->amcc, 14 + offset);
 234
 235                if (func == EEPROM_ANALOGOUTPUT) {
 236                        val = apci3501_eeprom_readw(devpriv->amcc, addr + 10);
 237                        return (val >> 4) & 0x3ff;
 238                }
 239        }
 240        return 0;
 241}
 242
 243static int apci3501_eeprom_insn_read(struct comedi_device *dev,
 244                                     struct comedi_subdevice *s,
 245                                     struct comedi_insn *insn,
 246                                     unsigned int *data)
 247{
 248        struct apci3501_private *devpriv = dev->private;
 249        unsigned short addr = CR_CHAN(insn->chanspec);
 250
 251        data[0] = apci3501_eeprom_readw(devpriv->amcc, 2 * addr);
 252
 253        return insn->n;
 254}
 255
 256static irqreturn_t apci3501_interrupt(int irq, void *d)
 257{
 258        struct comedi_device *dev = d;
 259        struct apci3501_private *devpriv = dev->private;
 260        unsigned int status;
 261        unsigned int ctrl;
 262
 263        /*  Disable Interrupt */
 264        ctrl = inl(devpriv->tcw + ADDI_TCW_CTRL_REG);
 265        ctrl &= ~(ADDI_TCW_CTRL_GATE | ADDI_TCW_CTRL_TRIG |
 266                  ADDI_TCW_CTRL_IRQ_ENA);
 267        outl(ctrl, devpriv->tcw + ADDI_TCW_CTRL_REG);
 268
 269        status = inl(devpriv->tcw + ADDI_TCW_IRQ_REG);
 270        if (!(status & ADDI_TCW_IRQ)) {
 271                dev_err(dev->class_dev, "IRQ from unknown source\n");
 272                return IRQ_NONE;
 273        }
 274
 275        /* Enable Interrupt Send a signal to from kernel to user space */
 276        send_sig(SIGIO, devpriv->tsk_Current, 0);
 277        ctrl = inl(devpriv->tcw + ADDI_TCW_CTRL_REG);
 278        ctrl &= ~(ADDI_TCW_CTRL_GATE | ADDI_TCW_CTRL_TRIG |
 279                  ADDI_TCW_CTRL_IRQ_ENA);
 280        ctrl |= ADDI_TCW_CTRL_IRQ_ENA;
 281        outl(ctrl, devpriv->tcw + ADDI_TCW_CTRL_REG);
 282        inl(devpriv->tcw + ADDI_TCW_STATUS_REG);
 283
 284        return IRQ_HANDLED;
 285}
 286
 287static int apci3501_reset(struct comedi_device *dev)
 288{
 289        unsigned int val;
 290        int chan;
 291        int ret;
 292
 293        /* Reset all digital outputs to "0" */
 294        outl(0x0, dev->iobase + APCI3501_DO_REG);
 295
 296        /* Default all analog outputs to 0V (bipolar) */
 297        outl(APCI3501_AO_CTRL_BIPOLAR,
 298             dev->iobase + APCI3501_AO_CTRL_STATUS_REG);
 299        val = APCI3501_AO_DATA_BIPOLAR | APCI3501_AO_DATA_VAL(0);
 300
 301        /* Set all analog output channels */
 302        for (chan = 0; chan < 8; chan++) {
 303                ret = apci3501_wait_for_dac(dev);
 304                if (ret) {
 305                        dev_warn(dev->class_dev,
 306                                 "%s: DAC not-ready for channel %i\n",
 307                                 __func__, chan);
 308                } else {
 309                        outl(val | APCI3501_AO_DATA_CHAN(chan),
 310                             dev->iobase + APCI3501_AO_DATA_REG);
 311                }
 312        }
 313
 314        return 0;
 315}
 316
 317static int apci3501_auto_attach(struct comedi_device *dev,
 318                                unsigned long context_unused)
 319{
 320        struct pci_dev *pcidev = comedi_to_pci_dev(dev);
 321        struct apci3501_private *devpriv;
 322        struct comedi_subdevice *s;
 323        int ao_n_chan;
 324        int ret;
 325
 326        devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
 327        if (!devpriv)
 328                return -ENOMEM;
 329
 330        ret = comedi_pci_enable(dev);
 331        if (ret)
 332                return ret;
 333
 334        devpriv->amcc = pci_resource_start(pcidev, 0);
 335        dev->iobase = pci_resource_start(pcidev, 1);
 336        devpriv->tcw = dev->iobase + APCI3501_TIMER_BASE;
 337
 338        ao_n_chan = apci3501_eeprom_get_ao_n_chan(dev);
 339
 340        if (pcidev->irq > 0) {
 341                ret = request_irq(pcidev->irq, apci3501_interrupt, IRQF_SHARED,
 342                                  dev->board_name, dev);
 343                if (ret == 0)
 344                        dev->irq = pcidev->irq;
 345        }
 346
 347        ret = comedi_alloc_subdevices(dev, 5);
 348        if (ret)
 349                return ret;
 350
 351        /* Initialize the analog output subdevice */
 352        s = &dev->subdevices[0];
 353        if (ao_n_chan) {
 354                s->type         = COMEDI_SUBD_AO;
 355                s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
 356                s->n_chan       = ao_n_chan;
 357                s->maxdata      = 0x3fff;
 358                s->range_table  = &apci3501_ao_range;
 359                s->insn_write   = apci3501_ao_insn_write;
 360
 361                ret = comedi_alloc_subdev_readback(s);
 362                if (ret)
 363                        return ret;
 364        } else {
 365                s->type         = COMEDI_SUBD_UNUSED;
 366        }
 367
 368        /* Initialize the digital input subdevice */
 369        s = &dev->subdevices[1];
 370        s->type         = COMEDI_SUBD_DI;
 371        s->subdev_flags = SDF_READABLE;
 372        s->n_chan       = 2;
 373        s->maxdata      = 1;
 374        s->range_table  = &range_digital;
 375        s->insn_bits    = apci3501_di_insn_bits;
 376
 377        /* Initialize the digital output subdevice */
 378        s = &dev->subdevices[2];
 379        s->type         = COMEDI_SUBD_DO;
 380        s->subdev_flags = SDF_WRITABLE;
 381        s->n_chan       = 2;
 382        s->maxdata      = 1;
 383        s->range_table  = &range_digital;
 384        s->insn_bits    = apci3501_do_insn_bits;
 385
 386        /* Initialize the timer/watchdog subdevice */
 387        s = &dev->subdevices[3];
 388        s->type = COMEDI_SUBD_TIMER;
 389        s->subdev_flags = SDF_WRITABLE;
 390        s->n_chan = 1;
 391        s->maxdata = 0;
 392        s->len_chanlist = 1;
 393        s->range_table = &range_digital;
 394        s->insn_write = apci3501_write_insn_timer;
 395        s->insn_read = apci3501_read_insn_timer;
 396        s->insn_config = apci3501_config_insn_timer;
 397
 398        /* Initialize the eeprom subdevice */
 399        s = &dev->subdevices[4];
 400        s->type         = COMEDI_SUBD_MEMORY;
 401        s->subdev_flags = SDF_READABLE | SDF_INTERNAL;
 402        s->n_chan       = 256;
 403        s->maxdata      = 0xffff;
 404        s->insn_read    = apci3501_eeprom_insn_read;
 405
 406        apci3501_reset(dev);
 407        return 0;
 408}
 409
 410static void apci3501_detach(struct comedi_device *dev)
 411{
 412        if (dev->iobase)
 413                apci3501_reset(dev);
 414        comedi_pci_detach(dev);
 415}
 416
 417static struct comedi_driver apci3501_driver = {
 418        .driver_name    = "addi_apci_3501",
 419        .module         = THIS_MODULE,
 420        .auto_attach    = apci3501_auto_attach,
 421        .detach         = apci3501_detach,
 422};
 423
 424static int apci3501_pci_probe(struct pci_dev *dev,
 425                              const struct pci_device_id *id)
 426{
 427        return comedi_pci_auto_config(dev, &apci3501_driver, id->driver_data);
 428}
 429
 430static const struct pci_device_id apci3501_pci_table[] = {
 431        { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3001) },
 432        { 0 }
 433};
 434MODULE_DEVICE_TABLE(pci, apci3501_pci_table);
 435
 436static struct pci_driver apci3501_pci_driver = {
 437        .name           = "addi_apci_3501",
 438        .id_table       = apci3501_pci_table,
 439        .probe          = apci3501_pci_probe,
 440        .remove         = comedi_pci_auto_unconfig,
 441};
 442module_comedi_pci_driver(apci3501_driver, apci3501_pci_driver);
 443
 444MODULE_DESCRIPTION("ADDI-DATA APCI-3501 Analog output board");
 445MODULE_AUTHOR("Comedi http://www.comedi.org");
 446MODULE_LICENSE("GPL");
 447
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.