linux/drivers/staging/comedi/drivers/amplc_pc236.c
<<
>>
Prefs
   1/*
   2    comedi/drivers/amplc_pc236.c
   3    Driver for Amplicon PC36AT and PCI236 DIO boards.
   4
   5    Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
   6
   7    COMEDI - Linux Control and Measurement Device Interface
   8    Copyright (C) 2000 David A. Schleef <ds@schleef.org>
   9
  10    This program is free software; you can redistribute it and/or modify
  11    it under the terms of the GNU General Public License as published by
  12    the Free Software Foundation; either version 2 of the License, or
  13    (at your option) any later version.
  14
  15    This program is distributed in the hope that it will be useful,
  16    but WITHOUT ANY WARRANTY; without even the implied warranty of
  17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18    GNU General Public License for more details.
  19
  20    You should have received a copy of the GNU General Public License
  21    along with this program; if not, write to the Free Software
  22    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  23
  24*/
  25/*
  26Driver: amplc_pc236
  27Description: Amplicon PC36AT, PCI236
  28Author: Ian Abbott <abbotti@mev.co.uk>
  29Devices: [Amplicon] PC36AT (pc36at), PCI236 (pci236 or amplc_pc236)
  30Updated: Wed, 01 Apr 2009 15:41:25 +0100
  31Status: works
  32
  33Configuration options - PC36AT:
  34  [0] - I/O port base address
  35  [1] - IRQ (optional)
  36
  37Configuration options - PCI236:
  38  [0] - PCI bus of device (optional)
  39  [1] - PCI slot of device (optional)
  40  If bus/slot is not specified, the first available PCI device will be
  41  used.
  42
  43The PC36AT ISA board and PCI236 PCI board have a single 8255 appearing
  44as subdevice 0.
  45
  46Subdevice 1 pretends to be a digital input device, but it always returns
  470 when read. However, if you run a command with scan_begin_src=TRIG_EXT,
  48a rising edge on port C bit 3 acts as an external trigger, which can be
  49used to wake up tasks.  This is like the comedi_parport device, but the
  50only way to physically disable the interrupt on the PC36AT is to remove
  51the IRQ jumper.  If no interrupt is connected, then subdevice 1 is
  52unused.
  53*/
  54
  55#include <linux/pci.h>
  56#include <linux/interrupt.h>
  57
  58#include "../comedidev.h"
  59
  60#include "comedi_fc.h"
  61#include "8255.h"
  62#include "plx9052.h"
  63
  64#define PC236_DRIVER_NAME       "amplc_pc236"
  65
  66#define DO_ISA  IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA)
  67#define DO_PCI  IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
  68
  69/* PCI236 PCI configuration register information */
  70#define PCI_DEVICE_ID_AMPLICON_PCI236 0x0009
  71#define PCI_DEVICE_ID_INVALID 0xffff
  72
  73/* PC36AT / PCI236 registers */
  74
  75#define PC236_IO_SIZE           4
  76#define PC236_LCR_IO_SIZE       128
  77
  78/* Disable, and clear, interrupts */
  79#define PCI236_INTR_DISABLE     (PLX9052_INTCSR_LI1POL |        \
  80                                 PLX9052_INTCSR_LI2POL |        \
  81                                 PLX9052_INTCSR_LI1SEL |        \
  82                                 PLX9052_INTCSR_LI1CLRINT)
  83
  84/* Enable, and clear, interrupts */
  85#define PCI236_INTR_ENABLE      (PLX9052_INTCSR_LI1ENAB |       \
  86                                 PLX9052_INTCSR_LI1POL |        \
  87                                 PLX9052_INTCSR_LI2POL |        \
  88                                 PLX9052_INTCSR_PCIENAB |       \
  89                                 PLX9052_INTCSR_LI1SEL |        \
  90                                 PLX9052_INTCSR_LI1CLRINT)
  91
  92/*
  93 * Board descriptions for Amplicon PC36AT and PCI236.
  94 */
  95
  96enum pc236_bustype { isa_bustype, pci_bustype };
  97enum pc236_model { pc36at_model, pci236_model, anypci_model };
  98
  99struct pc236_board {
 100        const char *name;
 101        unsigned short devid;
 102        enum pc236_bustype bustype;
 103        enum pc236_model model;
 104};
 105static const struct pc236_board pc236_boards[] = {
 106#if DO_ISA
 107        {
 108                .name = "pc36at",
 109                .bustype = isa_bustype,
 110                .model = pc36at_model,
 111        },
 112#endif
 113#if DO_PCI
 114        {
 115                .name = "pci236",
 116                .devid = PCI_DEVICE_ID_AMPLICON_PCI236,
 117                .bustype = pci_bustype,
 118                .model = pci236_model,
 119        },
 120        {
 121                .name = PC236_DRIVER_NAME,
 122                .devid = PCI_DEVICE_ID_INVALID,
 123                .bustype = pci_bustype,
 124                .model = anypci_model,  /* wildcard */
 125        },
 126#endif
 127};
 128
 129/* this structure is for data unique to this hardware driver.  If
 130   several hardware drivers keep similar information in this structure,
 131   feel free to suggest moving the variable to the struct comedi_device struct.
 132 */
 133struct pc236_private {
 134        unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
 135        int enable_irq;
 136};
 137
 138/* test if ISA supported and this is an ISA board */
 139static inline bool is_isa_board(const struct pc236_board *board)
 140{
 141        return DO_ISA && board->bustype == isa_bustype;
 142}
 143
 144/* test if PCI supported and this is a PCI board */
 145static inline bool is_pci_board(const struct pc236_board *board)
 146{
 147        return DO_PCI && board->bustype == pci_bustype;
 148}
 149
 150/*
 151 * This function looks for a board matching the supplied PCI device.
 152 */
 153static const struct pc236_board *pc236_find_pci_board(struct pci_dev *pci_dev)
 154{
 155        unsigned int i;
 156
 157        for (i = 0; i < ARRAY_SIZE(pc236_boards); i++)
 158                if (is_pci_board(&pc236_boards[i]) &&
 159                    pci_dev->device == pc236_boards[i].devid)
 160                        return &pc236_boards[i];
 161        return NULL;
 162}
 163
 164/*
 165 * This function looks for a PCI device matching the requested board name,
 166 * bus and slot.
 167 */
 168static struct pci_dev *pc236_find_pci_dev(struct comedi_device *dev,
 169                                          struct comedi_devconfig *it)
 170{
 171        const struct pc236_board *thisboard = comedi_board(dev);
 172        struct pci_dev *pci_dev = NULL;
 173        int bus = it->options[0];
 174        int slot = it->options[1];
 175
 176        for_each_pci_dev(pci_dev) {
 177                if (bus || slot) {
 178                        if (bus != pci_dev->bus->number ||
 179                            slot != PCI_SLOT(pci_dev->devfn))
 180                                continue;
 181                }
 182                if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON)
 183                        continue;
 184
 185                if (thisboard->model == anypci_model) {
 186                        /* Wildcard board matches any supported PCI board. */
 187                        const struct pc236_board *foundboard;
 188
 189                        foundboard = pc236_find_pci_board(pci_dev);
 190                        if (foundboard == NULL)
 191                                continue;
 192                        /* Replace wildcard board_ptr. */
 193                        dev->board_ptr = foundboard;
 194                } else {
 195                        /* Match specific model name. */
 196                        if (pci_dev->device != thisboard->devid)
 197                                continue;
 198                }
 199                return pci_dev;
 200        }
 201        dev_err(dev->class_dev,
 202                "No supported board found! (req. bus %d, slot %d)\n",
 203                bus, slot);
 204        return NULL;
 205}
 206
 207/*
 208 * This function is called to mark the interrupt as disabled (no command
 209 * configured on subdevice 1) and to physically disable the interrupt
 210 * (not possible on the PC36AT, except by removing the IRQ jumper!).
 211 */
 212static void pc236_intr_disable(struct comedi_device *dev)
 213{
 214        const struct pc236_board *thisboard = comedi_board(dev);
 215        struct pc236_private *devpriv = dev->private;
 216        unsigned long flags;
 217
 218        spin_lock_irqsave(&dev->spinlock, flags);
 219        devpriv->enable_irq = 0;
 220        if (is_pci_board(thisboard))
 221                outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
 222        spin_unlock_irqrestore(&dev->spinlock, flags);
 223}
 224
 225/*
 226 * This function is called to mark the interrupt as enabled (a command
 227 * configured on subdevice 1) and to physically enable the interrupt
 228 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
 229 */
 230static void pc236_intr_enable(struct comedi_device *dev)
 231{
 232        const struct pc236_board *thisboard = comedi_board(dev);
 233        struct pc236_private *devpriv = dev->private;
 234        unsigned long flags;
 235
 236        spin_lock_irqsave(&dev->spinlock, flags);
 237        devpriv->enable_irq = 1;
 238        if (is_pci_board(thisboard))
 239                outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
 240        spin_unlock_irqrestore(&dev->spinlock, flags);
 241}
 242
 243/*
 244 * This function is called when an interrupt occurs to check whether
 245 * the interrupt has been marked as enabled and was generated by the
 246 * board.  If so, the function prepares the hardware for the next
 247 * interrupt.
 248 * Returns 0 if the interrupt should be ignored.
 249 */
 250static int pc236_intr_check(struct comedi_device *dev)
 251{
 252        const struct pc236_board *thisboard = comedi_board(dev);
 253        struct pc236_private *devpriv = dev->private;
 254        int retval = 0;
 255        unsigned long flags;
 256        unsigned int intcsr;
 257
 258        spin_lock_irqsave(&dev->spinlock, flags);
 259        if (devpriv->enable_irq) {
 260                retval = 1;
 261                if (is_pci_board(thisboard)) {
 262                        intcsr = inl(devpriv->lcr_iobase + PLX9052_INTCSR);
 263                        if (!(intcsr & PLX9052_INTCSR_LI1STAT)) {
 264                                retval = 0;
 265                        } else {
 266                                /* Clear interrupt and keep it enabled. */
 267                                outl(PCI236_INTR_ENABLE,
 268                                     devpriv->lcr_iobase + PLX9052_INTCSR);
 269                        }
 270                }
 271        }
 272        spin_unlock_irqrestore(&dev->spinlock, flags);
 273
 274        return retval;
 275}
 276
 277/*
 278 * Input from subdevice 1.
 279 * Copied from the comedi_parport driver.
 280 */
 281static int pc236_intr_insn(struct comedi_device *dev,
 282                           struct comedi_subdevice *s, struct comedi_insn *insn,
 283                           unsigned int *data)
 284{
 285        data[1] = 0;
 286        return insn->n;
 287}
 288
 289/*
 290 * Subdevice 1 command test.
 291 * Copied from the comedi_parport driver.
 292 */
 293static int pc236_intr_cmdtest(struct comedi_device *dev,
 294                              struct comedi_subdevice *s,
 295                              struct comedi_cmd *cmd)
 296{
 297        int err = 0;
 298
 299        /* Step 1 : check if triggers are trivially valid */
 300
 301        err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
 302        err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
 303        err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
 304        err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
 305        err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
 306
 307        if (err)
 308                return 1;
 309
 310        /* Step 2a : make sure trigger sources are unique */
 311        /* Step 2b : and mutually compatible */
 312
 313        if (err)
 314                return 2;
 315
 316        /* Step 3: check it arguments are trivially valid */
 317
 318        err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
 319        err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
 320        err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
 321        err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, 1);
 322        err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
 323
 324        if (err)
 325                return 3;
 326
 327        /* step 4: ignored */
 328
 329        if (err)
 330                return 4;
 331
 332        return 0;
 333}
 334
 335/*
 336 * Subdevice 1 command.
 337 */
 338static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
 339{
 340        pc236_intr_enable(dev);
 341
 342        return 0;
 343}
 344
 345/*
 346 * Subdevice 1 cancel command.
 347 */
 348static int pc236_intr_cancel(struct comedi_device *dev,
 349                             struct comedi_subdevice *s)
 350{
 351        pc236_intr_disable(dev);
 352
 353        return 0;
 354}
 355
 356/*
 357 * Interrupt service routine.
 358 * Based on the comedi_parport driver.
 359 */
 360static irqreturn_t pc236_interrupt(int irq, void *d)
 361{
 362        struct comedi_device *dev = d;
 363        struct comedi_subdevice *s = &dev->subdevices[1];
 364        int handled;
 365
 366        handled = pc236_intr_check(dev);
 367        if (dev->attached && handled) {
 368                comedi_buf_put(s->async, 0);
 369                s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
 370                comedi_event(dev, s);
 371        }
 372        return IRQ_RETVAL(handled);
 373}
 374
 375static void pc236_report_attach(struct comedi_device *dev, unsigned int irq)
 376{
 377        const struct pc236_board *thisboard = comedi_board(dev);
 378        struct pci_dev *pcidev = comedi_to_pci_dev(dev);
 379        char tmpbuf[60];
 380        int tmplen;
 381
 382        if (is_isa_board(thisboard))
 383                tmplen = scnprintf(tmpbuf, sizeof(tmpbuf),
 384                                   "(base %#lx) ", dev->iobase);
 385        else if (is_pci_board(thisboard))
 386                tmplen = scnprintf(tmpbuf, sizeof(tmpbuf),
 387                                   "(pci %s) ", pci_name(pcidev));
 388        else
 389                tmplen = 0;
 390        if (irq)
 391                tmplen += scnprintf(&tmpbuf[tmplen], sizeof(tmpbuf) - tmplen,
 392                                    "(irq %u%s) ", irq,
 393                                    (dev->irq ? "" : " UNAVAILABLE"));
 394        else
 395                tmplen += scnprintf(&tmpbuf[tmplen], sizeof(tmpbuf) - tmplen,
 396                                    "(no irq) ");
 397        dev_info(dev->class_dev, "%s %sattached\n",
 398                 dev->board_name, tmpbuf);
 399}
 400
 401static int pc236_common_attach(struct comedi_device *dev, unsigned long iobase,
 402                               unsigned int irq, unsigned long req_irq_flags)
 403{
 404        const struct pc236_board *thisboard = comedi_board(dev);
 405        struct comedi_subdevice *s;
 406        int ret;
 407
 408        dev->board_name = thisboard->name;
 409        dev->iobase = iobase;
 410
 411        ret = comedi_alloc_subdevices(dev, 2);
 412        if (ret)
 413                return ret;
 414
 415        s = &dev->subdevices[0];
 416        /* digital i/o subdevice (8255) */
 417        ret = subdev_8255_init(dev, s, NULL, iobase);
 418        if (ret < 0) {
 419                dev_err(dev->class_dev, "error! out of memory!\n");
 420                return ret;
 421        }
 422        s = &dev->subdevices[1];
 423        dev->read_subdev = s;
 424        s->type = COMEDI_SUBD_UNUSED;
 425        pc236_intr_disable(dev);
 426        if (irq) {
 427                if (request_irq(irq, pc236_interrupt, req_irq_flags,
 428                                PC236_DRIVER_NAME, dev) >= 0) {
 429                        dev->irq = irq;
 430                        s->type = COMEDI_SUBD_DI;
 431                        s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
 432                        s->n_chan = 1;
 433                        s->maxdata = 1;
 434                        s->range_table = &range_digital;
 435                        s->insn_bits = pc236_intr_insn;
 436                        s->do_cmdtest = pc236_intr_cmdtest;
 437                        s->do_cmd = pc236_intr_cmd;
 438                        s->cancel = pc236_intr_cancel;
 439                }
 440        }
 441        pc236_report_attach(dev, irq);
 442        return 1;
 443}
 444
 445static int pc236_pci_common_attach(struct comedi_device *dev,
 446                                   struct pci_dev *pci_dev)
 447{
 448        struct pc236_private *devpriv = dev->private;
 449        unsigned long iobase;
 450        int ret;
 451
 452        comedi_set_hw_dev(dev, &pci_dev->dev);
 453
 454        ret = comedi_pci_enable(dev);
 455        if (ret)
 456                return ret;
 457
 458        devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
 459        iobase = pci_resource_start(pci_dev, 2);
 460        return pc236_common_attach(dev, iobase, pci_dev->irq, IRQF_SHARED);
 461}
 462
 463/*
 464 * Attach is called by the Comedi core to configure the driver
 465 * for a particular board.  If you specified a board_name array
 466 * in the driver structure, dev->board_ptr contains that
 467 * address.
 468 */
 469static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
 470{
 471        const struct pc236_board *thisboard = comedi_board(dev);
 472        struct pc236_private *devpriv;
 473        int ret;
 474
 475        devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
 476        if (!devpriv)
 477                return -ENOMEM;
 478        dev->private = devpriv;
 479
 480        /* Process options according to bus type. */
 481        if (is_isa_board(thisboard)) {
 482                ret = comedi_request_region(dev, it->options[0], PC236_IO_SIZE);
 483                if (ret)
 484                        return ret;
 485
 486                return pc236_common_attach(dev, dev->iobase, it->options[1], 0);
 487        } else if (is_pci_board(thisboard)) {
 488                struct pci_dev *pci_dev;
 489
 490                pci_dev = pc236_find_pci_dev(dev, it);
 491                if (!pci_dev)
 492                        return -EIO;
 493                return pc236_pci_common_attach(dev, pci_dev);
 494        } else {
 495                dev_err(dev->class_dev, PC236_DRIVER_NAME
 496                        ": BUG! cannot determine board type!\n");
 497                return -EINVAL;
 498        }
 499}
 500
 501/*
 502 * The auto_attach hook is called at PCI probe time via
 503 * comedi_pci_auto_config().  dev->board_ptr is NULL on entry.
 504 * There should be a board entry matching the supplied PCI device.
 505 */
 506static int pc236_auto_attach(struct comedi_device *dev,
 507                                       unsigned long context_unused)
 508{
 509        struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
 510        struct pc236_private *devpriv;
 511
 512        if (!DO_PCI)
 513                return -EINVAL;
 514
 515        dev_info(dev->class_dev, PC236_DRIVER_NAME ": attach pci %s\n",
 516                 pci_name(pci_dev));
 517
 518        devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
 519        if (!devpriv)
 520                return -ENOMEM;
 521        dev->private = devpriv;
 522
 523        dev->board_ptr = pc236_find_pci_board(pci_dev);
 524        if (dev->board_ptr == NULL) {
 525                dev_err(dev->class_dev, "BUG! cannot determine board type!\n");
 526                return -EINVAL;
 527        }
 528        /*
 529         * Need to 'get' the PCI device to match the 'put' in pc236_detach().
 530         * TODO: Remove the pci_dev_get() and matching pci_dev_put() once
 531         * support for manual attachment of PCI devices via pc236_attach()
 532         * has been removed.
 533         */
 534        pci_dev_get(pci_dev);
 535        return pc236_pci_common_attach(dev, pci_dev);
 536}
 537
 538static void pc236_detach(struct comedi_device *dev)
 539{
 540        const struct pc236_board *thisboard = comedi_board(dev);
 541
 542        if (!thisboard)
 543                return;
 544        if (dev->iobase)
 545                pc236_intr_disable(dev);
 546        comedi_spriv_free(dev, 0);
 547        if (is_isa_board(thisboard)) {
 548                comedi_legacy_detach(dev);
 549        } else if (is_pci_board(thisboard)) {
 550                struct pci_dev *pcidev = comedi_to_pci_dev(dev);
 551                if (dev->irq)
 552                        free_irq(dev->irq, dev);
 553                comedi_pci_disable(dev);
 554                if (pcidev)
 555                        pci_dev_put(pcidev);
 556        }
 557}
 558
 559/*
 560 * The struct comedi_driver structure tells the Comedi core module
 561 * which functions to call to configure/deconfigure (attach/detach)
 562 * the board, and also about the kernel module that contains
 563 * the device code.
 564 */
 565static struct comedi_driver amplc_pc236_driver = {
 566        .driver_name = PC236_DRIVER_NAME,
 567        .module = THIS_MODULE,
 568        .attach = pc236_attach,
 569        .auto_attach = pc236_auto_attach,
 570        .detach = pc236_detach,
 571        .board_name = &pc236_boards[0].name,
 572        .offset = sizeof(struct pc236_board),
 573        .num_names = ARRAY_SIZE(pc236_boards),
 574};
 575
 576#if DO_PCI
 577static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = {
 578        { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) },
 579        {0}
 580};
 581
 582MODULE_DEVICE_TABLE(pci, pc236_pci_table);
 583
 584static int amplc_pc236_pci_probe(struct pci_dev *dev,
 585                                 const struct pci_device_id *id)
 586{
 587        return comedi_pci_auto_config(dev, &amplc_pc236_driver,
 588                                      id->driver_data);
 589}
 590
 591static struct pci_driver amplc_pc236_pci_driver = {
 592        .name = PC236_DRIVER_NAME,
 593        .id_table = pc236_pci_table,
 594        .probe = &amplc_pc236_pci_probe,
 595        .remove         = comedi_pci_auto_unconfig,
 596};
 597
 598module_comedi_pci_driver(amplc_pc236_driver, amplc_pc236_pci_driver);
 599#else
 600module_comedi_driver(amplc_pc236_driver);
 601#endif
 602
 603MODULE_AUTHOR("Comedi http://www.comedi.org");
 604MODULE_DESCRIPTION("Comedi low-level driver");
 605MODULE_LICENSE("GPL");
 606