cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

icp_multi.c (9625B)


      1// SPDX-License-Identifier: GPL-2.0+
      2/*
      3 * icp_multi.c
      4 * Comedi driver for Inova ICP_MULTI board
      5 *
      6 * COMEDI - Linux Control and Measurement Device Interface
      7 * Copyright (C) 1997-2002 David A. Schleef <ds@schleef.org>
      8 */
      9
     10/*
     11 * Driver: icp_multi
     12 * Description: Inova ICP_MULTI
     13 * Devices: [Inova] ICP_MULTI (icp_multi)
     14 * Author: Anne Smorthit <anne.smorthit@sfwte.ch>
     15 * Status: works
     16 *
     17 * Configuration options: not applicable, uses PCI auto config
     18 *
     19 * The driver works for analog input and output and digital input and
     20 * output. It does not work with interrupts or with the counters. Currently
     21 * no support for DMA.
     22 *
     23 * It has 16 single-ended or 8 differential Analogue Input channels with
     24 * 12-bit resolution.  Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA.
     25 * Input ranges can be individually programmed for each channel.  Voltage or
     26 * current measurement is selected by jumper.
     27 *
     28 * There are 4 x 12-bit Analogue Outputs.  Ranges : 5V, 10V, +/-5V, +/-10V
     29 *
     30 * 16 x Digital Inputs, 24V
     31 *
     32 * 8 x Digital Outputs, 24V, 1A
     33 *
     34 * 4 x 16-bit counters - not implemented
     35 */
     36
     37#include <linux/module.h>
     38#include <linux/delay.h>
     39#include <linux/comedi/comedi_pci.h>
     40
     41#define ICP_MULTI_ADC_CSR	0x00	/* R/W: ADC command/status register */
     42#define ICP_MULTI_ADC_CSR_ST	BIT(0)	/* Start ADC */
     43#define ICP_MULTI_ADC_CSR_BSY	BIT(0)	/* ADC busy */
     44#define ICP_MULTI_ADC_CSR_BI	BIT(4)	/* Bipolar input range */
     45#define ICP_MULTI_ADC_CSR_RA	BIT(5)	/* Input range 0 = 5V, 1 = 10V */
     46#define ICP_MULTI_ADC_CSR_DI	BIT(6)	/* Input mode 1 = differential */
     47#define ICP_MULTI_ADC_CSR_DI_CHAN(x) (((x) & 0x7) << 9)
     48#define ICP_MULTI_ADC_CSR_SE_CHAN(x) (((x) & 0xf) << 8)
     49#define ICP_MULTI_AI		2	/* R:   Analogue input data */
     50#define ICP_MULTI_DAC_CSR	0x04	/* R/W: DAC command/status register */
     51#define ICP_MULTI_DAC_CSR_ST	BIT(0)	/* Start DAC */
     52#define ICP_MULTI_DAC_CSR_BSY	BIT(0)	/* DAC busy */
     53#define ICP_MULTI_DAC_CSR_BI	BIT(4)	/* Bipolar output range */
     54#define ICP_MULTI_DAC_CSR_RA	BIT(5)	/* Output range 0 = 5V, 1 = 10V */
     55#define ICP_MULTI_DAC_CSR_CHAN(x) (((x) & 0x3) << 8)
     56#define ICP_MULTI_AO		6	/* R/W: Analogue output data */
     57#define ICP_MULTI_DI		8	/* R/W: Digital inputs */
     58#define ICP_MULTI_DO		0x0A	/* R/W: Digital outputs */
     59#define ICP_MULTI_INT_EN	0x0c	/* R/W: Interrupt enable register */
     60#define ICP_MULTI_INT_STAT	0x0e	/* R/W: Interrupt status register */
     61#define ICP_MULTI_INT_ADC_RDY	BIT(0)	/* A/D conversion ready interrupt */
     62#define ICP_MULTI_INT_DAC_RDY	BIT(1)	/* D/A conversion ready interrupt */
     63#define ICP_MULTI_INT_DOUT_ERR	BIT(2)	/* Digital output error interrupt */
     64#define ICP_MULTI_INT_DIN_STAT	BIT(3)	/* Digital input status change int. */
     65#define ICP_MULTI_INT_CIE0	BIT(4)	/* Counter 0 overrun interrupt */
     66#define ICP_MULTI_INT_CIE1	BIT(5)	/* Counter 1 overrun interrupt */
     67#define ICP_MULTI_INT_CIE2	BIT(6)	/* Counter 2 overrun interrupt */
     68#define ICP_MULTI_INT_CIE3	BIT(7)	/* Counter 3 overrun interrupt */
     69#define ICP_MULTI_INT_MASK	0xff	/* All interrupts */
     70#define ICP_MULTI_CNTR0		0x10	/* R/W: Counter 0 */
     71#define ICP_MULTI_CNTR1		0x12	/* R/W: counter 1 */
     72#define ICP_MULTI_CNTR2		0x14	/* R/W: Counter 2 */
     73#define ICP_MULTI_CNTR3		0x16	/* R/W: Counter 3 */
     74
     75/* analog input and output have the same range options */
     76static const struct comedi_lrange icp_multi_ranges = {
     77	4, {
     78		UNI_RANGE(5),
     79		UNI_RANGE(10),
     80		BIP_RANGE(5),
     81		BIP_RANGE(10)
     82	}
     83};
     84
     85static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
     86
     87static int icp_multi_ai_eoc(struct comedi_device *dev,
     88			    struct comedi_subdevice *s,
     89			    struct comedi_insn *insn,
     90			    unsigned long context)
     91{
     92	unsigned int status;
     93
     94	status = readw(dev->mmio + ICP_MULTI_ADC_CSR);
     95	if ((status & ICP_MULTI_ADC_CSR_BSY) == 0)
     96		return 0;
     97	return -EBUSY;
     98}
     99
    100static int icp_multi_ai_insn_read(struct comedi_device *dev,
    101				  struct comedi_subdevice *s,
    102				  struct comedi_insn *insn,
    103				  unsigned int *data)
    104{
    105	unsigned int chan = CR_CHAN(insn->chanspec);
    106	unsigned int range = CR_RANGE(insn->chanspec);
    107	unsigned int aref = CR_AREF(insn->chanspec);
    108	unsigned int adc_csr;
    109	int ret = 0;
    110	int n;
    111
    112	/* Set mode and range data for specified channel */
    113	if (aref == AREF_DIFF) {
    114		adc_csr = ICP_MULTI_ADC_CSR_DI_CHAN(chan) |
    115			  ICP_MULTI_ADC_CSR_DI;
    116	} else {
    117		adc_csr = ICP_MULTI_ADC_CSR_SE_CHAN(chan);
    118	}
    119	adc_csr |= range_codes_analog[range];
    120	writew(adc_csr, dev->mmio + ICP_MULTI_ADC_CSR);
    121
    122	for (n = 0; n < insn->n; n++) {
    123		/*  Set start ADC bit */
    124		writew(adc_csr | ICP_MULTI_ADC_CSR_ST,
    125		       dev->mmio + ICP_MULTI_ADC_CSR);
    126
    127		udelay(1);
    128
    129		/*  Wait for conversion to complete, or get fed up waiting */
    130		ret = comedi_timeout(dev, s, insn, icp_multi_ai_eoc, 0);
    131		if (ret)
    132			break;
    133
    134		data[n] = (readw(dev->mmio + ICP_MULTI_AI) >> 4) & 0x0fff;
    135	}
    136
    137	return ret ? ret : n;
    138}
    139
    140static int icp_multi_ao_ready(struct comedi_device *dev,
    141			      struct comedi_subdevice *s,
    142			      struct comedi_insn *insn,
    143			      unsigned long context)
    144{
    145	unsigned int status;
    146
    147	status = readw(dev->mmio + ICP_MULTI_DAC_CSR);
    148	if ((status & ICP_MULTI_DAC_CSR_BSY) == 0)
    149		return 0;
    150	return -EBUSY;
    151}
    152
    153static int icp_multi_ao_insn_write(struct comedi_device *dev,
    154				   struct comedi_subdevice *s,
    155				   struct comedi_insn *insn,
    156				   unsigned int *data)
    157{
    158	unsigned int chan = CR_CHAN(insn->chanspec);
    159	unsigned int range = CR_RANGE(insn->chanspec);
    160	unsigned int dac_csr;
    161	int i;
    162
    163	/* Select channel and range */
    164	dac_csr = ICP_MULTI_DAC_CSR_CHAN(chan);
    165	dac_csr |= range_codes_analog[range];
    166	writew(dac_csr, dev->mmio + ICP_MULTI_DAC_CSR);
    167
    168	for (i = 0; i < insn->n; i++) {
    169		unsigned int val = data[i];
    170		int ret;
    171
    172		/* Wait for analog output to be ready for new data */
    173		ret = comedi_timeout(dev, s, insn, icp_multi_ao_ready, 0);
    174		if (ret)
    175			return ret;
    176
    177		writew(val, dev->mmio + ICP_MULTI_AO);
    178
    179		/* Set start conversion bit to write data to channel */
    180		writew(dac_csr | ICP_MULTI_DAC_CSR_ST,
    181		       dev->mmio + ICP_MULTI_DAC_CSR);
    182
    183		s->readback[chan] = val;
    184	}
    185
    186	return insn->n;
    187}
    188
    189static int icp_multi_di_insn_bits(struct comedi_device *dev,
    190				  struct comedi_subdevice *s,
    191				  struct comedi_insn *insn,
    192				  unsigned int *data)
    193{
    194	data[1] = readw(dev->mmio + ICP_MULTI_DI);
    195
    196	return insn->n;
    197}
    198
    199static int icp_multi_do_insn_bits(struct comedi_device *dev,
    200				  struct comedi_subdevice *s,
    201				  struct comedi_insn *insn,
    202				  unsigned int *data)
    203{
    204	if (comedi_dio_update_state(s, data))
    205		writew(s->state, dev->mmio + ICP_MULTI_DO);
    206
    207	data[1] = s->state;
    208
    209	return insn->n;
    210}
    211
    212static int icp_multi_reset(struct comedi_device *dev)
    213{
    214	int i;
    215
    216	/* Disable all interrupts and clear any requests */
    217	writew(0, dev->mmio + ICP_MULTI_INT_EN);
    218	writew(ICP_MULTI_INT_MASK, dev->mmio + ICP_MULTI_INT_STAT);
    219
    220	/* Reset the analog output channels to 0V */
    221	for (i = 0; i < 4; i++) {
    222		unsigned int dac_csr = ICP_MULTI_DAC_CSR_CHAN(i);
    223
    224		/* Select channel and 0..5V range */
    225		writew(dac_csr, dev->mmio + ICP_MULTI_DAC_CSR);
    226
    227		/* Output 0V */
    228		writew(0, dev->mmio + ICP_MULTI_AO);
    229
    230		/* Set start conversion bit to write data to channel */
    231		writew(dac_csr | ICP_MULTI_DAC_CSR_ST,
    232		       dev->mmio + ICP_MULTI_DAC_CSR);
    233		udelay(1);
    234	}
    235
    236	/* Digital outputs to 0 */
    237	writew(0, dev->mmio + ICP_MULTI_DO);
    238
    239	return 0;
    240}
    241
    242static int icp_multi_auto_attach(struct comedi_device *dev,
    243				 unsigned long context_unused)
    244{
    245	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
    246	struct comedi_subdevice *s;
    247	int ret;
    248
    249	ret = comedi_pci_enable(dev);
    250	if (ret)
    251		return ret;
    252
    253	dev->mmio = pci_ioremap_bar(pcidev, 2);
    254	if (!dev->mmio)
    255		return -ENOMEM;
    256
    257	ret = comedi_alloc_subdevices(dev, 4);
    258	if (ret)
    259		return ret;
    260
    261	icp_multi_reset(dev);
    262
    263	/* Analog Input subdevice */
    264	s = &dev->subdevices[0];
    265	s->type		= COMEDI_SUBD_AI;
    266	s->subdev_flags	= SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
    267	s->n_chan	= 16;
    268	s->maxdata	= 0x0fff;
    269	s->range_table	= &icp_multi_ranges;
    270	s->insn_read	= icp_multi_ai_insn_read;
    271
    272	/* Analog Output subdevice */
    273	s = &dev->subdevices[1];
    274	s->type		= COMEDI_SUBD_AO;
    275	s->subdev_flags	= SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
    276	s->n_chan	= 4;
    277	s->maxdata	= 0x0fff;
    278	s->range_table	= &icp_multi_ranges;
    279	s->insn_write	= icp_multi_ao_insn_write;
    280
    281	ret = comedi_alloc_subdev_readback(s);
    282	if (ret)
    283		return ret;
    284
    285	/* Digital Input subdevice */
    286	s = &dev->subdevices[2];
    287	s->type		= COMEDI_SUBD_DI;
    288	s->subdev_flags	= SDF_READABLE;
    289	s->n_chan	= 16;
    290	s->maxdata	= 1;
    291	s->range_table	= &range_digital;
    292	s->insn_bits	= icp_multi_di_insn_bits;
    293
    294	/* Digital Output subdevice */
    295	s = &dev->subdevices[3];
    296	s->type		= COMEDI_SUBD_DO;
    297	s->subdev_flags	= SDF_WRITABLE;
    298	s->n_chan	= 8;
    299	s->maxdata	= 1;
    300	s->range_table	= &range_digital;
    301	s->insn_bits	= icp_multi_do_insn_bits;
    302
    303	return 0;
    304}
    305
    306static struct comedi_driver icp_multi_driver = {
    307	.driver_name	= "icp_multi",
    308	.module		= THIS_MODULE,
    309	.auto_attach	= icp_multi_auto_attach,
    310	.detach		= comedi_pci_detach,
    311};
    312
    313static int icp_multi_pci_probe(struct pci_dev *dev,
    314			       const struct pci_device_id *id)
    315{
    316	return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
    317}
    318
    319static const struct pci_device_id icp_multi_pci_table[] = {
    320	{ PCI_DEVICE(PCI_VENDOR_ID_ICP, 0x8000) },
    321	{ 0 }
    322};
    323MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
    324
    325static struct pci_driver icp_multi_pci_driver = {
    326	.name		= "icp_multi",
    327	.id_table	= icp_multi_pci_table,
    328	.probe		= icp_multi_pci_probe,
    329	.remove		= comedi_pci_auto_unconfig,
    330};
    331module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
    332
    333MODULE_AUTHOR("Comedi https://www.comedi.org");
    334MODULE_DESCRIPTION("Comedi driver for Inova ICP_MULTI board");
    335MODULE_LICENSE("GPL");