Files
linux/drivers/comedi/drivers/adv_pci1760.c
Ian Abbott 2efb6edd52 comedi: adv_pci1760: Fix PWM instruction handling
(Actually, this is fixing the "Read the Current Status" command sent to
the device's outgoing mailbox, but it is only currently used for the PWM
instructions.)

The PCI-1760 is operated mostly by sending commands to a set of Outgoing
Mailbox registers, waiting for the command to complete, and reading the
result from the Incoming Mailbox registers.  One of these commands is
the "Read the Current Status" command.  The number of this command is
0x07 (see the User's Manual for the PCI-1760 at
<https://advdownload.advantech.com/productfile/Downloadfile2/1-11P6653/PCI-1760.pdf>.
The `PCI1760_CMD_GET_STATUS` macro defined in the driver should expand
to this command number 0x07, but unfortunately it currently expands to
0x03.  (Command number 0x03 is not defined in the User's Manual.)
Correct the definition of the `PCI1760_CMD_GET_STATUS` macro to fix it.

This is used by all the PWM subdevice related instructions handled by
`pci1760_pwm_insn_config()` which are probably all broken.  The effect
of sending the undefined command number 0x03 is not known.

Fixes: 14b93bb6bb ("staging: comedi: adv_pci_dio: separate out PCI-1760 support")
Cc: <stable@vger.kernel.org> # v4.5+
Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Link: https://lore.kernel.org/r/20230103143754.17564-1-abbotti@mev.co.uk
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-01-19 17:24:47 +01:00

424 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* COMEDI driver for the Advantech PCI-1760
* Copyright (C) 2015 H Hartley Sweeten <hsweeten@visionengravers.com>
*
* Based on the pci1760 support in the adv_pci_dio driver written by:
* Michal Dobes <dobes@tesnet.cz>
*
* COMEDI - Linux Control and Measurement Device Interface
* Copyright (C) 2000 David A. Schleef <ds@schleef.org>
*/
/*
* Driver: adv_pci1760
* Description: Advantech PCI-1760 Relay & Isolated Digital Input Card
* Devices: [Advantech] PCI-1760 (adv_pci1760)
* Author: H Hartley Sweeten <hsweeten@visionengravers.com>
* Updated: Fri, 13 Nov 2015 12:34:00 -0700
* Status: untested
*
* Configuration Options: not applicable, uses PCI auto config
*/
#include <linux/module.h>
#include <linux/comedi/comedi_pci.h>
/*
* PCI-1760 Register Map
*
* Outgoing Mailbox Bytes
* OMB3: Not used (must be 0)
* OMB2: The command code to the PCI-1760
* OMB1: The hi byte of the parameter for the command in OMB2
* OMB0: The lo byte of the parameter for the command in OMB2
*
* Incoming Mailbox Bytes
* IMB3: The Isolated Digital Input status (updated every 100us)
* IMB2: The current command (matches OMB2 when command is successful)
* IMB1: The hi byte of the feedback data for the command in OMB2
* IMB0: The lo byte of the feedback data for the command in OMB2
*
* Interrupt Control/Status
* INTCSR3: Not used (must be 0)
* INTCSR2: The interrupt status (read only)
* INTCSR1: Interrupt enable/disable
* INTCSR0: Not used (must be 0)
*/
#define PCI1760_OMB_REG(x) (0x0c + (x))
#define PCI1760_IMB_REG(x) (0x1c + (x))
#define PCI1760_INTCSR_REG(x) (0x38 + (x))
#define PCI1760_INTCSR1_IRQ_ENA BIT(5)
#define PCI1760_INTCSR2_OMB_IRQ BIT(0)
#define PCI1760_INTCSR2_IMB_IRQ BIT(1)
#define PCI1760_INTCSR2_IRQ_STATUS BIT(6)
#define PCI1760_INTCSR2_IRQ_ASSERTED BIT(7)
/* PCI-1760 command codes */
#define PCI1760_CMD_CLR_IMB2 0x00 /* Clears IMB2 */
#define PCI1760_CMD_SET_DO 0x01 /* Set output state */
#define PCI1760_CMD_GET_DO 0x02 /* Read output status */
#define PCI1760_CMD_GET_STATUS 0x07 /* Read current status */
#define PCI1760_CMD_GET_FW_VER 0x0e /* Read firmware version */
#define PCI1760_CMD_GET_HW_VER 0x0f /* Read hardware version */
#define PCI1760_CMD_SET_PWM_HI(x) (0x10 + (x) * 2) /* Set "hi" period */
#define PCI1760_CMD_SET_PWM_LO(x) (0x11 + (x) * 2) /* Set "lo" period */
#define PCI1760_CMD_SET_PWM_CNT(x) (0x14 + (x)) /* Set burst count */
#define PCI1760_CMD_ENA_PWM 0x1f /* Enable PWM outputs */
#define PCI1760_CMD_ENA_FILT 0x20 /* Enable input filter */
#define PCI1760_CMD_ENA_PAT_MATCH 0x21 /* Enable input pattern match */
#define PCI1760_CMD_SET_PAT_MATCH 0x22 /* Set input pattern match */
#define PCI1760_CMD_ENA_RISE_EDGE 0x23 /* Enable input rising edge */
#define PCI1760_CMD_ENA_FALL_EDGE 0x24 /* Enable input falling edge */
#define PCI1760_CMD_ENA_CNT 0x28 /* Enable counter */
#define PCI1760_CMD_RST_CNT 0x29 /* Reset counter */
#define PCI1760_CMD_ENA_CNT_OFLOW 0x2a /* Enable counter overflow */
#define PCI1760_CMD_ENA_CNT_MATCH 0x2b /* Enable counter match */
#define PCI1760_CMD_SET_CNT_EDGE 0x2c /* Set counter edge */
#define PCI1760_CMD_GET_CNT 0x2f /* Reads counter value */
#define PCI1760_CMD_SET_HI_SAMP(x) (0x30 + (x)) /* Set "hi" sample time */
#define PCI1760_CMD_SET_LO_SAMP(x) (0x38 + (x)) /* Set "lo" sample time */
#define PCI1760_CMD_SET_CNT(x) (0x40 + (x)) /* Set counter reset val */
#define PCI1760_CMD_SET_CNT_MATCH(x) (0x48 + (x)) /* Set counter match val */
#define PCI1760_CMD_GET_INT_FLAGS 0x60 /* Read interrupt flags */
#define PCI1760_CMD_GET_INT_FLAGS_MATCH BIT(0)
#define PCI1760_CMD_GET_INT_FLAGS_COS BIT(1)
#define PCI1760_CMD_GET_INT_FLAGS_OFLOW BIT(2)
#define PCI1760_CMD_GET_OS 0x61 /* Read edge change flags */
#define PCI1760_CMD_GET_CNT_STATUS 0x62 /* Read counter oflow/match */
#define PCI1760_CMD_TIMEOUT 250 /* 250 usec timeout */
#define PCI1760_CMD_RETRIES 3 /* limit number of retries */
#define PCI1760_PWM_TIMEBASE 100000 /* 1 unit = 100 usec */
static int pci1760_send_cmd(struct comedi_device *dev,
unsigned char cmd, unsigned short val)
{
unsigned long timeout;
/* send the command and parameter */
outb(val & 0xff, dev->iobase + PCI1760_OMB_REG(0));
outb((val >> 8) & 0xff, dev->iobase + PCI1760_OMB_REG(1));
outb(cmd, dev->iobase + PCI1760_OMB_REG(2));
outb(0, dev->iobase + PCI1760_OMB_REG(3));
/* datasheet says to allow up to 250 usec for the command to complete */
timeout = jiffies + usecs_to_jiffies(PCI1760_CMD_TIMEOUT);
do {
if (inb(dev->iobase + PCI1760_IMB_REG(2)) == cmd) {
/* command success; return the feedback data */
return inb(dev->iobase + PCI1760_IMB_REG(0)) |
(inb(dev->iobase + PCI1760_IMB_REG(1)) << 8);
}
cpu_relax();
} while (time_before(jiffies, timeout));
return -EBUSY;
}
static int pci1760_cmd(struct comedi_device *dev,
unsigned char cmd, unsigned short val)
{
int repeats;
int ret;
/* send PCI1760_CMD_CLR_IMB2 between identical commands */
if (inb(dev->iobase + PCI1760_IMB_REG(2)) == cmd) {
ret = pci1760_send_cmd(dev, PCI1760_CMD_CLR_IMB2, 0);
if (ret < 0) {
/* timeout? try it once more */
ret = pci1760_send_cmd(dev, PCI1760_CMD_CLR_IMB2, 0);
if (ret < 0)
return -ETIMEDOUT;
}
}
/* datasheet says to keep retrying the command */
for (repeats = 0; repeats < PCI1760_CMD_RETRIES; repeats++) {
ret = pci1760_send_cmd(dev, cmd, val);
if (ret >= 0)
return ret;
}
/* command failed! */
return -ETIMEDOUT;
}
static int pci1760_di_insn_bits(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
data[1] = inb(dev->iobase + PCI1760_IMB_REG(3));
return insn->n;
}
static int pci1760_do_insn_bits(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
int ret;
if (comedi_dio_update_state(s, data)) {
ret = pci1760_cmd(dev, PCI1760_CMD_SET_DO, s->state);
if (ret < 0)
return ret;
}
data[1] = s->state;
return insn->n;
}
static int pci1760_pwm_ns_to_div(unsigned int flags, unsigned int ns)
{
unsigned int divisor;
switch (flags) {
case CMDF_ROUND_NEAREST:
divisor = DIV_ROUND_CLOSEST(ns, PCI1760_PWM_TIMEBASE);
break;
case CMDF_ROUND_UP:
divisor = DIV_ROUND_UP(ns, PCI1760_PWM_TIMEBASE);
break;
case CMDF_ROUND_DOWN:
divisor = ns / PCI1760_PWM_TIMEBASE;
break;
default:
return -EINVAL;
}
if (divisor < 1)
divisor = 1;
if (divisor > 0xffff)
divisor = 0xffff;
return divisor;
}
static int pci1760_pwm_enable(struct comedi_device *dev,
unsigned int chan, bool enable)
{
int ret;
ret = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, PCI1760_CMD_ENA_PWM);
if (ret < 0)
return ret;
if (enable)
ret |= BIT(chan);
else
ret &= ~BIT(chan);
return pci1760_cmd(dev, PCI1760_CMD_ENA_PWM, ret);
}
static int pci1760_pwm_insn_config(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
unsigned int chan = CR_CHAN(insn->chanspec);
int hi_div;
int lo_div;
int ret;
switch (data[0]) {
case INSN_CONFIG_ARM:
ret = pci1760_pwm_enable(dev, chan, false);
if (ret < 0)
return ret;
if (data[1] > 0xffff)
return -EINVAL;
ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_CNT(chan), data[1]);
if (ret < 0)
return ret;
ret = pci1760_pwm_enable(dev, chan, true);
if (ret < 0)
return ret;
break;
case INSN_CONFIG_DISARM:
ret = pci1760_pwm_enable(dev, chan, false);
if (ret < 0)
return ret;
break;
case INSN_CONFIG_PWM_OUTPUT:
ret = pci1760_pwm_enable(dev, chan, false);
if (ret < 0)
return ret;
hi_div = pci1760_pwm_ns_to_div(data[1], data[2]);
lo_div = pci1760_pwm_ns_to_div(data[3], data[4]);
if (hi_div < 0 || lo_div < 0)
return -EINVAL;
if ((hi_div * PCI1760_PWM_TIMEBASE) != data[2] ||
(lo_div * PCI1760_PWM_TIMEBASE) != data[4]) {
data[2] = hi_div * PCI1760_PWM_TIMEBASE;
data[4] = lo_div * PCI1760_PWM_TIMEBASE;
return -EAGAIN;
}
ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_HI(chan), hi_div);
if (ret < 0)
return ret;
ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_LO(chan), lo_div);
if (ret < 0)
return ret;
break;
case INSN_CONFIG_GET_PWM_OUTPUT:
hi_div = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS,
PCI1760_CMD_SET_PWM_HI(chan));
lo_div = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS,
PCI1760_CMD_SET_PWM_LO(chan));
if (hi_div < 0 || lo_div < 0)
return -ETIMEDOUT;
data[1] = hi_div * PCI1760_PWM_TIMEBASE;
data[2] = lo_div * PCI1760_PWM_TIMEBASE;
break;
case INSN_CONFIG_GET_PWM_STATUS:
ret = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS,
PCI1760_CMD_ENA_PWM);
if (ret < 0)
return ret;
data[1] = (ret & BIT(chan)) ? 1 : 0;
break;
default:
return -EINVAL;
}
return insn->n;
}
static void pci1760_reset(struct comedi_device *dev)
{
int i;
/* disable interrupts (intcsr2 is read-only) */
outb(0, dev->iobase + PCI1760_INTCSR_REG(0));
outb(0, dev->iobase + PCI1760_INTCSR_REG(1));
outb(0, dev->iobase + PCI1760_INTCSR_REG(3));
/* disable counters */
pci1760_cmd(dev, PCI1760_CMD_ENA_CNT, 0);
/* disable overflow interrupts */
pci1760_cmd(dev, PCI1760_CMD_ENA_CNT_OFLOW, 0);
/* disable match */
pci1760_cmd(dev, PCI1760_CMD_ENA_CNT_MATCH, 0);
/* set match and counter reset values */
for (i = 0; i < 8; i++) {
pci1760_cmd(dev, PCI1760_CMD_SET_CNT_MATCH(i), 0x8000);
pci1760_cmd(dev, PCI1760_CMD_SET_CNT(i), 0x0000);
}
/* reset counters to reset values */
pci1760_cmd(dev, PCI1760_CMD_RST_CNT, 0xff);
/* set counter count edges */
pci1760_cmd(dev, PCI1760_CMD_SET_CNT_EDGE, 0);
/* disable input filters */
pci1760_cmd(dev, PCI1760_CMD_ENA_FILT, 0);
/* disable pattern matching */
pci1760_cmd(dev, PCI1760_CMD_ENA_PAT_MATCH, 0);
/* set pattern match value */
pci1760_cmd(dev, PCI1760_CMD_SET_PAT_MATCH, 0);
}
static int pci1760_auto_attach(struct comedi_device *dev,
unsigned long context)
{
struct pci_dev *pcidev = comedi_to_pci_dev(dev);
struct comedi_subdevice *s;
int ret;
ret = comedi_pci_enable(dev);
if (ret)
return ret;
dev->iobase = pci_resource_start(pcidev, 0);
pci1760_reset(dev);
ret = comedi_alloc_subdevices(dev, 4);
if (ret)
return ret;
/* Digital Input subdevice */
s = &dev->subdevices[0];
s->type = COMEDI_SUBD_DI;
s->subdev_flags = SDF_READABLE;
s->n_chan = 8;
s->maxdata = 1;
s->range_table = &range_digital;
s->insn_bits = pci1760_di_insn_bits;
/* Digital Output subdevice */
s = &dev->subdevices[1];
s->type = COMEDI_SUBD_DO;
s->subdev_flags = SDF_WRITABLE;
s->n_chan = 8;
s->maxdata = 1;
s->range_table = &range_digital;
s->insn_bits = pci1760_do_insn_bits;
/* get the current state of the outputs */
ret = pci1760_cmd(dev, PCI1760_CMD_GET_DO, 0);
if (ret < 0)
return ret;
s->state = ret;
/* PWM subdevice */
s = &dev->subdevices[2];
s->type = COMEDI_SUBD_PWM;
s->subdev_flags = SDF_PWM_COUNTER;
s->n_chan = 2;
s->insn_config = pci1760_pwm_insn_config;
/* Counter subdevice */
s = &dev->subdevices[3];
s->type = COMEDI_SUBD_UNUSED;
return 0;
}
static struct comedi_driver pci1760_driver = {
.driver_name = "adv_pci1760",
.module = THIS_MODULE,
.auto_attach = pci1760_auto_attach,
.detach = comedi_pci_detach,
};
static int pci1760_pci_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
return comedi_pci_auto_config(dev, &pci1760_driver, id->driver_data);
}
static const struct pci_device_id pci1760_pci_table[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1760) },
{ 0 }
};
MODULE_DEVICE_TABLE(pci, pci1760_pci_table);
static struct pci_driver pci1760_pci_driver = {
.name = "adv_pci1760",
.id_table = pci1760_pci_table,
.probe = pci1760_pci_probe,
.remove = comedi_pci_auto_unconfig,
};
module_comedi_pci_driver(pci1760_driver, pci1760_pci_driver);
MODULE_AUTHOR("Comedi https://www.comedi.org");
MODULE_DESCRIPTION("Comedi driver for Advantech PCI-1760");
MODULE_LICENSE("GPL");